Core concepts
Errors
Gemlead answers with an HTTP status and a JSON error whose type and code never change, so your code can branch on them.
The error object
Every error has the same shape. message is for people and may change, type and code are for your code. Every response, error or not, also carries its Request-Id header.
Attributes
- typestringThe family of the error, one per HTTP status.
- codestringWhat went wrong exactly. See the codes below.
- messagestringA sentence that explains the error.
- paramstring or nullThe parameter at fault, when there is one.
- request_idstringThe request's ID, the same as its
Request-Idheader. Send it to us when you need help. - doc_urlstringThis page, at the code's entry.
Response400
{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "score_min must be an integer from 0 to 100.",
"param": "score_min",
"request_id": "req_7Hq2LmX9pT4vK1sN",
"doc_url": "https://gemlead.ai/docs/errors#parameter_invalid"
}
}HTTP status codes
| Status | Type | Meaning |
|---|---|---|
| 400 | invalid_request_error | The request has a missing, unknown or invalid parameter. A method the path does not take gets this type too, with a 405. |
| 401 | authentication_error | No key, a wrong key or a revoked key. |
| 403 | permission_error | The key is valid but cannot do this, like a read key writing, or an account on the Free or Starter plan. |
| 404 | not_found_error | No such object on the key's account, or no such route. |
| 409 | idempotency_error | An Idempotency-Key was reused for another request, or its first request is still running. |
| 429 | rate_limit_error | Too many requests. Wait for Retry-After seconds. |
| 500 | api_error | Something went wrong on our side, or 501 for an endpoint not available yet. Retry later with the same Idempotency-Key. |
Error codes
| Code | Status | Meaning |
|---|---|---|
api_key_missing | 401 | The request has no Authorization or X-Api-Key header. |
api_key_invalid | 401 | No key matches. Check that you copied all of it, gl_live_ included. |
api_key_revoked | 401 | The key was revoked in the app. Create a new one. |
token_expired | 401 | An OAuth access token of an AI assistant expired. The assistant refreshes it on its own. |
insufficient_scope | 403 | The key has the read scope and the request changes data. Use a key with write. |
plan_required | 403 | The account's owner is on the Free or Starter plan. The API works on Business and Enterprise, trial included, and the same key works again once the account is on one of them. |
forbidden | 403 | The key's user may not do this on the X account, like a team member who changes the team without being an admin, or shares a persona. |
parameter_missing | 400 | A required parameter is missing. param names it. |
parameter_invalid | 400 | A parameter has a value the API does not accept, like a score above 100 or a date that is not ISO 8601. param names it. |
parameter_unknown | 400 | The query or the body has a parameter this endpoint does not take. param names it. |
body_invalid | 400 | The body is not valid JSON, or not a JSON object. |
cursor_invalid | 400 | The cursor is not one the API gave, or was made for other filters. |
idempotency_key_invalid | 400 | The Idempotency-Key is longer than 255 characters. |
method_not_allowed | 405 | This path does not take this method. The Allow header lists the ones it takes. |
url_invalid | 400 | A webhook URL is not HTTPS, has credentials in it, does not resolve, or points to a private address. |
webhook_endpoint_limit_reached | 400 | The account already has 10 webhook endpoints. Delete one first. |
resource_already_exists | 400 | It is there already, like a source the persona has with this method or a keyword search it has. |
limit_reached | 400 | A limit is reached, like 100 personas on an account, 500 keyword searches on a persona or 50 board columns. |
seed_source_refused | 400 | A starting source of a new persona is refused, like an investor, an accelerator or a media account. They bring no buyers. |
x_profile_not_found | 400 | X has no readable profile at this username. |
lead_without_x_id | 400 | The lead has no X account ID yet, so it cannot be followed. |
lead_not_convertible | 400 | The lead belongs to no persona, so it cannot become a source. |
campaign_archived | 400 | The campaign is archived. Duplicate it to run it again. |
campaign_not_started | 400 | The campaign is a draft, so there is nothing to pause. |
campaign_flow_empty | 400 | The campaign's flow has no step yet. Add one before you start it. |
column_protected | 400 | This column belongs to the board, like Inbox or a persona column, and cannot be deleted. |
shared_account_has_campaigns | 400 | An X account you take out of a shared persona still has campaigns on it. Delete them, then try again. |
owner_cannot_leave | 400 | The owner of an X account cannot leave its team. Remove the members instead. |
translation_off | 400 | The conversation's translation is off. Turn it on, or translate one message at a time. |
recipient_not_messageable | 400 | X refused the DM. The lead closed their DMs or blocked the account. |
media_kind_unsupported | 400 | The conversation cannot take this kind of file. A voice note needs an X Chat conversation. |
x_session_expired | 409 | The X account's session expired. Reconnect it in the app, then try again. |
assistant_busy | 409 | Julien AI is still answering a message of this chat, or 3 messages of this X account. Wait for that reply, then send again. |
conversation_unavailable | 409 | The conversation has no X user to write to yet. |
resource_missing | 404 | No object with this ID on the key's account. An ID of another account answers the same. |
route_not_found | 404 | No endpoint at this path. |
idempotency_key_reused | 409 | This Idempotency-Key was used in the last 24 hours for a different method, path or body. |
idempotency_key_in_use | 409 | The first request with this Idempotency-Key is still running. Retry in a second. |
rate_limit_exceeded | 429 | More than 120 requests in the minute with this key. Wait for Retry-After seconds. |
export_rate_limit_exceeded | 429 | More than 10 exports in the hour on this account. |
dm_paused | 429 | X limits the account's DMs for now. Retry-After says when they start again. |
dm_rate_limited | 429 | More than 10 DMs in the minute from this account. |
dm_daily_limit | 429 | 300 DMs today in UTC from this account, or X's own daily quota for it. |
assistant_rate_limited | 429 | More than 6 messages to Julien AI in the minute from this X account, or 150 in the hour for its owner, the app's chat included. |
assistant_daily_limit | 429 | 100 messages to Julien AI today in UTC from this X account. |
passcode_rate_limited | 429 | More than 5 X Chat passcode tries in the hour on this account. X counts every try. |
internal_error | 500 | An unexpected error on our side. The request_id helps us find it. |
request_timeout | 500 | The request took too long. Retry it, with the same Idempotency-Key if it changes data. |
not_implemented | 501 | This endpoint is not available yet. |
upstream_unavailable | 502 | X or a Gemlead service did not answer. Nothing changed, try again in a minute. |
Handle errors
- 400, 401, 403 and 404 never succeed as they are. Fix the request before sending it again.
- 409 with
idempotency_key_in_usesucceeds a second later. Retry with the sameIdempotency-Key. - 429 succeeds after
Retry-Afterseconds. See Rate limits. - 500 can be retried after a short wait. Send the same
Idempotency-Keyfor requests that change data, so nothing happens twice. - Log the
request_idof every error, it lets us find the request in seconds.