Error reference

Nines follows RFC 7807 — Problem Details for HTTP APIs. Error responses use the application/problem+json media type and include a type URI that dereferences to this page. Each section below documents one problem type: when it fires, the shape of the response, and how clients should recover.

Bad request

Type URI
https://nines.sh/docs/errors#bad_request
HTTP status
400

When it fires

The request body is unparseable, a required query parameter is missing or malformed, or the Content-Type does not match what the endpoint expects.

Example response

{
  "type": "https://nines.sh/docs/errors#bad_request",
  "title": "Bad request",
  "status": 400,
  "detail": "invalid JSON body",
  "instance": "/settings/api-keys"
}

Client recovery

Inspect the detail field. Fix the client — for example, send valid JSON, include the required query parameter, or set the correct Content-Type — and retry.

Validation failed

Type URI
https://nines.sh/docs/errors#validation
HTTP status
422

When it fires

One or more field values failed business-rule validation (e.g., required field omitted, value out of range, invalid format).

Errors array

The errors array contains one entry per offending field with the field name, a machine-readable code (e.g., required, too_low, invalid_url), and a human-readable message.

Example response

{
  "type": "https://nines.sh/docs/errors#validation",
  "title": "Invalid request",
  "status": 422,
  "detail": "One or more fields failed validation",
  "instance": "/settings/api-keys",
  "errors": [
    {"field": "name", "code": "required", "message": "name is required"}
  ]
}

Client recovery

Iterate the errors array, map each field back to its form input, and present the message to the user. Resubmit once the fields are corrected.

Unauthorized

Type URI
https://nines.sh/docs/errors#unauthorized
HTTP status
401

When it fires

The request lacks valid credentials — missing, expired, or revoked session cookie or API key.

Example response

{
  "type": "https://nines.sh/docs/errors#unauthorized",
  "title": "Unauthorized",
  "status": 401,
  "detail": "Authentication is required to access this resource",
  "instance": "/settings/api-keys"
}

Client recovery

Redirect the user to sign in again, or refresh the API key. Do not retry blindly — the same request will keep failing until credentials are provided.

Forbidden

Type URI
https://nines.sh/docs/errors#forbidden
HTTP status
403

When it fires

The caller is authenticated but lacks permission to perform this action on this resource (e.g., editing a monitor owned by another organization).

Example response

{
  "type": "https://nines.sh/docs/errors#forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "you cannot modify this monitor",
  "instance": "/monitors/abc123"
}

Client recovery

Show the detail message to the user. This is not a retryable error — the client should not attempt the same operation again with the same credentials.

Not found

Type URI
https://nines.sh/docs/errors#not_found
HTTP status
404

When it fires

The requested resource (monitor, incident, API key, etc.) does not exist, or has been deleted, or is owned by a different organization.

Example response

{
  "type": "https://nines.sh/docs/errors#not_found",
  "title": "Not found",
  "status": 404,
  "detail": "monitor not found",
  "instance": "/monitors/abc123"
}

Client recovery

Remove the resource from the client-side list or cache, then return the user to a valid view (index, dashboard, etc.).

Conflict

Type URI
https://nines.sh/docs/errors#conflict
HTTP status
409

When it fires

The request collides with existing state — for example, the slug is already taken, the email is already registered, or the resource was modified concurrently.

Example response

{
  "type": "https://nines.sh/docs/errors#conflict",
  "title": "Conflict",
  "status": 409,
  "detail": "email already in use",
  "instance": "/register"
}

Client recovery

Show the detail message to the user. If the conflict is correctable (e.g., pick a different slug), prompt for a new value and retry; otherwise surface the error.

Quota exceeded

Type URI
https://nines.sh/docs/errors#quota_exceeded
HTTP status
403

When it fires

The operation would exceed a plan-based limit — for example, creating more monitors than the current plan allows, or using features gated behind a higher tier.

Example response

{
  "type": "https://nines.sh/docs/errors#quota_exceeded",
  "title": "Quota exceeded",
  "status": 403,
  "detail": "monitor limit reached for the Free plan",
  "instance": "/monitors"
}

Client recovery

Offer the user a path to upgrade their plan, or free up capacity by removing existing resources. Do not retry — the limit will still be in effect.

Too many requests

Type URI
https://nines.sh/docs/errors#too_many_requests
HTTP status
429

When it fires

The caller exceeded a per-IP or per-user rate limit (for example, too many password reset requests in a short window). Applies equally to HTML and JSON clients.

Example response

{
  "type": "https://nines.sh/docs/errors#too_many_requests",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many password reset requests. Please wait and try again.",
  "instance": "/forgot-password"
}

Client recovery

Back off and retry later. Clients should respect the rate limit — hammering the endpoint will continue to fail.

Payload too large

Type URI
https://nines.sh/docs/errors#payload_too_large
HTTP status
413

When it fires

The request body exceeds the per-endpoint size limit (for example, posting more than 256 KB to the worker check-result ingest endpoint or more than 64 KB to a JSON CRUD endpoint).

Example response

{
  "type": "https://nines.sh/docs/errors#payload_too_large",
  "title": "Payload too large",
  "status": 413,
  "detail": "request body exceeds the 65536-byte limit for this endpoint",
  "instance": "/api/v1/notification-channels"
}

Client recovery

Trim the payload to fit the documented limit. The detail field reports the exact byte cap; do not retry the same body — it will keep failing.

Internal server error

Type URI
https://nines.sh/docs/errors#internal
HTTP status
500

When it fires

An unexpected server-side failure (database outage, bug, exhausted resources). The root cause is intentionally not surfaced to clients to avoid leaking internals.

Example response

{
  "type": "https://nines.sh/docs/errors#internal",
  "title": "Internal server error",
  "status": 500,
  "detail": "The server encountered an unexpected error",
  "instance": "/monitors"
}

Client recovery

Retry the request after a short backoff (1s, 2s, 4s). If it continues to fail, surface a generic error to the user and alert your team. Nines also logs these server-side with a trace id.

Device authorization pending

Type URI
https://nines.sh/docs/errors#device_authorization_pending
HTTP status
400

When it fires

RFC 8628 device-code flow: the CLI polled POST /oauth/token but the user has not yet approved the request.

Example response

{
  "type": "https://nines.sh/docs/errors#device_authorization_pending",
  "title": "authorization_pending",
  "status": 400,
  "detail": "the user has not yet approved the request",
  "instance": "/oauth/token"
}

Client recovery

Keep polling at the advertised interval until the user approves, denies, or the grant expires. Do not poll faster or a slow_down error will be returned.

Slow down

Type URI
https://nines.sh/docs/errors#device_slow_down
HTTP status
400

When it fires

RFC 8628 device-code flow: the CLI polled POST /oauth/token faster than the advertised interval.

Example response

{
  "type": "https://nines.sh/docs/errors#device_slow_down",
  "title": "slow_down",
  "status": 400,
  "detail": "polling too fast; slow down",
  "instance": "/oauth/token"
}

Client recovery

Increase the poll interval by at least 5 seconds and continue polling. Repeated slow_down responses indicate the client must back off further.

Device token expired

Type URI
https://nines.sh/docs/errors#device_expired_token
HTTP status
400

When it fires

RFC 8628 device-code flow: the device_code expired before the user approved, or it has already been exchanged once.

Example response

{
  "type": "https://nines.sh/docs/errors#device_expired_token",
  "title": "expired_token",
  "status": 400,
  "detail": "device_code has expired",
  "instance": "/oauth/token"
}

Client recovery

Restart the flow: request a new device_code via POST /oauth/device/code and prompt the user again.

Device access denied

Type URI
https://nines.sh/docs/errors#device_access_denied
HTTP status
400

When it fires

RFC 8628 device-code flow: the user clicked Deny on the approval page.

Example response

{
  "type": "https://nines.sh/docs/errors#device_access_denied",
  "title": "access_denied",
  "status": 400,
  "detail": "the user denied the authorization request",
  "instance": "/oauth/token"
}

Client recovery

Stop polling. Prompt the user to try again from the start if they changed their mind.

Device invalid grant

Type URI
https://nines.sh/docs/errors#device_invalid_grant
HTTP status
400

When it fires

RFC 8628 device-code flow: the token request is missing device_code or grant_type is not urn:ietf:params:oauth:grant-type:device_code.

Example response

{
  "type": "https://nines.sh/docs/errors#device_invalid_grant",
  "title": "invalid_request",
  "status": 400,
  "detail": "device_code is required",
  "instance": "/oauth/token"
}

Client recovery

Fix the request body. grant_type must be the RFC 8628 URN and device_code must be a non-empty string returned from POST /oauth/device/code.

Need more help? Contact support or head back to the main documentation.