Skip to main content
Every non-success response from /api/v1 is an RFC 7807 problem detail, served with Content-Type: application/problem+json:
Some problems carry extra fields, listed with each problem below. A 429 carries retry_after (seconds) and a Retry-After header; wait that long before retrying.
Match on title, not detaildetail exists to be read by a person looking at a failed request. It interpolates runtime values and is reworded freely. Client logic that switches on detail will break; logic that switches on title will not.

Handling errors

JavaScript

Status codes

401, 403, 429 and 500 can come back from every endpoint. The rest are per-endpoint; each operation page lists the ones it can return.

On 404

A missing object and an object owned by another organization return the identical response. This is deliberate: a distinguishable 403 would let a caller enumerate which IDs exist in other tenants. Treat 404 as “not available to this key” rather than “does not exist”. An ID in the path that is not a well-formed uuid gets the same 404 as an unknown one. Only endpoints addressing a specific object return 404. Collection endpoints such as GET /services return 200 with an empty array when there is nothing to list.

Success

Successful calls return 200, including most creates, with these exceptions: The API does not use 204. Every DELETE returns 200 with a body.

Rate limiting

Limits are counted per organization, across every API key it owns: a per-minute limit and a daily quota set by the plan. Responses carry X-RateLimit-* headers for the minute and X-RateLimit-Daily-* headers for the day, and a 429 carries a Retry-After header. Read the ceilings from the headers rather than hard-coding a number, and back off using retry_after rather than a fixed interval. See Rate limits for the plan table and every header.

Problem reference

unauthenticated

401. No bearer token was sent, or the key is invalid or revoked. detail says which.

forbidden

403. The key is valid but lacks the scope the endpoint requires. detail names the missing scope.

plan_does_not_permit_api

403. The organization’s plan does not include the public API. The key stays valid: upgrading restores access without rotating it. When a plan with API access exists, the body carries an upgrade object.

feature_locked

403. The organization’s plan does not include the feature behind the endpoint. Returned by:
  • the SLA statement endpoints, when the plan does not include the SLA Ledger;
  • POST /agents, when the plan includes no agents. The body also carries quota and, when a plan with agents exists, upgrade;
  • GET /audit-log, when the plan does not include audit log export (Business and Enterprise);
  • GET /incidents/{id}/evidence, when the plan does not include the signed evidence export (Business and up);
  • PATCH /pages/{id} with hide_branding: true, when the plan does not include removing Observer branding. Extra field: errors, a list of { path, message }.

quota_exceeded

403. POST /agents only: the organization already has as many agents as its plan allows. Extra fields: quota (capability, used, cap, plan) and upgrade when a larger plan exists. Delete an agent that is no longer needed, or upgrade. GET /me reports usage and limits before you try.

item_suspended_plan

403. The request changes an item that is suspended because the organization has more of that kind than its plan allows (after a downgrade). Nothing was deleted. Returned by PATCH /pages/{id} and POST /config/apply. Extra fields, when known: quota, upgrade, and errors ({ path, message }). Choose which items stay active in the console under billing, or upgrade.

rate_limited

429. The per-minute limit or the daily quota is used up, or, on POST /metrics/test, too many probe tests were started. Extra fields: retry_after (seconds), and on the API limits limit (scope, window of minute or day, max, plan) and upgrade when a larger plan exists. See Rate limits.

queue_full

429. POST /metrics/test: the agent already has 20 jobs waiting. Extra field: retry_after (10 seconds), also sent as Retry-After. Retry after that, once the agent has worked through its queue.

internal_error

500. An unhandled server error. Safe to retry.

unavailable

503. SLA statements are not available yet. Retry later.

claim_unavailable

503. POST /agents or POST /agents/{id}/rotate-key with delivery: "claim": claim links are not available on this deployment. Omit delivery to get the key in the response.

probe_test_failed

502. POST /metrics/test or GET /metrics/test/{job_id}: the agent could not run the test. Extra fields: job_id, agent_id, and reason.

probe_test_expired

504. The agent did not answer before the test expired (2 minutes). Check that it is online and retry. Extra fields: job_id and agent_id.

not_found

404. The object does not exist or belongs to another organization.

metric_not_found

404. The metric named in the path does not exist or belongs to another organization.

agent_not_found

404. POST /metrics/test: the agent_id does not exist or belongs to another organization.

invalid_json

400. The body could not be read, is not valid JSON, or is not a JSON object. Every endpoint that takes a body returns this, including POST /config/apply, POST /change-events and PATCH /pages/{id}.

invalid_body

400. POST /incidents/{id}/notes: the note’s body field is missing or longer than 20,000 characters.

invalid_request

400. A request field failed validation. detail names the field.

invalid_title

400. The title is missing or blank when creating an incident or maintenance, or is not a non-empty string on PATCH /incidents/{id} or PATCH /maintenances/{id}.

invalid_initial_message

400. POST /incidents: initial_message is not an object, its type is not one of the message types, or its description is not a string. Nothing is created.

invalid_severity

400. severity is not one of the accepted values.

invalid_postmortem_url

400. postmortem_url is not an http or https URL.

invalid_type

400. A message type is not one of the accepted values.

invalid_description

400. A message description is missing or invalid, or an agent description on POST /agents is longer than 500 characters.

invalid_note

400. The note on a manual metric status is neither a string nor null.

invalid_status

400. A status value is not one of the accepted values: the status filter on GET /metrics (healthy, degraded, unhealthy, no_data, unknown), the status set on a manual metric (healthy, degraded, unhealthy, no_data), or the status on an incident draft request.

invalid_window

400. A time window parameter is malformed or out of range.

missing_window

400. A required time window is missing.

missing_param

400. A required query parameter is missing.

invalid_range

400. The requested time range is malformed, or its end is before its start.

invalid_resolution

400. The requested history resolution is not supported.

range_too_large

400. The requested time range exceeds the maximum for the endpoint.

invalid_period

400. period on GET /sla/statements must be YYYY-MM.

invalid_customer_id

400. customer_id on GET /sla/statements must be a uuid.

invalid_state

400. A state filter is not one of the accepted values: on GET /incidents and GET /maintenances it must be open, resolved or all; on GET /sla/statements it must be a statement state.

invalid_since

400. since on GET /incidents, GET /maintenances or GET /audit-log is not an ISO 8601 date (2026-01-31) or date-time (2026-01-31T12:00:00Z), or names an impossible date or time. A date-time without an offset is read as UTC.

invalid_until

400. until on GET /audit-log is not an ISO 8601 date or date-time.

invalid_action

400. action on GET /audit-log is not an audit action such as page.access_changed, or a prefix ending in ..

invalid_cursor

400. cursor on a list endpoint is not a next_cursor value that endpoint returned. Omit cursor to start from the first page.

invalid_name

400. POST /agents: name is missing, empty, or longer than 120 characters.

invalid_prometheus_url

400. POST /agents: prometheus_url is not an http or https URL.

invalid_invalidate_immediately

400. POST /agents/{id}/rotate-key: invalidate_immediately is not a boolean.

invalid_delivery

400. POST /agents or POST /agents/{id}/rotate-key: delivery is not plaintext or claim.

invalid_include

400. include on an agent read is not install.

invalid_target

400. target on an agent read with include=install is not one of the install targets (docker, compose, kubernetes, systemd, launchd, binary).

invalid_source

400. POST /incidents/{id}/notes: source (the tool that produced the note) is missing or longer than 80 characters.

invalid_agent_id

400. POST /metrics/test: agent_id is not an agent’s uuid.

invalid_source_type

400. POST /metrics/test: source_type is missing.

unsupported_source_type

400. POST /metrics/test: the source type cannot be tested.

invalid_probe_config

400. POST /metrics/test: source_config is not an object, or fails validation for its source type. Extra field, when the config failed validation: errors, a list of { path, message }.

invalid_thresholds

400. POST /metrics/test: thresholds is not an object with healthy_operation, healthy_value, unhealthy_operation and unhealthy_value.

invalid_wait_seconds

400. POST /metrics/test: wait_seconds is not an integer from 0 to 20.

invalid_customer_targeting

400. customer_targeting is malformed: an unknown mode, customers missing or empty for selected, customers present for none or all, more than 500 entries, or an unknown field. Extra field: errors, a list of { path, message }.

already_published

409. The incident is already published.

already_resolved

409. The incident is already resolved.

already_started

409. The maintenance has already started.

already_completed

409. The maintenance has already completed.

already_canceled

409. The maintenance has already been canceled.

canceled

409. The maintenance was canceled, so it cannot start or complete.

not_started

409. The maintenance has not started yet, so it cannot complete.

not_manual_metric

409. The metric is agent-driven; only manual metrics accept a status through the API.

not_config_managed

409. Per-object DELETE on a metric, service, SLO, or status page that was made in the console. The API deletes only objects that config apply manages (managed_by: config). Delete it in the console, or apply a document that names it first, which adopts it.

in_use

409. Per-object DELETE on an object that others depend on: SLOs on a metric or service, status pages showing a metric, or active subscribers of a page. Deleting it would delete or detach them too. Extra field: referenced_by, which lists the dependents (slos and pages as { id, config_key, name }, and subscribers as a count). Remove the references first, or repeat the request with cascade=true.

custom_domain_attached

409. DELETE /pages/{id}: the page has a custom domain. Remove the domain in the console first.

note_limit_reached

409. POST /incidents/{id}/notes: the incident already holds 500 notes.

demo_readonly

409. PATCH /pages/{id} on a demo page. Demo data is read-only: remove it in the console, then create your own.

agent_unavailable

409. POST /metrics/test: no agent can run the test right now. The agent is offline, has never connected, is older than 1.7.0, or has a revoked key, or no agent_id was given for a source only an agent can run. Extra fields: reason, agent_id, min_agent_version, and private_url: true when the target is not public.

payload_too_large

413. The request body is too large: the config document on POST /config/apply exceeds 1 MB, or the request on POST /incidents/{id}/notes exceeds 64 KB.

config_invalid

422. The config document parsed but failed validation. Nothing is applied. Extra fields: errors, a list of { path, message }, and warnings, in the same shape (see POST /config/apply).

invalid_page_settings

422. PATCH /pages/{id}: the page settings failed validation, or name a theme preset that does not exist. Extra field: errors, a list of { path, message }.

access_mode_console_only

422. PATCH /pages/{id}: access_mode can only be switched to public through the API. Other modes need credentials or an allow-list; set them in the page editor in the console. Extra field: errors.

invalid_change_event

422. The change event failed validation: for example occurred_at is not a valid timestamp, or service_id is not a service’s uuid. detail names the problem.

unknown_services

422. An affected_services value on POST /incidents, PATCH /incidents/{id}, POST /maintenances or PATCH /maintenances/{id} matches no service in your organization. Nothing is written. Extra field: unknown_services, which echoes only the values you sent that did not match. A service of another organization is reported the same way as one that does not exist.

unknown_customers

422. A customer_targeting.customers or visible_to_customer_ids value matches no customer in your organization. Extra field: unknown_customers, which echoes only the values you sent that did not match. A customer of another organization is reported the same way as a value that does not exist.

customer_targeting_conflict

422. The update is customer-scoped (visible_to_customer_ids is set), so customer_targeting must be selected with exactly those customers. Omit customer_targeting to keep the two in sync automatically.