---
title: "Error codes and known API pitfalls"
description: "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](/monitoring/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](/api/change-requests) 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](/docs/api/maintenance-windows/preview-occurrences)) 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](/api/organization/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](/api/agent-auth)) 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](/api/agent-auth/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](/api/oauth-connections)) 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](/api/status-pages/list-subscribers), [delete](/api/status-pages/delete-subscriber), and [export](/api/status-pages/export-subscribers) 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/:customerDomainPublicId`
- `PATCH /api/customer-domains/:customerDomainPublicId`
- `DELETE /api/customer-domains/:customerDomainPublicId`
- `GET /api/customer-ips/:customerIpPublicId`
- `PATCH /api/customer-ips/:customerIpPublicId`
- `DELETE /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](/api/incident-management) (`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](/api/incident-management/events-ingest) only) The request body exceeds 256 KB. |
| `invalidJson` | (`422`, [Events Ingest](/api/incident-management/events-ingest) only) The request body is not valid JSON. |
| `payloadTooDeep` | (`422`, [Events Ingest](/api/incident-management/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](/api/incident-management/acknowledge-incident)) The requested `status` is not a valid target for this endpoint (`resolved` and `merged` are not, use [Resolve Incident](/api/incident-management/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](/api/incident-management/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](/api/incident-management/create-incident), [Schedules](/api/incident-management/schedules), [Schedule Overrides](/api/incident-management/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](/api/incident-management/schedules) create) `teamId` does not exist, or belongs to another organization. |
| `imScheduleNotFound` | (`404`, [Schedule Overrides](/api/incident-management/schedule-overrides)) `:id` does not exist, or belongs to another organization. |
| `invalidTeamId` | (`422`, [Create Incident](/api/incident-management/create-incident)) `teamId` does not belong to your organization. |
| `invalidCustomerId` | (`422`, [Create Incident](/api/incident-management/create-incident)) `customerId` is given but does not belong to your organization. |
| `imRoutingInvalidPolicyId` | (`422`, [Create Incident](/api/incident-management/create-incident)) `escalationPolicyId` is given but does not belong to your organization. |
| `userNotFound` | (`404`, [Schedule Overrides](/api/incident-management/schedule-overrides) add) `userId` does not belong to your organization. |

`GET /api/im/incidents`, [Create Incident](/api/incident-management/create-incident), the status/resolve endpoints, [Schedules](/api/incident-management/schedules) create, and [Schedule Overrides](/api/incident-management/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](/api/organization/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](/api/organization/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:

```json
{
  "billingEmail": "billing@deinkunde.com"
}
```

This endpoint currently accepts only `billingEmail`.

