# URLert API, v1

The URLert API, version 1 (a beta). It checks a link, a message or a page before a program or an AI assistant acts on it, with the same checks as the website. There are six operations, each a POST with a JSON body, with a token made in Account, then AI agents, sent as `Authorization: Bearer <token>`. There is no operation that deletes a check or an account or changes how you sign in.

A call is `POST /api/v1/<operation>`. A check answers at once with `status` `queued`: call `get_result` every few seconds with its `id` until `status` is `done` or `failed`. A check that fails is still a 200 answer; read `failure`. The limit is 20 checks a day for each account, across the API and the AI assistants' tools together; `get_result`, `get_guide` and `report_mistake` don't count, and the count starts again at 00:00 UTC. `RateLimit` and `RateLimit-Policy` headers describe the API's call rate, not the daily limit. Send an `Idempotency-Key` with a check or a report: a repeat of the same call gives back the first answer and doesn't count again. This is a beta: a change that would break a program gets a version 2, and version 1 is retired with a date announced in advance.

Every failure is `application/problem+json` with `status`, `code`, `detail` (words for a person) and `errors` (the fields at fault). Branch on `code`. A 400 `bad_request` means fix the request. A 400 `invalid_text` means send the message's text. A 401 `invalid_token` means make a new token. A 403 `insufficient_scope` means the token isn't allowed this operation. A 404 `not_found` says "No check has this id. Checks are deleted after 30 days." and means stop polling. A 409 `no_result_yet` says "This check has no result to report yet" and means wait until the check is done. A 429 `daily_limit` says "This account has made 20 checks today. The limit is 20 a day. The count starts again at 00:00 UTC." and a 429 `checks_paused` says "Checks from assistants and programs are paused until 00:00 UTC. The website still works for people."; both send `Retry-After`, the seconds until 00:00 UTC, so wait that long and don't spread the calls over more accounts. A 429 `rate_limited` also sends `Retry-After`: slow down. A 500 or 503 is ours: retry with backoff, and the checks are safe to retry with an `Idempotency-Key`.

## Calling it

Every operation is a `POST` to `https://www.urlert.com/api/v1/<operation>` with a JSON object of its params (`{}` for none), and answers JSON. Sign in with an API token from the app's agents page: `Authorization: Bearer <token>`. Without one, you can call only what anyone may.

- **Lists** come a page at a time: send `limit` (1 to 100, 50 when left out), and get `{ "data": [...], "next": "<cursor>" }`. Send `after` with `next`, and the same params, for the next page, until `next` is null. A row that doesn't change during a walk comes exactly once; one whose order changes meanwhile may be missed or come twice.
- **Retrying an action:** send `Idempotency-Key` with a value of your own (1 to 255 characters, such as a UUID). A retry with the same key and body gets the first answer again, with `Idempotent-Replayed: true`, for 24 hours; only a call that succeeded is kept, so one that failed runs again. This follows Stripe's documented behaviour.
- **Limits:** each answer has `RateLimit-Policy` and `RateLimit` headers; past a limit, 429, with `Retry-After` in seconds when the limit knows when it lifts.
- **Values:** a time is an ISO 8601 string in UTC, a date `YYYY-MM-DD`, money a whole number of cents, an id a string with its kind's prefix, and every field of an answer is there, null when empty.

## check_link

Checks one link before you open it or act on it. Answers at once with the check's `id` and status `queued`: call get_result with the id until its status is `done` or `failed`. Counts as one of the account's 20 checks a day. Can answer 400 `bad_request` (the link isn't one web link, or `country` isn't two capital letters), 429 `daily_limit` or 429 `checks_paused`.

`POST https://www.urlert.com/api/v1/check_link`, signed in. Takes `Idempotency-Key`.

| Param | Type | |
|---|---|---|
| `link` | text | required |
| `action` | text or null | optional |
| `task` | text or null | optional |
| `country` | text or null | optional |

It answers:

```json
{
  "id": "id",
  "status": "queued",
  "outcome": "malicious",
  "outcome_name": "outcome_name",
  "asks_for": [
    "asks_for"
  ],
  "reasons": [
    {
      "text": "text"
    }
  ],
  "sites": [
    {
      "host": "host",
      "domain": "domain",
      "age_days": 3,
      "age_note": "age_note",
      "anyone_can_publish": true,
      "scam_lists": [
        "scam_lists"
      ]
    }
  ],
  "opened_link": "opened_link",
  "final_link": "final_link",
  "views": [
    {
      "device": "device",
      "state": "state"
    }
  ],
  "reused": true,
  "checked_at": "2030-01-15T09:30:00.000Z",
  "expires_at": "2030-01-15T09:30:00.000Z",
  "failure": "failure",
  "message": "message",
  "shared_copy": true,
  "result_url": "result_url"
}
```

It can fail with 400, 401, 403, 404, 409, 429, 500, 503, including 409 `conflict` from its own checks. A failure it passes on from another service can have that service's status.

Its own codes, to branch on (for any other failure, branch on `status`):

- [`checks_paused`](#checks_paused) (429): Checks from assistants and programs are paused until 00:00 UTC. The website still works for people.
- [`daily_limit`](#daily_limit) (429): This account has made 20 checks today. The limit is 20 a day. The count starts again at 00:00 UTC.

```sh
curl -X POST https://www.urlert.com/api/v1/check_link -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" -d '{"link":"link","action":"action","task":"task","country":"country"}'
```

## check_message

Checks the text of a message, and the links in it. Answers at once with the check's `id`: call get_result until its status is `done` or `failed`. Counts as one of the account's 20 checks a day. Can answer 400 `bad_request` (the message is over 5,000 characters, the sender over 200, or `country` isn't two capital letters), 400 `invalid_text` (send the message's text), 429 `daily_limit` or 429 `checks_paused`.

`POST https://www.urlert.com/api/v1/check_message`, signed in. Takes `Idempotency-Key`.

| Param | Type | |
|---|---|---|
| `text` | text | required |
| `sender` | text or null | optional |
| `source` | text or null | optional |
| `action` | text or null | optional |
| `task` | text or null | optional |
| `country` | text or null | optional |

It answers:

```json
{
  "id": "id",
  "status": "queued",
  "outcome": "malicious",
  "outcome_name": "outcome_name",
  "asks_for": [
    "asks_for"
  ],
  "reasons": [
    {
      "text": "text"
    }
  ],
  "sites": [
    {
      "host": "host",
      "domain": "domain",
      "age_days": 3,
      "age_note": "age_note",
      "anyone_can_publish": true,
      "scam_lists": [
        "scam_lists"
      ]
    }
  ],
  "opened_link": "opened_link",
  "final_link": "final_link",
  "views": [
    {
      "device": "device",
      "state": "state"
    }
  ],
  "reused": true,
  "checked_at": "2030-01-15T09:30:00.000Z",
  "expires_at": "2030-01-15T09:30:00.000Z",
  "failure": "failure",
  "message": "message",
  "shared_copy": true,
  "result_url": "result_url"
}
```

It can fail with 400, 401, 403, 404, 409, 429, 500, 503, including 409 `conflict` from its own checks. A failure it passes on from another service can have that service's status.

Its own codes, to branch on (for any other failure, branch on `status`):

- [`checks_paused`](#checks_paused) (429): Checks from assistants and programs are paused until 00:00 UTC. The website still works for people.
- [`daily_limit`](#daily_limit) (429): This account has made 20 checks today. The limit is 20 a day. The count starts again at 00:00 UTC.
- [`invalid_text`](#invalid_text) (400): Send the message's text.

```sh
curl -X POST https://www.urlert.com/api/v1/check_message -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" -d '{"text":"text","sender":"sender","source":"source","action":"action","task":"task","country":"country"}'
```

## check_page

Checks a page from your own view of it, which we don't open. The views say `not_opened`. Counts as one of the account's 20 checks a day. Can answer 400 `bad_request` (the link isn't one web link, or `page_text` is empty or over 20,000 characters), 429 `daily_limit` or 429 `checks_paused`.

`POST https://www.urlert.com/api/v1/check_page`, signed in. Takes `Idempotency-Key`.

| Param | Type | |
|---|---|---|
| `link` | text | required |
| `page_text` | text | required |
| `action` | text or null | optional |
| `task` | text or null | optional |

It answers:

```json
{
  "id": "id",
  "status": "queued",
  "outcome": "malicious",
  "outcome_name": "outcome_name",
  "asks_for": [
    "asks_for"
  ],
  "reasons": [
    {
      "text": "text"
    }
  ],
  "sites": [
    {
      "host": "host",
      "domain": "domain",
      "age_days": 3,
      "age_note": "age_note",
      "anyone_can_publish": true,
      "scam_lists": [
        "scam_lists"
      ]
    }
  ],
  "opened_link": "opened_link",
  "final_link": "final_link",
  "views": [
    {
      "device": "device",
      "state": "state"
    }
  ],
  "reused": true,
  "checked_at": "2030-01-15T09:30:00.000Z",
  "expires_at": "2030-01-15T09:30:00.000Z",
  "failure": "failure",
  "message": "message",
  "shared_copy": true,
  "result_url": "result_url"
}
```

It can fail with 400, 401, 403, 404, 409, 429, 500, 503, including 409 `conflict` from its own checks. A failure it passes on from another service can have that service's status.

Its own codes, to branch on (for any other failure, branch on `status`):

- [`checks_paused`](#checks_paused) (429): Checks from assistants and programs are paused until 00:00 UTC. The website still works for people.
- [`daily_limit`](#daily_limit) (429): This account has made 20 checks today. The limit is 20 a day. The count starts again at 00:00 UTC.

```sh
curl -X POST https://www.urlert.com/api/v1/check_page -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" -d '{"link":"link","page_text":"page_text","action":"action","task":"task"}'
```

## get_result

A check's result by its id. Does not count against the day's 20 checks. Answers 404 `not_found` for an id nobody has, or one whose 30 days are over.

`POST https://www.urlert.com/api/v1/get_result`, signed in.

| Param | Type | |
|---|---|---|
| `id` | text | required |

It answers:

```json
{
  "id": "id",
  "status": "queued",
  "outcome": "malicious",
  "outcome_name": "outcome_name",
  "asks_for": [
    "asks_for"
  ],
  "reasons": [
    {
      "text": "text"
    }
  ],
  "sites": [
    {
      "host": "host",
      "domain": "domain",
      "age_days": 3,
      "age_note": "age_note",
      "anyone_can_publish": true,
      "scam_lists": [
        "scam_lists"
      ]
    }
  ],
  "opened_link": "opened_link",
  "final_link": "final_link",
  "views": [
    {
      "device": "device",
      "state": "state"
    }
  ],
  "reused": true,
  "checked_at": "2030-01-15T09:30:00.000Z",
  "expires_at": "2030-01-15T09:30:00.000Z",
  "failure": "failure",
  "message": "message",
  "shared_copy": true,
  "result_url": "result_url"
}
```

It can fail with 400, 401, 403, 404, 429, 500, 503. A failure it passes on from another service can have that service's status.

```sh
curl -X POST https://www.urlert.com/api/v1/get_result -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" -d '{"id":"id"}'
```

## get_guide

The steps for someone who already acted, most urgent first, or one scam guide. Does not count against the day's 20 checks.

`POST https://www.urlert.com/api/v1/get_guide`, signed in.

| Param | Type | |
|---|---|---|
| `did` | a list of one of `"money"`, `"card"`, `"installed"`, `"remote"`, `"password"`, `"id"`, `"replied"`, `"assistant"`, `"opened"` | optional |
| `scam` | one of `"how-scams-pretend"`, `"parcel-toll-tax-fees"`, `"bank-and-card-alerts"`, `"safe-account-scams"`, `"fake-invoices"`, `"prizes-and-refunds"`, `"jobs-and-paid-tasks"`, `"fake-shops"`, `"marketplace"`, `"investments"`, `"crypto-wallets"`, `"threats-and-blackmail"`, `"codes-and-sign-in-requests"`, `"fake-sign-in-pages"`, `"qr-codes"`, `"fake-downloads"`, `"fake-verify-human"`, `"tech-support"`, `"id-theft"`, `"hi-mom-new-number"`, `"online-romance"`, `"call-or-reply"`, `"ai-assistants"` or null | optional |

It answers:

```json
{
  "title": "title",
  "steps": [
    {
      "head": "head",
      "body": "body",
      "link": "link"
    }
  ],
  "good_to_know": [
    {
      "head": "head",
      "body": "body",
      "link": "link"
    }
  ],
  "guides": [
    {
      "key": "key",
      "title": "title"
    }
  ],
  "scam": {
    "key": "key",
    "title": "title",
    "summary": "summary",
    "examples": [
      "examples"
    ],
    "signs": [
      {
        "head": "head",
        "body": "body",
        "link": "link"
      }
    ],
    "steps": [
      {
        "head": "head",
        "body": "body",
        "link": "link"
      }
    ],
    "link": "link"
  },
  "scams": [
    {
      "key": "key",
      "title": "title"
    }
  ]
}
```

It can fail with 400, 401, 403, 404, 429, 500, 503. A failure it passes on from another service can have that service's status.

```sh
curl -X POST https://www.urlert.com/api/v1/get_guide -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" -d '{"did":["money"],"scam":"how-scams-pretend"}'
```

## report_mistake

Tells us a result is wrong. One report for each check from each account: a second answers 200 with the first one's `sent_at`. Does not count against the day's 20 checks. Can answer 404 `not_found`, or 409 `no_result_yet` while the check has no result.

`POST https://www.urlert.com/api/v1/report_mistake`, signed in. Takes `Idempotency-Key`.

| Param | Type | |
|---|---|---|
| `id` | text | required |
| `choice` | one of `"missed"`, `"false_alarm"`, `"other"` | required |
| `details` | text or null | optional, 1 to 1000 characters |

It answers:

```json
{
  "id": "id",
  "sent_at": "2030-01-15T09:30:00.000Z"
}
```

It can fail with 400, 401, 403, 404, 409, 429, 500, 503, including 409 `conflict` from its own checks. A failure it passes on from another service can have that service's status.

Its own codes, to branch on (for any other failure, branch on `status`):

- [`no_result_yet`](#no_result_yet) (409): This check has no result to report yet

```sh
curl -X POST https://www.urlert.com/api/v1/report_mistake -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" -d '{"id":"id","choice":"missed","details":"details"}'
```

## Types

### AgentOutcome

What we found. A closed list: a new value would be a new version of the API, so a program can branch on every one of them. `malicious` and `scam` are the two strongest findings, `risky` and `suspicious` ask for care, `nothing_found` means nothing wrong was found (not a promise that the message or page is safe), and `cant_tell` means we could not tell and need more to go on.

One of "malicious", "scam", "risky", "suspicious", "nothing_found", "cant_tell".

### AgentResultV1

A check, as the API answers for it. Every field is always there: null or an empty list when it has nothing to say. Times are ISO 8601, in UTC.

| Field | Type | |
|---|---|---|
| `id` | text | The check's id, for `get_result` and `report_mistake`. It works for 30 days. |
| `status` | one of `"queued"`, `"running"`, `"done"`, `"failed"` | `queued`, `running`, `done` or `failed`; a closed list. Call `get_result` until it is `done` or `failed`. A check that ends `failed` is still a 200 answer: read `failure`. |
| `outcome` | one of `"malicious"`, `"scam"`, `"risky"`, `"suspicious"`, `"nothing_found"`, `"cant_tell"` or null | Null until the check is done, and then one of `malicious`, `scam`, `risky`, `suspicious`, `nothing_found` and `cant_tell` (a closed list). |
| `outcome_name` | text or null | The outcome in words, for showing to a person. Never branch on it. Null when `outcome` is. |
| `asks_for` | a list of text | What the message or page asks the person for, from `money`, `account`, `device`, `data`, `attention` and `agents` (instructions hidden for AI assistants). An open list. Empty when it asks for nothing or the check is not done. |
| `reasons` | a list of ReasonV1 | The sentences under "Why" on the check's result page, in the same order and words, at most four (for a shared copy, as the shared page says them). Empty until the check is done. |
| `sites` | a list of SiteV1 | The sites the check looked at. |
| `opened_link` | text or null | The link we opened. Null when nothing was opened; when `shared_copy` is true it shows only the site. |
| `final_link` | text or null | The address the opened page ended on after redirects. Null when nothing was opened, and when `shared_copy` is true. |
| `views` | a list of ViewV1 | How our phone and computer views of the link came out. |
| `reused` | true or false | True when this answer is the result of an identical recent check and not a new look. |
| `checked_at` | a time (ISO 8601, UTC) or null | When the result was found (the earlier check's time when `reused`). Null until the check is done. |
| `expires_at` | a time (ISO 8601, UTC) | When the check is deleted, after which `get_result` answers 404. |
| `failure` | text or null | Null unless the check failed, and then why: `unavailable` (try again in a few minutes), `our_limit` (we reached our own limit for the day: try after 00:00 UTC) or `too_long`. An open list. |
| `message` | text or null | One plain sentence about the state, for a person, or null when there is nothing to add. |
| `shared_copy` | true or false | True when the account asking did not make this check, so addresses are cut to their site. |
| `result_url` | text | The page a person can open for this check. Anyone with the link sees the shared copy. |

### CheckStatus

Where a check is: `queued`, `running`, `done` or `failed`. A closed list.

One of "queued", "running", "done", "failed".

### GuideKey

What the person did: one of the nine "What did you do?" guides, `money`, `card`, `installed`, `remote`, `password`, `id`, `replied`, `assistant` or `opened`. A closed list.

One of "money", "card", "installed", "remote", "password", "id", "replied", "assistant", "opened".

### GuideV1

The help for someone who already acted, and the scam guides.

| Field | Type | |
|---|---|---|
| `title` | text | What the person did, in one line. |
| `steps` | a list of StepV1 | What to do, most urgent first. Empty when `did` was empty. |
| `good_to_know` | a list of StepV1 | What is useful after. |
| `guides` | a list of KeyTitleV1 | Every kind of "what did you do" guide, so a program can ask again for more. |
| `scam` | ScamV1 or null | One scam guide when `scam` was asked for, otherwise null. |
| `scams` | a list of KeyTitleV1 | Every scam guide's key and title. |

### KeyTitleV1

A guide's key and its title.

| Field | Type | |
|---|---|---|
| `key` | text | The guide's key. An open list: a new guide can be added in version 1, so don't treat the keys as fixed. |
| `title` | text | The guide's title. |

### MistakeReportV1

What a report that a result is wrong answers.

| Field | Type | |
|---|---|---|
| `id` | text | The check's id. |
| `sent_at` | a time (ISO 8601, UTC) | When the report was sent (an ISO 8601 time in UTC). A second report for the same check from the same account answers the same, with the first report's time. |

### ReasonV1

One reason for the outcome.

| Field | Type | |
|---|---|---|
| `text` | text | One sentence for a person, a line of the Why on the check's result page. It quotes the message or the page in curly quotes where that helps. |

### ReportChoice

What is wrong with the result: `missed` (it's a scam, but we said it wasn't), `false_alarm` (it's real, but we said it's a scam) or `other`. A closed list.

One of "missed", "false_alarm", "other".

### ScamKey

The key of a scam guide, which is also its address on the website (`/scams/<key>`). A closed list for now; `scams` in `get_guide` lists them all.

One of "how-scams-pretend", "parcel-toll-tax-fees", "bank-and-card-alerts", "safe-account-scams", "fake-invoices", "prizes-and-refunds", "jobs-and-paid-tasks", "fake-shops", "marketplace", "investments", "crypto-wallets", "threats-and-blackmail", "codes-and-sign-in-requests", "fake-sign-in-pages", "qr-codes", "fake-downloads", "fake-verify-human", "tech-support", "id-theft", "hi-mom-new-number", "online-romance", "call-or-reply", "ai-assistants".

### ScamV1

One scam guide.

| Field | Type | |
|---|---|---|
| `key` | text | The scam guide's key, which is also its address on the website (`/scams/<key>`). |
| `title` | text | The guide's name. |
| `summary` | text | What the scam is. |
| `examples` | a list of text | Messages that look like it. |
| `signs` | a list of StepV1 | How to spot one. |
| `steps` | a list of StepV1 | What to do, with the US reporting line where the guide reports. |
| `link` | text | The guide's full address on the website. |

### SiteV1

A site the check looked at.

| Field | Type | |
|---|---|---|
| `host` | text | The host as it appeared. |
| `domain` | text | The name registered for the host. |
| `age_days` | a whole number or null | The days since the domain was registered, or null when we have no age; then `age_note` says why. |
| `age_note` | text or null | Why `age_days` is null: `ip_address` (the address is an IP address), `unregistered`, `lookup_failed`, `not_checked` or `no_date` (the registry has no date). Null when `age_days` is set. An open list: a value you don't know means a reason we haven't documented. |
| `anyone_can_publish` | true or false | True when the site is a service where anyone can put up a page, so the site's name says little about who made the page. |
| `scam_lists` | a list of text or null | The names of the lists of known scam sites this site is on. An empty list when it was checked and is on none. Null when it was not checked against lists: no list could be read when the check ran, or the check is from before we kept this. |

### StepV1

One step of a guide.

| Field | Type | |
|---|---|---|
| `head` | text | The step's first line. |
| `body` | text | What to do. |
| `link` | text or null | An address to open for this step, or null. |

### ViewV1

How one view of the link came out.

| Field | Type | |
|---|---|---|
| `device` | text | `phone` or `computer`. |
| `state` | text | What we saw or why we saw nothing: `picture` (we opened it), `blank`, `human_check` (a check that asks for proof of being human was in the way), `provider_warning` (the hosting company had already blocked the page), `refused` (the site refused to show us the page), `unsupported` (we can't open pages on this site), `gone`, `file` (the link is a file, not a page), `failed` (we couldn't open it this time), `no_response`, `cut_off` (the link was cut off, so we don't have the whole address), `slow`, `no_dns` (the address doesn't exist), `cert` (the site's security certificate is broken), `site_error`, `not_opened` (we didn't open it: for example you sent your own view of the page), `daily_limit` (the account's share of opened pages for the day is used: try tomorrow) and `our_limit` (we reached our own limit or capacity for opening pages). An open list: a value you don't know means a reason we haven't documented. |

## Errors

A failure is `application/problem+json`: `{ "type", "status", "code", "detail", "errors": [{ "field", "detail" }] }`. `detail` says what's wrong, `errors` names the field at fault, and `code` is the failure's own code when its operation lists one, else the runtime's or the status's name, as below. Branch on `code` for the codes an operation lists, and on `status` for the rest: `detail` is words for a person, and may change.

- <a id="checks_paused"></a>`checks_paused` (429): Checks from assistants and programs are paused until 00:00 UTC. The website still works for people.
- <a id="daily_limit"></a>`daily_limit` (429): This account has made 20 checks today. The limit is 20 a day. The count starts again at 00:00 UTC.
- <a id="invalid_text"></a>`invalid_text` (400): Send the message's text.
- <a id="no_result_yet"></a>`no_result_yet` (409): This check has no result to report yet
- <a id="invalid_token"></a>`invalid_token` (401): The token is unknown, revoked or expired: make another on the app's agents page.
- <a id="insufficient_scope"></a>`insufficient_scope` (403): The token is limited to other operations.
- <a id="outside_token_record"></a>`outside_token_record` (403): The token works within one record, and this would change something outside it: outside it, the token reads only what someone signed out reads.
- <a id="first_step"></a>`first_step` (403): Its person hasn't taken the app's first step yet, such as agreeing to its terms: `detail` says where they take it, in the app.
- <a id="step_up"></a>`step_up` (403): The caller is a member of a record or a holder of an app role that asks more of a sign-in than this session or token showed: `need` says what, `second` (a second factor), `google` (a Google sign-in) or `enrol` (the person has no second factor yet). Sign in again with it, then make a new token: a token keeps what the sign-in that made it showed.
- <a id="token_record_left"></a>`token_record_left` (403): The token works within a record its person no longer belongs to, or that's gone. It works again if they rejoin.
- <a id="rate_limited"></a>`rate_limited` (429): Too many calls: wait the seconds `Retry-After` says, then try again.
- <a id="idempotency_key_in_use"></a>`idempotency_key_in_use` (409): A call with this `Idempotency-Key` is still running: try again in a second.
- <a id="idempotency_key_reused"></a>`idempotency_key_reused` (422): This `Idempotency-Key` was used with another body: a new request needs a new key.
- <a id="idempotency_unkept"></a>`idempotency_unkept` (409): The first call with this `Idempotency-Key` succeeded, but its answer held a secret, which is never kept: read the result with a query, or make a new request with a new key if repeating it is safe.
- <a id="cursor_mismatch"></a>`cursor_mismatch` (400): `after` came from another operation or other params: start again without it.
- <a id="version_retired"></a>`version_retired` (410): The version was retired: move to the newest one.
- <a id="bad_request"></a>`bad_request` (400)
- <a id="unauthorized"></a>`unauthorized` (401)
- <a id="payment_required"></a>`payment_required` (402)
- <a id="forbidden"></a>`forbidden` (403)
- <a id="not_found"></a>`not_found` (404)
- <a id="conflict"></a>`conflict` (409)
- <a id="gone"></a>`gone` (410)
- <a id="unprocessable_content"></a>`unprocessable_content` (422)
- <a id="too_many_requests"></a>`too_many_requests` (429)
- <a id="internal_server_error"></a>`internal_server_error` (500)
- <a id="service_unavailable"></a>`service_unavailable` (503)
