Docs
Developers

Errors

The APROS API error shape, the HTTP status codes and error codes you can receive, how to report a server fault with its errorId, and how to retry safely with Idempotency-Key.

When a request fails, the API answers with a non-2xx HTTP status and a JSON body in one shape:

{
  "error": "Permission denied: VIEW_INVOICE required",
  "code": "ACCESS_DENIED"
}
FieldTypeWhen it is present
errorstringAlways. A sentence in English for people and logs. Do not parse it: the wording can change.
codestringWhen the API refuses on purpose: a missing permission, a validation rule, a plan limit, a switched-off module. Stable, so your code can branch on it.
detailsobjectWith some codes, to say exactly what was wrong (for example which query parameter or which field).
errorIdstringOn server faults (500). The same ID is in the server log next to the cause.

Some refusals carry one more field named in the table below, such as issues for validation errors or requiredPermission for an access-level refusal.

Handle errors in your code

  1. Check the HTTP status first. A 2xx is success; anything else carries the shape above.
  2. If the body has a code, branch on it. Codes are the contract; messages are not.
  3. If there is no code, act on the status. A few endpoints still answer some 400 and 404 responses without one.
  4. On 500, log the errorId so you can report it.

Status codes

StatusMeaning
400 Bad RequestThe request is malformed or failed validation. Fix it before you retry.
401 UnauthorizedNo API key, or the key is unknown, revoked or expired.
403 ForbiddenThe key is valid but may not do this: a missing scope or permission, a switched-off module, a plan limit, or a disabled account.
404 Not FoundThe record or path does not exist in your workspace.
409 ConflictThe request conflicts with the current state, for example a reused Idempotency-Key.
429 Too Many RequestsThe key went over its rate limit. Wait for the number of seconds in the Retry-After header. See Rate limits.
500 Internal Server ErrorA fault on the APROS side. The body has an errorId.
503 Service UnavailableThe workspace cannot serve this request right now, for example during an update. Retry later.

Error codes

These codes come from the API's shared error handling, so you can meet them on any endpoint. Individual endpoints add their own codes, such as STAGE_NOT_FOUND on POST /api/leads; the error text explains each one.

StatuscodeWhat happenedExtra fields
400VALIDATION_ERRORThe request body failed validation.issues: one entry per problem, each with the field's path and a message.
400UNKNOWN_QUERY_PARAMA query parameter the endpoint does not accept. The endpoint refuses it rather than ignore it, so you never get an unfiltered list that looks filtered.details.unknownParams, details.acceptedParams
400DUPLICATE_QUERY_PARAMA query parameter appears more than once where the endpoint takes one value.details.duplicatedParams
400INVALID_QUERY_PARAMA query parameter has a value the endpoint cannot use.details.param, details.message
400UNKNOWN_CUSTOM_FIELD_FILTERA cf_ filter names a custom field that does not exist.details.unknownParams
400INVALID_IDAn ID in the path or in a filter is not a valid ID.
400INVALID_REFERENCEThe body refers to a record that does not exist in your workspace.details.field
400INVALID_CURRENCYA currency code that is unknown or not active in your workspace.
400IDEMPOTENCY_KEY_INVALIDThe Idempotency-Key header is longer than 255 characters.
401AUTHENTICATION_ERRORNo valid API key.
403ACCESS_DENIEDThe key's scopes, or its owner's role, do not include the permission. The message names what is missing.
403ACCESS_LEVEL_NONEThe key's owner has no access level for this action on this kind of record.requiredPermission: the permission to grant the role, or to add to the key's scopes.
403ACCOUNT_DISABLEDThe person the key belongs to is suspended or terminated, so their keys no longer work.
403MODULE_DISABLEDThe module this endpoint belongs to is switched off for your workspace.Can include key (the module) and reason: plan if your plan does not include it, self if an administrator switched it off.
403QUOTA_EXCEEDEDA plan limit is reached. Only creating new records is refused; existing data stays readable.details.quota, details.used, details.limit
404NOT_FOUNDThe record does not exist in your workspace.
409IDEMPOTENCY_KEY_REUSEDThis Idempotency-Key was already used with a different request body.
409IDEMPOTENCY_KEY_IN_FLIGHTThe first request with this Idempotency-Key is still running.
429RATE_LIMITEDThe key went over its rate limit.Retry-After response header, in seconds
503SCHEMA_OUT_OF_DATEYour workspace is being updated and this part is not ready yet.

A validation error looks like this:

{
  "error": "Validation failed",
  "code": "VALIDATION_ERROR",
  "issues": [
    {
      "code": "invalid_value",
      "values": ["HOT", "WARM", "COLD"],
      "path": ["temperature"],
      "message": "Invalid option: expected one of \"HOT\"|\"WARM\"|\"COLD\""
    }
  ]
}

A request body that is not valid JSON answers 400 with { "error": "Invalid JSON body" }.

Server faults and errorId

A 500 means something failed on the APROS side, not in your request. The body never explains the cause; it carries a short ID instead:

{ "error": "Failed to fetch leads", "errorId": "e_3f9a1c" }

The same errorId is written to the server log next to the real cause. To get the fault investigated, write to hello@apri-code.com and include:

  • the errorId,
  • your workspace address,
  • the method and path you called, and roughly when.

You do not need to send your API key or the request body.

Retrying safely

Retry only what can succeed on a second try:

ResponseRetry?
429Yes, after the number of seconds in Retry-After.
500, 503, or a network error with no responseYes, with backoff (for example 1 s, 2 s, 4 s).
Any other 4xxNo. The same request will fail the same way; fix it first.

Retrying a create is risky: if the first request succeeded but its response was lost, a retry creates a second record. To prevent that, send an Idempotency-Key header with a value that is unique for the operation, such as a UUID, and reuse the same value when you retry:

curl -X POST "https://<your-workspace>.apros.app/api/tasks" \
  -H "Authorization: Bearer $APROS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c9a52-8d3e-4b7a-a2f0-9c4d1e8b7a63" \
  -d '{ "title": "Call back Koval Studio" }'

How APROS treats the header:

  • Same key, same body: APROS returns the stored response of the first successful request. The operation does not run again.
  • Same key, different body: 409 with IDEMPOTENCY_KEY_REUSED. Use a new key for every distinct operation.
  • Same key while the first request is still running: 409 with IDEMPOTENCY_KEY_IN_FLIGHT. Nothing was created twice; retry later with the same key.
  • After a failure: only successful (2xx) responses are stored. If the first attempt failed, the key is free, and a retry with it runs the operation again.
  • Keys are kept for 24 hours and belong to the person the API key belongs to.
  • Without the header, the endpoint behaves as usual and does no replay protection.

These endpoints accept Idempotency-Key:

EndpointOperation
POST /api/leadsCreate a lead
POST /api/leads/{id}/convertConvert a lead
POST /api/contactsCreate a contact
POST /api/companiesCreate a company
POST /api/dealsCreate a deal
POST /api/activitiesCreate an activity
POST /api/crm/commentsAdd a CRM comment
POST /api/projectsCreate a project
POST /api/tasksCreate a task
POST /api/tasks/{id}/checklistAdd a checklist item
POST /api/tasks/{id}/commentsAdd a task comment
POST /api/invoicesCreate an invoice
POST /api/invoices/{id}/duplicateDuplicate an invoice
POST /api/invoices/{id}/payment-linkCreate a payment link
POST /api/invoices/{id}/paymentsRecord a payment
POST /api/invoices/{id}/payments/{paymentId}/reverseReverse a payment
POST /api/invoices/{id}/sendSend an invoice

The Booking API is a separate public API for booking widgets, and its errors use a different shape. Its page lists them.