Skip to content
Beta

Reference Errors

Errors

The error format, how your integration should handle each kind of error, and every error code with when it happens and what to do.

When a request fails, the API answers with an HTTP status and a JSON body that says what went wrong. Every error body links to its entry on this page.

Error format

Errors follow RFC 9457 and come as application/problem+json. For example, GET /v1/leads?limit=500 answers:

JSON
{
  "type": "https://totemstudio.ai/developers/errors#invalid_request",
  "title": "Invalid request",
  "status": 400,
  "code": "invalid_request",
  "request_id": "req_0f6b2c1e-8d4a-4b7e-9c3f-5a2d1e7b9c40",
  "param": "limit",
  "doc_url": "https://totemstudio.ai/developers/errors#invalid_request"
}
FieldWhat it holds
codeWhat went wrong, as a stable code. Branch on it.
statusThe HTTP status, repeated.
titleA short description in English, for people. It can change, so never branch on it.
type and doc_urlA link to the code's entry below.
request_idThe id of this request, the same as the Totem-Request-Id header.
paramPresent only when one header, query parameter or body field is at fault. It names it, and never repeats the value you sent.

These headers come with an error:

  • Totem-Request-Id, on every response the API itself sends. Log it, and quote it when you ask Support for help. Never send us your key or your customers' data.
  • Retry-After, on every 429 the API itself returns: a whole number of seconds to wait.
  • WWW-Authenticate: Bearer realm="Totem API", on every 401.

New codes can appear at any time. Handle a code you do not know by its HTTP status, as the next section describes.

During the Beta, the firewall in front of the API answers some requests itself, before they reach the API. Its answer has no JSON error body, only the firewall's own default page, and no Retry-After or Totem-Request-Id. Do not parse its body as JSON: handle it by its status:

  • 404 for a path outside /v1/, or for any method other than GET and POST, HEAD and OPTIONS included;
  • 403 for a request with a body, unless it is POST /v1/leads/search, and for a body larger than 16 KiB;
  • 429 when one address sends too many requests, for example many requests without a well-formed key.

Handling errors

Decide by HTTP status first, then by code:

  • 400: fix the request. Sending the same request again fails the same way. param says what to fix. One 400 is not your request's fault: a conversations cursor stops working when the conversation it points at is erased, so start that list again from the first page, as Lists and pagination explains. Two codes need more than a fix: team_required means your key reads several teams and the request must name one in Totem-Team; secret_key_in_browser means the key was used from a browser, so treat it as leaked.
  • 401: fix the key. Do not retry in a loop: repeated failures lock your IP address or your key out for a while. key_expired means the key was rolled and its grace period is over: deploy the new key.
  • 403: access is refused for now. Keep your data and your cursors, and skip the team or the endpoint. Some refusals can end on their own, such as capability_disabled or a team whose access Totem has paused; others need someone to act, such as plan_not_entitled or ip_not_allowed; and sector_restricted does not end by waiting: it lasts as long as Totem's current sector policy applies, and the changelog records any change to it. Each code below says which. While access is refused, the changes feed delivers nothing for the team, removals included, so if a refusal lasts longer than your retention policy allows, delete your copy.
  • 404: check what you asked for. resource_missing means the record does not exist, is gone, or is not ready yet; the changes feed delivers new records once they are ready. team_not_accessible means the key may no longer read the team: confirm it with GET /v1/teams before you delete anything. A 404 without a JSON error body comes from the firewall: check the method and the path.
  • 409 and 410: the changes feed needs you to act. resync_required and sync_cursor_expired ask for a full resync. capture_not_ready asks you to wait a few minutes, while the key's other teams keep syncing.
  • 429: slow down. Wait the seconds in Retry-After, then retry the same request. rate_limited usually clears within seconds, but after an authentication lockout it lasts 600 seconds. quota_exceeded lasts until 00:00 UTC, when the daily quota resets. A 429 from the firewall has no Retry-After: back off exponentially. Rate limits explains both limits.
  • 5xx: retry later. Retry with exponential backoff, keeping your cursors. If it keeps failing, contact Support with the Totem-Request-Id.

In every case, never save a changes-feed cursor after a failed request.

All error codes

Each code has its own anchor, which is where doc_url points.

400 Bad request

  • invalid_request

    Invalid request
    When it happens

    Something in the request is not valid: an unknown, repeated or out-of-range query parameter, a query string or body on an endpoint that takes none, a cursor issued for another endpoint, filter or team, a malformed Totem-Team header, or a body that is not the JSON this endpoint expects. param names the parameter, header or field at fault. A conversations cursor is also refused once the conversation it points at has been erased.

    What to do

    Correct what param names before you retry: the same request fails the same way. For a cursor, send the next_cursor you received from the same endpoint, with the same filters and team. If a conversations cursor you did not change is refused, start that list again from the first page.

    Retry
    No. The same request fails the same way until you act on it.
  • team_required

    Team required
    When it happens

    The key reads more than one team and this endpoint reads one team's data, but the request does not say which team.

    What to do

    Send the team's id (team_…, as listed by GET /v1/teams) in the Totem-Team header.

    Retry
    No. The same request fails the same way until you act on it.
  • secret_key_in_browser

    Secret key sent from a browser
    When it happens

    The request carried a secret key together with an Origin header, which means it came from a browser. Totem refuses it before reading anything.

    What to do

    Call the API only from your server. If the key has been in a web page, a mobile app or a browser extension, treat it as leaked: roll it and mark it as compromised, so the old key stops working at once.

    Retry
    No. The same request fails the same way until you act on it.

401 Unauthorized

  • invalid_credentials

    Invalid credentials
    When it happens

    The Authorization header is missing or malformed, or the key is unknown, wrong or revoked, or its integration is no longer active.

    What to do

    Send Authorization: Bearer <key> with a current secret key. Do not retry in a loop: repeated failures from one IP address, or against one key, lock it out for a while with 429 rate_limited.

    Retry
    No. The same request fails the same way until you act on it.
  • key_expired

    Key expired
    When it happens

    The key was rolled and its grace period is over.

    What to do

    Deploy the key that replaced it: the old key is past its grace period and will not work again. If there is no replacement, create a new key in Settings → Developers.

    Retry
    No. The same request fails the same way until you act on it.

403 Forbidden

  • insufficient_scope

    Insufficient scope
    When it happens

    The key lacks a permission this endpoint needs. Every v1.0a key holds the full read set, so this should not happen.

    What to do

    Contact support and quote the Totem-Request-Id of the response.

    Retry
    No. The same request fails the same way until you act on it.
  • plan_not_entitled

    Plan not entitled
    When it happens

    The organization's plan does not include the Totem API at the moment.

    What to do

    Keep your data and cursors, and pause calls until the customer has a plan with API access again; then continue where you left off. While access is refused, the feed delivers nothing for the key's teams, removals included: if the refusal lasts longer than your retention policy allows, delete your copy.

    Retry
    Later, once access is restored. Keep your data and cursor meanwhile.
  • ip_not_allowed

    IP address not allowed
    When it happens

    The key is valid, but the request came from an IP address outside the integration's allowlist.

    What to do

    Call from an allowed address, or ask an administrator to add yours to the allowlist in Settings → Developers.

    Retry
    No. The same request fails the same way until you act on it.
  • team_state_restricted

    Team restricted
    When it happens

    The team is restricted for now: it is disabled (for example, suspended for non-payment), archived or billing-only, or its API access is paused. GET /v1/teams still lists it.

    What to do

    Keep the team's data and cursor, skip it for this run and try again later. The key's other teams are not affected. While the team is restricted, the feed delivers nothing for it, removals included: if the restriction lasts longer than your retention policy allows, delete the team's copy.

    Retry
    Later, once access is restored. Keep your data and cursor meanwhile.
  • sector_restricted

    Sector restricted
    When it happens

    Totem does not export this team's data through the API because the team works in a sensitive sector, such as health care, where its records can reveal health information.

    What to do

    Skip the team: waiting does not change this. The feed delivers nothing for it, removals included, so keep what you already have only as long as your retention policy allows, then delete the team's copy. Talk to the customer or to Totem if you need its data.

    Retry
    No. Waiting does not change it.
  • capability_disabled

    Capability disabled
    When it happens

    Totem has temporarily switched this endpoint off for every key.

    What to do

    Keep your data and cursors and try again later; the other endpoints keep working.

    Retry
    Later, once access is restored. Keep your data and cursor meanwhile.

404 Not found

  • resource_missing

    Resource not found
    When it happens

    The record does not exist, was removed, belongs to a team this key does not read, or was created so recently that Totem has not prepared it yet. A GET or POST under /v1/ that matches no endpoint also gets this code. Any other method, and any path outside /v1/, gets a plain 404 from the firewall in front of the API, with no problem body and no Totem-Request-Id.

    What to do

    Check the id and the path. If the record was created moments ago, do not retry in a loop: the changes feed delivers it as soon as it is ready.

    Retry
    No. The same request fails the same way until you act on it.
  • team_not_accessible

    Team not accessible
    When it happens

    The Totem-Team header names a team this key does not read: one it never covered, one removed from the key or from the organization, or one that was deleted. Without the header, it means the key no longer reads any team.

    What to do

    Re-read GET /v1/teams before you act. If the team is no longer listed, delete its copy and its cursor; if it is still listed, keep everything and try again on the next run.

    Retry
    Only if GET /v1/teams still lists the team: then on the next run.

409 Conflict

  • resync_required

    Resync required
    When it happens

    Totem cannot continue the feed from this cursor: the data was restored from a backup, or a gap was found in the team's changes.

    What to do

    Run a full resync: forget the versions you stored for the team, read the feed again without a cursor, and once it has caught up delete every local record of the team it did not deliver. Keep using your current copy until then.

    Retry
    No. The same request fails the same way until you act on it.
  • capture_not_ready

    Team data is still being prepared
    When it happens

    Totem is still preparing this team's data for the API. This happens once per team, after it is first added to any key; adding it to another key later does not repeat it.

    What to do

    Keep whatever you hold for the team and try again in a few minutes; the key's other teams keep syncing meanwhile.

    Retry
    Yes, later: the same request succeeds once the data is ready.

410 Gone

  • sync_cursor_expired

    Sync cursor expired
    When it happens

    The cursor is more than 30 days old, counted from the point in the feed it reached (or, while an initial sync is still running, from when that sync started).

    What to do

    Run a full resync, as for resync_required. To prevent it, save the next_cursor of every page, empty pages included: the cursor of a team you poll regularly never ages out.

    Retry
    No. The same request fails the same way until you act on it.

429 Too many requests

  • rate_limited

    Rate limited
    When it happens

    The key went over its rate limit (items per minute), or too many failed authentication attempts from this IP address or against this key have locked it out for a while.

    What to do

    Wait the seconds in Retry-After, then retry the same request. Each request sets aside its full limit up front and gives back what it does not use, so a smaller limit keeps a nearly spent allowance moving.

    Retry
    Yes, after the seconds in Retry-After.
  • quota_exceeded

    Daily quota exceeded
    When it happens

    The key has spent its daily quota of items. Every item returned by the changes feed and by every list counts toward it, including GET /v1/teams, GET /v1/members, GET /v1/booking_services and a lead's activities. The quota resets at 00:00 UTC.

    What to do

    Stop syncing until 00:00 UTC (Retry-After counts the seconds) and resume then with the same cursor: nothing is lost. Requests for one record, such as GET /v1/leads/{id}, search and GET /v1/me do not count toward the quota and keep working.

    Retry
    Yes, after the seconds in Retry-After.

500 Internal server error

  • internal_error

    Internal error
    When it happens

    Something went wrong inside Totem while answering this request.

    What to do

    Retry with exponential backoff. If it keeps failing, contact support and quote the Totem-Request-Id.

    Retry
    Yes, with exponential backoff.

503 Service unavailable

  • api_disabled

    API disabled
    When it happens

    Totem has temporarily switched the API off for every key.

    What to do

    Keep your data and cursors and retry later with exponential backoff.

    Retry
    Yes, with exponential backoff.
  • service_unavailable

    Service unavailable
    When it happens

    Reserved: the API itself does not return this code today. A 503 from the load balancer or the hosting platform in front of the API arrives without a problem body; treat it like this code.

    What to do

    Retry with exponential backoff; your data and cursors stay valid.

    Retry
    Yes, with exponential backoff.

API version 1.0a