Error format
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
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:
Non-recoverable errors indicate a problem with the request or credentials. Do not retry automatically: