Docs menu

Errors

Every error is an RFC 9457 problem document. Branch on its type and detail_key; show detail to people.

The problem document

HTTP/1.1 404 Not Found
Content-Type: application/problem+json

{
  "type": "https://officehero.io/errors/not-found",
  "title": "Not found",
  "status": 404,
  "detail": "Invoice not found.",
  "code": "not_found",
  "detail_key": "errors.integrations.invoices.not_found"
}
MemberAlwaysUse it to
typeyesDecide the category (see the table below). Stable.
titleyesNothing — a short label for the type. It is translated to your Accept-Language like detail (Nicht gefunden for de), so never compare it.
statusyesThe HTTP status, repeated.
detailyesShow a human what happened. It is translated to your Accept-Language and its wording can change, so never compare it.
detail_keyalmost alwaysBranch on the exact cause, e.g. errors.integrations.invoices.void_failed. Stable. Absent on a 405 (wrong method), on a rate-limit 429, on an unexpected 500 and on the two idempotency 409s, whose key is in detail.
codeusuallyA short token. On a 422 about one field it is that field's validation code (e.g. unknown_field); on other refusals the last segment of detail_key (not_found, endpoint_not_active, rate_limited).
field, errors[]validation onlyWhich fields were wrong: each entry has path, code, message.
missing_scope, missing_scopeson 403The scope your key needs; for a webhook subscription, every read scope it needs.
unknown_event_typeson 422 unknown_event_typeThe event types that are not in the catalogue.
flag_codeon 403 feature_disabledThe feature that is switched off (integrations.api.enabled).
retry_after_son rate-limit 429Seconds to wait (also the Retry-After header).

Error types

typeStatusMeaning
…/errors/validation 400, 422 The request was understood but something in it is not acceptable.
…/errors/unauthorized 401 No valid API key was sent.
…/errors/forbidden 403 The key is valid but may not do this.
…/errors/not-found 404 There is no such resource in your company.
…/errors/conflict 409 The request conflicts with the current state of the data.
…/errors/idempotency-conflict 409 The Idempotency-Key was already used for a different request.
…/errors/rate-limited 429 A rate limit or quota was reached.
…/errors/feature_disabled 403 The Integrations API is switched off for the key's company.
…/errors/method-not-allowed 405 The path exists but not with this HTTP method.
…/errors/gone 410 The requested OH-Version has passed its sunset date.
…/errors/internal 500 Something failed on OfficeHero's side.
…/errors/service-unavailable 503 A dependency is briefly unavailable.

detail_key values

The detail_key values this API answers with, grouped by resource. Read endpoints also answer …<resource>.invalid_cursor (422) for an unreadable cursor and …<resource>.not_found (404) for an unknown id. The Platform group holds keys from the layers every OfficeHero API shares — an id that is not a UUID, an unknown path, a reference to a record you do not have, Finance rules. A 405, a rate-limit 429 and an unexpected 500 carry no detail_key.

Platform

detail_keyStatusEnglish detail
errors.common.tenant_not_found404Tenant not found
errors.common.validation.failed422Some of the values you sent are not valid. Correct the fields marked with an error and send the form again.
errors.common.validation.invalid_input422That request wasn't valid — check the values you sent and try again.
errors.common.validation.invalid_reference422That references something that doesn't exist, or that you can't see.
errors.finance.validation.currency_not_enabled422That currency is not enabled for your organisation — enable it in currency settings or pick another.
errors.finance.validation.invalid_invoice_status_for_void422This invoice can no longer be voided in its current status
errors.idempotency.body_mismatch409This Idempotency-Key was already used with a different request body (sent as the raw key in detail).
errors.not_found404Resource not found
errors.platform.feature_flag.disabled403This feature isn't enabled for your plan
errors.platform.idempotency.replay_unavailable409The original request already completed but its response was too large to replay — read the resource instead (sent as the raw key in detail).

bills

detail_keyStatusEnglish detail
errors.integrations.bills.invalid_cursor422The pagination cursor is invalid or malformed.
errors.integrations.bills.not_found404Bill not found.

deliveries

detail_keyStatusEnglish detail
errors.integrations.deliveries.endpoint_not_active409Deliveries can only be replayed to an active endpoint. Resume the endpoint first.
errors.integrations.deliveries.invalid_since422The since parameter must be a valid ISO-8601 timestamp.
errors.integrations.deliveries.not_found404Delivery not found.
errors.integrations.deliveries.pii_not_granted403This delivery carries personal data and the endpoint is not approved to receive it. OfficeHero must enable the endpoint for personal data first — contact support.

documents

detail_keyStatusEnglish detail
errors.integrations.documents.invalid_cursor422The pagination cursor is invalid or malformed.
errors.integrations.documents.not_found404Document not found.

employees

detail_keyStatusEnglish detail
errors.integrations.employees.invalid_cursor422The pagination cursor is invalid or malformed.
errors.integrations.employees.not_found404Employee not found.

fuel logs

detail_keyStatusEnglish detail
errors.integrations.fuel_logs.invalid_cursor422The pagination cursor is invalid or malformed.
errors.integrations.fuel_logs.not_found404Fuel log not found.

Every endpoint

detail_keyStatusEnglish detail
errors.integrations.invalid_cursor422The pagination cursor is invalid or malformed.
errors.integrations.missing_scope403The API key does not have the scope required for this endpoint.
errors.integrations.unauthorized401Authentication required. Provide a valid API key.
errors.integrations.unsupported_version400The requested OH-Version is not supported.
errors.integrations.version_sunset410The requested OH-Version has been sunset. Migrate to a supported version.

invoices

detail_keyStatusEnglish detail
errors.integrations.invoices.create_failed422The invoice could not be created.
errors.integrations.invoices.invalid_body422This request could not be read. It must be valid JSON: an object or a list, no more than 63 levels deep and no larger than 2 MiB.
errors.integrations.invoices.invalid_cursor422The pagination cursor is invalid or malformed.
errors.integrations.invoices.line_invalid422One or more invoice lines are invalid.
errors.integrations.invoices.no_writable_fields422No writable fields were provided.
errors.integrations.invoices.not_found404Invoice not found.
errors.integrations.invoices.required422A required field is missing: {field}.
errors.integrations.invoices.update_failed422The invoice could not be updated.
errors.integrations.invoices.validation_failed422The invoice failed validation.
errors.integrations.invoices.void_failed422The invoice could not be voided.

leave requests

detail_keyStatusEnglish detail
errors.integrations.leave_requests.invalid_cursor422The pagination cursor is invalid or malformed.
errors.integrations.leave_requests.not_found404Leave request not found.

payments

detail_keyStatusEnglish detail
errors.integrations.payments.invalid_cursor422The pagination cursor is invalid or malformed.
errors.integrations.payments.not_found404Payment not found.

purchase orders

detail_keyStatusEnglish detail
errors.integrations.purchase_orders.invalid_cursor422The pagination cursor is invalid or malformed.
errors.integrations.purchase_orders.not_found404Purchase order not found.

tickets

detail_keyStatusEnglish detail
errors.integrations.tickets.invalid_cursor422The pagination cursor is invalid or malformed.
errors.integrations.tickets.not_found404Ticket not found.

timesheets

detail_keyStatusEnglish detail
errors.integrations.timesheets.invalid_cursor422The pagination cursor is invalid or malformed.
errors.integrations.timesheets.not_found404Timesheet not found.

vehicles

detail_keyStatusEnglish detail
errors.integrations.vehicles.invalid_cursor422The pagination cursor is invalid or malformed.
errors.integrations.vehicles.not_found404Vehicle not found.

webhooks

detail_keyStatusEnglish detail
errors.integrations.webhooks.event_types_required422At least one event type is required.
errors.integrations.webhooks.invalid_body422The webhook could not be saved. Give it a name, the address to call, and at least one event type.
errors.integrations.webhooks.invalid_pinned_version422The pinned OH-Version is not a supported version.
errors.integrations.webhooks.not_found404Webhook endpoint not found.
errors.integrations.webhooks.owner_key_inactive409This endpoint belongs to an API key that was revoked or has expired, so it cannot be resumed. Create a new endpoint with an active key.
errors.integrations.webhooks.quota_exceeded429The webhook endpoint quota for this tenant has been reached.
errors.integrations.webhooks.required422A required field is missing: {field}.
errors.integrations.webhooks.unknown_event_type422One or more event types are not in the event catalog.
errors.integrations.webhooks.url_not_publicly_routable422The webhook URL must be publicly routable (no private, loopback, or link-local addresses).

work orders

detail_keyStatusEnglish detail
errors.integrations.work_orders.invalid_cursor422The pagination cursor is invalid or malformed.
errors.integrations.work_orders.not_found404Work order not found.

What to retry

StatusRetry?
400, 401, 403, 404, 410, 422No — change the request first.
409Not with the same Idempotency-Key and a different body.
429Yes, after Retry-After.
500, 502, 503, 504, network errorsYes, with exponential backoff and jitter; for writes reuse the same Idempotency-Key.

Troubleshooting

  • 401 on every call — the header must be exactly Authorization: Bearer oh_live_…. Check the key has not expired or been revoked.
  • 403 with missing_scope — the key was created without that scope; create a new key.
  • "CORS error" in a browser — the API answers browser requests only from this portal. Call it from your server.
  • 429 — see Rate limits.
  • Anything else — contact support with the x-trace-id response header, the time (UTC), the method and path, and your key's prefix (never the secret).