
# API reference

Generated from the OpenAPI 3.1 document at `/api/openapi.json`; do not edit by hand.

Send documents for signature and follow them, from your own software.

**Authentication.** Send `Authorization: Bearer <key>`. Make keys in Settings → Developers and give each only the scopes it needs: `accounts:read`, `accounts:write`, `booking:read`, `booking:write`, `documents:read`, `documents:write`, `growth:read`, `growth:write`, `items:read`, `items:write`, `lead:write`, `money:read`, `money:write`, `network:read`, `network:write`, `notifications:read`, `notifications:write`, `people:read`, `people:write`, `platform:read`, `platform:write`, `records:read`, `records:write`, `settings:read`, `settings:write`, `plays:read`, `plays:write`, `drafts:write`, `templates:read`, `templates:write`, `webhooks:read`, `webhooks:write`, `reports:read`, `account:read`, `embedded:write`. A key that starts `dsk_test_` works in **test mode**: documents it sends are practice runs (emailed only to the sender, stamped TEST, never counted or billed), it can only see and act on test documents (any other document answers `404`), and it can sign them itself with `POST /api/v1/documents/{id}/test-sign`. A key can also be limited to a list of addresses, given an expiry, or made **draft-only**.

**Draft-only keys.** A draft-only key can make and edit drafts but cannot send. Asking it to send answers `202` with an `approval`, the document stays a draft, and a person approves or declines in Settings → Developers → Approvals. Keys can also have a daily limit of drafts and sends; past it you get `429`.

**Payments.** A template may contain payment fields (text tag `type=payment` with one of `amount`, `formula`, `price` or `link`). The signer pays on Stripe's hosted page; the money goes to your own connected Stripe account; Docustay's fee is 2% on the Free plan and 0% on Pro, plus Stripe's own fees. The document's `payments` list shows each payment, its receipt and the fee. Card details never reach Docustay.

**Idempotency.** Requests that create a document or a template, or send one, need an `Idempotency-Key` header (8–200 characters). Repeating a request with the same key and body returns the first answer instead of doing it twice.

**Your own reference.** Documents and templates take `externalId` (your id for it) and `metadata` (up to 20 short key/value pairs). List with `externalId=` or `metadata[key]=value` to find them again, and both come back in webhooks.

**Errors.** Every error is `{ "error": { "kind", "message", "requestId", "retryable" } }`. `kind` is one of `invalid`, `unauthenticated`, `forbidden`, `not_found`, `conflict`, `rate_limited`, `dependency`, `timeout`. Quote the `requestId` to support, or search it in Settings → Developers → API log.

**Rate limits.** Each key has its own per-minute budget. Every answer carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds); over the budget you get `429` with a `Retry-After` header.

**Versions.** Every answer carries `Docustay-Version`, a date. A change that could break a caller gets a new date and is announced in the changelog before it ships; additions (a new field, a new route) are not breaking and do not change the date.

**Pagination.** Lists take `limit` (1–200) and `cursor`; the answer carries `page.nextCursor` (null on the last page).

**Signing links.** No response contains a signing link or access code, with one exception you control: a seat you send in `embedded` can be given an embedded signing session (`POST /api/v1/documents/{id}/embed-session`), a one-hour link for a frame on a website you listed. A key can start and follow a signature, and put your own signer in front of the form for seats you marked yourself; it cannot sign for anyone.

## GET /api/v1/documents/templates

**Templates you can send**

Answers:

- `200` Approved, current templates.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/documents/templates \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## GET /api/v1/documents

**List documents**

Filter by your own reference: `externalId=…`, and `metadata[key]=value` (repeat for more keys; a document must match all).

| Parameter | In | Required | Notes |
|---|---|---|---|
| `state` | query | no | string |
| `limit` | query | no | integer |
| `cursor` | query | no | string |
| `externalId` | query | no | Only documents with this `externalId`. |

Answers:

- `200` A page of documents, newest first.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/documents \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## POST /api/v1/documents

**Make a document from words and people**

People first: each person has a name, an email and (optionally) a role word from the text. The document is left as a **draft** unless `sendNow` is true. Needs an `Idempotency-Key`.

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `email` | object | no | Your own wording for the emails about THIS document. The workspace's saved wording (Settings → Email templates) applies underneath; what you give here wins. |
| `metadata` | object | no | Your own key-value text (up to 20 keys; keys 1–40 letters, digits, dot, dash or underscore; values up to 500 characters). Returned in every response and every webhook for this document; never shown to a signer. |
| `externalId` | string | no | Your own id for this thing, so you can match it to your records. Filter lists with `externalId=`. |
| `title` | string | yes |  |
| `source` | object | yes | A written document: TipTap JSON `{type:"doc",content:[…]}`. Field tags (`fieldTag` with name, type, role, required, autofill, editable, placeholder, tooltip, locked, options, default…), merge chips (`variableChip`; `custom.<key>` chips are the template's questions), and headings with `audience: "sender"` (Sender only: in nothing a signer sees, signs or receives). |
| `people` | array of object | yes |  |
| `facts` | object | no | Merge text: `org.name`, `sender.name`, `custom.<key>` … |
| `variableValues` | object | no |  |
| `paper` | `letter` · `a4` | no |  |
| `send` | object | no |  |
| `description` | string | no |  |
| `sendNow` | boolean | no | Also send it. The same checks as the screen's Send apply (a payment field needs Stripe connected). |

Answers:

- `201` The document.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/documents \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"email":{"subject":"string","message":"string","replyTo":"string","senderName":"string","locale":"string"},"metadata":{"crmDealId":"D-1042"},"externalId":"contract-2026-0042","title":"string","source":"string","people":[{"externalId":"contract-2026-0042","metadata":{"crmDealId":"D-1042"},"name":"string","email":"string","role":"string","action":"signer","order":0}],"facts":"string","variableValues":"string","paper":"letter","send":{"replyTo":"string","senderName":"string","subject":"string","message":"string","expiryDays":"string","reminderDays":[0],"signingOrder":"any","language":"string","internalNote":"string","requireDeclineReason":false},"description":"string","sendNow":false}'
```

## GET /api/v1/documents/{id}

**One document and where each person is**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Answers:

- `200` The document.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `404` No such document.
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/documents/<id> \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## GET /api/v1/documents/{id}/signed.pdf

**Download the signed copy**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Answers:

- `200` The sealed PDF, with its certificate.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `404` Not signed yet, or no such document.
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/documents/<id>/signed.pdf \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## POST /api/v1/documents/send

**Send a template to people to sign**

Makes a document from the template, puts each person on their seat and sends it. Needs an `Idempotency-Key` header (8–200 characters): a retry with the same key and body does not send again.

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `templateId` | string | yes |  |
| `email` | object | no | Your own wording for the emails about THIS document. The workspace's saved wording (Settings → Email templates) applies underneath; what you give here wins. |
| `metadata` | object | no | Your own key-value text (up to 20 keys; keys 1–40 letters, digits, dot, dash or underscore; values up to 500 characters). Returned in every response and every webhook for this document; never shown to a signer. |
| `externalId` | string | no | Your own id for this thing, so you can match it to your records. Filter lists with `externalId=`. |
| `title` | string | no |  |
| `message` | string or null | no |  |
| `subject` | string | no |  |
| `language` | `en` · `es` · `fr` · `de` · `pt` · `it` · `nl` · `pl` · `tr` · `ru` · `ar` · `ja` · `zh` · `ko` | no |  |
| `expiryDays` | integer | no |  |
| `embedded` | array of string | no | Seats signed inside your own app instead of by emailed link (for example ["signer_1"]). Nobody is emailed for these seats: you ask for a session with POST /api/v1/documents/{id}/embed-session and show it on a website you listed under Settings → Developers → Embedding. |
| `parties` | array of object | yes | One entry for each seat of the template (see GET /api/v1/documents/templates). |

Answers:

- `201` Sent.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/documents/send \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"templateId":"6f1c1f0e-7a0c-4d1d-9b5e-2f0a7b1c3d4e","email":{"subject":"string","message":"string","replyTo":"string","senderName":"string","locale":"string"},"metadata":{"crmDealId":"D-1042"},"externalId":"contract-2026-0042","title":"string","message":"string","subject":"string","language":"en","expiryDays":0,"embedded":["string"],"parties":[{"seat":"string","name":"string","email":"string","externalId":"contract-2026-0042","metadata":{"crmDealId":"D-1042"}}]}'
```

## POST /api/v1/templates/html

**Make a template from HTML with text tags**

Prints your HTML to a PDF and places a field wherever a text tag sits, then keeps the result as a template you can send with `POST /api/v1/documents/send`. Needs an `Idempotency-Key` header.

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `metadata` | object | no | Your own key-value text (up to 20 keys; keys 1–40 letters, digits, dot, dash or underscore; values up to 500 characters). Returned in every response and every webhook for this document; never shown to a signer. |
| `externalId` | string | no | Your own id for this thing, so you can match it to your records. Filter lists with `externalId=`. |
| `name` | string | yes |  |
| `html` | string | yes | HTML with text tags such as {{Tenant signature;type=signature;role=Tenant}}. Scripts, styles, frames, forms and remote addresses are removed; pictures must be data: addresses. Up to 2 MB. |
| `paper` | `letter` · `a4` | no |  |

Answers:

- `201` The template.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/templates/html \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"metadata":{"crmDealId":"D-1042"},"externalId":"contract-2026-0042","name":"string","html":"string","paper":"letter"}'
```

## POST /api/v1/templates/file

**Make a template from a file with text tags**

Reads a PDF, Word, Excel, PowerPoint or image file, places a field wherever a text tag is written in it, and keeps the result as a template. Needs an `Idempotency-Key` header.

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `metadata` | object | no | Your own key-value text (up to 20 keys; keys 1–40 letters, digits, dot, dash or underscore; values up to 500 characters). Returned in every response and every webhook for this document; never shown to a signer. |
| `externalId` | string | no | Your own id for this thing, so you can match it to your records. Filter lists with `externalId=`. |
| `name` | string | yes |  |
| `fileName` | string | yes |  |
| `file` | string | yes | The file, base64-encoded: a PDF, Word, Excel, PowerPoint or image file with text tags written in it. |

Answers:

- `201` The template.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/templates/file \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"metadata":{"crmDealId":"D-1042"},"externalId":"contract-2026-0042","name":"string","fileName":"string","file":"string"}'
```

## POST /api/v1/templates/source

**Make a template from a document source**

Keeps a document written in the shared editor's format as a template (profile `typeset` by default). Needs an `Idempotency-Key` header.

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `metadata` | object | no | Your own key-value text (up to 20 keys; keys 1–40 letters, digits, dot, dash or underscore; values up to 500 characters). Returned in every response and every webhook for this document; never shown to a signer. |
| `externalId` | string | no | Your own id for this thing, so you can match it to your records. Filter lists with `externalId=`. |
| `name` | string | yes |  |
| `source` | object | yes | The document source: a ProseMirror/TipTap JSON document (`{type:"doc",content:[…]}`) using headings, paragraphs, lists, tables, `fieldTag`, `signatureBlock`, `variableChip`. See the guide on document sources. |
| `profile` | `typeset` · `contract` | no |  |
| `paper` | `letter` · `a4` | no |  |

Answers:

- `201` The template.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/templates/source \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"metadata":{"crmDealId":"D-1042"},"externalId":"contract-2026-0042","name":"string","source":"string","profile":"typeset","paper":"letter"}'
```

## POST /api/v1/documents/ai/draft

**Ask for an AI-written draft**

Starts a draft and answers at once with a job id; poll `GET /api/v1/documents/ai/jobs/{id}`. The draft is a suggestion for a person to read; nothing is saved or sent. Refused plainly when AI drafting is switched off for the workspace or a limit is reached.

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `prompt` | string | no |  |
| `brief` | object | no | The wizard's answers (see GET /api/v1/ai/wizard for the questions). |
| `mode` | `template` · `document` | no |  |

Answers:

- `202` Started.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/documents/ai/draft \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"string","brief":{"type":"string","kind":"string","answers":"string","final":"string","payment":{"collect":false,"amount":"1500","currency":"usd","payer_role":"string","description":"string","schedule_note":"string"}},"mode":"template"}'
```

## GET /api/v1/documents/ai/jobs/{id}

**Read an AI draft job**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Answers:

- `200` The job. When `state` is `done`, `source` is the draft and `patches.notes` lists what to check.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `404` No such job.
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/documents/ai/jobs/<id> \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## GET /api/v1/usage

**Documents and emails used against your plan**

Documents sent this calendar month (UTC) and emails sent today, with the plan's limits. `limit` is null when the plan has no limit.

Answers:

- `200` Usage.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/usage \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## GET /api/v1/templates/{id}

**One template and its seats**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Answers:

- `200` The template.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `404` No such template.
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/templates/<id> \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## GET /api/v1/documents/{id}/audit-log.csv

**The document's audit log as CSV**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Answers:

- `200` One row per event: time (UTC), event, description, person, document, detail.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `404` No such document.
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/documents/<id>/audit-log.csv \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## POST /api/v1/documents/{id}/remind

**Remind the people who still have to sign**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `seat` | string | no | Remind only this seat (for example signer_2). Leave out to remind everyone who is up. |

Answers:

- `200` Reminded.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `409` Nobody is waiting to be reminded.
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/documents/<id>/remind \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"seat":"string"}'
```

## POST /api/v1/documents/{id}/embed-session

**Start an embedded signing session for a seat**

For a seat that was sent in `embedded`. Returns a link that works for one hour and is meant for an iframe or the `<docustay-sign>` element on one of your allowed websites. It is the only way a key can put someone in front of a signing form, and only for seats you marked embedded yourself. Needs an `Idempotency-Key` header.

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `seat` | string | yes |  |
| `origin` | string | yes | The website that will show the form; must be on your allowed list. |

Answers:

- `200` The session.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `409` The seat was not sent as embedded, it is not their turn, the website is not allowed, or embedding is off.
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/documents/<id>/embed-session \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"seat":"signer_1","origin":"https://app.example.com"}'
```

## GET /api/v1/reports

**Counts of what was sent and completed**

Real documents only — documents made in test mode are never counted. Dates are in the workspace's own time zone; `to` is inclusive; the default window is the last 30 days.

| Parameter | In | Required | Notes |
|---|---|---|---|
| `from` | query | no | string |
| `to` | query | no | string |
| `team` | query | no | string |

Answers:

- `200` The report.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/reports \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## GET /api/v1/teams

**Teams and their members**

Answers:

- `200` The teams.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/teams \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## GET /api/v1/email-templates

**The workspace's email wording**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `locale` | query | no | string |

Answers:

- `200` The eight message types with the standard and the saved wording.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/email-templates \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## PUT /api/v1/email-templates/{kind}

**Set the wording of one email**

Variables look like `{{document.title}}`; each message type accepts only its own (see GET). A blank field means the standard wording.

| Parameter | In | Required | Notes |
|---|---|---|---|
| `kind` | path | yes | invitation, reminder, completed, declined, voided, expired, code or receipt |

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `locale` | string | no |  |
| `subject` | string or null | no |  |
| `preview` | string or null | no |  |
| `body` | string or null | no |  |
| `buttonLabel` | string or null | no |  |
| `footer` | string or null | no |  |

Answers:

- `200` The saved wording.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X PUT https://app.docustay.app/api/v1/email-templates/<kind> \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{"locale":"string","subject":"string","preview":"string","body":"string","buttonLabel":"string","footer":"string"}'
```

## DELETE /api/v1/email-templates/{kind}

**Back to the standard wording**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `kind` | path | yes | invitation, reminder, completed, declined, voided, expired, code or receipt |
| `locale` | query | no | string |

Answers:

- `200` The standard wording again.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X DELETE https://app.docustay.app/api/v1/email-templates/<kind> \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## POST /api/v1/email-templates/{kind}/preview

**Preview an email with made-up names**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `kind` | path | yes | invitation, reminder, completed, declined, voided, expired, code or receipt |

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `locale` | string | no |  |
| `subject` | string or null | no |  |
| `preview` | string or null | no |  |
| `body` | string or null | no |  |
| `buttonLabel` | string or null | no |  |
| `footer` | string or null | no |  |

Answers:

- `200` Subject, preview line, body and the HTML.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/email-templates/<kind>/preview \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"locale":"string","subject":"string","preview":"string","body":"string","buttonLabel":"string","footer":"string"}'
```

## GET /api/v1/emails

**The log of email about your documents**

Every email sent about a document, to a person named on it: kind, subject, status (sent, delivered, bounced, complained, failed, paused). Filter by `documentId`, `to`, `status`, `kind`. No bodies, no links.

| Parameter | In | Required | Notes |
|---|---|---|---|
| `documentId` | query | no | string |
| `to` | query | no | string |
| `status` | query | no | string |
| `kind` | query | no | string |
| `limit` | query | no | integer |
| `cursor` | query | no | string |

Answers:

- `200` A page of emails, newest first.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/emails \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## POST /api/v1/documents/{id}/test-sign

**Sign a test document (test mode only)**

Signs one seat of a **test** document with sample answers, through the same code a person's signature uses: consent, signing order, the audit trail and your webhooks all happen. Only a test key (`dsk_test_…`) may call it, and only on a test document; a live key gets `403`. A seat with a payment field cannot be test-signed: pay it with a Stripe test card.

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `seat` | string | yes | The seat to sign, for example `signer_1`. |

Answers:

- `200` Signed.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/documents/<id>/test-sign \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"seat":"signer_1"}'
```

## POST /api/v1/documents/{id}/void

**Void a document that is still out for signature**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `reason` | string | no |  |

Answers:

- `200` Voided.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/documents/<id>/void \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"reason":"string"}'
```

## POST /api/v1/documents/check

**Check a document's words before making it**

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `source` | object | yes | A written document: TipTap JSON `{type:"doc",content:[…]}`. Field tags (`fieldTag` with name, type, role, required, autofill, editable, placeholder, tooltip, locked, options, default…), merge chips (`variableChip`; `custom.<key>` chips are the template's questions), and headings with `audience: "sender"` (Sender only: in nothing a signer sees, signs or receives). |
| `facts` | object | no |  |
| `people` | array of object | no | With people, each person's own merge text is known, as when the document is made. |
| `variableValues` | object | no |  |
| `requireSignature` | boolean | no |  |
| `forTemplate` | boolean | no |  |

Answers:

- `200` What a person would be told.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/documents/check \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"source":"string","facts":"string","people":[{"externalId":"contract-2026-0042","metadata":{"crmDealId":"D-1042"},"name":"string","email":"string","role":"string","action":"signer","order":0}],"variableValues":"string","requireSignature":false,"forTemplate":false}'
```

## GET /api/v1/documents/{id}/source

**The words, people and choices of a draft**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Answers:

- `200` The draft's source.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/documents/<id>/source \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## POST /api/v1/documents/{id}/revise

**Change the words of a draft**

A draft is never edited in place: the new words are printed into a NEW draft that takes the people and choices, and the old draft is deleted. **The id changes** (use `id` in the answer). Needs an `Idempotency-Key`.

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `source` | object | yes | A written document: TipTap JSON `{type:"doc",content:[…]}`. Field tags (`fieldTag` with name, type, role, required, autofill, editable, placeholder, tooltip, locked, options, default…), merge chips (`variableChip`; `custom.<key>` chips are the template's questions), and headings with `audience: "sender"` (Sender only: in nothing a signer sees, signs or receives). |
| `facts` | object | no |  |
| `variableValues` | object | no |  |
| `roleMap` | object | no | Old role word → new role word when you renamed a role. |

Answers:

- `201` The new draft.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/documents/<id>/revise \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"source":"string","facts":"string","variableValues":"string","roleMap":"string"}'
```

## POST /api/v1/documents/{id}/send

**Send a draft**

Sends a draft made with `POST /api/v1/documents`. The same checks as the screen's Send; a payment field needs Stripe connected. Needs an `Idempotency-Key`.

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Answers:

- `201` Sent.
- `202` A draft-only key: the request waits for a person to approve it in the app. The document is still a draft.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/documents/<id>/send \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

## POST /api/v1/templates/{id}/documents

**Use a written template for people**

Makes a document from a template made with `POST /api/v1/templates/source`: the people, the template's questions answered in `variableValues` (see `GET /api/v1/templates/{id}`), the template's own defaults first. A draft unless `sendNow`. Needs an `Idempotency-Key`.

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `email` | object | no | Your own wording for the emails about THIS document. The workspace's saved wording (Settings → Email templates) applies underneath; what you give here wins. |
| `metadata` | object | no | Your own key-value text (up to 20 keys; keys 1–40 letters, digits, dot, dash or underscore; values up to 500 characters). Returned in every response and every webhook for this document; never shown to a signer. |
| `externalId` | string | no | Your own id for this thing, so you can match it to your records. Filter lists with `externalId=`. |
| `title` | string | no |  |
| `people` | array of object | yes |  |
| `variableValues` | object | no |  |
| `facts` | object | no |  |
| `send` | object | no |  |
| `sendNow` | boolean | no |  |

Answers:

- `201` The document.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/templates/<id>/documents \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"email":{"subject":"string","message":"string","replyTo":"string","senderName":"string","locale":"string"},"metadata":{"crmDealId":"D-1042"},"externalId":"contract-2026-0042","title":"string","people":[{"externalId":"contract-2026-0042","metadata":{"crmDealId":"D-1042"},"name":"string","email":"string","role":"string","action":"signer","order":0}],"variableValues":"string","facts":"string","send":{"replyTo":"string","senderName":"string","subject":"string","message":"string","expiryDays":"string","reminderDays":[0],"signingOrder":"any","language":"string","internalNote":"string","requireDeclineReason":false},"sendNow":false}'
```

## POST /api/v1/templates/{id}/defaults

**Set what a document made from a template starts with**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `description` | string or null | no |  |
| `clientDescription` | string or null | no |  |
| `message` | string or null | no |  |
| `send` | object | no |  |

Answers:

- `200` Saved.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/templates/<id>/defaults \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"description":"string","clientDescription":"string","message":"string","send":{"replyTo":"string","senderName":"string","subject":"string","message":"string","expiryDays":"string","reminderDays":[0],"signingOrder":"any","language":"string","internalNote":"string","requireDeclineReason":false}}'
```

## POST /api/v1/documents/ai/edit

**Ask the assistant to change a document**

Starts an edit job and answers at once; poll `GET /api/v1/documents/ai/jobs/{id}`. The answer is a list of **proposed changes** (`patches.applied`); nothing is changed until you apply the ones you accept with your own copy of the source. If you pass the template's questions in `variables`, `patches.values` carries proposed answers.

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `request` | string | yes |  |
| `source` | object | yes | A written document: TipTap JSON `{type:"doc",content:[…]}`. Field tags (`fieldTag` with name, type, role, required, autofill, editable, placeholder, tooltip, locked, options, default…), merge chips (`variableChip`; `custom.<key>` chips are the template's questions), and headings with `audience: "sender"` (Sender only: in nothing a signer sees, signs or receives). |
| `mode` | `template` · `document` | no |  |
| `variables` | array of object | no |  |

Answers:

- `202` Started.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/documents/ai/edit \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"request":"string","source":"string","mode":"template","variables":[{"key":"string","label":"string","kind":"text"}]}'
```

## POST /api/v1/documents/ai/jobs/{id}/cancel

**Cancel an AI job**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Answers:

- `200` The job.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/documents/ai/jobs/<id>/cancel \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

## GET /api/v1/ai/wizard

**The wizard's questions**

The questions the screen's Describe wizard asks, so an integration asks the same ones and sends the answers as `brief` to `POST /api/v1/documents/ai/draft`. Includes the payment questions and the currencies offered.

Answers:

- `200` The questions.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/ai/wizard \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## GET /api/v1/payments/status

**Is Stripe ready to take a payment?**

`connected` (details submitted, charges on, nothing due), `needs_attention` (Stripe still needs information) or `not_connected`. A document with a payment field can only be sent when this says `connected`.

Answers:

- `200` The status.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/payments/status \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## GET /api/v1/plays

**List plays**

The built-in plays and your published ones: name, one-line description, kind and category.

| Parameter | In | Required | Notes |
|---|---|---|---|
| `kind` | query | no | string |
| `category` | query | no | string |

Answers:

- `200` List plays
- `400` Not valid.
- `403` Not allowed (Pro) or the key lacks the scope.

```bash
curl https://app.docustay.app/api/v1/plays \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## POST /api/v1/plays

**Save a play (a new version)**

`source` is the play file: Markdown with a block of YAML at the top. Every save is a new immutable version; publish it to use it. Data only: a play cannot add tools, run code, send or take payments.

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `source` | string | yes |  |

Answers:

- `201` Save a play (a new version)
- `400` Not valid.
- `403` Not allowed (Pro) or the key lacks the scope.

```bash
curl -X POST https://app.docustay.app/api/v1/plays \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"source":"string"}'
```

## GET /api/v1/plays/{name}

**One play**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `name` | path | yes | string |

Answers:

- `200` One play
- `400` Not valid.
- `403` Not allowed (Pro) or the key lacks the scope.

```bash
curl https://app.docustay.app/api/v1/plays/<name> \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## DELETE /api/v1/plays/{name}

**Archive a play**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `name` | path | yes | string |

Answers:

- `200` Archive a play
- `400` Not valid.
- `403` Not allowed (Pro) or the key lacks the scope.

```bash
curl -X DELETE https://app.docustay.app/api/v1/plays/<name> \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## POST /api/v1/plays/test

**Check a play without saving it**

A dry run: parses the play file, reports every problem, and shows what the picker and the interview would use. Creates nothing.

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `source` | string | yes |  |

Answers:

- `200` Check a play without saving it
- `400` Not valid.
- `403` Not allowed (Pro) or the key lacks the scope.

```bash
curl -X POST https://app.docustay.app/api/v1/plays/test \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"source":"string"}'
```

## POST /api/v1/plays/{name}/publish

**Publish a play version**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `name` | path | yes | string |

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `version` | integer | no |  |

Answers:

- `200` Publish a play version
- `400` Not valid.
- `403` Not allowed (Pro) or the key lacks the scope.

```bash
curl -X POST https://app.docustay.app/api/v1/plays/<name>/publish \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"version":0}'
```

## POST /api/v1/plays/{name}/duplicate

**Copy a play under a new name**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `name` | path | yes | string |

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `as` | string | no |  |

Answers:

- `201` Copy a play under a new name
- `400` Not valid.
- `403` Not allowed (Pro) or the key lacks the scope.

```bash
curl -X POST https://app.docustay.app/api/v1/plays/<name>/duplicate \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"as":"string"}'
```

## GET /api/v1/playbooks

**List playbooks**

Named bundles of plays.

Answers:

- `200` List playbooks
- `400` Not valid.
- `403` Not allowed (Pro) or the key lacks the scope.

```bash
curl https://app.docustay.app/api/v1/playbooks \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## POST /api/v1/playbooks

**Save a playbook**

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `name` | string | yes |  |
| `title` | string | yes |  |
| `description` | string | no |  |
| `plays` | array of string | yes |  |

Answers:

- `201` Save a playbook
- `400` Not valid.
- `403` Not allowed (Pro) or the key lacks the scope.

```bash
curl -X POST https://app.docustay.app/api/v1/playbooks \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name":"string","title":"string","description":"string","plays":["string"]}'
```

## POST /api/v1/drafts

**Start a draft from a play**

Returns `status: needs_input` with up to 4 questions (each with 2 to 4 options and a `draftRef`) until the play's must-ask questions are answered, then `status: draft` with the new draft's id. Nothing is ever sent: a draft is a draft. Answer with `POST /api/v1/drafts/{draftRef}/answers`.

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `play` | string | yes |  |
| `title` | string | no |  |
| `people` | array of object | yes |  |
| `answers` | object | no |  |
| `useDefaults` | boolean | no |  |

Answers:

- `200` Start a draft from a play
- `400` Not valid.
- `403` Not allowed (Pro) or the key lacks the scope.

```bash
curl -X POST https://app.docustay.app/api/v1/drafts \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"play":"string","title":"string","people":[{"name":"string","email":"string","role":"string","action":"signer"}],"answers":"string","useDefaults":false}'
```

## POST /api/v1/drafts/{draftRef}/answers

**Answer a play's questions**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `draftRef` | path | yes | string |

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `answers` | object | yes |  |
| `useDefaults` | boolean | no |  |

Answers:

- `200` Answer a play's questions
- `400` Not valid.
- `403` Not allowed (Pro) or the key lacks the scope.

```bash
curl -X POST https://app.docustay.app/api/v1/drafts/<draftRef>/answers \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"answers":"string","useDefaults":false}'
```

## GET /api/v1/webhooks/topics

**The events a webhook can subscribe to**

Answers:

- `200` The event names.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/webhooks/topics \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## GET /api/v1/webhooks

**List webhooks**

Answers:

- `200` Your webhook endpoints, newest first.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/webhooks \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## POST /api/v1/webhooks

**Add a webhook**

Give an `https` `url` that can be reached from the internet, or `relay: true` for an endpoint that keeps its signed deliveries for `docustay listen` to fetch to your own computer. The signing secret is in the answer **once**. Events are signed with HMAC-SHA256 (Standard Webhooks headers `webhook-id`, `webhook-timestamp`, `webhook-signature`; also `keystone-signature`).

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `url` | string | no |  |
| `relay` | boolean | no |  |
| `name` | string | no |  |
| `eventTypes` | array of string | yes | From `GET /api/v1/webhooks/topics`. |

Answers:

- `201` Added. `secret` is shown once.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/webhooks \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"url":"string","relay":false,"name":"string","eventTypes":["string"]}'
```

## DELETE /api/v1/webhooks/{id}

**Remove a webhook**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Answers:

- `200` Removed: it receives nothing more. Its past deliveries stay in the record.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X DELETE https://app.docustay.app/api/v1/webhooks/<id> \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## POST /api/v1/webhooks/{id}/test

**Send a test event**

Sends a sample signed event (`test: true` in its payload) to the webhook now and returns the receiver's answer. Nothing is stored and failures here never count toward disabling the webhook.

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `topic` | string | no |  |

Answers:

- `200` What the receiver answered.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/webhooks/<id>/test \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"topic":"string"}'
```

## POST /api/v1/webhooks/{id}/rotate

**Rotate the signing secret**

Makes a new secret (shown once). For `windowHours` (default 24) every event is signed with both the new and the old secret, so your receiver can switch without a gap.

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `windowHours` | integer | no |  |

Answers:

- `200` The new secret.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/webhooks/<id>/rotate \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"windowHours":0}'
```

## GET /api/v1/webhooks/{id}/deliveries

**Recent deliveries**

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |

Answers:

- `200` The last 50 delivery attempts, newest first.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/webhooks/<id>/deliveries \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## GET /api/v1/webhooks/{id}/relay

**Fetch stored deliveries of a relay webhook**

For `docustay listen`. Returns deliveries stored since `after` (an id from a previous answer; leave out for the newest few), oldest first, each with the exact headers to forward so the signature checks out. Waits up to `wait` seconds (max 25) when there is nothing yet. Kept 24 hours.

| Parameter | In | Required | Notes |
|---|---|---|---|
| `id` | path | yes | string |
| `after` | query | no | string |
| `wait` | query | no | integer or null |
| `limit` | query | no | integer |

Answers:

- `200` Stored deliveries.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl https://app.docustay.app/api/v1/webhooks/<id>/relay \
  -H "Authorization: Bearer $DOCUSTAY_KEY"
```

## POST /api/v1/embedded/builder-session

**Start an embedded template builder session**

Returns a link that works for one hour and is meant for an iframe or the `<docustay-builder>` element on one of your allowed websites. The builder lets the person write a document, drop signature and other fields on it, and save it as a template; the saved template's id comes back to your page in a `saved` event. The link holds a key of its own that can only read and write templates and stops working after an hour. Needs an `Idempotency-Key` header.

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `origin` | string | yes | The website that will show the builder; must be on your allowed list. |
| `name` | string | no | A name to start the template with. |

Answers:

- `200` The session.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/embedded/builder-session \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"origin":"https://app.example.com","name":"string"}'
```

## POST /api/v1/embedded/scribe-session

**Start an embedded Scribe session**

Returns a link that works for one hour and is meant for an iframe or the `<docustay-scribe>` element on one of your allowed websites. The person picks a play, names who signs, answers a few clickable questions and gets a DRAFT; the draft's id comes back to your page in a `drafted` event. The link holds a key of its own that is draft-only (it cannot send: asking to send files an approval a person decides in Docustay), allows 50 drafts a day and stops working after an hour. Needs an `Idempotency-Key` header.

Request body (JSON):

| Field | Type | Required | Notes |
|---|---|---|---|
| `origin` | string | yes | The website that will show Scribe; must be on your allowed list. |
| `name` | string | no |  |

Answers:

- `200` The session.
- `400` The request is not valid. `message` says which field and why.
- `401` The key is missing, wrong, revoked, or lacks the scope. Every credential failure gets this same answer.
- `403` The key is valid but not allowed to do this (a plan limit, or the action is not permitted for keys).
- `429` Too many requests. Wait `Retry-After` seconds, then try again.

```bash
curl -X POST https://app.docustay.app/api/v1/embedded/scribe-session \
  -H "Authorization: Bearer $DOCUSTAY_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"origin":"https://app.example.com","name":"string"}'
```
