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:
{
"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"
}| Field | What it holds |
|---|---|
code | What went wrong, as a stable code. Branch on it. |
status | The HTTP status, repeated. |
title | A short description in English, for people. It can change, so never branch on it. |
type and doc_url | A link to the code's entry below. |
request_id | The id of this request, the same as the Totem-Request-Id header. |
param | Present 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 every429the API itself returns: a whole number of seconds to wait.WWW-Authenticate: Bearer realm="Totem API", on every401.
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:
404for a path outside/v1/, or for any method other thanGETandPOST,HEADandOPTIONSincluded;403for a request with a body, unless it isPOST /v1/leads/search, and for a body larger than 16 KiB;429when 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.paramsays what to fix. One400is 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_requiredmeans your key reads several teams and the request must name one inTotem-Team;secret_key_in_browsermeans 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_expiredmeans 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 ascapability_disabledor a team whose access Totem has paused; others need someone to act, such asplan_not_entitledorip_not_allowed; andsector_restricteddoes 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_missingmeans the record does not exist, is gone, or is not ready yet; the changes feed delivers new records once they are ready.team_not_accessiblemeans the key may no longer read the team: confirm it withGET /v1/teamsbefore you delete anything. A404without a JSON error body comes from the firewall: check the method and the path.409and410: the changes feed needs you to act.resync_requiredandsync_cursor_expiredask for a full resync.capture_not_readyasks you to wait a few minutes, while the key's other teams keep syncing.429: slow down. Wait the seconds inRetry-After, then retry the same request.rate_limitedusually clears within seconds, but after an authentication lockout it lasts 600 seconds.quota_exceededlasts until 00:00 UTC, when the daily quota resets. A429from the firewall has noRetry-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 theTotem-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 requestinvalid_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-Teamheader, or a body that is not the JSON this endpoint expects.paramnames 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
paramnames before you retry: the same request fails the same way. For a cursor, send thenext_cursoryou 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 requiredteam_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 byGET /v1/teams) in theTotem-Teamheader.- Retry
- No. The same request fails the same way until you act on it.
Secret key sent from a browsersecret_key_in_browser- When it happens
The request carried a secret key together with an
Originheader, 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 credentialsinvalid_credentials- When it happens
The
Authorizationheader 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 with429 rate_limited.- Retry
- No. The same request fails the same way until you act on it.
Key expiredkey_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 scopeinsufficient_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-Idof the response.- Retry
- No. The same request fails the same way until you act on it.
Plan not entitledplan_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 address not allowedip_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 restrictedteam_state_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/teamsstill 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 restrictedsector_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 disabledcapability_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 not foundresource_missing- 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
GETorPOSTunder/v1/that matches no endpoint also gets this code. Any other method, and any path outside/v1/, gets a plain404from the firewall in front of the API, with no problem body and noTotem-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 accessibleteam_not_accessible- When it happens
The
Totem-Teamheader 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/teamsbefore 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/teamsstill lists the team: then on the next run.
409 Conflict
Resync requiredresync_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.
Team data is still being preparedcapture_not_ready- 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 expiredsync_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 thenext_cursorof 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 limitedrate_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 fulllimitup front and gives back what it does not use, so a smallerlimitkeeps a nearly spent allowance moving.- Retry
- Yes, after the seconds in
Retry-After.
Daily quota exceededquota_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_servicesand a lead's activities. The quota resets at 00:00 UTC.- What to do
Stop syncing until 00:00 UTC (
Retry-Aftercounts the seconds) and resume then with the same cursor: nothing is lost. Requests for one record, such asGET /v1/leads/{id}, search andGET /v1/medo not count toward the quota and keep working.- Retry
- Yes, after the seconds in
Retry-After.
500 Internal server error
Internal errorinternal_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 disabledapi_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.