Error codes and known API pitfalls
This page documents real integration issues that have occurred in production or testing.
Schema
| Error Code | Cause | Solution / Bug |
|---|---|---|
404 Website not found | Some website sub-endpoints previously accepted only the legacy numeric website ID, even though the docs showed websitePublicId. | Fixed. GET /api/websites/:websitePublicId/check-history and GET /api/websites/:websitePublicId/uptime-stats now accept public IDs and still keep legacy-ID compatibility. |
403 Forbidden on GET /api/organizations/:organizationPublicId | The route previously interpreted the path parameter directly as a number. A UUID therefore failed during permission checks. | Fixed. Organization detail and billing routes now resolve publicId correctly. |
400 Bad Request with ZodError on customer-ips or customer-domains | Query parameters like organizationId=, page=, or perPage= were sent as empty strings. Zod coerced them to 0, which then violated min constraints. | Fixed for organizationId, page, and perPage. Optional still means: omit unused parameters instead of sending empty strings. |
Monitor ownership error codes
Since the managed vs. self-service model, monitor write endpoints of all types (website, DNS, ICMP, SMTP, SSH, FTP, IMAP/POP, domain, DNSBL) can return these 403 codes in data.code:
| Code | Meaning |
|---|---|
managed_by_organization | A customer-scoped caller tried to edit/delete a managed monitor without the canEditManaged exception. Open a change request instead. |
selfServiceNotAllowed | A customer-scoped caller tried to create a monitor but the customer's resolved allowSelfService is false. |
selfServiceQuotaReached | Creating (or flipping to) a self_service monitor would exceed the customer's maxSelfServiceUrls: the quota counts all monitor types together. |
managementTypeOrgOnly | A non-org-admin tried to change a monitor's managementType. Class flips are organization-admin-only. |
tooManyOpenRequests | (429) The customer already has 10 open change requests. |
Customer package error codes
POST /api/customers and PATCH /api/customers/:customerPublicId resolve the package a customer is on. Both reject a package the organization does not have:
| Code | Meaning |
|---|---|
invalidPackageType | (400) The packageId does not belong to this organization, or the legacy packageType matched neither a configured package key nor a package display name of this organization. |
PATCH previously stored an unknown packageType verbatim and left packageId empty. Such a customer was attached to no package config at all, which left its data retention unresolvable. Send packageId, or a packageType that exists on the organization.
Check mode error codes (SSH/SMTP/FTP/IMAP-POP monitors)
The credentialed monitor kinds (SSH, SMTP, FTP, IMAP/POP) support a checkMode field (protocol | tcp, default protocol). In tcp mode the monitor performs a bare TCP port-reachability check and no credentials are required or stored. Create and update endpoints for these four kinds can return these 400 codes in data.code:
| Code | Meaning |
|---|---|
portRequiredForTcp | IMAP/POP create or update: checkMode is (or resolves to) tcp, but no port is set on the request or already stored on the monitor. Only IMAP/POP requires an explicit port for tcp mode. SSH, SMTP, and FTP fall back to their protocol default port. |
invalidCheckMode | Update (PATCH) endpoints only: checkMode was provided but is neither protocol nor tcp. |
Incident error codes
Only manual (status-page) incidents can be deleted. DELETE /api/incidents/:id returns this data.code:
| Code | Meaning |
|---|---|
notManualIncident | (403) The incident is monitor-generated. Only manual incidents created by an admin can be deleted; automatic incidents are worker-owned and are never deletable via the API. |
Maintenance window error codes
POST /api/maintenance-windows/preview-occurrences (docs) can return this data.code:
| Code | Meaning |
|---|---|
invalidWindow | (400) endTime is not after startTime. |
Organization Reports error codes
Endpoints under /api/organization/reports and /api/organization/report-runs can return these data.code values:
| Code | Meaning |
|---|---|
invalidReportSchedule | A weekly report is missing weekday, or a monthly report is missing dayOfMonth (1-28). |
invalidReportScope | Report scope ids are not owned by the organization, or threshold mode has no threshold set. |
reportRunNotFound | The requested report run does not exist for this organization. |
reportRunNoPdf | The report run has no archived PDF (it was an email-only report or was skipped). |
Data export error codes
Endpoints under /api/organization/data-export (see Request Data Export) can return these data.code values:
| Code | Meaning |
|---|---|
invalidExportRecipient | (400) recipientEmails contains an address that is not a member of this organization. The rejected addresses are listed in data.unknown. |
exportAlreadyRunning | (409) An export of this organization is already queued or generating. The running export is in data.export. |
exportCooldown | (429) An export was requested within the cooldown window (data.cooldownHours, default 6). |
exportNotReady | (409) Download was requested while the export is queued, generating or failed. Current state in data.status. |
exportExpired | (410) The 7-day download window has closed and the file has been deleted. |
missingExportId | (400) The route was called without an export id. |
Agent-auth error codes
Read-only agent access tokens (wsma_, see Agent authentication) are hard-gated to a GET/HEAD-only allowlist. Any write, or any path outside the allowlist, returns this data.code:
| Code | Meaning |
|---|---|
agentTokenReadOnly | (403) An agent access token (wsma_) attempted a write, or a path outside its read-only allowlist. Agent tokens are read-only. |
Claim flow error codes
The service_auth registration and claim ceremony (see Claim flow) can return these error / data.code values, in addition to the base agent-auth codes above:
| Code | Meaning |
|---|---|
service_auth_not_enabled | (400) POST /agent/identity: the service_auth identity type is not enabled on this deployment. |
invalid_claim_token | (400 at POST /agent/identity/claim and POST /api/agent-claim/confirm, 404 at GET /api/agent-claim/context) The claim token, claim-attempt token, or user_code is unknown/expired/wrong-type, the signed-in user is not the bound email, or the confirming user's access cannot be resolved to a single organization/customer scope. |
claimed_or_in_flight | (409) POST /agent/identity/claim: the registration is already claimed. |
claim_expired | (400) POST /agent/identity/claim: the outer 24h claim window (from registration creation) has elapsed. |
authorization_pending | (400) POST /oauth2/token with the claim grant: the user has not confirmed yet; keep polling. |
slow_down | (400) POST /oauth2/token with the claim grant: polled faster than the returned interval (5s); back off. |
expired_token | (400) POST /oauth2/token with the claim grant: the claim token/registration expired, was revoked, or was already redeemed. |
Connected apps (OAuth connections) error codes
GET /api/oauth/connections and DELETE /api/oauth/connections/:clientId (see Connected apps) can return these data.code values, in addition to the standard 401 unauthorized:
| Code | Meaning |
|---|---|
missingClientId | (400) DELETE only: the :clientId path parameter is missing. |
unknownOauthConnection | (404) DELETE only: the caller has no OAuth connection for that client_id (already revoked, expired, or never connected). |
Status page subscriber error codes
List, delete, and export endpoints for status page subscribers can return these data.code values, in addition to the standard 401 unauthorized and the two-tier statusPageNotFound behavior described on each page (a malformed or unknown :id is a code-less 400/404 from public-ID resolution; only an :id that resolves but fails the organization/customer-scope check carries data.code: statusPageNotFound):
| Code | Meaning |
|---|---|
invalidSubscriberId | (400, delete only) :subscriberId does not parse as a positive integer. |
subscriberNotFound | (404, delete only) :subscriberId does not exist, or belongs to a different status page than :id. |
Public IDs in DNS and DNSBL monitor routes
The following path endpoints support public IDs in docs and client integrations:
GET /api/customer-domains/:customerDomainPublicIdPATCH /api/customer-domains/:customerDomainPublicIdDELETE /api/customer-domains/:customerDomainPublicIdGET /api/customer-ips/:customerIpPublicIdPATCH /api/customer-ips/:customerIpPublicIdDELETE /api/customer-ips/:customerIpPublicId
Legacy numeric IDs are still accepted for backward compatibility, but public IDs are the recommended integration format.
Incident Management (IM) API error codes
The public Incident Management API (POST /api/im/events, GET/POST /api/im/incidents/**, GET /api/im/teams, GET/POST /api/im/schedules/**, GET /api/im/on-call) requires an organization-wide API token and can return these data.code values:
| Code | Meaning |
|---|---|
imAccessDenied | (403) The caller is not allowed into Incident Management: no session/token, a customer-scoped API token, a customer (readonly) or globalsupporter login, or an org login without an IM-eligible role (admin, editor, responder). |
imNotEnabled | (403) Incident Management has not been activated for this organization. |
payloadTooLarge | (413, Events Ingest only) The request body exceeds 256 KB. |
invalidJson | (422, Events Ingest only) The request body is not valid JSON. |
payloadTooDeep | (422, Events Ingest only) The parsed JSON payload nests more than 20 levels deep. |
imIncidentNotFound | (404) The incident does not exist, or belongs to another organization. |
imIncidentInvalidStatusTransition | (422, Acknowledge / Update Incident Status) The requested status is not a valid target for this endpoint (resolved and merged are not, use Resolve Incident instead), the incident's current status cannot transition through this endpoint, or status equals the incident's current status. |
imIncidentAlreadyClosed | (422, Resolve Incident) The incident is already resolved or merged. |
imIncidentStatusConflict | (409, status/resolve endpoints) The incident's status changed concurrently between the read and the write. Safe to retry. |
imTeamWriteDenied | (403, Create Incident, Schedules, Schedule Overrides) The caller's role is not admin and they are not a team admin (im_team_member.imRole = 'admin') of the target team. |
imTeamNotFound | (404, Schedules create) teamId does not exist, or belongs to another organization. |
imScheduleNotFound | (404, Schedule Overrides) :id does not exist, or belongs to another organization. |
invalidTeamId | (422, Create Incident) teamId does not belong to your organization. |
invalidCustomerId | (422, Create Incident) customerId is given but does not belong to your organization. |
imRoutingInvalidPolicyId | (422, Create Incident) escalationPolicyId is given but does not belong to your organization. |
userNotFound | (404, Schedule Overrides add) userId does not belong to your organization. |
GET /api/im/incidents, Create Incident, the status/resolve endpoints, Schedules create, and Schedule Overrides add can also return the generic 400 invalidRequestBody for malformed input (e.g. an unknown status filter value, a non-integer :id, a missing/malformed rotation layer). POST /api/im/events can additionally return 429 Too Many Requests (600 requests/minute per organization, with a retryAfter field but no data.code), or 503 with data.code: unavailable if the event could not be queued, safe to retry.
MFA error codes
These error codes are specific to two-step verification (MFA) and can be returned by otherwise unrelated endpoints once the underlying gate applies:
| Code | Status | Meaning |
|---|---|---|
mfa_enrollment_required | 403 | Returned by any gated /api/* call when the caller (a platform admin/supporter when MFA_ENFORCE_PLATFORM_ADMINS is on, or a member of an organization whose matching per-role switch requireMfaAdmins, requireMfaEditors or requireMfaReadonly is on) has not enabled two-step verification yet. Set up two-step verification, then retry the request. |
mfa_step_up_required | 403 | Returned only by API token mutations (POST, PATCH and DELETE on /api/organization/tokens, and POST and DELETE on /api/customer/tokens; customer-scoped tokens cannot be updated) and only when the caller has two-step verification enabled: the caller has not verified a code in the last 5 minutes. Re-verify a code, then retry within the 5-minute window. Callers without MFA enabled never see this code; creating and revoking tokens works unchanged for them. |
mfa_step_up_locked | 429 | Returned by the internal, session-only step-up endpoint (POST, /api/account/mfa/step-up, not part of the public API surface) after 5 wrong codes from the same account. Locked for 15 minutes (data.retryAfterSeconds); wait and re-verify with your authenticator once it elapses. |
nonUserSessionForbidden | 403 | Returned by POST /api/users/:id/mfa/reset for any API token or agent access token caller, and by PATCH /api/organization(s) when the request body includes requireMfaAdmins, requireMfaEditors or requireMfaReadonly and the caller is an API token or agent access token. Both MFA-administration actions require a real, logged-in user session, an organization-scoped API token with admin-level access is not enough. |
These codes are data.code values on the JSON error body, alongside the listed HTTP status.
VAT ID validation error codes
POST /api/organizations/:organizationPublicId/vat-id/validate (see Validate VAT ID) can return these data.code values, in addition to the standard 401 unauthorized, 403 forbidden and 404 organizationNotFound:
| Code | Status | Meaning |
|---|---|---|
vatIdMissing | 422 | The organization has no vatId stored. Set one first with Update Organization. |
vatIdChangedDuringValidation | 409 | vatId or countryCode was edited concurrently while the EU VIES lookup was in flight. The in-flight verdict is discarded rather than written to data nobody actually checked. Safe to retry. |
Update Billing Details: required body
Endpoint:
PATCH /api/organizations/:organizationPublicId/billing
Currently supported request body:
{
"billingEmail": "billing@deinkunde.com"
}This endpoint currently accepts only billingEmail.