# Errors

Every failure uses the same [response format](https://onekey.dev/docs/response-envelope.md) 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

| Field | Meaning |
| --- | --- |
| `type` | Stable machine string in upper snake case. Branch on this. |
| `code` | The same word in lower case (`invalid_cursor`). Kept for older clients; `type` and `code` never disagree. |
| `message` | Human text. It may change and never contains text from the source platform, so do not parse it. |
| `request_id` | Correlation id, equal to the `X-Request-Id` header and `metadata.request_id`. Quote it if you contact support. |
| `doc_url` | `https://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](https://onekey.dev/docs/rate-limits.md).

## All types at a glance

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

| Status | `error.type` | Meaning |
| --- | --- | --- |
| 400 | [`BAD_REQUEST`](#bad-request) | The request was malformed (for example an oversized body). |
| 400 | [`COHORT_BUDGET_EXCEEDED`](#cohort-budget-exceeded) | The worst-case price of a cohort query is above the max_credits you set; nothing was held or run. |
| 400 | [`COHORT_IDENTITY_PLATFORM_UNSUPPORTED`](#cohort-identity-platform-unsupported) | A member names a platform that has no recent-posts feed to query; nothing was stored. |
| 400 | [`IDEMPOTENCY_KEY_INVALID`](#idempotency-key-invalid) | The Idempotency-Key header is not a UUID. |
| 400 | [`INVALID_REQUEST`](#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. |
| 400 | [`INVALID_TOKEN`](#invalid-token) | The password-reset or email-verification token is invalid or expired. |
| 401 | [`CLERK_TOKEN_INVALID`](#clerk-token-invalid) | The Google / GitHub sign-in token (Clerk) is invalid or expired; sign in again. |
| 401 | [`INVALID_CREDENTIALS`](#invalid-credentials) | Email or password did not match (dashboard sign-in). |
| 401 | [`NOT_AUTHENTICATED`](#not-authenticated) | A dashboard session cookie is required for this route. |
| 401 | [`UNAUTHORIZED`](#unauthorized) | The x-api-key header is missing, malformed, or revoked. |
| 402 | [`INSUFFICIENT_CREDITS`](#insufficient-credits) | The account balance cannot cover this call; buy a pack to continue. |
| 402 | [`KEY_BUDGET_EXCEEDED`](#key-budget-exceeded) | This API key reached its credit_limit; raise or clear the budget, or use another key. |
| 403 | [`CSRF_REJECTED`](#csrf-rejected) | A state-changing dashboard request lacked the CSRF header or came from an unknown origin. |
| 403 | [`PAID_ACCESS_RESTRICTED`](#paid-access-restricted) | This account may not use paid data routes yet (verify your email or buy a pack). |
| 403 | [`PASSWORD_INCORRECT`](#password-incorrect) | Changing the password: the current password did not match. |
| 404 | [`BILLING_WEBHOOK_NOT_FOUND`](#billing-webhook-not-found) | No billing webhook with that id exists on this account. |
| 404 | [`COHORT_NOT_FOUND`](#cohort-not-found) | No cohort with that id exists on this account, or it passed its retention and was deleted. |
| 404 | [`COHORT_QUERY_NOT_FOUND`](#cohort-query-not-found) | No cohort query with that id exists on this account, or it expired and was deleted. |
| 404 | [`JOB_NOT_FOUND`](#job-not-found) | No job with that id exists on this account, or its results expired and were deleted. |
| 404 | [`MONITOR_NOT_FOUND`](#monitor-not-found) | No monitor with that id exists on this account. |
| 404 | [`NOT_FOUND`](#not-found) | The endpoint does not exist, or the upstream platform has no such item. |
| 405 | [`METHOD_NOT_ALLOWED`](#method-not-allowed) | This route does not accept that HTTP method. |
| 409 | [`ALREADY_VERIFIED`](#already-verified) | The email address is already verified; no verification email was sent. |
| 409 | [`BILLING_WEBHOOK_LIMIT`](#billing-webhook-limit) | The account already has the maximum number of billing webhooks. |
| 409 | [`COHORT_IDENTITY_CONFLICT`](#cohort-identity-conflict) | Two different external_ids claim the same platform and handle inside one cohort. |
| 409 | [`COHORT_LIMIT_REACHED`](#cohort-limit-reached) | The account already has the maximum number of cohorts; delete one. |
| 409 | [`COHORT_MEMBER_LIMIT`](#cohort-member-limit) | A cohort holds at most 10,000 members. |
| 409 | [`COHORT_QUERY_NOT_READY`](#cohort-query-not-ready) | The cohort query has not succeeded yet, so there are no results to read; poll its status. |
| 409 | [`DELETE_ALREADY_REQUESTED`](#delete-already-requested) | Account deletion was already requested; an operator completes it. |
| 409 | [`IDEMPOTENCY_IN_PROGRESS`](#idempotency-in-progress) | A request with the same Idempotency-Key is still running; retry after 1 second. |
| 409 | [`IDEMPOTENCY_REPLAY_UNAVAILABLE`](#idempotency-replay-unavailable) | The original response exceeded 64 KB and was not stored for replay. |
| 409 | [`JOB_FINISHED`](#job-finished) | The job already finished, so it cannot be cancelled. |
| 409 | [`JOB_LIMIT_REACHED`](#job-limit-reached) | The account (or this key) already has the maximum number of unfinished jobs. |
| 409 | [`KEY_LIMIT`](#key-limit) | The account already has the maximum number of active API keys. |
| 409 | [`MONITOR_LIMIT_REACHED`](#monitor-limit-reached) | The account already has the maximum number of active monitors; pause or delete one. |
| 409 | [`MONITOR_RUN_IN_PROGRESS`](#monitor-run-in-progress) | The monitor is being run by the scheduler right now; retry the manual run shortly. |
| 409 | [`WEBHOOK_DISABLED`](#webhook-disabled) | The monitor's webhook was switched off after repeated delivery failures; PATCH a new webhook.url to re-enable it. |
| 422 | [`IDEMPOTENCY_KEY_REUSED`](#idempotency-key-reused) | The Idempotency-Key was already used for a different endpoint or parameters. |
| 422 | [`INVALID_BILLING_WEBHOOK`](#invalid-billing-webhook) | A billing webhook setting is malformed (events, low_threshold_credits, url, description). |
| 422 | [`INVALID_COHORT`](#invalid-cohort) | A cohort request is malformed (unknown field, bad handle, missing caps, too many members in one call). |
| 422 | [`INVALID_CURSOR`](#invalid-cursor) | The pagination cursor is invalid, was tampered with, or belongs to a different query. |
| 422 | [`INVALID_INTERVAL`](#invalid-interval) | A monitor interval must be one of 15m, 1h, 6h, 12h, 24h. |
| 422 | [`INVALID_JOB`](#invalid-job) | A job request is malformed (bad body, too many items, unknown fields); nothing was held or charged. |
| 422 | [`INVALID_LABEL`](#invalid-label) | The label= parameter named a preset that does not exist; nothing was charged. |
| 422 | [`INVALID_PARAMS`](#invalid-params) | A query parameter is missing, too long, or has the wrong format. |
| 422 | [`INVALID_RULE`](#invalid-rule) | A monitor alert rule is malformed (see the rule grammar in the monitors docs). |
| 422 | [`INVALID_TARGET`](#invalid-target) | A monitor target names an unknown endpoint or recipe, or a malformed body. |
| 422 | [`NO_SEARCHABLE_PLATFORMS`](#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. |
| 422 | [`PASSWORD_POLICY`](#password-policy) | The new password does not meet the policy (length, common passwords, not the email). |
| 422 | [`SIGNUP_FAILED`](#signup-failed) | The account could not be created with those details. |
| 422 | [`UNSUPPORTED_PLATFORM`](#unsupported-platform) | A multi-platform recipe named a platform that has no endpoint in that role; nothing was charged. |
| 422 | [`UNSUPPORTED_URL`](#unsupported-url) | GET /v1/lookup: no endpoint recognises that URL shape; nothing was charged. |
| 422 | [`WEBHOOK_URL_REJECTED`](#webhook-url-rejected) | A webhook URL must be https on a public host; private, loopback, link-local and metadata addresses are refused. |
| 429 | [`AI_BUDGET_EXCEEDED`](#ai-budget-exceeded) | Today's AI judgment allowance for this account (or the service) is used up; see Retry-After. |
| 429 | [`CONCURRENCY_LIMITED`](#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-*. |
| 429 | [`RATE_LIMITED`](#rate-limited) | Too many requests for this key, too many failed sign-ins, or the dashboard playground / resend limit; see Retry-After. |
| 500 | [`HTTP_ERROR`](#http-error) | A generic HTTP-level failure that has no more specific type. |
| 500 | [`INTERNAL_ERROR`](#internal-error) | Something failed on our side; the request_id identifies it for support. |
| 502 | [`UPSTREAM_BLOCKED`](#upstream-blocked) | The upstream platform refused our request; nothing was charged. |
| 502 | [`UPSTREAM_ERROR`](#upstream-error) | The upstream platform returned an error; nothing was charged. |
| 502 | [`UPSTREAM_RATE_LIMITED`](#upstream-rate-limited) | The upstream platform is rate limiting us; retry later, nothing was charged. |
| 502 | [`UPSTREAM_TIMEOUT`](#upstream-timeout) | The upstream platform did not answer in time; nothing was charged. |
| 503 | [`AI_UNAVAILABLE`](#ai-unavailable) | AI judgments are temporarily unavailable; retry, or call without strict=true to get unjudged rows. |
| 503 | [`CLERK_NOT_CONFIGURED`](#clerk-not-configured) | Google / GitHub sign-in is not enabled on this server; sign in with email and password. |
| 503 | [`IDEMPOTENCY_STORE_UNAVAILABLE`](#idempotency-store-unavailable) | The 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](https://onekey.dev/docs/pagination.md).
- **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](https://onekey.dev/docs/labels.md).
- **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](https://onekey.dev/docs/platforms.md) 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](https://onekey.dev/docs/universal-search.md#the-planner) and [Reports](https://onekey.dev/docs/reports.md#choosing-platforms).
- **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](https://onekey.dev/dashboard/keys). Repeated bad attempts from one address are rate limited.
- **Retry:** No, not with the same key.
- **Charged:** No.

#### PAID_ACCESS_RESTRICTED

**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](https://onekey.dev/docs/rate-limits.md).
- **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](https://onekey.dev/docs/rate-limits.md#concurrent-requests).
- **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](https://onekey.dev/pricing). `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](https://onekey.dev/dashboard/keys), or use another key. See [Per-key credit limits](https://onekey.dev/docs/key-limits.md).
- **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](https://onekey.dev/docs/labels.md), [relevance](https://onekey.dev/docs/relevance.md) or [themes](https://onekey.dev/docs/themes.md). 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](https://onekey.dev/docs/ai-pricing.md#limits).
- **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](https://onekey.dev/docs/labels.md#when-nothing-could-be-judged).
- **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](https://onekey.dev/docs/idempotency.md).

#### 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](https://onekey.dev/docs/idempotency.md).
- **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](https://onekey.dev/docs/monitors.md).

#### 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](https://onekey.dev/docs/monitors.md#endpoints).
- **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](https://onekey.dev/docs/monitors.md#what-a-monitor-is).
- **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](https://onekey.dev/docs/monitors.md#delivery-retries-and-auto-disable).
- **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](https://onekey.dev/docs/monitors.md#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](https://onekey.dev/docs/platforms.md).
- **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](https://onekey.dev/docs/monitors.md#webhook-url-rules-ssrf).
- **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](https://onekey.dev/docs/cohorts.md).

#### 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](https://onekey.dev/docs/cohorts.md#what-is-stored).
- **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_id`s claim the same platform and handle in this cohort. Send one `external_id` per identity, or remove the old one first. See [Cohorts](https://onekey.dev/docs/cohorts.md#2-add-members).
- **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](https://onekey.dev/docs/cohorts.md).
- **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](https://onekey.dev/docs/jobs.md) and [Billing webhooks](https://onekey.dev/docs/billing-webhooks.md).

#### 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](https://onekey.dev/docs/jobs.md#read-a-job).
- **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](https://onekey.dev/docs/jobs.md#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](https://onekey.dev/docs/jobs.md#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](https://onekey.dev/docs/billing-webhooks.md#create-an-endpoint).
- **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](https://onekey.dev/docs/billing-webhooks.md#create-an-endpoint).
- **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](https://onekey.dev/docs/caching.md)).

#### 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](https://onekey.dev/docs/status.md).
- **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](https://onekey.dev/docs/status.md).
- **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](https://onekey.dev/forgot) 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](https://onekey.dev/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.

#### CLERK_TOKEN_INVALID

**401** `clerk_token_invalid`. The Google / GitHub sign-in token (Clerk) is invalid or expired; sign in again.

- **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_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.

#### CLERK_NOT_CONFIGURED

**503** `clerk_not_configured`. Google / GitHub sign-in is not enabled on this server; sign in with email and password.

- **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`](https://onekey.dev/docs/idempotency.md).
- 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

```python

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()

```

```javascript

async function onekey(path, params, key, tries = 4) {
  let res;
  for (let attempt = 0; attempt < tries; attempt++) {
    res = await fetch(`https://api.onekey.dev${path}?${new URLSearchParams(params)}`, { headers: { "x-api-key": key } });
    const wait = Number(res.headers.get("retry-after") || 0);
    if (res.status === 429 || res.status === 503 || (res.status === 409 && wait)) await new Promise((r) => setTimeout(r, 1000 * (wait || 1)));
    else if (res.status >= 500) await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
    else break;
  }
  return res.json();
}

```
