# Platform API

## Overview

The Platform API integrates the Docue.com platform into your system. Everything you do through the API happens inside your Docue workspace: documents you draft, send for signing, or upload are all available when you log in at https://app.docue.com.

With the Platform API you can:

- Draft documents from your workspace's templates, prefill their values programmatically, and finalize them for signing.
- Send an existing PDF out for electronic signing in a single request.
- Manage the workspace archive: upload, list, fetch, download, and delete documents, organize them into folders, and attach custom properties to them.


**Please note that this is a separate product from the Embed API.** The Platform API works against your Docue.com workspace. If you instead want to embed Docue's document-building UI into your own branded product, without a Docue.com account, see the [Embed API](https://developers.docue.com/embed-api).

### Prerequisites

To use this API you need an **API key** tied to a Docue.com workspace. If you do not have one yet, please contact us at [support@docue.com](mailto:support@docue.com).

All endpoints require Bearer authentication. Include the API key in the `Authorization` header of every request:

```
Authorization: Bearer <api_key>
```

The production base URL is `https://api.docue.com`. All routes are under `/public/v2`. A good first call is [Get current workspace](/platform-api/openapi/workspace/getworkspace) — it returns the workspace tied to your key and is the simplest way to verify that authentication is configured correctly. Use [List workspace users](/platform-api/openapi/workspace/getworkspaceusers) when you need user identifiers for assigning document owners.

See the [API Reference](/platform-api/openapi) for the full list of endpoints.

## Guide

The Platform API is organized around a few core resources. Understanding how they relate makes the rest of the API straightforward.

> **Core concepts**
- **Workspace** — the account your API key belongs to. Everything you create lives inside it and shows up at app.docue.com.
- **User** — a member of the workspace who can be assigned as a document owner. A document references its owner through `owner_id`.
- **Folder** — a container in the archive. Every document lives in a folder, and folders can be nested. Your workspace has a `root_folder_id` you can build under.
- **Document** — an item in the archive (a drafted contract, an uploaded PDF, a signed agreement). You can download a document's file content with [Download document](/platform-api/openapi/documents/downloaddocument).
- **Contract** — the signing- and drafting-related state behind a document: its stage, signing method, builder state, and signatures. A document points to its contract via `contract_id`.
- **Template** — a reusable, lawyer-maintained starting point for a new document. Templates are managed for your workspace; you start documents from them.
- **Webhook** — a subscription that pushes notifications to your own systems when something happens in the workspace (a document is created, a contract is completed, and so on). See [Webhooks](#webhooks).



```mermaid
graph LR
    W[Workspace] --> U[User]
    W[Workspace] --> F[Folder]
    F --> D[Document]
    D -->|owner_id| U
    D -->|contract_id| C[Contract]
    T[Template] -. start .-> D
    C --> S[Signatures]
    C --> B[Builder state]
```

### The contract id vs. the document id

The single most common integration gotcha: **the builder endpoints are keyed by the contract id, not the document id.**

When you start a document from a template, the response is a **document**. That document carries a `contract_id` field. The drafting endpoints — `GET`/`PATCH /public/v2/contracts/{id}/builder/state`, `/public/v2/contracts/{id}/builder/value-schema`, `/public/v2/contracts/{id}/builder/sections`, and `/public/v2/contracts/{id}/builder/finalize` — all expect that `contract_id` as their `{id}`, *not* the document's own `id`. Conversely, the document endpoints (`/public/v2/documents/{id}`, download) use the document `id`.

When you send a PDF for signing it is the other way around: the response is a **contract**, and you use its `document_id` to work with the document later.

### Contract lifecycle

A contract moves through a small set of stages. Most endpoints care about whether the contract is still editable (`created`) or already locked.

```mermaid
stateDiagram-v2
    [*] --> created: start from template
    created --> finalized: finalize (canvas / strong)
    created --> completed: finalize (manual / no signees)
    finalized --> completed: all parties sign
    finalized --> cancelled: not signed before due date
    completed --> [*]
    cancelled --> [*]
```

While a contract is in the `created` stage you can freely read and patch its builder state. Once it is `finalized` or `completed`, the content is locked. You can still read the builder state for **24 hours** after finalization; after that, [Get contract builder state](/platform-api/openapi/builder/getcontractbuilderstate) returns `410 Gone`.

### Drafting a document from a template

This is the richest flow. You start a draft from a template, optionally prefill its fields, and then finalize it for signing — all without anyone opening the builder UI.

```mermaid
sequenceDiagram
    participant App as Integrator
    participant API as Platform API

    App ->> API: GET /public/v2/templates?filter[type]=tailored
    API -->> App: List of templates (id, builder_version)
    App ->> API: POST /public/v2/templates/{template_id}/start (folder_id, owner_id, title)
    API -->> App: Document (note its contract_id)
    App ->> API: GET /public/v2/contracts/{contract_id}/builder/state
    API -->> App: Current values, sections, appearance
    App ->> API: PATCH /public/v2/contracts/{contract_id}/builder/state (operations)
    API -->> App: Updated state
    App ->> API: POST /public/v2/contracts/{contract_id}/builder/finalize
    API -->> App: Finalized contract (stage = finalized, document_id)
```

Step by step:

1. **List templates** with [List templates](/platform-api/openapi/builder/gettemplates). The `filter[type]=tailored` parameter is required. Note each template's `builder_version` (see the callout below).
2. **Start a document** with [Create document from template](/platform-api/openapi/builder/starttemplate), passing the target `folder_id` and a `title`. You can also pass a workspace user's `id` as `owner_id`. The response is a document in the `created` stage; grab its `contract_id`.
3. **Read the builder state** with [Get contract builder state](/platform-api/openapi/builder/getcontractbuilderstate) to see the current `value`, `sections`, and `appearance`.
4. **Prefill values** with [Update contract builder state](/platform-api/openapi/builder/patchcontractbuilderstate) using JSON Patch operations (see [Working with the builder state](#working-with-the-builder-state) below).
5. **Finalize** with [Finalize contract](/platform-api/openapi/builder/finalizecontractbuilder) to lock the document and start signing.


**Hand off to the builder UI (optional).** You do not have to finalize through the API. After creating the contract — and optionally prefilling some values — you can let the end-user complete the document in Docue's builder UI instead. Each contract exposes a `builder_url` for this: fetch it with `GET /public/v2/documents/{id}?include=contract` (it is `contract.builder_url`) and redirect the user there. A common pattern is to prefill the values you already know via the API, then send the user to `builder_url` to review, fill in the rest, and finalize. Opening the link requires Docue credentials and access to the document.

> **Builder 2.0 is required for the builder endpoints.**
The `/public/v2/contracts/{id}/builder/*` endpoints only work for contracts whose `builder_version` is `"2"`. Starting from a template that uses an older builder produces a `builder_version` of `"1"`, and the builder endpoints return `409 Conflict`. Check `builder_version` on the template (and on the contract) before relying on these endpoints. You can include the contract on a document response with `?include=contract` to read its `builder_version` and `stage`.


### Working with the builder state

The builder state is the source of truth for a document's field values, its sections, and its visual appearance. You update it with [JSON Patch (RFC 6902)](https://datatracker.ietf.org/doc/html/rfc6902) operations via [Update contract builder state](/platform-api/openapi/builder/patchcontractbuilderstate).

**Key behavior:**

- **Batch updates** — you can send multiple operations in a single request.
- **Sequential execution** — operations are applied **in the order they appear** (top to bottom), so later operations can build on earlier ones.
- **All-or-nothing by default** — if any operation fails, none of them are saved. Opt into [partial success](#partial-success) to keep the operations that succeed.
- **Editable only** — patching is only allowed while the contract is in the `created` stage and `builder_version` is `"2"`. Otherwise the endpoint returns `409 Conflict`.


Before sending values, familiarize yourself with the response shape of [Get contract builder state](/platform-api/openapi/builder/getcontractbuilderstate). The `value` object holds all field values, and its schema is defined by the template.

#### The shape of `value`

The `value` object consists of **fieldsets** and **fields**. Each root-level key is a fieldset. A fieldset is a collection of fields and can be either non-repeatable (an object, like `basic`) or repeatable (an array of objects, like `parties`).

```json
{
  "_type": "value",
  "_id": "0196ae5b-2e9d-729f-aba5-fca3d9d646a4",
  "basic": {
    "_type": "value",
    "_id": "0196ae5b-2e9d-729f-aba6-03235f09a720",
    "title": {
      "_type": "translatable",
      "en-GB": "Employment Agreement"
    }
  },
  "parties": [
    {
      "_type": "value",
      "_id": "0196ae5b-2e9d-729f-aba6-07fd964cae09",
      "type": "company",
      "company_name": "Docue",
      "first_name": "John",
      "last_name": "Doe"
    }
  ]
}
```

Notes when constructing values:

- **Translatable text** — many fields are localized. Provide them as a `translatable` object whose keys are the document's language codes (for example `{ "_type": "translatable", "en-GB": "Employment Agreement" }`). The document's languages are listed in the state's `languages` array.
- **Identifiers** — every fieldset instance has an `_id` (a UUIDv7). When adding a new fieldset instance, generate a fresh UUIDv7 for its `_id`.
- **Schema** — the exact set of fieldsets and fields depends on the template. Use the (experimental) [Get contract builder value schema](/platform-api/openapi/builder/getcontractbuildervalueschema) endpoint to discover the structure and validation rules of the `value` before writing to it.


> **Tip:** To append a new item to the end of a repeatable fieldset, use the path `/value/<fieldset>/-`, for example `/value/parties/-`.


#### Examples

##### Update a field value

```http
PATCH /public/v2/contracts/{id}/builder/state

{
  "operations": [
    {
      "op": "replace",
      "path": "/value/basic/title",
      "value": {
        "_type": "translatable",
        "en-GB": "Employment Agreement (revised)"
      }
    }
  ]
}
```

##### Add and remove repeatable fieldset instances

```http
PATCH /public/v2/contracts/{id}/builder/state

{
  "operations": [
    {
      "op": "add",
      "path": "/value/parties/-",
      "value": {
        "_type": "value",
        "_id": "0196ae5b-2e9d-729f-aba6-07fd964cae10",
        "type": "person",
        "first_name": "Jane",
        "last_name": "Doe",
        "email": "jane.doe@example.org"
      }
    },
    {
      "op": "remove",
      "path": "/value/parties/0"
    }
  ]
}
```

##### Update the document appearance

Typography, logo, and footer settings live under `/appearance`. Patch individual properties or replace the whole object.

```http
PATCH /public/v2/contracts/{id}/builder/state

{
  "operations": [
    {
      "op": "replace",
      "path": "/appearance/footer",
      "value": {
        "enabled": true,
        "content": "Acme Inc."
      }
    }
  ]
}
```

#### Partial success

By default a patch is all-or-nothing: if any operation fails, nothing is saved and the endpoint returns `422`. Set `allow_partial` to `true` to save the operations that succeed and get a report of the ones that failed:

```http
PATCH /public/v2/contracts/{id}/builder/state

{
  "allow_partial": true,
  "operations": [
    {
      "op": "replace",
      "path": "/appearance/footer",
      "value": { "enabled": true, "content": "Acme Inc." }
    },
    {
      "op": "replace",
      "path": "/value/basic/title",
      "value": "Employment Agreement (revised)"
    }
  ]
}
```

The second operation fails because the title is a translatable field and expects a `translatable` object, not a string. The footer is saved:

```json
{
  "data": {
    "jurisdiction": "FI",
    "languages": ["en-GB"],
    "value": { "...": "..." },
    "sections": [],
    "appearance": {
      "footer": { "enabled": true, "content": "Acme Inc." }
    }
  },
  "meta": {
    "failed_operations": [
      {
        "index": 1,
        "path": "/value/basic/title",
        "error": {
          "code": "invalid_operation",
          "message": "Operation \"replace\" at \"/value/basic/title\" failed: invalid value",
          "details": {
            "value": ["Invalid input: expected object, received string"]
          }
        }
      }
    ]
  }
}
```

`meta.failed_operations` lists the failed operations in request order; every operation not listed was applied. Each entry has:

- `index` — the zero-based position of the operation in `operations`.
- `path` — the operation's `path`.
- `error.code` — `invalid_operation` when the operation failed validation, or `operation_failed` when it could not be applied to the state. Use this, not `message`, in your code.
- `error.message` — a human-readable reason.
- `error.details` — validation messages, when available.


With `allow_partial: true`, the response is always `200`, so check `meta.failed_operations` to see what was saved:

| `meta.failed_operations` | Result |
|  --- | --- |
| Empty | Every operation was applied and saved. |
| Some operations | The other operations were applied and saved. |
| Every operation | Nothing was saved. `data` holds the current state. |


Without `allow_partial`, the same request returns `422` and saves nothing:

```json
{
  "message": "Operation \"replace\" at \"/value/basic/title\" failed: invalid value",
  "validationErrors": {
    "value": ["Invalid input: expected object, received string"]
  }
}
```

**How operations are evaluated.** Operations run in order, and each one sees the changes of the operations applied before it. A failed operation leaves no changes behind, so later operations that depend on it may fail too. Be careful with array index paths: if `remove /value/parties/0` fails, a later `replace /value/parties/1/email` targets the second party of the array as it is, not the one you may have expected.

**Errors that fail the whole request.** Authentication and permission errors (`401`, `403`), a missing contract (`404`), a contract that is not editable (`409`), and malformed requests (`422` with `validationErrors` — for example more than 100 operations, an unsupported `op`, or a non-boolean `allow_partial`) are returned for the request as a whole, and nothing is applied.

> **Retrying.** A `5xx` response or a dropped connection does not guarantee that nothing was saved. Read the current state with [Get contract builder state](/platform-api/openapi/builder/getcontractbuilderstate) before retrying, and keep in mind that operations are not idempotent — resending an `add` to `/value/<fieldset>/-` appends another instance.


### Finalizing and sending for signing

When the draft is ready, finalize it with [Finalize contract](/platform-api/openapi/builder/finalizecontractbuilder). Finalizing locks the content and moves the contract out of the editable `created` stage.

The signees are parsed automatically from the builder state, including the contact details found for each of them. How each signee is invited depends on those contacts:

- email only → invited by email,
- phone only → invited by SMS,
- both → invited by both.


Two parameters control finalization:

- `signature_method` — `canvas` for drawn signatures, `strong` for strong electronic identification, or `manual` to complete the document without electronic signing.
- `send_invitations` — when `true`, invitations are dispatched immediately. When `false`, the contract is finalized but no invitations are sent (you can send them later from app.docue.com).


The resulting `stage` tells you what happened:

- `finalized` — the contract is locked and awaiting signatures.
- `completed` — the document was completed without signing. This happens when there were no signees in the builder state, or when `signature_method` is `manual`.


### Sending an existing PDF for signing

If you already have a finished PDF and just want signatures, you do not need the builder flow at all. [Send PDF for signing](/platform-api/openapi/signing/createcontractforsigning) uploads a PDF, creates the signees, and finalizes the contract for signing — in a single request.

```mermaid
sequenceDiagram
    participant App as Integrator
    participant API as Platform API
    participant Signee

    App ->> API: POST /public/v2/contracts (multipart: file + parties)
    API -->> App: Contract (stage = finalized, document_id)
    opt send_invitations = true
        API ->> Signee: Signing invitation (email / SMS)
        Signee ->> API: Signs the document
    end
    App ->> API: GET /public/v2/documents/{document_id}/download
    API -->> App: Signed PDF
```

Key points:

- The request must be `multipart/form-data` because it includes a file upload. Only **PDF** files are accepted, and the maximum size is **13 MB** per file. Optional `attachments` (up to 10) are merged with the primary `file` into a single document before signing.
- Provide at least one party in `parties[]`. A party with an `email` is invited by email, one with a `phone` by SMS, and one with both by both. Use `send_invitations` to control whether invitations go out immediately.
- The response is a **contract**. Its `id` is the contract identifier; use the `document_id` field to work with the document elsewhere in the API (for example, to fetch or download it).
- After sending, you usually do not poll for the result. Instead, set up a [webhook](#webhooks) that listens for the `contract-completed` event. Its payload is the contract but does not include a document id, so use the contract's `id` to find the document with [List documents](/platform-api/openapi/documents/getdocuments) (`GET /public/v2/documents?filter[contract_id]={contract_id}`), then fetch the finished file with [Download document](/platform-api/openapi/documents/downloaddocument).


### Managing the archive

All documents live in folders inside the workspace archive, and you can manage both through the API.

**Folders** — list, create, fetch, update, and delete folders with the [Folders endpoints](/platform-api/openapi/folders/getfolders). Use the `include` parameter (`parent`, `breadcrumb`, `has_children`) to pull in related data. Deleting a folder also deletes every document and subfolder inside it.

**Documents** — [upload](/platform-api/openapi/documents/createdocuments), [list](/platform-api/openapi/documents/getdocuments), [fetch](/platform-api/openapi/documents/getdocument), [update](/platform-api/openapi/documents/updatedocument), [download](/platform-api/openapi/documents/downloaddocument), and [delete](/platform-api/openapi/documents/deletedocument) documents. When listing, narrow results with the `filter` parameters (by `folder_id`, by contract `stage`, or by `contract_id`) and order them with `sort`. Use the `include` parameter to embed optional relationships such as `contract`, `owner`, or `tags`.

#### Uploading documents

Use [Upload documents](/platform-api/openapi/documents/createdocuments) to store existing files — such as signed paper contracts, scans, or Office documents — in the archive. Uploaded documents are stored as-is and are not sent for signing; to upload a PDF for signing, see [Sending an existing PDF for signing](#sending-an-existing-pdf-for-signing).

- The request must be `multipart/form-data`. Pass the target `folder_id` and one to five documents using array notation (`documents[0][title]`, `documents[0][file]`, and so on). All documents in a request go into the same folder.
- The maximum size is **45 MB** per file. Supported types include PDF, Microsoft Office and OpenDocument files, Apple iWork files, common image formats, plain text, CSV, RTF, and email messages.
- Optionally set `documents[N][description]` and tags with `documents[N][metadata][tags][]`.
- Pass `owner_id` to assign a workspace user as the owner of all created documents, or omit it to use the API key's service account.
- The response contains the created documents in the order they were submitted.


```bash
curl https://api.docue.com/public/v2/documents \
  -H "Authorization: Bearer <api_key>" \
  -H "Accept: application/json" \
  -F "folder_id=0ZABao" \
  -F "documents[0][title]=Lease Agreement, Main Street 1" \
  -F "documents[0][file]=@lease-agreement.pdf" \
  -F "documents[0][metadata][tags][]=lease" \
  -F "documents[1][title]=Floor plan" \
  -F "documents[1][file]=@floor-plan.png"
```

#### Updating documents

Use [Update document](/platform-api/openapi/documents/updatedocument) to change an existing document. Send only the fields you want to change — omitted fields stay as they are.

| Field | Purpose |
|  --- | --- |
| `title` | Rename the document. |
| `description` | Set the notes shown in the app, or `null` to clear them. |
| `folder_id` | Move the document to another folder (write access to the destination is required). |
| `owner_id` | Assign a workspace user as owner, or `null` to clear the owner. |


```http
PATCH /public/v2/documents/6DdwkO HTTP/1.1
Authorization: Bearer <api_key>
Content-Type: application/json

{
  "title": "Employment Agreement, Jane Doe",
  "owner_id": "ObB8Vd"
}
```

**Document owners.** An owner is a workspace user. Call [List workspace users](/platform-api/openapi/workspace/getworkspaceusers) to get valid user ids, then pass one as `owner_id`. The user must belong to the authenticated workspace.

You can set the owner when [creating a document from a template](/platform-api/openapi/builder/starttemplate), or later with the update endpoint above:

- Pass `owner_id` to assign a specific user.
- Omit `owner_id` when creating from a template to use the API key's service account as the owner.
- Pass `owner_id: null` to create or update the document with no owner.


Document responses always include the nullable `owner_id`. Add `include=owner` when you [fetch](/platform-api/openapi/documents/getdocument) or [list](/platform-api/openapi/documents/getdocuments) documents to embed the owner's details.

#### Document tags

Use [Set document tags](/platform-api/openapi/documents/setdocumenttags) to assign tags to a document. The `PUT` request replaces the document's complete tag list, so include every tag you want to keep. Send an empty `tags` array to remove all tags. Tag values in the request must be unique.

```http
PUT /public/v2/documents/6DdwkO/tags HTTP/1.1
Authorization: Bearer <api_key>
Content-Type: application/json

{
  "tags": ["legal", "q1-2026"]
}
```

Retrieve the tags with [List document tags](/platform-api/openapi/documents/getdocumenttags):

```http
GET /public/v2/documents/6DdwkO/tags HTTP/1.1
Authorization: Bearer <api_key>
```

```json
{
  "data": [
    {
      "_type": "tag",
      "tag": "legal"
    },
    {
      "_type": "tag",
      "tag": "q1-2026"
    }
  ]
}
```

To retrieve tags together with documents, add `include=tags` when you [fetch one document](/platform-api/openapi/documents/getdocument) or [list documents](/platform-api/openapi/documents/getdocuments). The response then contains the same tag objects in the document's `tags` field.

#### Custom properties

Custom properties let you store your own structured data on documents, such as a cost center or a contract value. A custom property is defined once for the workspace, and each document can then hold one value for it.

**1. Define the custom property.** [Create a custom property](/platform-api/openapi/metadata/createproperty) with a `title` and a `data_type` (`string`, `numeric`, `date`, or `boolean`). Use [List custom properties](/platform-api/openapi/metadata/getproperties) to find existing ones.

```http
POST /public/v2/metadata/properties HTTP/1.1
Authorization: Bearer <api_key>
Content-Type: application/json

{
  "title": "Contract value",
  "data_type": "numeric"
}
```

You can later [rename it](/platform-api/openapi/metadata/updateproperty) by changing `title` or `description`; `data_type` cannot be changed. [Deleting a custom property](/platform-api/openapi/metadata/deleteproperty) also removes its values from every document. Built-in properties have `source: system` and cannot be changed or deleted.

**2. Add a value to a document.** [Set document property value](/platform-api/openapi/metadata/createdocumentpropertyvalue) with the custom property's `id`. The `value` must match the `data_type`; send dates as `YYYY-MM-DD`.

```http
POST /public/v2/documents/6DdwkO/properties/Xk3pQa/values HTTP/1.1
Authorization: Bearer <api_key>
Content-Type: application/json

{
  "value": 12000
}
```

**3. Edit or remove the value.** A document holds only one value per custom property, so a second `POST` returns `409`. Find the existing value's `id` with [List document property values](/platform-api/openapi/metadata/getdocumentpropertyvalues), then [update](/platform-api/openapi/metadata/updatedocumentpropertyvalue) or [remove](/platform-api/openapi/metadata/deletedocumentpropertyvalue) it.

```http
PATCH /public/v2/documents/6DdwkO/properties/Xk3pQa/values/Lm9vRt HTTP/1.1
Authorization: Bearer <api_key>
Content-Type: application/json

{
  "value": 15000
}
```

### Webhooks

Instead of polling the API for changes, you can let Docue push notifications to your own systems as they happen. A **webhook** subscribes to a set of events; whenever a matching event occurs in your workspace, Docue sends an HTTP `POST` request to the URLs you configured.

A webhook has two parts:

- **Configuration** — the webhook itself: a `name`, an `enabled` flag, an optional `description`, and the list of `accepted_events` it listens to.
- **Endpoints** — one or more URLs that receive the deliveries. Each endpoint can carry optional authentication (`basic` with a username and password, or `bearer` with a token) that Docue includes on every request it sends to that endpoint.


Manage webhooks with the [Webhooks endpoints](/platform-api/openapi/webhooks/createwebhook): [create](/platform-api/openapi/webhooks/createwebhook), [list](/platform-api/openapi/webhooks/getwebhooks), [update](/platform-api/openapi/webhooks/updatewebhook), and [delete](/platform-api/openapi/webhooks/deletewebhook) them. Use `include=endpoints` when listing to load each webhook's endpoints.

```mermaid
sequenceDiagram
    participant Docue as Docue
    participant EP as Your endpoint

    Note over Docue: An event occurs<br/>(e.g. contract completed)
    Docue ->> EP: POST <your endpoint URL> (event envelope)
    alt endpoint responds 2xx
        EP -->> Docue: 200 OK
        Note over Docue: Delivery succeeded
    else non-2xx / no response
        EP -->> Docue: 5xx / timeout
        Note over Docue: Retried with backoff (up to 8 attempts)
    end
```

#### Delivery format

Each delivery is an HTTP `POST` with a JSON body — the **event envelope**:

```json
{
  "id": "01J9Z6E0Q2VHB8N6Q4N0YQK7AB",
  "occurred_at": "2026-06-01T13:26:29Z",
  "event_type": "contract-completed",
  "content": { }
}
```

- `id` — a unique identifier for the event (a ULID). Use it to deduplicate deliveries; the same event may be delivered more than once.
- `occurred_at` — when the event occurred.
- `event_type` — one of the [webhook events](#webhook-events) below.
- `content` — the resource the event is about. Its shape depends on the event: document events carry the document, contract events carry the contract, and signature events carry the signature (the same objects you get from the corresponding API endpoints).


Each request also carries identifying headers:

| Header | Description |
|  --- | --- |
| `docue-webhook-event-id` | The event `id`, matching the envelope. |
| `docue-webhook-event-type` | The event type. |
| `docue-webhook-endpoint-id` | Identifier of the endpoint that is receiving the delivery. |
| `docue-workspace-id` | Identifier of the workspace the event belongs to. |


If an endpoint has authentication configured, Docue also sends the corresponding `Authorization` header (`Basic` or `Bearer`).

#### Responding and retries

Your endpoint should return a `2xx` status code to acknowledge a delivery. Any other status (or a timeout / connection error) is treated as a failure, and Docue retries the delivery with an increasing backoff — up to **8 attempts** in total. The wait before each retry grows: about **5 minutes** before the first retry, **1 hour** before the second, and **10 hours** before every retry after that. Acknowledge quickly and do any heavy processing asynchronously on your side.

#### Webhook events

These are the events a webhook can subscribe to via `accepted_events`. More may be added over time.

| Event | When it fires | `content` |
|  --- | --- | --- |
| `document-created` | A new document is created in the workspace — for example when one is drafted from a template, uploaded, or created as part of sending a PDF for signing. | The document |
| `document-updated` | An existing document's attributes change — for example its title, folder, or notes are updated. | The document |
| `contract-completed` | A contract reaches the `completed` stage: every party has signed, or the contract was finalized with the `manual` signing method or with no signees. | The contract |
| `contract-cancelled` | A contract is cancelled — for example its signing process is cancelled, or it expires before all parties have signed. | The contract |
| `signature-updated` | A single signature (one signee on a contract) changes — for example its state changes or its contact details are edited. | The signature |


#### Testing a webhook

To verify your endpoints are wired up correctly, use [Test webhook](/platform-api/openapi/webhooks/testwebhook). Given a document, it builds a `document-updated` event from it and dispatches it to the webhook's endpoints — regardless of the webhook's `accepted_events`, so the webhook does not need to listen to `document-updated` to be testable. Use it to confirm your endpoint receives deliveries, validates the headers, and responds with a `2xx`.

## Architectural notes

- **Everything is scoped to your workspace.** The API key authenticates one workspace, and all resources you create or read belong to it. Anything you do is mirrored in the Docue.com web app.
- **Builder state retention.** After a contract is finalized or completed, its builder state remains readable for 24 hours, then becomes unavailable (`410 Gone`).
- **Draft expiry.** Drafts created from a template expire if left untouched; see `draft_expires_at` on the contract.
- **No long-term guarantees on experimental endpoints.** [Get contract builder value schema](/platform-api/openapi/builder/getcontractbuildervalueschema) and [Get contract builder sections](/platform-api/openapi/builder/getcontractbuildersections) are experimental and their response shapes may change without notice — avoid depending on their exact structure in production.


### Common error responses

| Status | Meaning |
|  --- | --- |
| `401` | The API key is missing or invalid. |
| `403` | The workspace lacks write access to the target folder. |
| `404` | The requested template, contract, document, or folder was not found in the workspace. |
| `409` | The contract is not Builder 2.0, or is no longer in the `created` stage (for patch/finalize), or there were not enough signatures to send for signing. |
| `410` | The builder state is no longer available (more than 24 hours after finalization). |
| `422` | The request payload failed validation. The response includes a `validationErrors` map. A [partial builder state patch](#partial-success) reports failed operations in a `200` response instead. |


If you have any questions about the Platform API, please feel free to contact [support@docue.com](mailto:support@docue.com).