Error format
| Field | Description |
|---|---|
code | Machine-readable error code. Use in error handling logic. |
message | Human-readable description for developers. Do not show to end users. |
request_id | Unique request ID. Include when contacting support. |
A 2xx response never contains
error. A non-2xx response always contains error. The
request_id is also returned in the X-Request-ID response header on every request, including
successful ones.HTTP status codes
| Status | Meaning |
|---|---|
200 | Success. Resource read or updated. |
201 | Created successfully. |
204 | No content. Resource deleted. Empty body. |
400 | Malformed request or missing required parameter. |
401 | Unauthenticated. Token missing, invalid, or expired. |
403 | Forbidden. Valid token but insufficient scope or denied operation. |
404 | Not found. Resource does not exist, was deleted, or belongs to another workspace. |
409 | Conflict. The request cannot be completed due to a conflict with existing data. |
422 | Validation failed. See the fields object. |
429 | Rate limit exceeded. |
500 | Internal error. Something went wrong on the server. |
Error code reference
token_expired (401)
token_expired (401)
"Access token expired. Refresh using the refresh token."The access token has expired. Access tokens are valid for 1 hour. Use your refresh_token to
obtain a new one via POST /oauth/token with grant_type=refresh_token. If the refresh token has
also expired, the user must re-authorize.insufficient_scope (403)
insufficient_scope (403)
"This endpoint requires the '{scope}' scope."The token does not have the required scope for this endpoint. The {scope} placeholder in the
message is the exact scope that is missing (for example, "This endpoint requires the 'people:write' scope."). The user authorized your app with a set of scopes that does not include
what this operation requires. You need to request a new authorization with the correct scopes.forbidden (403)
forbidden (403)
The token is valid and has the right scope, but the operation was denied by a database-level
permission check. The message varies by context:
- Create:
"You do not have permission to create this {entity}. Check that all fields are within your access level." - Update:
"You do not have permission to update this {entity} or one of the provided values is not allowed for your access level." - Delete:
"You do not have permission to delete this {entity}."
owner_user_id to a user other than yourself when your role does not allow
it.not_found (404)
not_found (404)
"{Entity} not found." for example, "Person not found." or "Deal not found."The requested resource does not exist, has been soft-deleted, or belongs to a different workspace.
The API does not distinguish between these cases to avoid leaking information about other
workspaces.conflict (409)
conflict (409)
"A record with this value already exists."The request conflicts with existing data. The response also includes a fields object identifying
the conflicting field. Common cause: attempting to register an app with an email that is already
in use.validation_error (422)
validation_error (422)
"Validation failed."Input failed validation. The response includes a fields object mapping each invalid field to a
description of the problem. Fix the fields listed and retry. See Validation errors for the full response shape.bad_request (400)
bad_request (400)
Returned in two situations:
- Malformed body:
"Request body must be valid JSON."the request body could not be parsed as JSON. - Empty update:
"No fields provided for update."aPATCHrequest was sent with no fields to modify.
rate_limit_exceeded (429)
rate_limit_exceeded (429)
"Rate limit exceeded. Try again in {N} seconds."Too many requests. The {N} in the message is the exact number of seconds to wait. The same
value is also in the Retry-After response header. See Rate Limits for
the full limits by method and endpoint type.internal_error (500)
internal_error (500)
Returned in two situations:
- Unexpected server error:
"An unexpected error occurred."something failed on the server. Retry with exponential backoff. If the error persists, contact support with therequest_id. - Partial update:
"{Entity} updated but associations failed. Retry the associations."the main record was saved but the association update (tags, lists, deals, or custom fields) failed. The record itself is consistent. Retry only the association fields.
OAuth error codes
These codes are returned exclusively by the OAuth endpoints (/oauth/authorize, /oauth/token, /oauth/revoke, /v1/auth/register-app, /v1/auth/rotate-app).
invalid_client (401)
invalid_client (401)
"Invalid client credentials." the client_id or client_secret is incorrect. Verify your
credentials. If you suspect the client_secret has been compromised, rotate it via POST /v1/auth/rotate-app.Also returned as 401 from /oauth/authorize when the client_id does not exist or the app is
not active, and from /v1/auth/rotate-app when the email sent does not match the one registered
with the app.invalid_grant (400)
invalid_grant (400)
"The provided authorization grant is invalid, expired, or does not match."The authorization code is invalid, expired, or has already been used. Authorization codes expire
in 10 minutes and are single-use. Also returned when a refresh_token is invalid or expired. In
either case, the user must go through the full authorization flow again.invalid_request (400)
invalid_request (400)
A required OAuth parameter is missing or invalid. The message identifies the specific parameter.
Examples:
"Missing required parameter: client_id.""Missing required parameter: redirect_uri.""redirect_uri does not match any registered URI.""Missing required parameter: token."(revoke)"token must be a valid access_token. refresh_tokens are not accepted."(revoke)
unsupported_grant_type (400)
unsupported_grant_type (400)
"grant_type must be \"authorization_code\" or \"refresh_token\"."The grant_type field sent to /oauth/token is not one of the two supported values.unsupported_response_type (400)
unsupported_response_type (400)
"response_type must be \"code\"."The response_type parameter sent to /oauth/authorize is not "code".Validation errors
When validation fails, the response includes afields object mapping each field to its problem:
The request_id field
Every request, successful or not, gets a uniquerequest_id. It appears in:
- The
error.request_idfield on error responses - The
X-Request-IDresponse header on all responses
request_id. It allows the support team to locate the exact request in the logs and diagnose what happened.
Handling errors in production
Recoverable vs non-recoverable errors
Not all errors should be retried. Retrying a non-recoverable error wastes resources and delays surfacing the real problem. Recoverable errors are transient. Retry with backoff:| Status | Code | Strategy |
|---|---|---|
429 | rate_limit_exceeded | Wait the number of seconds in Retry-After, then retry. |
500 | internal_error | Retry with exponential backoff (1s, 2s, 4s). Give up after 3 attempts. |
| Status | Code | What to do |
|---|---|---|
400 | bad_request | Fix the request. The body is malformed or the update has no fields. |
400 | invalid_grant | The code or refresh token is invalid. Redirect the user through the OAuth flow. |
400 | invalid_request | A required OAuth parameter is missing or invalid. Fix the request. |
400 | unsupported_grant_type | Use authorization_code or refresh_token. |
401 | token_expired | Refresh the access token, then retry the original request. |
401 | unauthorized | Check that the token is present and correctly formatted. |
401 | invalid_client | Check your client_id and client_secret. Do not retry automatically. |
403 | insufficient_scope | Request a new authorization with the required scopes. |
403 | forbidden | Fix the request. The operation or field values are not allowed for your access level. |
404 | not_found | The resource does not exist. Do not retry. |
409 | conflict | Resolve the conflict before retrying. |
422 | validation_error | Fix the fields listed in fields. Do not retry with the same payload. |