---
title: "Support endpoints — API reference | Data Tactical Group"
route: "/docs/api/reference/support"
updated: "2026-10-23"
---

# Support

7 endpoints, generated from the API's own OpenAPI document. Support tickets, open to signed-out visitors.

## POST `/v2/support/chat`

**Ask the support assistant**

Credentials: App session. Operation id `suppChat`.

One message to DTG's support assistant, an AI assistant that can make mistakes. It answers only from the published help and methodology pages, never returns a data figure, has no access to your account, and offers a support ticket when a person is needed (the app opens it with `POST /v2/support/tickets` and the conversation's `chat_token`). Send the whole conversation each time (`messages`, your message last); nothing is kept between calls except the anonymous chat record below. **Signed out:** the first message carries a human-verification token (`x-captcha-response` header or `turnstile_token`); the answer carries `chat_token`, which the following messages send instead. **Signed in** (a session): no token is needed; the session is not shown to the assistant. A conversation that does not become a ticket is kept, with no account or address, for 30 days (by default) to find gaps in the help pages. When the assistant cannot answer (it is switched to tickets only, or it is unavailable) the answer is a fixed reply with the ticket offer, and `mode` says which. Limits (provisional): 10 messages a minute and 60 an hour from one address, 40 an hour in one conversation (or, signed in, from one person); 20 of your messages in a conversation, 2,000 characters each.

### Parameters

**`x-captcha-response` header · string**

Signed out, first message only: the Cloudflare Turnstile token. Takes precedence over `turnstile_token` in the body.

### Request body

Required; `application/json`.

**`messages` array of object required**

The conversation so far, oldest first, ending with your message: `{role: "user" | "assistant", content}`. Send the assistant's earlier answers as they were shown (`reply.text`).

**`messages[].role` "user" | "assistant" required**

`user` for your messages, `assistant` for the answers.

**`messages[].content` string required**

The message text (at most 2,000 characters).

**`chat_token` string**

The `chat_token` of the previous answer in this conversation. Signed out, it stands in for the human-verification token after the first message.

**`turnstile_token` string**

Signed out, first message only: the human-verification (Cloudflare Turnstile) token; `x-captcha-response` is the other way to send it.

**`page_url` string**

The page the conversation is on (kept with the anonymous chat record). Optional.

### Responses

#### `200` success

The assistant's answer.

As `application/json`.

**`mode` "assistant" | "tickets\_only" | "degraded" always present**

`assistant` (answered), `tickets_only` (the assistant is switched off: the fixed reply and the ticket offer), `degraded` (the assistant is unavailable: the fixed reply names the fallback mailbox).

**`kind` "routed" | "model" | "fallback" | "tickets\_only" | "degraded" always present**

How the answer was made: `routed` (a fixed, approved reply to a request the assistant never fulfils), `model` (the model's answer, checked), `fallback` (the model's answer failed the checks twice; a ticket is offered instead), or the two fixed replies.

**`reply` SuppChatReply always present**

The answer.

**`reply.text` string always present**

The answer as it is shown, in Markdown: the answer, then a sentence linking the pages it came from ("See" and each page title, linked to its path), then the ticket offer when there is one.

**`reply.answer` string always present**

The answer alone, without the links or the ticket offer.

**`reply.links` array of object always present**

The help pages the answer came from (at most two), each a page that exists on the site.

**`reply.links[].route` string always present**

The page's path on the site.

**`reply.links[].title` string always present**

The page's title.

**`reply.links[].url` string always present**

The page's full address.

**`reply.ticket_offer` object or null always present**

Present when a person is needed: the category to open the ticket under and the offer's wording. Null otherwise.

**`reply.ticket_offer.category` "billing" | "data\_error" | "sales" | "access" | "other"**

The category to open the ticket under.

**`reply.ticket_offer.text` string**

The offer, as shown.

**`chat_token` string always present**

Send it with the next message of this conversation, and with the ticket if one is opened from it.

**`corpus_version` string or null always present**

The version of the help content the answer was made from (null when none was loaded).

**`model` string or null always present**

The model that answered (null for a fixed reply).

**`label` string always present**

The label a chat window shows at all times: `AI assistant`.

**`disclosure` string always present**

The disclosure a chat window shows at all times.

**`fallback_mailbox` string**

With `mode: degraded`: the address to write to.

**`usage` object**

Development instances only, never production: the provider-reported token counts of this turn (`{input, cacheRead, cacheWrite, output, reasoning}`), for the eval.

**`meta` object**

Development instances only, never production: how the layers answered (`routed`, `attempts`, `violations`, `reason`), for the eval.

#### `400` error

Bad Request. Codes: [`bad_request`](/docs/api/errors#code-bad-request) (`messages` missing or malformed, or the last message is not yours), [`chat_too_long`](/docs/api/errors#code-chat-too-long) (more than 20 of your messages, or a message over 2,000 characters), [`captcha_missing`](/docs/api/errors#code-captcha-missing) (signed out, first message, no human-verification token; `errors` repeats it), [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`captcha_failed`](/docs/api/errors#code-captcha-failed) (signed out: the human-verification token did not verify), [`bad_chat_token`](/docs/api/errors#code-bad-chat-token) (signed out: the `chat_token` is not valid or has expired; start a new conversation).

As `application/problem+json`.

#### `413` error

Payload Too Large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the body is over 256 KB).

As `application/problem+json`.

#### `429` error

Too Many Requests. [`chat_rate_limited`](/docs/api/errors#code-chat-rate-limited) (one of the assistant's limits above), or [`rate_limited`](/docs/api/errors#code-rate-limited) (the ceiling of 120 requests a minute per credential; a signed-out caller is limited by address); both with `Retry-After`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`bad_request`](/docs/api/errors#code-bad-request) (400), [`chat_too_long`](/docs/api/errors#code-chat-too-long) (400), [`captcha_missing`](/docs/api/errors#code-captcha-missing) (400), [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`captcha_failed`](/docs/api/errors#code-captcha-failed) (403), [`bad_chat_token`](/docs/api/errors#code-bad-chat-token) (403), [`body_too_large`](/docs/api/errors#code-body-too-large) (413), [`chat_rate_limited`](/docs/api/errors#code-chat-rate-limited) (429), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## GET `/v2/support/chat/status`

**Whether the support assistant is answering**

Credentials: none. Operation id `suppChatStatus`.

What a chat window needs before it opens: whether the assistant is answering (`assistant`), switched to tickets only (`tickets_only`), or unavailable (`degraded`: the window shows the fixed reply and the fallback mailbox instead of a chat box); the label and the disclosure it shows at all times; the human-verification site key; the help-content version. Anyone may call it; no credential is read.

### Responses

#### `200` success

The assistant's state.

As `application/json`.

**`mode` "assistant" | "tickets\_only" | "degraded" always present**

`assistant` (answering), `tickets_only` (switched off; tickets only), `degraded` (unavailable; show the fixed reply and the fallback mailbox, never an empty chat box).

**`label` string always present**

`AI assistant`.

**`disclosure` string always present**

The disclosure a chat window shows at all times.

**`corpus_version` string or null always present**

The help content's version, null when none is loaded.

**`turnstile_site_key` string always present**

The Cloudflare Turnstile site key for the first signed-out message.

**`fallback_mailbox` string always present**

The support mailbox to show when the assistant is unavailable.

#### `429` error

Too Many Requests. Only [`rate_limited`](/docs/api/errors#code-rate-limited) here: the ceiling of 120 requests a minute per credential (a signed-out caller is limited by address), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/support/chat-logs`

**Store an anonymous support-assistant conversation**

Credentials: none. Operation id `suppChatLogCreate`.

Stores a support-assistant conversation that did not become a ticket, to find gaps in the help content. Anyone may call it; no credential is read, and the record has no account or address, so it cannot be tied to a person. It is deleted after 30 days (by default). Nothing is mailed.

### Request body

Required; `application/json`.

**`transcript` required**

Required. The conversation, as any JSON value (typically an array of turns). Stored with no account and no address: nothing links it to a person.

**`corpus_version` string**

The version of the help content the assistant answered from. Optional.

**`model` string**

The model that answered. Optional.

### Responses

#### `201` success

Stored.

As `application/json`.

**`id` string (uuid) always present**

The stored record's id.

**`created_at` string (date-time) always present**

When it was stored.

**`expires_at` string (date-time) always present**

When it is deleted (30 days after it was stored, by default).

#### `400` error

Bad Request. Codes: [`bad_request`](/docs/api/errors#code-bad-request) (`transcript` is missing), [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON).

As `application/problem+json`.

#### `413` error

Payload Too Large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the body is over 256 KB).

As `application/problem+json`.

#### `429` error

Too Many Requests. Only [`rate_limited`](/docs/api/errors#code-rate-limited) here: the ceiling of 120 requests a minute per credential (a signed-out caller is limited by address), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`bad_request`](/docs/api/errors#code-bad-request) (400), [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`body_too_large`](/docs/api/errors#code-body-too-large) (413), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/support/tickets`

**Open a support ticket (signed in or signed out)**

Credentials: App session. Operation id `suppTicketCreate`.

Opens a support ticket. Sales enquiries (more seats than the National plan includes, invoiced or purchase-order buying) are the `sales` category. **Signed in** (a session): the ticket is yours, names the organization you are working in (a suspended or closed one included, or none), is live at once, and a confirmation mail with a link to the thread goes to your account's address. **Signed out** (no session; an API key counts as signed out, since support is for people): send `contact_email` and a human-verification token (`x-captcha-response` header or `turnstile_token`). Only a verification mail is sent; the ticket goes live when its link is followed (`POST /v2/support/tickets/verify`), within 1 hour. A signed-out ticket is never attached to an account, even one with the same address. At most 10 tickets per account, or 5 per address, in any 24 hours.

### Parameters

**`x-captcha-response` header · string**

Signed out only: the Cloudflare Turnstile token. Takes precedence over `turnstile_token` in the body.

### Request body

Required; `application/json`.

**`category` "billing" | "data\_error" | "sales" | "access" | "other" required**

Required. What the ticket is about: `billing` (charges, invoices, payment), `data_error` (a value you believe is wrong), `sales` (more seats than the National plan includes, or buying by invoice or purchase order), `access` (signing in, membership, permissions), `other` (anything else).

**`contact_email` string (email)**

The address DTG replies to. Required when you are not signed in (it then receives one verification mail, and the ticket goes live only when the link in it is followed). Ignored when you are signed in: the ticket's address is always your account's, so a thread can never be pointed at someone else's inbox. Leading and trailing spaces are trimmed.

**`body` string**

Your opening message, stored as the first message of the thread. Optional (a ticket can be opened from a chat transcript alone). Leading and trailing spaces are trimmed; a blank value adds no message.

**`page_url` string**

The page you were on when you asked for help, so support can see what you saw. Optional.

**`corpus_version` string**

The version of the help content the support assistant answered from, when the ticket follows a chat, so a wrong answer can be traced to the text that produced it. Optional.

**`transcript`**

The support-assistant conversation that preceded the ticket, as any JSON value (typically an array of turns). Optional. Kept with the ticket and removed 90 days after the ticket closes; the ticket and its messages remain.

**`turnstile_token` string**

Signed out only: the human-verification (Cloudflare Turnstile) token. The `x-captcha-response` header is the other way to send it and wins when both are present. Ignored when you are signed in, and not needed with `chat_token`.

**`chat_token` string**

When the ticket follows a conversation with the support assistant: the conversation's `chat_token`. The ticket then takes the conversation as the assistant recorded it, with its help-content version (`transcript` and `corpus_version` in the body are ignored), the anonymous chat record is deleted, and, signed out, no human-verification token is needed (the conversation passed it). A conversation opens one ticket.

### Responses

#### `201` success

Opened.

As `application/json`.

**`id` string (uuid) always present**

The ticket's id. Use it with `GET /v2/support/tickets/{id}` (signed in, or with the link token from the mail).

**`status` "open" | "waiting" | "closed" always present**

Always `open` for a new ticket.

**`category` "billing" | "data\_error" | "sales" | "access" | "other" always present**

The category you chose.

**`email_verified` boolean always present**

`true` when you were signed in (the ticket is live at once and a confirmation mail with a link to the thread is on its way). `false` when you were signed out: a verification mail went to `contact_email`, its link is valid for 1 hour, and the ticket reaches DTG only once it is followed. Nothing else is sent to an unverified address.

**`created_at` string (date-time) always present**

When the ticket was opened.

**`response_time` null always present**

Always null. DTG does not promise a response time.

**`fallback_mailbox` string always present**

DTG's support mailbox, to write to directly if a mail from this thread does not arrive.

#### `400` error

Bad Request. Codes: [`bad_request`](/docs/api/errors#code-bad-request) (`category` is missing or not one of the five; `contact_email` missing when signed out), [`captcha_missing`](/docs/api/errors#code-captcha-missing) (signed out, no human-verification token; `errors` repeats it), [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`captcha_failed`](/docs/api/errors#code-captcha-failed) (signed out: the human-verification token did not verify), [`ticket_rate_limited`](/docs/api/errors#code-ticket-rate-limited) (10 tickets from this account, or 5 to this address, in the last 24 hours), [`bad_chat_token`](/docs/api/errors#code-bad-chat-token) (the `chat_token` is not valid, has expired, or its conversation already opened a ticket).

As `application/problem+json`.

#### `404` error

Not Found. Codes: [`not_found`](/docs/api/errors#code-not-found) (the `X-DTG-Org` header, sent on a signed-in request, names an organization you are not an active member of).

As `application/problem+json`.

#### `413` error

Payload Too Large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the body is over 256 KB).

As `application/problem+json`.

#### `429` error

Too Many Requests. Only [`rate_limited`](/docs/api/errors#code-rate-limited) here: the ceiling of 120 requests a minute per credential (a signed-out caller is limited by address), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`bad_request`](/docs/api/errors#code-bad-request) (400), [`captcha_missing`](/docs/api/errors#code-captcha-missing) (400), [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`captcha_failed`](/docs/api/errors#code-captcha-failed) (403), [`ticket_rate_limited`](/docs/api/errors#code-ticket-rate-limited) (403), [`bad_chat_token`](/docs/api/errors#code-bad-chat-token) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`body_too_large`](/docs/api/errors#code-body-too-large) (413), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/support/tickets/verify`

**Verify a signed-out ticket's address**

Credentials: none. Operation id `suppTicketVerify`.

Confirms the address of a ticket opened signed out, using the token from the verification mail (the app's `/support/verify?token=` page posts it here). The ticket goes live, and a confirmation mail with a link to the thread is sent. No credential is read. A token works once and expires 1 hour after the ticket was opened.

### Request body

Required; `application/json`.

**`token` string required**

The token from the verification link mailed for a signed-out ticket (the `token` query value of the link).

### Responses

#### `200` success

Verified; the ticket is live.

As `application/json`.

**`id` string (uuid) always present**

The ticket that is now live.

**`email_verified` boolean always present**

Always `true`.

**`fallback_mailbox` string always present**

DTG's support mailbox, to write to directly if a mail from this thread does not arrive.

#### `400` error

Bad Request. Codes: [`bad_request`](/docs/api/errors#code-bad-request) (`token` is missing; or the link is not valid: expired, already used, or never issued, one answer for all three), [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON).

As `application/problem+json`.

#### `413` error

Payload Too Large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the body is over 256 KB).

As `application/problem+json`.

#### `429` error

Too Many Requests. Only [`rate_limited`](/docs/api/errors#code-rate-limited) here: the ceiling of 120 requests a minute per credential (a signed-out caller is limited by address), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`bad_request`](/docs/api/errors#code-bad-request) (400), [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`body_too_large`](/docs/api/errors#code-body-too-large) (413), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## GET `/v2/support/tickets/{id}`

**Read a ticket and its thread**

Credentials: App session. Operation id `suppTicketGet`.

Returns a ticket and every message in it. Authorized by the signed link in a support mail (`token`) or by the session of the account that opened it. Replies from DTG show as `operator`, never naming who.

### Parameters

**`id` path · string (uuid) required**

The ticket's id (a UUID), as returned when the ticket was opened. A value that is not UUID-shaped answers `404 not_found`.

**`token` query · string**

The signed thread link token from a support mail (valid 30 days, for this one ticket). Send it, or call with your session as the ticket's owner. When present it is the only authorization checked.

### Responses

#### `200` success

The ticket.

As `application/json`.

**`id` string (uuid) always present**

The ticket's id.

**`created_at` string (date-time) always present**

When the ticket was opened.

**`updated_at` string (date-time) always present**

When the ticket last changed (a message, a status change).

**`status` "open" | "waiting" | "closed" always present**

Where the ticket stands: `open` (live and with DTG's support staff), `waiting` (set by DTG's support staff while the ticket waits on something), `closed` (resolved; replying reopens it).

**`category` "billing" | "data\_error" | "sales" | "access" | "other" always present**

What the ticket is about: `billing` (charges, invoices, payment), `data_error` (a value you believe is wrong), `sales` (more seats than the National plan includes, or buying by invoice or purchase order), `access` (signing in, membership, permissions), `other` (anything else).

**`page_url` string or null always present**

The page named when the ticket was opened, or null.

**`email_verified` boolean always present**

Always `true` here: an unverified ticket is not readable.

**`messages` array of SuppMessage always present**

Every message in the thread, oldest first.

**`messages[].id` string (uuid) always present**

The message's id.

**`messages[].created_at` string (date-time) always present**

When the message was added.

**`messages[].author` "customer" | "operator" | "system" always present**

Who wrote it: `customer` (you, or whoever holds the thread link), `operator` (DTG's support staff; which member of staff is never shown), `system` (a message the service added).

**`messages[].body` string always present**

The message text.

**`fallback_mailbox` string always present**

DTG's support mailbox, to write to directly if a mail from this thread does not arrive.

#### `401` error

Unauthorized. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no `token`, and no session; an API key is not accepted here).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`bad_thread_token`](/docs/api/errors#code-bad-thread-token) (the token is invalid, expired or for another ticket; or, on a session, the ticket is not yours or does not exist, one answer for both).

As `application/problem+json`.

#### `404` error

Not Found. Codes: [`not_found`](/docs/api/errors#code-not-found) (the id is not UUID-shaped; the ticket does not exist or its address was never verified; or the `X-DTG-Org` header, sent on a signed-in request, names an organization you are not an active member of).

As `application/problem+json`.

#### `429` error

Too Many Requests. Only [`rate_limited`](/docs/api/errors#code-rate-limited) here: the ceiling of 120 requests a minute per credential (a signed-out caller is limited by address), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`bad_thread_token`](/docs/api/errors#code-bad-thread-token) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/support/tickets/{id}/messages`

**Reply on a ticket**

Credentials: App session. Operation id `suppTicketReply`.

Adds your message to the thread. Authorized like the read (the mail's `token`, or the opening account's session). Replying to a `closed` ticket reopens it (`open`) and cancels the scheduled removal of its transcript. Nothing is mailed.

### Parameters

**`id` path · string (uuid) required**

The ticket's id (a UUID), as returned when the ticket was opened. A value that is not UUID-shaped answers `404 not_found`.

**`token` query · string**

The signed thread link token from a support mail (valid 30 days, for this one ticket). Send it, or call with your session as the ticket's owner. When present it is the only authorization checked.

### Request body

Required; `application/json`.

**`body` string required**

Required. Your reply. Leading and trailing spaces are trimmed; it may not be blank.

### Responses

#### `201` success

Added.

As `application/json`.

**`id` string (uuid) always present**

The message's id.

**`created_at` string (date-time) always present**

When the message was added.

**`author` "customer" | "operator" | "system" always present**

Who wrote it: `customer` (you, or whoever holds the thread link), `operator` (DTG's support staff; which member of staff is never shown), `system` (a message the service added).

**`body` string always present**

The message text.

#### `400` error

Bad Request. Codes: [`bad_request`](/docs/api/errors#code-bad-request) (`body` is missing or blank), [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON).

As `application/problem+json`.

#### `401` error

Unauthorized. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no `token`, and no session; an API key is not accepted here).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`bad_thread_token`](/docs/api/errors#code-bad-thread-token) (the token is invalid, expired or for another ticket; or, on a session, the ticket is not yours or does not exist, one answer for both).

As `application/problem+json`.

#### `404` error

Not Found. Codes: [`not_found`](/docs/api/errors#code-not-found) (the id is not UUID-shaped; the ticket does not exist or its address was never verified; or the `X-DTG-Org` header, sent on a signed-in request, names an organization you are not an active member of).

As `application/problem+json`.

#### `413` error

Payload Too Large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the body is over 256 KB).

As `application/problem+json`.

#### `429` error

Too Many Requests. Only [`rate_limited`](/docs/api/errors#code-rate-limited) here: the ceiling of 120 requests a minute per credential (a signed-out caller is limited by address), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`bad_request`](/docs/api/errors#code-bad-request) (400), [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`bad_thread_token`](/docs/api/errors#code-bad-thread-token) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`body_too_large`](/docs/api/errors#code-body-too-large) (413), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

Generated from `taigan/openapi.yaml` at catahoula `77925fab712f`(API document version 2.0).
