Skip to main content

Error Codes

Every 4xx and 5xx response from the Media Agency API carries the canonical JSON envelope with a stable machine-readable code. Clients should branch on error.code (not on the English error.message or the raw HTTP status).

Envelope shape

{
"error": {
"code": "listing_not_found",
"message": "No listing matches that identifier.",
"request_id": "req_01HX5Y7Z2M3N4P5Q6R7S8T9U0V",
"details": {
"listing_number": "ABC123"
}
}
}
  • code - short snake_case identifier; switch on this.
  • message - human-readable one-liner; safe to surface to end users.
  • request_id - mirrors the X-Request-Id response header; include it in every support ticket.
  • details - optional structured context; empty object when not populated.

Codes

Authentication

CodeHTTPSummaryRemediation
auth.missing_key401No Authorization header, or malformed Bearer token format.Send Authorization: Bearer ma_live_ak_<ULID>. Format is documented at /api-reference/authentication.
auth.invalid_key401API key not recognised, revoked, or past expiry.Rotate from Settings > API Keys. Deleted keys cannot be recovered; mint a fresh key and update your secrets store.
auth.scope_denied403Key authenticated but lacks the scope for this endpoint.Mint a new key with the required scope, or contact support if your plan does not include the requested surface.

Idempotency

CodeHTTPSummaryRemediation
idempotency.missing400Mutating request without X-Idempotency-Key header.Add X-Idempotency-Key: <UUIDv4 or ULID> to every POST and PATCH. Keys are scoped per-agency for 24 hours.
idempotency_key_conflict409Same key reused with a different body within 24h.Use a fresh key for the new body, or replay the exact original body to get the cached response back.
idempotency_key_in_progress409Replay while the first call with this key is still in flight.Wait for the first request to complete, then retry. The server serializes in-flight replays to avoid double-writes.

Listing Lifecycle

CodeHTTPSummaryRemediation
listing_not_found404No listing matches that identifier (also used for cross-agency reads).Verify the listing_number and confirm the API key belongs to the owning agency. Cross-agency reads always return 404.
endpoint_not_in_scope404URL under /api/v1/listings/ that does not match a public endpoint.Consult /api-reference/endpoints for the 10 supported routes.
listing_not_activated409Status transition attempted before activation / credit capture.Complete activation in the dashboard at /property/{listing_number}/edit before retrying the status PATCH.
no_op_transition409new_status equals the current status; no change was performed.Read the current status via GET /listings/{listing_number} before PATCHing. Duplicated transitions are refused loudly.
outside_cancellation_window409Cancel attempted past the no-fee cutoff window.Route the cancellation through the dashboard so a human reviewer can decide whether to waive the fee.
listing_already_cancelled409Cancel attempted on a listing already in the cancelled state.Second cancels are rejected to prevent duplicate webhook emission. Read the current status before retrying.
section_not_exposed404PATCH targeted a section not exposed via the public API.Only matterport and floorplan sections are mutable via the public API in v1. Other sections must go through the UI.
invalid_section_payload422Section payload parsed but failed a higher-level semantic rule.Inspect error.details for the offending field and reason. Common cause: operation='set' without a url field.

Users

CodeHTTPSummaryRemediation
user_not_found404No user with that id (also used for cross-agency reads).Verify the user_id and confirm the API key's agency owns that user. Cross-agency reads always return 404.

Rate Limiting

CodeHTTPSummaryRemediation
rate_limit.exceeded429Per-key request budget exhausted for the current window.Honour the Retry-After header with exponential jitter. See /api-reference/rate-limits for per-endpoint budgets.

Validation

CodeHTTPSummaryRemediation
validation.failed422Request body failed Pydantic schema validation.Consult error.details.fields for the per-field failure reasons and correct the payload before retrying.

Placeholders

CodeHTTPSummaryRemediation
not_implemented501Endpoint reserved for a future wave; URL is live but body is not.Check the changelog at /api-reference/changelog for the planned ship date of the section referenced in details.

Server

CodeHTTPSummaryRemediation
server.internal500Unexpected server error. Includes request_id for support escalation.Safe to retry with the SAME X-Idempotency-Key. If the error persists, file a support ticket quoting the request_id.

Security non-negotiable: 404 hides 403

Cross-agency reads always return 404 listing_not_found or 404 user_not_found, never 403. Returning 403 would confirm that a given id belongs to a different agency, enabling id enumeration across the tenant boundary. This is deliberate; do not report it as a bug.

Source of truth

The canonical registry lives at dashboard/fastapi_backend/app/api/v1/errors.py. This page is auto-generated from that registry - do NOT hand-edit. Add a new code to the Python module and re-run node docs/scripts/generate-all.mjs; the CI drift-guard at .github/workflows/docs.yml blocks PRs whose generated output does not match disk.