Docs
View as Markdown

Errors

Every failure uses the same response format with success: false, data: null and an error object. This page lists every error.type the API can return, generated from the same registry the API serves at GET https://api.onekey.dev/v1/errors (public, no key), so it cannot drift from the code.

json

{
  "success": false,
  "data": null,
  "metadata": {
    "request_id": "7dd14539f2f24ca1bc1eb63809293092"
  },
  "error": {
    "code": "invalid_cursor",
    "type": "INVALID_CURSOR",
    "message": "The cursor is invalid, expired, or from another query",
    "request_id": "7dd14539f2f24ca1bc1eb63809293092",
    "doc_url": "https://onekey.dev/errors/INVALID_CURSOR"
  }
}

The error object#

FieldMeaning
typeStable machine string in upper snake case. Branch on this.
codeThe same word in lower case (invalid_cursor). Kept for older clients; type and code never disagree.
messageHuman text. It may change and never contains text from the source platform, so do not parse it.
request_idCorrelation id, equal to the X-Request-Id header and metadata.request_id. Quote it if you contact support.
doc_urlhttps://onekey.dev/errors/{type}, which resolves to the matching section of this page.

On a 429, and on the two idempotency answers that ask you to wait, a Retry-After header says how many seconds to wait. See Rate limits & headers.

All types at a glance#

63 types. Jump to any of them for what to do.

Statuserror.typeMeaning
400BAD_REQUESTThe request was malformed (for example an oversized body).
400COHORT_BUDGET_EXCEEDEDThe worst-case price of a cohort query is above the max_credits you set; nothing was held or run.
400COHORT_IDENTITY_PLATFORM_UNSUPPORTEDA member names a platform that has no recent-posts feed to query; nothing was stored.
400IDEMPOTENCY_KEY_INVALIDThe Idempotency-Key header is not a UUID.
400INVALID_REQUESTA batch or streaming request was malformed (bad body, too many items, or an Idempotency-Key on a GET stream); nothing was run or billed.
400INVALID_TOKENThe password-reset or email-verification token is invalid or expired.
401INVALID_CREDENTIALSEmail or password did not match (dashboard sign-in).
401NOT_AUTHENTICATEDA dashboard session cookie is required for this route.
401UNAUTHORIZEDThe x-api-key header is missing, malformed, or revoked.
402INSUFFICIENT_CREDITSThe account balance cannot cover this call; buy a pack to continue.
402KEY_BUDGET_EXCEEDEDThis API key reached its credit_limit; raise or clear the budget, or use another key.
403CSRF_REJECTEDA state-changing dashboard request lacked the CSRF header or came from an unknown origin.
403PAID_ACCESS_RESTRICTEDThis account may not use paid data routes yet (verify your email or buy a pack).
403PASSWORD_INCORRECTChanging the password: the current password did not match.
404BILLING_WEBHOOK_NOT_FOUNDNo billing webhook with that id exists on this account.
404COHORT_NOT_FOUNDNo cohort with that id exists on this account, or it passed its retention and was deleted.
404COHORT_QUERY_NOT_FOUNDNo cohort query with that id exists on this account, or it expired and was deleted.
404JOB_NOT_FOUNDNo job with that id exists on this account, or its results expired and were deleted.
404MONITOR_NOT_FOUNDNo monitor with that id exists on this account.
404NOT_FOUNDThe endpoint does not exist, or the upstream platform has no such item.
405METHOD_NOT_ALLOWEDThis route does not accept that HTTP method.
409ALREADY_VERIFIEDThe email address is already verified; no verification email was sent.
409BILLING_WEBHOOK_LIMITThe account already has the maximum number of billing webhooks.
409COHORT_IDENTITY_CONFLICTTwo different external_ids claim the same platform and handle inside one cohort.
409COHORT_LIMIT_REACHEDThe account already has the maximum number of cohorts; delete one.
409COHORT_MEMBER_LIMITA cohort holds at most 10,000 members.
409COHORT_QUERY_NOT_READYThe cohort query has not succeeded yet, so there are no results to read; poll its status.
409DELETE_ALREADY_REQUESTEDAccount deletion was already requested; an operator completes it.
409IDEMPOTENCY_IN_PROGRESSA request with the same Idempotency-Key is still running; retry after 1 second.
409IDEMPOTENCY_REPLAY_UNAVAILABLEThe original response exceeded 64 KB and was not stored for replay.
409JOB_FINISHEDThe job already finished, so it cannot be cancelled.
409JOB_LIMIT_REACHEDThe account (or this key) already has the maximum number of unfinished jobs.
409KEY_LIMITThe account already has the maximum number of active API keys.
409MONITOR_LIMIT_REACHEDThe account already has the maximum number of active monitors; pause or delete one.
409MONITOR_RUN_IN_PROGRESSThe monitor is being run by the scheduler right now; retry the manual run shortly.
409WEBHOOK_DISABLEDThe monitor's webhook was switched off after repeated delivery failures; PATCH a new webhook.url to re-enable it.
422IDEMPOTENCY_KEY_REUSEDThe Idempotency-Key was already used for a different endpoint or parameters.
422INVALID_BILLING_WEBHOOKA billing webhook setting is malformed (events, low_threshold_credits, url, description).
422INVALID_COHORTA cohort request is malformed (unknown field, bad handle, missing caps, too many members in one call).
422INVALID_CURSORThe pagination cursor is invalid, was tampered with, or belongs to a different query.
422INVALID_INTERVALA monitor interval must be one of 15m, 1h, 6h, 12h, 24h.
422INVALID_JOBA job request is malformed (bad body, too many items, unknown fields); nothing was held or charged.
422INVALID_LABELThe label= parameter named a preset that does not exist; nothing was charged.
422INVALID_PARAMSA query parameter is missing, too long, or has the wrong format.
422INVALID_RULEA monitor alert rule is malformed (see the rule grammar in the monitors docs).
422INVALID_TARGETA monitor target names an unknown endpoint or recipe, or a malformed body.
422NO_SEARCHABLE_PLATFORMSUniversal search or a report: the platform hints, category or explicit list left no platform with an endpoint in the needed role; nothing was charged.
422PASSWORD_POLICYThe new password does not meet the policy (length, common passwords, not the email).
422SIGNUP_FAILEDThe account could not be created with those details.
422UNSUPPORTED_PLATFORMA multi-platform recipe named a platform that has no endpoint in that role; nothing was charged.
422UNSUPPORTED_URLGET /v1/lookup: no endpoint recognises that URL shape; nothing was charged.
422WEBHOOK_URL_REJECTEDA webhook URL must be https on a public host; private, loopback, link-local and metadata addresses are refused.
429AI_BUDGET_EXCEEDEDToday's AI judgment allowance for this account (or the service) is used up; see Retry-After.
429CONCURRENCY_LIMITEDToo many requests from this key are in flight at once (default cap 50); retry as others finish, see Retry-After and X-Concurrency-*.
429RATE_LIMITEDToo many requests for this key, too many failed sign-ins, or the dashboard playground / resend limit; see Retry-After.
500HTTP_ERRORA generic HTTP-level failure that has no more specific type.
500INTERNAL_ERRORSomething failed on our side; the request_id identifies it for support.
502UPSTREAM_BLOCKEDThe upstream platform refused our request; nothing was charged.
502UPSTREAM_ERRORThe upstream platform returned an error; nothing was charged.
502UPSTREAM_RATE_LIMITEDThe upstream platform is rate limiting us; retry later, nothing was charged.
502UPSTREAM_TIMEOUTThe upstream platform did not answer in time; nothing was charged.
503AI_UNAVAILABLEAI judgments are temporarily unavailable; retry, or call without strict=true to get unjudged rows.
503IDEMPOTENCY_STORE_UNAVAILABLEThe idempotency store is unreachable, so the request was refused before billing.

Your request#

The request itself needs changing. The same request will fail the same way, so do not retry it.

BAD_REQUEST#

400 bad_request. The request was malformed (for example an oversized body).

  • What to do: Fix the request. The body or headers could not be read at all.
  • Retry: No.
  • Charged: No.

INVALID_REQUEST#

400 invalid_request. A batch or streaming request was malformed (bad body, too many items, or an Idempotency-Key on a GET stream); nothing was run or billed.

  • What to do: Fix the request body or headers as the message describes. Nothing was run, so there is nothing to undo.
  • Retry: No, not unchanged.
  • Charged: No.

INVALID_PARAMS#

422 invalid_params. A query parameter is missing, too long, or has the wrong format.

  • What to do: Compare with the parameters table on the endpoint page: a required parameter is missing, or a value is too long or has the wrong format. The message names the parameter.
  • Retry: No.
  • Charged: No, rejected before any charge.

INVALID_CURSOR#

422 invalid_cursor. The pagination cursor is invalid, was tampered with, or belongs to a different query.

  • What to do: Send pagination.next_cursor back exactly as you received it, as the cursor parameter, with the same endpoint and the same other parameters. Start again from the first page if you no longer have a valid cursor. See Pagination.
  • Retry: No.
  • Charged: No.

INVALID_LABEL#

422 invalid_label. The label= parameter named a preset that does not exist; nothing was charged.

  • What to do: The label= parameter named a preset that does not exist. Use only sentiment, question, purchase_intent, complaint and issue, comma-separated, at most five. The message lists the names it did not recognise. See Labels.
  • Retry: No, not unchanged.
  • Charged: No, rejected before anything runs.

UNSUPPORTED_URL#

422 unsupported_url. GET /v1/lookup: no endpoint recognises that URL shape; nothing was charged.

  • What to do: The URL you passed does not match any endpoint. Call the platform endpoint directly with its own parameters instead; the platform catalog lists them.
  • Retry: No.
  • Charged: No.

UNSUPPORTED_PLATFORM#

422 unsupported_platform. A multi-platform recipe named a platform that has no endpoint in that role; nothing was charged.

  • What to do: Remove that platform from the request, or use one that has an endpoint in that role. GET /v1/platforms (free, no key) lists what each platform offers.
  • Retry: No, not unchanged.
  • Charged: No.

NO_SEARCHABLE_PLATFORMS#

422 no_searchable_platforms. Universal search or a report: the platform hints, category or explicit list left no platform with an endpoint in the needed role; nothing was charged.

  • What to do: Universal search or a report found no platform to search. Either the platform hints in your query name only platforms with no search endpoint (for example x on instagram), your category holds none, or no form of the query fits the search rules of the platforms available. Name platforms that have a search endpoint in platforms=, drop the hint, or remove the category; dry_run=true shows the plan for free. See Universal search and Reports.
  • Retry: No, not unchanged.
  • Charged: No, refused before any leg runs.

NOT_FOUND#

404 not_found. The endpoint does not exist, or the upstream platform has no such item.

  • What to do: Check the endpoint path (/v1/{platform}/{endpoint}) and the spelling of the parameter. For a lookup, the record does not exist on the platform.
  • Retry: No.
  • Charged: No. A lookup that reaches the platform is reserved and refunded.

METHOD_NOT_ALLOWED#

405 method_not_allowed. This route does not accept that HTTP method.

  • What to do: Data endpoints are GET. Dashboard routes use the method shown on their docs page.
  • Retry: No.
  • Charged: No.

Keys, access and rate limits#

Who is calling, and how often.

UNAUTHORIZED#

401 unauthorized. The x-api-key header is missing, malformed, or revoked.

  • What to do: Send the key in the x-api-key header and check it is not revoked in your dashboard. Repeated bad attempts from one address are rate limited.
  • Retry: No, not with the same key.
  • Charged: No.

403 paid_access_restricted. This account may not use paid data routes yet (verify your email or buy a pack).

  • What to do: This endpoint needs a data route that is only open to accounts with a verified email and a purchased pack. Verify your email or buy a pack, then retry.
  • Retry: After the account qualifies.
  • Charged: No.

RATE_LIMITED#

429 rate_limited. Too many requests for this key, too many failed sign-ins, or the dashboard playground / resend limit; see Retry-After.

  • What to do: Wait the number of seconds in the Retry-After header, then retry. X-RateLimit-Remaining tells you how close you are before it happens. See Rate limits & headers.
  • Retry: Yes, after Retry-After.
  • Charged: No.

CONCURRENCY_LIMITED#

429 concurrency_limited. Too many requests from this key are in flight at once (default cap 50); retry as others finish, see Retry-After and X-Concurrency-*.

  • What to do: This key already has the maximum number of requests in flight (default 50). Lower your parallelism, or wait for some to finish and retry after Retry-After (1 second). X-Concurrency-Remaining shows how much room is left. See Rate limits & headers.
  • Retry: Yes, after Retry-After.
  • Charged: No.

Credits and keys#

Nothing is charged for any of these.

INSUFFICIENT_CREDITS#

402 insufficient_credits. The account balance cannot cover this call; buy a pack to continue.

  • What to do: The account balance cannot cover this call. Buy credits. GET /v1/account shows the balance for free.
  • Retry: After topping up.
  • Charged: No.

KEY_BUDGET_EXCEEDED#

402 key_budget_exceeded. This API key reached its credit_limit; raise or clear the budget, or use another key.

  • What to do: This key has reached its own credit limit; the account balance is untouched and other keys still work. Raise or clear the limit in the dashboard, or use another key. See Per-key credit limits.
  • Retry: After the limit is raised or cleared.
  • Charged: No.

KEY_LIMIT#

409 key_limit. The account already has the maximum number of active API keys.

  • What to do: Revoke a key you no longer use before creating another. The maximum number of active keys is shown in the dashboard.
  • Retry: After revoking a key.
  • Charged: Not applicable.

AI enrichment#

Only when a call asks for labels, relevance or themes. Preview: available only when enabled for your account.

AI_BUDGET_EXCEEDED#

429 ai_budget_exceeded. Today's AI judgment allowance for this account (or the service) is used up; see Retry-After.

  • What to do: Nothing could be judged because the daily enrichment allowance for your account, or for the service as a whole, is used up. Wait Retry-After seconds (until the next UTC day), or add strict=false to get the rows back unjudged. A call that judged only some rows is a partial result, not this error. See AI pricing.
  • Retry: Yes, after Retry-After; or now with strict=false.
  • Charged: No. The reservation is refunded in full; the endpoint data is cached, so repeating the call is a free hit for the data.

AI_UNAVAILABLE#

503 ai_unavailable. AI judgments are temporarily unavailable; retry, or call without strict=true to get unjudged rows.

  • What to do: Nothing could be judged: enrichment is not enabled for this account yet, or the judging service is temporarily unavailable. Retry after a few seconds (Retry-After), or add strict=false to get the rows back with labels.status set to unavailable and no judgments. See Labels.
  • Retry: Yes, after a short wait, if the service was only unavailable. Not if enrichment is not enabled for your account.
  • Charged: No. Refused before the data is fetched when enrichment is off; otherwise the enrichment reservation is refunded in full.

Idempotency#

Only when you send an Idempotency-Key header. See Idempotency.

IDEMPOTENCY_KEY_INVALID#

400 idempotency_key_invalid. The Idempotency-Key header is not a UUID.

  • What to do: The Idempotency-Key header must be a UUID (any version, for example 6f1c2d3e-4b5a-4c6d-8e7f-90a1b2c3d4e5). See Idempotency.
  • Retry: With a valid key.
  • Charged: No, refused before billing.

IDEMPOTENCY_IN_PROGRESS#

409 idempotency_in_progress. A request with the same Idempotency-Key is still running; retry after 1 second.

  • What to do: The first request with this key is still running. Wait one second (Retry-After: 1) and send the same request again; you will get the replayed result once it completes.
  • Retry: Yes, after one second.
  • Charged: No, this attempt is not billed. The first request is billed once when it completes.

IDEMPOTENCY_REPLAY_UNAVAILABLE#

409 idempotency_replay_unavailable. The original response exceeded 64 KB and was not stored for replay.

  • What to do: The original call succeeded and was billed, but its response was over 64 KB and was not kept for replay. Send the request again with a new Idempotency-Key; that is a new, billed call.
  • Retry: Only with a new key.
  • Charged: No, nothing is charged for this answer.

IDEMPOTENCY_KEY_REUSED#

422 idempotency_key_reused. The Idempotency-Key was already used for a different endpoint or parameters.

  • What to do: The same key was used before for a different endpoint or different parameters. Use a fresh UUID for each distinct request.
  • Retry: With a new key.
  • Charged: No.

IDEMPOTENCY_STORE_UNAVAILABLE#

503 idempotency_store_unavailable. The idempotency store is unreachable, so the request was refused before billing.

  • What to do: The store that guarantees a single charge is unreachable, so the request was refused before billing rather than risk billing twice. Wait one second (Retry-After: 1) and retry with the same key.
  • Retry: Yes, after one second.
  • Charged: No.

Monitors and webhooks#

Only on the /v1/monitors routes. Nothing is charged for any of these. See Monitors.

MONITOR_NOT_FOUND#

404 monitor_not_found. No monitor with that id exists on this account.

  • What to do: No monitor with that id exists on this account. List yours with GET /v1/monitors and check the id (mon_...). A deleted monitor is gone for good. See Monitors.
  • Retry: No.
  • Charged: No.

MONITOR_LIMIT_REACHED#

409 monitor_limit_reached. The account already has the maximum number of active monitors; pause or delete one.

  • What to do: The account already has 25 active monitors. Pause or delete one, then create or reactivate this one. Paused monitors do not count. See Monitors.
  • Retry: After pausing or deleting a monitor.
  • Charged: No.

MONITOR_RUN_IN_PROGRESS#

409 monitor_run_in_progress. The monitor is being run by the scheduler right now; retry the manual run shortly.

  • What to do: The scheduler is running this monitor right now, so a manual run was refused. Wait a few seconds and run it again, or just wait for the scheduled run.
  • Retry: Yes, after a few seconds.
  • Charged: No, this attempt is not billed. The run already in progress is billed once, as usual.

WEBHOOK_DISABLED#

409 webhook_disabled. The monitor's webhook was switched off after repeated delivery failures; PATCH a new webhook.url to re-enable it.

  • What to do: Ten delivery attempts in a row failed, so the webhook of this monitor was switched off. Runs continue and you can still read their deltas from GET /v1/monitors/{id}/runs. Fix your endpoint, then PATCH the monitor with {"webhook": {"url": "..."}} (the same URL is fine) to switch it back on. See Monitors.
  • Retry: After the webhook is re-enabled with a PATCH.
  • Charged: No.

INVALID_INTERVAL#

422 invalid_interval. A monitor interval must be one of 15m, 1h, 6h, 12h, 24h.

  • What to do: Use one of 15m, 1h, 6h, 12h or 24h. Nothing shorter is accepted.
  • Retry: No, not unchanged.
  • Charged: No.

INVALID_RULE#

422 invalid_rule. A monitor alert rule is malformed (see the rule grammar in the monitors docs).

  • What to do: The message names the rule that is wrong. At most 10 rules; on is any_change, new_items, field_change or threshold; field_change and threshold need a dotted field; threshold needs an op and a value; unknown keys are refused. See Alert rules.
  • Retry: No, not unchanged.
  • Charged: No.

INVALID_TARGET#

422 invalid_target. A monitor target names an unknown endpoint or recipe, or a malformed body.

  • What to do: The target must name an existing platform and endpoint, or a recipe of search/multi, creator/card or lookup, and the webhook and name must be well formed. Check the endpoint on the platform catalog.
  • Retry: No, not unchanged.
  • Charged: No.

WEBHOOK_URL_REJECTED#

422 webhook_url_rejected. A webhook URL must be https on a public host; private, loopback, link-local and metadata addresses are refused.

  • What to do: The webhook must be an https URL on port 443 to a public host. Private, loopback, link-local and cloud-metadata addresses, internal names, IP literals and URLs with credentials are refused, and so is a host that resolves to any of them. To test locally, use a public HTTPS tunnel. See Monitors.
  • Retry: No, not unchanged.
  • Charged: No.

Cohorts#

Only on the /v1/cohorts and /v1/cohort-queries routes. Nothing is charged for any of these. See Cohorts.

COHORT_NOT_FOUND#

404 cohort_not_found. No cohort with that id exists on this account, or it passed its retention and was deleted.

  • What to do: No cohort with that id exists on this account, or it passed its retention and was deleted. List yours with GET /v1/cohorts. See Cohorts.
  • Retry: No.
  • Charged: No.

COHORT_QUERY_NOT_FOUND#

404 cohort_query_not_found. No cohort query with that id exists on this account, or it expired and was deleted.

  • What to do: No cohort query with that id exists on this account, or it expired with its cohort. Submit it again with POST /v1/cohorts/{id}/queries.
  • Retry: No.
  • Charged: No.

COHORT_LIMIT_REACHED#

409 cohort_limit_reached. The account already has the maximum number of cohorts; delete one.

  • What to do: The account has 50 cohorts. Delete one with DELETE /v1/cohorts/{id}.
  • Retry: After deleting one.
  • Charged: No.

COHORT_MEMBER_LIMIT#

409 cohort_member_limit. A cohort holds at most 10,000 members.

  • What to do: A cohort holds at most 10,000 members. Remove some with DELETE /v1/cohorts/{id}/members or use a second cohort.
  • Retry: No, not unchanged.
  • Charged: No.

COHORT_IDENTITY_CONFLICT#

409 cohort_identity_conflict. Two different external_ids claim the same platform and handle inside one cohort.

  • What to do: Two different external_ids claim the same platform and handle in this cohort. Send one external_id per identity, or remove the old one first. See Cohorts.
  • Retry: No, not unchanged.
  • Charged: No.

COHORT_QUERY_NOT_READY#

409 cohort_query_not_ready. The cohort query has not succeeded yet, so there are no results to read; poll its status.

  • What to do: The query has not succeeded. Poll GET /v1/cohort-queries/{id} until status is succeeded, then read the results. A failed or cancelled query has none.
  • Retry: Yes, after it succeeds.
  • Charged: No.

COHORT_IDENTITY_PLATFORM_UNSUPPORTED#

400 cohort_identity_platform_unsupported. A member names a platform that has no recent-posts feed to query; nothing was stored.

  • What to do: A member names a platform with no recent-posts feed. GET /v1/cohorts lists the supported platforms. Nothing in the request was stored.
  • Retry: No.
  • Charged: No.

COHORT_BUDGET_EXCEEDED#

400 cohort_budget_exceeded. The worst-case price of a cohort query is above the max_credits you set; nothing was held or run.

  • What to do: The worst-case price of the query (in the message) is above your max_credits. Raise it, narrow platforms, or use a smaller cohort. Nothing was held or run.
  • Retry: No, not unchanged.
  • Charged: No.

INVALID_COHORT#

422 invalid_cohort. A cohort request is malformed (unknown field, bad handle, missing caps, too many members in one call).

  • What to do: Fix the body as the message says: unknown fields (a member is platform, handle and an optional external_id only), a bad handle, missing caps (max_pages_per_identity, max_items_per_identity, max_credits have no defaults) or a date_from that is not a full RFC 3339 timestamp. See Cohorts.
  • Retry: No, not unchanged.
  • Charged: No.

Jobs and billing webhooks#

Only on /v1/jobs and /v1/billing/webhooks. Reading and managing these is free; a refused job holds nothing. See Background jobs and Billing webhooks.

JOB_NOT_FOUND#

404 job_not_found. No job with that id exists on this account, or its results expired and were deleted.

  • What to do: No job with that id exists on this account, or its results passed the 7 days they are kept after the job finished. List yours with GET /v1/jobs and check the id (job_...). See Background jobs.
  • Retry: No.
  • Charged: No.

JOB_LIMIT_REACHED#

409 job_limit_reached. The account (or this key) already has the maximum number of unfinished jobs.

  • What to do: The account has 10 unfinished jobs, or this key has 3. Wait for one to finish (poll GET /v1/jobs) or cancel one with DELETE /v1/jobs/{id}, then submit again. See Limits.
  • Retry: After a job finishes or is cancelled.
  • Charged: No, nothing was held.

JOB_FINISHED#

409 job_finished. The job already finished, so it cannot be cancelled.

  • What to do: The job already completed, was cancelled or failed, so there is nothing left to cancel. Read its results with GET /v1/jobs/{id}.
  • Retry: No.
  • Charged: No.

INVALID_JOB#

422 invalid_job. A job request is malformed (bad body, too many items, unknown fields); nothing was held or charged.

  • What to do: The message names the problem. Send exactly one of requests (up to 5,000 items of platform, endpoint, params) or crawl (endpoint and urls for a web endpoint); AI parameters are not supported; unknown fields are refused. See Submit a job.
  • Retry: No, not unchanged.
  • Charged: No, nothing was held.

BILLING_WEBHOOK_NOT_FOUND#

404 billing_webhook_not_found. No billing webhook with that id exists on this account.

  • What to do: No billing webhook with that id exists on this account. List yours with GET /v1/billing/webhooks (whk_...). A deleted endpoint is gone for good. See Billing webhooks.
  • Retry: No.
  • Charged: No.

BILLING_WEBHOOK_LIMIT#

409 billing_webhook_limit. The account already has the maximum number of billing webhooks.

  • What to do: The account has 5 billing webhooks. Delete one you no longer use, or change its url and events with PATCH instead of creating another.
  • Retry: After deleting an endpoint.
  • Charged: No.

INVALID_BILLING_WEBHOOK#

422 invalid_billing_webhook. A billing webhook setting is malformed (events, low_threshold_credits, url, description).

  • What to do: The message names the field. events is a non-empty list of credits.low, credits.depleted, purchase.completed; low_threshold_credits is a number of credits, 0 or more; description is at most 120 characters; unknown fields are refused. See Billing webhooks.
  • Retry: No, not unchanged.
  • Charged: No.

The source platform#

The platform Onekey fetches from did not answer usefully. These are refunded and worth retrying with backoff. If Onekey holds a recent copy it serves that instead, as a 200 with metadata.stale: true (see Caching).

UPSTREAM_ERROR#

502 upstream_error. The upstream platform returned an error; nothing was charged.

  • What to do: The source platform answered with an error. Retry with exponential backoff (for example 1 s, 2 s, 4 s). If it keeps failing, check /v1/status.
  • Retry: Yes, with backoff.
  • Charged: No, refunded.

UPSTREAM_TIMEOUT#

502 upstream_timeout. The upstream platform did not answer in time; nothing was charged.

  • What to do: The source platform was too slow. Retry with backoff.
  • Retry: Yes, with backoff.
  • Charged: No, refunded.

UPSTREAM_BLOCKED#

502 upstream_blocked. The upstream platform refused our request; nothing was charged.

  • What to do: The source platform refused Onekey's request. Retry later; if it persists, check /v1/status.
  • Retry: Later.
  • Charged: No, refunded.

UPSTREAM_RATE_LIMITED#

502 upstream_rate_limited. The upstream platform is rate limiting us; retry later, nothing was charged.

  • What to do: The source platform is throttling Onekey. Retry after a short wait.
  • Retry: Yes, after a short wait.
  • Charged: No, refunded.

Our side#

Something broke in the API. Quote the request_id if you write to us.

INTERNAL_ERROR#

500 internal_error. Something failed on our side; the request_id identifies it for support.

  • What to do: Something failed on our side. Retry once; if it repeats, send us the request_id.
  • Retry: Once.
  • Charged: No. Any reservation is refunded.

HTTP_ERROR#

500 http_error. A generic HTTP-level failure that has no more specific type.

  • What to do: A framework-level failure with no more specific type. Retry once; if it repeats, send us the request_id.
  • Retry: Once.
  • Charged: No.

Dashboard only#

Returned by the sign-in and account routes the dashboard uses, never by data endpoints.

INVALID_CREDENTIALS#

401 invalid_credentials. Email or password did not match (dashboard sign-in).

  • What to do: Dashboard sign-in only: the email or password did not match. Use Forgot password if needed.
  • Retry: With the right credentials.
  • Charged: Not applicable.

NOT_AUTHENTICATED#

401 not_authenticated. A dashboard session cookie is required for this route.

  • What to do: Dashboard routes need a signed-in browser session, not an API key. Sign in at /login.
  • Retry: After signing in.
  • Charged: Not applicable.

CSRF_REJECTED#

403 csrf_rejected. A state-changing dashboard request lacked the CSRF header or came from an unknown origin.

  • What to do: Dashboard routes that change state need the CSRF header and a request from the dashboard origin. Use the dashboard rather than calling these routes from elsewhere.
  • Retry: No.
  • Charged: Not applicable.

INVALID_TOKEN#

400 invalid_token. The password-reset or email-verification token is invalid or expired.

  • What to do: The password-reset or email-verification link is invalid or has expired. Request a new one.
  • Retry: With a new link.
  • Charged: Not applicable.

SIGNUP_FAILED#

422 signup_failed. The account could not be created with those details.

  • What to do: The account could not be created with those details. Check the email address and password requirements.
  • Retry: With different details.
  • Charged: Not applicable.

Other#

Recently added types.

PASSWORD_INCORRECT#

403 password_incorrect. Changing the password: the current password did not match.

  • What to do: Read the message and quote the request_id if you contact support.
  • Retry: Check the status code: 4xx will fail the same way; 5xx may succeed on retry.
  • Charged: Failed calls are not charged.

PASSWORD_POLICY#

422 password_policy. The new password does not meet the policy (length, common passwords, not the email).

  • What to do: Read the message and quote the request_id if you contact support.
  • Retry: Check the status code: 4xx will fail the same way; 5xx may succeed on retry.
  • Charged: Failed calls are not charged.

ALREADY_VERIFIED#

409 already_verified. The email address is already verified; no verification email was sent.

  • What to do: Read the message and quote the request_id if you contact support.
  • Retry: Check the status code: 4xx will fail the same way; 5xx may succeed on retry.
  • Charged: Failed calls are not charged.

DELETE_ALREADY_REQUESTED#

409 delete_already_requested. Account deletion was already requested; an operator completes it.

  • What to do: Read the message and quote the request_id if you contact support.
  • Retry: Check the status code: 4xx will fail the same way; 5xx may succeed on retry.
  • Charged: Failed calls are not charged.

Retry guidance#

  • Retry: 429 after Retry-After; 502 and 500 with exponential backoff and jitter (for example 1 s, 2 s, 4 s, up to 3 or 4 tries); the two idempotency 409/503 answers after one second.
  • Do not retry: 400, 401, 402, 403, 404, 405, 422. The same request fails the same way.
  • Failed calls are not charged, so a retry that does not work costs nothing. To make a retry that *does* work safe against double billing, send an Idempotency-Key.
  • One exception to "not charged": a request that was refused by the source because of your own input (for example a URL you supplied that the target rejected) after a paid data route already ran keeps its charge. This is rare and the response is still the 502 shown above.

Example: safe retry loop#


import time, requests

def onekey(path, params, key, tries=4):
    for attempt in range(tries):
        r = requests.get(f"https://api.onekey.dev{path}", params=params, headers={"x-api-key": key}, timeout=30)
        if r.status_code in (429, 503) or (r.status_code == 409 and r.json()["error"]["type"] == "IDEMPOTENCY_IN_PROGRESS"):
            time.sleep(int(r.headers.get("Retry-After", "1")))
        elif r.status_code >= 500:
            time.sleep(2 ** attempt)
        else:
            return r.json()  # success, or a 4xx you should not retry
    return r.json()