Skip to main content

Errors

Most errors are returned as RFC 7807 application/problem+json:

{
"type": "urn:smartepi:error:invalid_reference",
"title": "Invalid reference",
"status": 422,
"detail": "Unknown or cross-tenant reference: costCenterId"
}

The type is a stable URN identifier for the error class (urn:smartepi:error:<token>), not a link to fetch — branch on it programmatically; do not expect it to resolve.

Application errors vs. gateway rejections

Two layers can reject a request, and they look different:

  • Application errors (400, 404, 405, 409, 422, 500) come from the API itself. They are RFC 7807 application/problem+json (as above) and carry an X-Request-Id header — include it when reporting an issue.
  • Gateway rejections (401, 403, 429) are produced before the application runs. They are not RFC 7807 and do not carry X-Request-Id, and their body shape differs by case: 401 and 429 are a plain lowercase {"message":"…"}, but a 403 (rejected key or blocked IP) returns the raw AWS authorizer-deny body {"Message":"User is not authorized to access this resource with an explicit deny in an identity-based policy"} — note the capital Message and that this exact text is an AWS string that may change. Branch on the HTTP status code, never on the body text. So a missing key (401), a bad/inactive key or blocked IP (403), and a rate-limit (429) will not look like the problem+json shape above — branch on the HTTP status for these.

Status codes

StatusMeaningWhat to do
400Malformed request (bad JSON, invalid field, an invalid cursor), or a missing If-Match/_version on update/delete.Fix the request body/params; only ever send back a cursor you received; send If-Match on writes.
401No API key was sent (missing Authorization header). Body: {"message":"Unauthorized"}.Send the Authorization: Bearer sk_… header.
403A key was sent but was rejected: the key is invalid / unknown / revoked / expired, OR its IP allow-list blocked the source IP, OR the key was just created and has not finished activating (see Authentication). Body: the raw AWS authorizer-deny JSON {"Message":"User is not authorized to access this resource with an explicit deny in an identity-based policy"} (capital Message). Branch on the status, not the text.Check the key is valid and active; if it is brand-new, wait ~1 minute and retry; if you use an IP allow-list, call from an allowed IP.
404Not found, or not yours to access.The id does not exist for you.
405Method not allowed on this resource.e.g. writing a read-only resource, or get-by-id on a list-only transaction.
409Version conflict (stale _version, type: urn:smartepi:error:conflict), a duplicate entity name within your tenant (type: urn:smartepi:error:duplicate_name), or a delete blocked because the entity is still referenced (type: urn:smartepi:error:resource_in_use).Re-read then retry for conflicts (see Concurrency); use a different name for duplicates; re-point or remove the dependents before deleting.
422Foreign-key validation failed.A referenced id is missing or not yours.
429Rate limit exceeded (metered per organization). Body: {"message":"Too Many Requests"} (gateway-shaped, lowercase message, with x-amzn-errortype: TooManyRequestsException).Back off and retry with your own exponential backoff. A Retry-After header is not guaranteed (the burst-rate throttle does not send one), so do not depend on it. Branch on the 429 status, not the body text.
500Server error.Retry with backoff; report the X-Request-Id.

Notes on 404 vs 403

A 404 is returned both when a record genuinely does not exist and when it isn't accessible to your API key — the API never reveals the existence of data outside your organization. Do not treat 404 as "definitely deleted".

422 — foreign-key validation

On a write, every foreign key you set (e.g. costCenterId, sectorId, jobRoleId, groupId, a group rule's productId) must reference a record that belongs to your organization. If any does not, the whole write is rejected with 422 and a detail naming the offending field — nothing is persisted.