/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.
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 distinguishable403
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 return200, 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 carryX-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 carriesquotaand, 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}withhide_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.
