---
title: "TurboSign API Integration"
description: "Integrate TurboSign API with single-step document preparation. Send documents for electronic signatures in one API call with simplified workflow."
url: "https://docs.turbodocx.com/docs/TurboSign/API-Signatures"
---

# TurboSign API Integration

> Integrate TurboSign API with single-step document preparation. Send documents for electronic signatures in one API call with simplified workflow.

This comprehensive guide walks you through the TurboSign single-step API integration. Learn how to programmatically upload documents, configure recipients, set up signature fields, and send documents for electronic signatures using a single, streamlined API call.

![TurboSign API Integration Overview](https://docs.turbodocx.com/img/turbosign/api/api-illustration.png)

## Overview

The TurboSign API provides a simplified single-step process to prepare documents for electronic signatures. Instead of multiple API calls, you can now accomplish everything in one request.

### Two Endpoint Options

TurboSign offers two single-step endpoints to fit different workflows:

1. **Prepare for Review** - Upload and get preview URL (no emails sent)
2. **Prepare for Signing** - Upload and send immediately (emails sent)

![TurboSign Single-Step Workflow](https://docs.turbodocx.com/img/turbosign/api/types.svg)

### Key Features

- **Single API Call**: Upload document, add recipients, and configure fields in one request
- **RESTful API**: Standard HTTP methods with multipart/form-data
- **Bearer Token Authentication**: Secure API access using JWT tokens
- **Multiple Recipients**: Support for multiple signers with custom signing order
- **Flexible Field Placement**: Position signature fields using anchors or coordinates
- **Multiple File Sources**: Upload file, or reference deliverableId, templateId, or fileLink
- **Real-time Status Updates**: Track document status throughout the signing process
- **Webhook Integration**: Receive notifications when signing is complete

:::tip Prefer using an SDK?
We offer official SDKs that handle authentication, error handling, and type safety for you.

[View all SDKs →](https://docs.turbodocx.com/docs/SDKs.md)
:::

## TLDR; Complete Working Example 🚀

Don't want to read all the details? Here's what you need to know:

### Available Field Types

| Type         | Description                | Use Case                                     |
| ------------ | -------------------------- | -------------------------------------------- |
| `signature`  | Electronic signature field | Legal signatures                             |
| `initial`    | Initial field              | Document initials, paragraph acknowledgments |
| `date`       | Date picker field          | Signing date, agreement date                 |
| `full_name`  | Full name field            | Automatically fills signer's complete name   |
| `first_name` | First name field           | Automatically fills signer's first name      |
| `last_name`  | Last name field            | Automatically fills signer's last name       |
| `title`      | Title/job title field      | Professional title or position               |
| `company`    | Company name field         | Organization or company name                 |
| `email`      | Email address field        | Signer's email address                       |
| `text`       | Generic text input field   | Custom text, notes, or any other text input  |
| `checkbox`   | Checkbox field             | Acknowledgments, consent, agreements         |

### Quick Start: Prepare for Signing (Most Common)

Use this endpoint to send documents immediately for signing:

### Alternative: Prepare for Review

Use this endpoint when you need a preview URL to verify field placement:

### Quick Comparison

| Feature              | prepare-for-review             | prepare-for-signing       |
| -------------------- | ------------------------------ | ------------------------- |
| Sends emails?        | ❌ No                          | ✅ Yes                    |
| Returns preview URL? | ✅ Yes                         | ❌ No                     |
| Returns recipients?  | ✅ Yes                         | ✅ Yes                    |
| Final status         | REVIEW_READY                   | UNDER_REVIEW              |
| Use when             | Need to verify field placement | Ready to send immediately |

Now that you've seen the whole thing, let's dive into the details...

## Prerequisites

To have an AI coding agent scaffold the integration, install the TurboDocx Quickstart skill with `npx skills add TurboDocx/quickstart` and run `/turbodocx-sdk turbosign` in the agent.

Before you begin, ensure you have:

- **API Access Token**: Bearer token for authentication
- **Organization ID**: Your organization identifier
- **PDF Document**: Document ready for signature collection

### Getting Your Credentials

1. **Login to TurboDocx**: Visit [https://www.turbodocx.com](https://www.turbodocx.com)
2. **Navigate to Settings**: Click your account avatar, then click **Settings**
3. **API Keys Section**: Open **API keys** (under **Developers**) to generate or retrieve your API access token
4. **Organization ID**: Copy your organization ID from the **Features and integrations** page

![TurboSign API Key](https://docs.turbodocx.com/img/turbosign/api/api-key.png)
![TurboSign Organization ID](https://docs.turbodocx.com/img/turbosign/api/org-id.png)

## Authentication

All TurboSign API requests require authentication using a Bearer token in the Authorization header:

```http
Authorization: Bearer YOUR_API_TOKEN
```

Additional required headers for all requests:

```http
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client
```

## Choosing Your Endpoint

TurboSign offers two single-step endpoints to fit different workflows. Choose the one that best matches your use case.

### When to Use prepare-for-review

✅ **Use this endpoint when you want to:**

- Verify field placement before sending to recipients
- Get a preview URL to review the document in TurboSign's interface
- Manually trigger email sending after verifying field placement
- Ensure correct field positioning before recipients receive emails

**Workflow**: Upload → Get preview URL → Review in browser → Manually send when ready

### When to Use prepare-for-signing

✅ **Use this endpoint when you want to:**

- Send documents immediately without preview step
- Automate the entire signature process end-to-end
- Use with verified templates or confident field positioning
- Skip manual review and send directly to recipients

**Workflow**: Upload → Emails sent automatically → Recipients sign

## Endpoint 1: Prepare for Review

Creates a signature request and returns a preview URL. No emails are sent to recipients.

### Endpoint

```http
POST https://api.turbodocx.com/turbosign/single/prepare-for-review
```

### Headers

```http
Content-Type: multipart/form-data
Authorization: Bearer YOUR_API_TOKEN
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client
```

### Request Body (multipart/form-data)

⚠️ **Important**: Recipients and fields must be sent as JSON strings in form-data

| Field               | Type           | Required      | Description                                |
| ------------------- | -------------- | ------------- | ------------------------------------------ |
| file                | File           | Conditional\* | PDF, DOCX, or PPTX file to upload          |
| deliverableId       | String (UUID)  | Conditional\* | Reference to existing deliverable          |
| templateId          | String (UUID)  | Conditional\* | Reference to existing template (DOCX, PPTX, or a [PDF signature template](https://docs.turbodocx.com/docs/TurboDocx-Templating/API-Templates.md#pdf-signature-templates)). Required when any recipient has a `role` |
| fileLink            | String (URL)   | Conditional\* | URL to download file from                  |
| documentName        | String         | No            | Document name in TurboSign (max 255 chars) |
| documentDescription | String         | No            | Document description (max 1000 chars)      |
| recipients          | String (JSON)  | **Yes**       | JSON string array of recipient objects     |
| fields              | String (JSON)  | Conditional\*\* | JSON string array of field objects. Must not be empty. Optional only when a recipient has a `role` |
| senderName          | String         | No            | Name of sender (max 255 chars). Defaults to your API key's name. |
| senderEmail         | String (email) | **Yes**       | Reply-to address on the signature email and the sender recorded in the audit trail |
| ccEmails            | String (JSON)  | No            | JSON string array of CC email addresses    |
| remindersEnabled    | Boolean        | No            | Send reminder emails to signers who haven't signed |
| reminderDelay       | String (JSON)  | No            | Time to the FIRST reminder, `{"value":3,"unit":"days"}` |
| reminderInterval    | String (JSON)  | No            | Gap between later reminders                |
| maxReminders        | Number         | No            | Cap per signer, max `50`. `-1` unlimited, `0` none |
| expirationEnabled   | Boolean        | No            | Close the signing window after `expireAfter` |
| expireAfter         | String (JSON)  | No            | How long the document stays signable       |
| expirationWarning   | String (JSON)  | No            | How far before expiry warnings start. `0` = never warn |
| expirationWarningInterval | String (JSON) | No       | Gap between warnings once they start       |

\* **File Source**: Must provide exactly ONE of: file, deliverableId, templateId, or fileLink

\*\* **Fields**: Required, unless at least one recipient names a template signer `role`. Omitting `fields` without a role returns `400 FieldsRequired`. See [Sending with signer roles](#sending-with-signer-roles).

A PDF template is sent as it was stored at upload: TurboDocx refuses password-protected PDFs, repairs damaged ones where it can, and flattens fillable form fields into the page (see [PDF signature templates](https://docs.turbodocx.com/docs/TurboDocx-Templating/API-Templates.md#pdf-signature-templates)). The request's `recipients` and `fields` are used as sent. A signature setup saved on the template in the TurboDocx app is applied only when a recipient names one of its roles (see [Sending with signer roles](#sending-with-signer-roles)).

:::tip Reminders & expiration are optional
The eight schedule fields are **per-document overrides**. Omit any of them and that setting is
inherited from your organization's E-Signature defaults; omit all of them and the document simply
follows the org policy as it stands at send time. Both reminders and expiration ship **off** by
default, so leaving these out preserves today's behaviour exactly.

The resolved schedule is **frozen onto the document when it is sent** — changing your org defaults
later never alters a document already out for signature.

**Durations are `{value, unit}` objects sent as JSON strings**, because `multipart/form-data`
cannot carry a nested value:

```
reminderDelay={"value":3,"unit":"days"}
expireAfter={"value":30,"unit":"hours"}
```

`unit` is `"hours"` or `"days"`. `value` is a whole number, minimum 1 and at most
**999 days (23976 hours)** — except `expirationWarning`, where `0` means "never send a warning".

See [Reminders & Expiration](#reminders--expiration) for the full behaviour.
:::

:::warning senderEmail is required
`senderEmail` must be supplied on every API/SDK signature request. A request authenticated with
an API key has no mailbox of its own, so TurboDocx **rejects the request with HTTP 400 and the
error code `SenderEmailRequired`** rather than sending from an unmonitored address. `senderName`
is optional — it defaults to the name of your API key (shown in the recipient's email and the
audit trail); if no name can be resolved at all the API returns `400 SenderNameRequired`.

TurboQuote works differently: quotes have **no `senderEmail` request field** — the sender is
resolved from the org quote template. See
[Prepared By & Sender Identity](https://docs.turbodocx.com/docs/TurboQuote/Prepared-By-and-Sender-Identity.md).
:::

### Recipients JSON Format

Recipients must be stringified before adding to form-data:

```javascript
const recipients = JSON.stringify([
  {
    name: "John Smith",
    email: "john.smith@company.com",
    signingOrder: 1,
    metadata: {
      color: "hsl(200, 75%, 50%)",
      lightColor: "hsl(200, 75%, 93%)",
    },
  },
  {
    name: "Jane Doe",
    email: "jane.doe@partner.com",
    signingOrder: 2,
    metadata: {
      color: "hsl(270, 75%, 50%)",
      lightColor: "hsl(270, 75%, 93%)",
    },
  },
]);
formData.append("recipients", recipients);
```

### Fields JSON Format

Fields reference recipients by **email** (not recipientId) and must be stringified:

#### Template-based (recommended):

```javascript
const fields = JSON.stringify([
  {
    recipientEmail: "john.smith@company.com",
    type: "signature",
    template: {
      anchor: "{Signature1}",
      placement: "replace",
      size: { width: 200, height: 80 },
      offset: { x: 0, y: 0 },
    },
    required: true,
  },
  {
    recipientEmail: "john.smith@company.com",
    type: "date",
    template: {
      anchor: "{Date1}",
      placement: "replace",
      size: { width: 150, height: 30 },
    },
    required: true,
  },
]);
formData.append("fields", fields);
```

#### Coordinate-based:

```javascript
const fields = JSON.stringify([
  {
    recipientEmail: "john.smith@company.com",
    type: "signature",
    page: 1,
    x: 100,
    y: 200,
    width: 200,
    height: 80,
    pageWidth: 612,
    pageHeight: 792,
    required: true,
  },
]);
formData.append("fields", fields);
```

### Sending with signer roles {#sending-with-signer-roles}

A PDF template in TurboDocx can carry a saved signature setup: signer **roles** (for example "Client" and "Countersigner") with fields placed on the PDF for each role. Each role has a stable **key** (for example `client`), shown under **Use via API** on the template page (turn on **Dev Mode** to see it) and returned by [Get Template Signature Setup](#endpoint-8-get-template-signature-setup). A role is either a **slot** with no saved signer, filled in each time you send, or it has a **saved signer** (a name and email used when you leave the role out). Roles saved before keys existed answer to `signer_1`, `signer_2` and so on, by signing order.

Signer roles work with **PDF signature templates** only, because the saved field positions are measured on that PDF. To send a Word or PowerPoint template, generate a deliverable and send it with `deliverableId`.

#### How role keys are made {#how-role-keys-are-made}

The key is made from the role name in TurboDocx: lowercase, with spaces and symbols turned into underscores.

| Role name       | Key               |
| --------------- | ----------------- |
| Client          | `client`          |
| Account Manager | `account_manager` |
| Counter-signer  | `counter_signer`  |
| Signer 1        | `signer_1`        |

- A role left with the default name ("Signer 1", "Signer 2") has the key `signer_1`, `signer_2`. These keys follow the role's position, so they renumber if roles are reordered, added or removed. Give a role its own name if your code depends on it.
- If two roles would make the same key, the second gets a number, such as `client_2`. TurboDocx does not allow two roles with the same name, so this is rare.
- Once a template is saved, the key of a role you named never changes, even if you rename the role later.
- Keys are matched ignoring case.

To find the keys, turn on **Dev Mode** in TurboDocx. The template page then shows a **Use via API** panel, and the **Code** button on the send page shows **Send this template by role**. Both give ready-to-paste code with your template ID, organization ID and role keys. You can also call [Get Template Signature Setup](#endpoint-8-get-template-signature-setup).

:::tip Use the key, not the display name
Pass the key (`signer_1`), not the name shown in the app (`Signer 1`). A display name fails with `UnknownSignerRole`, and the error lists the valid keys: `Unknown role "Signer 1". This template's roles are: signer_1, signer_2`.
:::

To send such a template, pass its `templateId` and give each recipient a `role`:

```bash
curl -X POST https://api.turbodocx.com/turbosign/single/prepare-for-signing \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "x-rapiddocx-org-id: YOUR_ORGANIZATION_ID" \
  -F "templateId=TEMPLATE_ID" \
  -F "senderEmail=you@company.com" \
  -F 'recipients=[{"role":"client","name":"Jane Doe","email":"jane@client.com"}]'
```

How a role send behaves:

- **Role keys** are matched case-insensitively. `role` requires `templateId`.
- **Fields come from the template.** The fields saved for each role are placed for the signer who fills it, so `fields` can be left out. Any `fields` you do pass are added to the template's (for example an extra witness signature).
- **Roles you leave out** use the template's saved signer. A role with no saved signer must be filled, or the request fails with `SignerRoleUnfilled`.
- **Signing order** follows the template's role order, then any recipients without a role in their own `signingOrder`. The final order is renumbered so it runs 1, 2, 3 and so on. A recipient with a `role` needs no `signingOrder`.
- **Template defaults fill the gaps.** The template's saved CC emails, document description, document name and reminder and expiration settings apply to anything the request leaves out. Values in the request always win.
- **Identity verification.** A role's saved verification choice applies unless the recipient sets its own `identityVerification`. A role saved with SMS verification needs the recipient's `phone`. If your organization no longer allows a role's saved choice (for example SMS was turned off), the organization default applies instead of failing the send. A choice you set on the recipient yourself is still checked as usual. See [the recipient](https://docs.turbodocx.com/docs/TurboSign/embedded-signing/reference.md#the-recipient) in the embedded signing reference for those fields.
- **Every signer needs a field.** A role send where a signer ends up with no fields fails with `SignerHasNoFields` (Prepare for Review skips this check, because you place fields in the review screen afterwards).
- **Without roles nothing changes.** `fields` is still required when no recipient names a role.

#### Claiming a role with a different signer {#claiming-a-role-with-a-different-signer}

A role with a saved signer can still be given to someone else for one send. Pass a recipient with that `role` and the new person's `name` and `email`, and the new person takes the role and all of its fields. The template is not changed.

```json
[{ "role": "countersigner", "name": "Alex Kim", "email": "alex@acme.com" }]
```

What changes for the new signer:

- **Name and email fields** are filled from the new recipient. A first name field takes the first word of `name` and a last name field takes the rest. If that leaves a value empty (a one-word name has no last name), the field is left blank and unlocked so the signer types it.
- **Title and company fields** lose the saved person's values and are unlocked, so the new signer fills them in.
- **A field set to keep its own default value** in TurboDocx keeps that value.
- **Identity verification** keeps the role's saved choice unless the recipient sets its own. The saved person's phone is never used: a role saved with SMS verification needs the new recipient's `phone`, or the send fails with `OtpPhoneRequired`.
- **Email addresses** must stay unique. If the new signer's email matches another role's signer, or a recipient without a role, the send fails with `DuplicateRoleEmail`.

#### Signer role errors

These return `400` with a body of the form `{ "message": "...", "type": "..." }`:

| `type`                     | Example `message`                                                                   | Cause                                                        |
| -------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| `UnknownSignerRole`        | `Unknown role "clinet". This template's roles are: client, countersigner`           | A `role` does not match any of the template's role keys      |
| `DuplicateSignerRole`      | `Role "client" was given more than one recipient`                                   | Two recipients name the same role                            |
| `SignerRoleUnfilled`       | `Role "client" needs a signer. Pass recipients: [{ role: "<role>", name, email }]`   | A role has no saved signer and no recipient fills it         |
| `TemplateHasNoSignerRoles` |                                                                                     | The template has no saved signer roles                       |
| `RoleRequiresTemplate`     |                                                                                     | A recipient has a `role` but the request has no `templateId` |
| `DuplicateRoleEmail`       |                                                                                     | Two roles resolved to the same email address, or a recipient without a role has a role signer's email |
| `SignerHasNoFields`        | `Signer "Jane Doe" has no fields to sign. Place a field for them or remove them.`  | A signer ends up with no fields after the template's fields are placed |
| `OtpPhoneRequired`         |                                                                                     | A role requires SMS verification and the recipient has no `phone` |
| `FieldsRequired`           |                                                                                     | `fields` was omitted and no recipient names a role           |
| `TemplateRolesNeedPdfTemplate` |                                                                                | Roles were sent for a Word or PowerPoint template            |

#### Document and field errors {#document-and-field-errors}

These also return `400` with a body of the form `{ "message": "...", "type": "..." }` (the signature setup endpoints return `{ "error": "...", "code": "..." }` instead):

| `type`                                | Example `message`                                                                                   | Cause                                                                                              |
| ------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `FieldsRequired`                      | `fields is required. Leave it out only when sending a templateId whose recipients name a role (recipients[].role).` | `fields` was omitted or empty and no recipient names a role |
| `FieldPageOutOfRange`                 | `A field is placed on page 5, but the document has 3 pages.`                                        | A coordinate-based field has a `page` the document doesn't have                                    |
| `SignatureSetupTemplateMismatch`      |                                                                                                     | A signature setup placed on an uploaded PDF was saved onto a template that isn't a PDF template     |
| `SignatureSetupPageOutOfRange`        | `A field is placed on page 5, but this PDF has 3 pages.`                                            | A signature setup saved onto a PDF template has a field past the PDF's last page                   |

#### Locked templates and permissions

- Saving or deleting a template's signature setup returns `423` while someone is editing the template in TurboDocx. Retry once they have finished.
- Get Template Signature Setup requires the administrator, contributor or user role. A viewer is refused.

#### Reading a draft's signers {#reading-a-drafts-signers}

`GET /turbosign/documents/{documentId}/with-recipients` returns a draft and its signers. When the draft was created from a template with signer roles, each signer that fills a role also has `templateRoleLabel`: the role's name as shown in TurboDocx (for example `"Client"`). It is for display only. To send by role, pass the role's key from [Get Template Signature Setup](#endpoint-8-get-template-signature-setup), not this label.

Example response, trimmed to the signer fields:

```json
{
  "data": {
    "document": { "id": "DOCUMENT_ID", "name": "Service Agreement", "status": "DRAFT" },
    "recipients": [
      {
        "id": "RECIPIENT_ID",
        "name": "Jane Doe",
        "email": "jane@client.com",
        "signingOrder": 1,
        "metadata": {},
        "otpChannel": "none",
        "phone": null,
        "templateRoleLabel": "Client"
      }
    ]
  }
}
```

A signer that doesn't fill a template role has no `templateRoleLabel`.

#### Editing a draft's signers

When you change a draft's signers with `PATCH /turbosign/documents/{documentId}/update-with-recipients`, the recipients you send replace the draft's current signers. A recipient with an `id` is updated in place; a recipient without an `id` is added as a new signer; a current signer you leave out is removed, along with that signer's fields. This also applies to a draft created from a template, so sending recipients without `id` replaces the signers the template saved. The draft must still be in `DRAFT` status, otherwise the request returns `409`.

### Response

```json
{
  "success": true,
  "documentId": "4a20eca5-7944-430c-97d5-fcce4be24296",
  "status": "REVIEW_READY",
  "previewUrl": "https://www.turbodocx.com/sign/preview/abc123...",
  "recipients": [
    {
      "id": "5f673f37-9912-4e72-85aa-8f3649760f6b",
      "name": "John Smith",
      "email": "john.smith@company.com",
      "signingOrder": 1,
      "metadata": {
        "color": "hsl(200, 75%, 50%)",
        "lightColor": "hsl(200, 75%, 93%)"
      }
    }
  ],
  "message": "Document prepared for review. Use the preview URL to review and assign fields."
}
```

### Response Fields

| Field      | Type          | Description                                    |
| ---------- | ------------- | ---------------------------------------------- |
| success    | Boolean       | Request success status                         |
| documentId | String (UUID) | Unique document identifier - save for tracking |
| status     | String        | Document status (REVIEW_READY)                 |
| previewUrl | String (URL)  | URL to preview and verify document             |
| recipients | Array         | Array of recipient objects with generated IDs  |
| message    | String        | Human-readable success message                 |

### Code Examples

### Next Steps After Review

Once you've reviewed the document via the preview URL click "Send for Signing" button on the preview page to send emails to recipients

## Endpoint 2: Prepare for Signing

Creates a signature request and immediately sends emails to recipients. Use this for production workflows when you're confident in your field positioning.

### Endpoint

```http
POST https://api.turbodocx.com/turbosign/single/prepare-for-signing
```

### Headers

```http
Content-Type: multipart/form-data
Authorization: Bearer YOUR_API_TOKEN
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client
```

### Request Body (multipart/form-data)

The request format is **identical** to prepare-for-review. See the "Endpoint 1: Prepare for Review" section above for detailed field documentation.

Signer roles work the same way here: pass `templateId` and give each recipient a `role`. See [Sending with signer roles](#sending-with-signer-roles).

### Response

```json
{
  "success": true,
  "documentId": "4a20eca5-7944-430c-97d5-fcce4be24296",
  "status": "UNDER_REVIEW",
  "recipients": [
    {
      "id": "5f673f37-9912-4e72-85aa-8f3649760f6b",
      "name": "John Smith",
      "email": "john.smith@company.com",
      "signingOrder": 1,
      "metadata": {
        "color": "hsl(200, 75%, 50%)",
        "lightColor": "hsl(200, 75%, 93%)"
      }
    }
  ],
  "message": "Document sent for signing. Emails are being sent to recipients."
}
```

### Response Fields

| Field      | Type          | Description                                    |
| ---------- | ------------- | ---------------------------------------------- |
| success    | Boolean       | Request success status                         |
| documentId | String (UUID) | Unique document identifier - save for tracking |
| status     | String        | Document status (UNDER_REVIEW)                 |
| recipients | Array         | Array of recipient objects with generated IDs  |
| message    | String        | Human-readable success message                 |

⚠️ **Note**: This endpoint returns immediately after creating the document. Email sending happens asynchronously in the background. Use webhooks to receive notification when the document is fully signed. Documents sent with an API key don't email a "fully signed" copy to the sender; signers and CC recipients still get their completion emails.

### Code Examples

:::tip Need to resend emails?
If a recipient hasn't received or has lost their signing email, you can resend it using the [Resend Email endpoint](#endpoint-6-resend-email). You'll need the `recipientIds` from the response of this endpoint.
:::

## Endpoint 3: Download Signed Document

After a document has been signed by all recipients (status: `COMPLETED`), you can download the final signed PDF document.

### Endpoint

```http
GET https://api.turbodocx.com/turbosign/documents/{documentId}/download
```

### Headers

```http
Authorization: Bearer YOUR_API_TOKEN
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client
```

### Path Parameters

| Parameter  | Type          | Required | Description                      |
| ---------- | ------------- | -------- | -------------------------------- |
| documentId | String (UUID) | Yes      | The unique identifier of the document |

### Response

```json
{
  "downloadUrl": "https://s3.amazonaws.com/bucket/path/to/document.pdf?X-Amz-...",
  "fileName": "Signed_Contract_2024.pdf"
}
```

### Response Fields

| Field       | Type   | Description                                                |
| ----------- | ------ | ---------------------------------------------------------- |
| downloadUrl | String | Presigned S3 URL to download the signed PDF (expires in 1 hour) |
| fileName    | String | Original filename of the signed document                   |

:::warning Document Status Requirement
This endpoint only returns a download URL when the document status is `COMPLETED`. If the document is still pending signatures, you will receive an error response.
:::

### Usage Notes

- The presigned URL expires after **1 hour**. Request a new URL if the previous one has expired.
- The downloaded PDF includes all signatures embedded and is legally binding.
- For large documents, consider streaming the download rather than loading the entire file into memory.

## Endpoint 4: Get Audit Trail

Retrieve the complete audit trail for a document, including all events and timestamps. The audit trail provides a tamper-evident record of all actions taken on the document using a cryptographic hash chain.

### Endpoint

```http
GET https://api.turbodocx.com/turbosign/documents/{documentId}/audit-trail
```

### Headers

```http
Authorization: Bearer YOUR_API_TOKEN
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client
```

### Path Parameters

| Parameter  | Type          | Required | Description                      |
| ---------- | ------------- | -------- | -------------------------------- |
| documentId | String (UUID) | Yes      | The unique identifier of the document |

### Response

```json
{
  "data": {
    "document": {
      "id": "4a20eca5-7944-430c-97d5-fcce4be24296",
      "name": "Service Agreement 2024"
    },
    "auditTrail": [
      {
        "id": "entry-uuid-1",
        "documentId": "4a20eca5-7944-430c-97d5-fcce4be24296",
        "actionType": "prepared_for_review",
        "timestamp": "2024-01-15T10:30:00.000Z",
        "previousHash": null,
        "currentHash": "a1b2c3d4e5f6...",
        "createdOn": "2024-01-15T10:30:00.000Z",
        "details": {
          "ipAddress": "192.168.1.100",
          "userAgent": "Mozilla/5.0..."
        },
        "user": {
          "name": "Admin User",
          "email": "admin@company.com"
        },
        "userId": "user-uuid-1"
      },
      {
        "id": "entry-uuid-2",
        "documentId": "4a20eca5-7944-430c-97d5-fcce4be24296",
        "actionType": "document_sent",
        "timestamp": "2024-01-15T10:31:00.000Z",
        "previousHash": "a1b2c3d4e5f6...",
        "currentHash": "b2c3d4e5f6g7...",
        "createdOn": "2024-01-15T10:31:00.000Z",
        "details": {
          "recipientCount": 2
        },
        "user": {
          "name": "Admin User",
          "email": "admin@company.com"
        },
        "userId": "user-uuid-1"
      },
      {
        "id": "entry-uuid-3",
        "documentId": "4a20eca5-7944-430c-97d5-fcce4be24296",
        "actionType": "document_viewed",
        "timestamp": "2024-01-15T11:00:00.000Z",
        "previousHash": "b2c3d4e5f6g7...",
        "currentHash": "c3d4e5f6g7h8...",
        "createdOn": "2024-01-15T11:00:00.000Z",
        "details": {
          "ipAddress": "10.0.0.50"
        },
        "recipient": {
          "name": "John Smith",
          "email": "john.smith@company.com"
        },
        "recipientId": "recipient-uuid-1"
      },
      {
        "id": "entry-uuid-4",
        "documentId": "4a20eca5-7944-430c-97d5-fcce4be24296",
        "actionType": "document_signed",
        "timestamp": "2024-01-15T11:05:00.000Z",
        "previousHash": "c3d4e5f6g7h8...",
        "currentHash": "d4e5f6g7h8i9...",
        "createdOn": "2024-01-15T11:05:00.000Z",
        "details": {
          "signatureType": "electronic",
          "ipAddress": "10.0.0.50"
        },
        "recipient": {
          "name": "John Smith",
          "email": "john.smith@company.com"
        },
        "recipientId": "recipient-uuid-1"
      }
    ]
  }
}
```

### Response Fields

#### Document Object

| Field | Type          | Description                 |
| ----- | ------------- | --------------------------- |
| id    | String (UUID) | Document identifier         |
| name  | String        | Document name               |

#### Audit Trail Entry Object

| Field        | Type          | Description                                           |
| ------------ | ------------- | ----------------------------------------------------- |
| id           | String (UUID) | Unique identifier for the audit entry                 |
| documentId   | String (UUID) | Document this entry belongs to                        |
| actionType   | String        | Type of action (see Action Types below)               |
| timestamp    | String (ISO)  | When the action occurred                              |
| previousHash | String        | Hash of the previous entry (null for first entry)     |
| currentHash  | String        | Hash of this entry (forms hash chain)                 |
| createdOn    | String (ISO)  | When the entry was created                            |
| details      | Object        | Additional action-specific details                    |
| user         | Object        | User who performed action (for sender actions)        |
| userId       | String (UUID) | User ID (when applicable)                             |
| recipient    | Object        | Recipient who performed action (for signer actions)   |
| recipientId  | String (UUID) | Recipient ID (when applicable)                        |

#### Action Types

| Action Type                   | Description                                         |
| ----------------------------- | --------------------------------------------------- |
| `prepared_for_review`         | Document was uploaded and prepared for review       |
| `document_sent`               | Document was sent to recipients for signing         |
| `document_viewed`             | Recipient opened/viewed the document                |
| `document_signed`             | Recipient signed the document                       |
| `document_voided`             | Document was voided/cancelled                       |
| `document_resent`             | Reminder/resend email was sent to recipient         |
| `email_notification_sent`     | Signing invitation email was sent                   |
| `cc_email_notification_sent`  | CC notification email was sent                      |

### Hash Chain Verification

The audit trail uses a cryptographic hash chain for tamper-evidence:

- Each entry's `currentHash` is computed from the entry data plus the `previousHash`
- The first entry has `previousHash: null`
- To verify integrity, recompute each hash and compare
- Any modification to historical entries would break the chain

This provides strong evidence that the audit trail has not been tampered with after creation.

### Usage Notes

- The audit trail is available at any document status (not just completed documents)
- All timestamps are in ISO 8601 format (UTC timezone)
- The `details` object varies by action type and may contain IP addresses, user agents, and other contextual information
- Audit trail entries are immutable and cannot be modified or deleted

## Endpoint 5: Get Recipients

Retrieve every recipient on a document with their signing status, their email history, and who sent the document. This is the endpoint to poll when you want to know **who has signed and who you are still waiting on**.

### Endpoint

```http
GET https://api.turbodocx.com/turbosign/documents/{documentId}/recipients
```

### Headers

```http
Authorization: Bearer YOUR_API_TOKEN
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client
```

### Path Parameters

| Parameter  | Type          | Required | Description                           |
| ---------- | ------------- | -------- | ------------------------------------- |
| documentId | String (UUID) | Yes      | The unique identifier of the document |

### Response

```json
{
  "data": {
    "document": {
      "id": "4a20eca5-7944-430c-97d5-fcce4be24296",
      "name": "Service Agreement 2024",
      "status": "under_review",
      "createdOn": "2024-01-15T10:30:00.000Z",
      "sentOn": "2024-01-15T10:35:00.000Z",
      "expiresAt": null,
      "sentBy": {
        "name": "Jane Sender",
        "email": "jane@acme.com"
      }
    },
    "recipients": [
      {
        "id": "recipient-uuid-1",
        "name": "John Doe",
        "email": "john@example.com",
        "status": "completed",
        "effectiveStatus": "completed",
        "signedOn": "2024-01-16T14:02:00.000Z",
        "signingOrder": 1,
        "delivery": {
          "firstSentOn": "2024-01-15T10:35:00.000Z",
          "lastSentOn": "2024-01-15T10:35:00.000Z",
          "totalSent": 1,
          "reminderCount": 0,
          "lastRemindedAt": "2024-01-15T10:35:00.000Z",
          "warningCount": 0,
          "lastWarningAt": null
        }
      },
      {
        "id": "recipient-uuid-2",
        "name": "Jane Smith",
        "email": "jane@example.com",
        "status": "pending",
        "effectiveStatus": "pending",
        "signedOn": null,
        "signingOrder": 2,
        "delivery": {
          "firstSentOn": "2024-01-16T14:02:05.000Z",
          "lastSentOn": "2024-01-20T09:00:00.000Z",
          "totalSent": 2,
          "reminderCount": 1,
          "lastRemindedAt": "2024-01-20T09:00:00.000Z",
          "warningCount": 0,
          "lastWarningAt": null
        }
      }
    ],
    "summary": {
      "total": 2,
      "pending": 1,
      "viewed": 0,
      "completed": 1,
      "voided": 0,
      "expired": 0,
      "waitingOn": 1
    }
  }
}
```

### Response Fields

#### `document`

| Field     | Type   | Description                                                                    |
| --------- | ------ | ------------------------------------------------------------------------------ |
| status    | String | Document-level status (`draft`, `under_review`, `completed`, `voided`, `expired`, …) |
| sentOn    | String \| null | When the document was dispatched to recipients; `null` while it is still a draft |
| expiresAt | String \| null | When the signing window closes; `null` if it never expires              |
| sentBy    | Object | `{ name, email }` — the real sender, never the internal service account         |

#### `recipients[]`

| Field           | Type           | Description                                                       |
| --------------- | -------------- | ----------------------------------------------------------------- |
| status          | String         | Raw status: `pending`, `viewed` or `completed`                     |
| effectiveStatus | String         | `pending`, `viewed`, `completed`, `voided` or `expired` — **use this for display** |
| signedOn        | String \| null | When this recipient signed; `null` while pending or viewed         |
| signingOrder    | Number         | Position in the signing sequence                                   |
| delivery        | Object         | This recipient's email history (see below)                         |

#### `recipients[].delivery`

| Field          | Type           | Description                                                              |
| -------------- | -------------- | ------------------------------------------------------------------------ |
| firstSentOn    | String \| null | First email of any kind; `null` if this recipient has never been emailed  |
| lastSentOn     | String \| null | Most recent email of any kind                                            |
| totalSent      | Number         | Signature request + resends + reminders + expiry warnings + terminal notices |
| reminderCount  | Number         | **Automatic (scheduled) reminders only** — see the note below            |
| lastRemindedAt | String \| null | **When the reminder cadence clock was last reset** — see the note below  |
| warningCount   | Number         | Expiry warnings sent. Only a warning touches this                        |
| lastWarningAt  | String \| null | When the last expiry warning went out. Only a warning touches this       |

:::warning `reminderCount` and `lastRemindedAt` do not mean what their names suggest

**`reminderCount` counts automatic (scheduled) reminders only.** It is the counter that the
`maxReminders` schedule setting caps. A manual "remind now" (the Send Reminder action) is a
standalone nudge that deliberately does **not** consume the cap budget, so it does **not**
increment this — even though the email it sends *does* appear in `totalSent`. A recipient can
therefore read `reminderCount: 0` while reminder emails have genuinely been sent.

**`lastRemindedAt` is a cadence clock, not a record of a reminder.** Four things stamp it: the
initial signature-request send (so the first reminder is measured from the invitation), each
scheduled reminder, each manual "remind now", and each expiry warning (a deliberate cross-reset
so a reminder never lands adjacent to a warning). Only the second of those bumps
`reminderCount`.

The common consequence: a freshly-sent document returns a **non-null `lastRemindedAt` equal to
the invitation timestamp, alongside `reminderCount: 0`**. That is correct — nobody has been
reminded. To answer "have we actually chased this person", read `totalSent` (all emails) or the
audit trail's `reminder_sent` entries, not `reminderCount`.

`warningCount` / `lastWarningAt` have no such caveat — only an expiry warning touches them.

:::

#### `summary`

Counts by `effectiveStatus`, plus `waitingOn` — the recipients who have not finished (`pending` + `viewed`). `waitingOn` drops to zero once the document reaches a terminal state.

### Two status fields, and they differ on purpose

There is no per-recipient declined, voided or expired state in the system — a recipient row only ever holds `pending`, `viewed` or `completed`. That means on a voided or expired document, an unsigned signer **still reads `pending`** in `status`.

`effectiveStatus` layers the document's outcome on top, so that same signer reads `voided` or `expired`. Branch on `effectiveStatus`, not `status`, or you will chase people whose signing links are already dead.

A completed signature is never revoked: someone who signed before the document was voided still reads `completed` in both fields.

`effectiveStatus` also reflects a **lapsed deadline immediately**. A document past its `expiresAt` keeps its old status until a background sweep updates it, but the signing links are already refused at that moment — so `effectiveStatus` reports `expired` right away rather than waiting for the row to change.

### Usage Notes

- Available at any document status, including drafts (a draft returns its recipients with `sentOn: null` and `delivery.totalSent: 0`)
- Recipients are returned ordered by `signingOrder`
- `delivery` counts emails to that signer only — CC notifications are excluded, since a CC address is not a signer
- On a document with a signing order, only the current turn has been emailed; `delivery.totalSent === 0` distinguishes "not yet invited" from "invited and not acted"
- Returns `404` if the document does not exist or belongs to another organization
- All timestamps are ISO 8601 (UTC)

## Endpoint 6: Resend Email

Resend signature request emails to one or more recipients who haven't yet completed signing. Only recipients at the current signing order who haven't completed are eligible for resend.

This applies to documents that have already been **sent for signing** — there must be an original invitation to resend. A document still in review (no signing emails sent yet) has no eligible recipients.

### Endpoint

```http
POST https://api.turbodocx.com/turbosign/documents/{documentId}/resend-email
```

### Headers

```http
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client
```

### Path Parameters

| Parameter  | Type          | Required | Description                           |
| ---------- | ------------- | -------- | ------------------------------------- |
| documentId | String (UUID) | Yes      | The unique identifier of the document |

### Request Body (JSON)

| Field        | Type           | Required | Description                                                      |
| ------------ | -------------- | -------- | ---------------------------------------------------------------- |
| recipientIds | Array (UUID[]) | **Yes**  | Array of recipient UUIDs to resend emails to (min 1, unique)     |

```json
{
  "recipientIds": [
    "5f673f37-9912-4e72-85aa-8f3649760f6b",
    "7a891c23-4d56-4e78-9abc-def012345678"
  ]
}
```

:::info Where do I get recipient IDs?
Recipient IDs are returned in the response of the **Prepare for Review** ([Endpoint 1](#endpoint-1-prepare-for-review)) and **Prepare for Signing** ([Endpoint 2](#endpoint-2-prepare-for-signing)) endpoints. Save these IDs when creating your signature request.
:::

### Response

```json
{
  "data": {
    "success": true,
    "recipientCount": 2
  }
}
```

### Response Fields

| Field               | Type    | Description                                 |
| ------------------- | ------- | ------------------------------------------- |
| data.success        | Boolean | Whether the resend was successful           |
| data.recipientCount | Number  | Number of recipients who received the email |

### Error Responses

| Status | Error Message                                                         | Cause                                                                    |
| ------ | --------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| 404    | `"Document not found"`                                                | Document doesn't exist, has been deleted, or belongs to another organization |
| 400    | `"All recipients have already completed signing"`                     | No eligible recipients remain                                            |
| 400    | `"Some recipients are not eligible for email resend at this time"`    | Requested recipients aren't at the current signing order or already completed |

The 400 "not eligible" error also returns an `invalidRecipientIds` array showing which IDs were rejected:

```json
{
  "error": "Some recipients are not eligible for email resend at this time",
  "invalidRecipientIds": ["7a891c23-4d56-4e78-9abc-def012345678"]
}
```

To recover, retry the request without the listed IDs, or wait until those recipients reach the current signing order before resending to them.

### Usage Notes

- Only recipients at the **current signing order** who haven't completed signing are eligible for resend. If your document uses sequential signing (signingOrder 1, 2, 3...), that means the current active step only.
- The request is **all-or-nothing**: if any requested ID is ineligible, the whole request is rejected with a 400 and **no** emails are sent. Fix the `recipientIds` and retry.
- Each resend creates a `document_resent` entry in the [audit trail](#endpoint-4-get-audit-trail) for tracking
- The `recipientIds` array must contain at least one ID and all IDs must be unique UUIDs

## Endpoint 7: Send Reminder

Send a reminder email to a document's outstanding signers.

This is a **standalone nudge**, deliberately decoupled from the automatic reminder schedule: it ignores the configured cadence, works even when reminders are disabled or the per-signer cap is already spent, and does **not** consume that cap. Use it when someone asks you to "chase them again" outside the normal rhythm.

Distinct from [Resend Email](#endpoint-6-resend-email): **resend** re-sends the original invitation; **remind** sends the reminder copy.

### Endpoint

```http
POST https://api.turbodocx.com/turbosign/documents/{documentId}/send-reminder
```

### Headers

```http
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client
```

### Path Parameters

| Parameter  | Type          | Required | Description                           |
| ---------- | ------------- | -------- | ------------------------------------- |
| documentId | String (UUID) | Yes      | The unique identifier of the document |

### Request Body (JSON)

| Field        | Type           | Required | Description                                                              |
| ------------ | -------------- | -------- | ------------------------------------------------------------------------ |
| recipientIds | Array (UUID[]) | No       | Subset to remind. **Omit to remind every eligible signer.** Min 1, unique |

Omit the body entirely (or send `{}`) to remind everyone whose turn it is:

```json
{}
```

Or name specific signers:

```json
{
  "recipientIds": ["5f673f37-9912-4e72-85aa-8f3649760f6b"]
}
```

:::warning Don't send an empty array
`recipientIds` must contain at least one ID **when the key is present**. Sending `{"recipientIds": []}` is rejected with a 400. To remind everyone, omit the key.
:::

### Response

```json
{
  "data": {
    "results": [
      { "recipientId": "5f673f37-9912-4e72-85aa-8f3649760f6b", "status": "sent", "reminderCount": 2, "phase": "reminder" },
      { "recipientId": "7a891c23-4d56-4e78-9abc-def012345678", "status": "skipped_wrong_order" }
    ]
  }
}
```

### Response Fields

| Field                | Type   | Description                                                       |
| -------------------- | ------ | ----------------------------------------------------------------- |
| data.results         | Array  | One entry per recipient considered — including those skipped       |
| results[].recipientId | String | The recipient this outcome refers to                              |
| results[].status     | String | What happened — see the table below                                |
| results[].reminderCount | Number | Reminder count after the send. Only meaningful when `sent`      |
| results[].phase      | String | `reminder` or `expiring` — which email copy was used               |

A recipient who was **not** emailed is reported rather than silently dropped, so you can tell nobody received anything:

| `status`              | Meaning                                                        |
| --------------------- | -------------------------------------------------------------- |
| `sent`                | The reminder email was sent                                     |
| `failed`              | The send was attempted and the mail transport rejected it       |
| `skipped_completed`   | That recipient has already signed                               |
| `skipped_wrong_order` | They're at a later signing order — it isn't their turn yet      |
| `skipped_not_due`     | Scheduled path only: their next reminder isn't due              |
| `skipped_max_reached` | Scheduled path only: their reminder cap is spent                |
| `skipped_disabled`    | Scheduled path only: reminders are off for this document        |
| `skipped_claim_lost`  | A concurrent request won the send; no duplicate was sent        |

### Error Responses

| Status | Error Message                                                     | Cause                                                                        |
| ------ | ----------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| 404    | `"Document not found"`                                            | Document doesn't exist, has been deleted, or belongs to another organization  |
| 409    | `"This document has expired, so its signers can no longer be reminded."` | The signing window has closed — the link is dead, so a reminder would be useless |
| 409    | `"This document has been voided, so its signers can no longer be reminded."` | The document was voided                                                  |
| 400    | `"Some recipients are not eligible for a reminder at this time"`   | A named recipient isn't a current-order pending signer                        |

The 409 responses carry `code: "DocumentNotActionable"` plus the document's `status`, so a client can refresh a stale row rather than guessing.

The 400 returns an `invalidRecipientIds` array showing which IDs were rejected. The request is **all-or-nothing** — if any named ID is ineligible, **no** emails are sent:

```json
{
  "error": "Some recipients are not eligible for a reminder at this time",
  "invalidRecipientIds": ["7a891c23-4d56-4e78-9abc-def012345678"]
}
```

### Usage Notes

- Only recipients at the **current signing order** who haven't completed are eligible.
- Unlike the automatic schedule, this is **not** gated by `remindersEnabled` or `maxReminders`, and it does **not** increment the reminder count — so it works on a document that never turned reminders on, or one whose cap is already spent.
- Each send creates a `reminder_sent` entry in the [audit trail](#endpoint-4-get-audit-trail), recorded against the user who triggered it.

---

## Endpoint 8: Get Template Signature Setup

List the signer roles saved on a template, so you know which `role` keys to pass when [sending it with signer roles](#sending-with-signer-roles).

### Endpoint

```http
GET https://api.turbodocx.com/turbosign/templates/{templateId}/signature-setup
```

### Headers

```http
Authorization: Bearer YOUR_API_TOKEN
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client
```

### Path Parameters

| Parameter  | Type          | Required | Description                           |
| ---------- | ------------- | -------- | ------------------------------------- |
| templateId | String (UUID) | Yes      | The unique identifier of the template |

### Response

```json
{
  "data": {
    "templateId": "TEMPLATE_ID",
    "roles": [
      {
        "key": "client",
        "label": "Client",
        "order": 1,
        "hasSavedSigner": false,
        "fieldCount": 3
      },
      {
        "key": "countersigner",
        "label": "Countersigner",
        "order": 2,
        "hasSavedSigner": true,
        "defaultName": "Sam Lee",
        "defaultEmail": "sam@acme.com",
        "fieldCount": 2
      }
    ]
  }
}
```

### Response Fields

| Field                       | Type    | Description                                                                 |
| --------------------------- | ------- | --------------------------------------------------------------------------- |
| data.templateId             | String  | The template's ID                                                           |
| data.roles                  | Array   | The template's signer roles, in signing order                               |
| data.roles[].key            | String  | The value to pass as a recipient's `role`                                   |
| data.roles[].label          | String  | The role's name as shown in TurboDocx                                       |
| data.roles[].order          | Number  | The role's signing order                                                    |
| data.roles[].hasSavedSigner | Boolean | `true` when the template has a saved signer, used if you leave the role out |
| data.roles[].defaultName    | String  | The saved signer's name. Present only when `hasSavedSigner` is `true`      |
| data.roles[].defaultEmail   | String  | The saved signer's email. Present only when `hasSavedSigner` is `true`     |
| data.roles[].fieldCount     | Number  | How many fields are saved for the role                                      |

### Error Responses

| Status | `type`                  | Cause                                                                        |
| ------ | ----------------------- | ---------------------------------------------------------------------------- |
| 404    | `SignatureSetupNotFound` | `"No saved signature setup found for this template"`: the template doesn't exist in your organization, or has no saved signature setup |
| 400    |                         | `templateId` is not a valid UUID                                             |

### Usage Notes

- Works with an API key or a user token. The caller needs the administrator, contributor or user role; the viewer role is refused, because the response lists saved signer emails.
- The same role keys are shown in the app under **Use via API** on the template page when **Dev Mode** is on.
- Roles saved before keys existed are returned with keys `signer_1`, `signer_2` and so on.

## Endpoint 9: Update Template Signature Settings {#endpoint-9-update-template-signature-settings}

Change the settings of a template's saved signature setup: CC recipients, the email message, reminders and expiration, and the document name. The signer roles, their keys and the fields are never changed, so integrations that send the template by role keep working. This is what **Edit** next to **Email to signers** or **Reminders & expiration** does on the template's page in TurboDocx (see [Change or remove the setup](https://docs.turbodocx.com/docs/TurboSign/Signature-Setups-on-Templates.md#change-or-remove-the-setup)).

### Endpoint

```http
PATCH https://api.turbodocx.com/turbosign/templates/{templateId}/signature-setup/settings
```

### Headers

```http
Authorization: Bearer YOUR_API_TOKEN
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
Content-Type: application/json
User-Agent: TurboDocx API Client
```

### Path Parameters

| Parameter  | Type          | Required | Description                           |
| ---------- | ------------- | -------- | ------------------------------------- |
| templateId | String (UUID) | Yes      | The unique identifier of the template |

### Request Body (JSON)

Send at least one of these. A setting you leave out keeps its saved value.

| Field               | Type   | Description                                                                                                         |
| ------------------- | ------ | ------------------------------------------------------------------------------------------------------------------- |
| ccEmails            | Array  | CC email addresses copied on every document sent from the template. Up to 15, no duplicates. Replaces the saved list; `[]` clears it |
| documentDescription | String | The message in the signature email (max 1000 chars). `""` clears it                                                 |
| scheduleOverrides   | Object | Reminder and expiration settings for this template, using the same eight fields as the [schedule fields](#endpoint-1-prepare-for-review) at send time (no `expiresAt`). Replaces the saved overrides; `{}` follows your organization's defaults |
| documentName        | String | The name documents are sent under (max 255 chars). PDF signature templates only; ignored for a Word or PowerPoint template. `""` uses the template's name |

Any other key, such as `roles` or `fields`, is rejected with `400`.

```bash
curl -X PATCH https://api.turbodocx.com/turbosign/templates/TEMPLATE_ID/signature-setup/settings \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "x-rapiddocx-org-id: YOUR_ORGANIZATION_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "ccEmails": ["legal@acme.com"],
    "documentDescription": "Please review and sign the attached agreement.",
    "scheduleOverrides": {
      "remindersEnabled": true,
      "reminderDelay": { "value": 3, "unit": "days" }
    }
  }'
```

### Response

```json
{
  "data": {
    "templateId": "TEMPLATE_ID",
    "setup": {
      "roles": [ ... ],
      "fields": [ ... ],
      "ccEmails": ["legal@acme.com"],
      "documentDescription": "Please review and sign the attached agreement.",
      "scheduleOverrides": {
        "remindersEnabled": true,
        "reminderDelay": { "value": 3, "unit": "days" }
      },
      "savedOn": "2026-10-20T14:30:00.000Z"
    }
  }
}
```

`data.setup` is the template's whole saved signature setup after the change, including its unchanged roles and fields.

### Error Responses

Errors return `{ "message": "...", "type": "...", "error": "...", "code": "..." }`, where `type` and `code` hold the same value.

| Status | `type`                   | Cause                                                                                   |
| ------ | ------------------------ | --------------------------------------------------------------------------------------- |
| 400    |                          | No setting was sent (`Send at least one of ccEmails, documentDescription, scheduleOverrides, documentName`), an unknown key was sent, or a value is invalid |
| 400    | `CcMatchesSigner`        | A CC address is also a saved signer on the template                                     |
| 404    | `SignatureSetupNotFound` | The template has no saved signature setup                                                |
| 404    | `TemplateNotFound`       | The template doesn't exist in your organization                                         |
| 423    |                          | Someone is editing the template in TurboDocx. Retry once they have finished             |

### Usage Notes

- Requires the administrator or contributor role.
- Your organization's own reminder and expiration settings are never changed. `scheduleOverrides` only applies to documents sent from this template.

## Endpoint 10: Delete Template Signature Setup {#endpoint-10-delete-template-signature-setup}

Remove a template's saved signature setup: its signer roles, fields, CC recipients, message, reminders and document name. Documents already sent are not affected. New sends from the template start with no signers or fields, and a role send fails with `TemplateHasNoSignerRoles`.

### Endpoint

```http
DELETE https://api.turbodocx.com/turbosign/templates/{templateId}/save-signature-setup
```

### Headers

```http
Authorization: Bearer YOUR_API_TOKEN
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client
```

### Path Parameters

| Parameter  | Type          | Required | Description                           |
| ---------- | ------------- | -------- | ------------------------------------- |
| templateId | String (UUID) | Yes      | The unique identifier of the template |

### Response

```json
{
  "data": {
    "templateId": "TEMPLATE_ID"
  }
}
```

### Error Responses

| Status | Body                                                         | Cause                                                               |
| ------ | ------------------------------------------------------------ | ------------------------------------------------------------------- |
| 404    | `{ "error": "Template not found", "code": "TemplateNotFound" }` | The template doesn't exist in your organization                     |
| 423    | `{ "error": "Resource is locked", ... }`                      | Someone is editing the template in TurboDocx. Retry once they have finished |

### Usage Notes

- Requires the administrator or contributor role.
- Deleting a template that has no saved setup also returns `200`.
- To change only the settings and keep the roles and fields, use [Update Template Signature Settings](#endpoint-9-update-template-signature-settings) instead.

## Send a template set up in TurboDocx {#send-a-template-set-up-in-turbodocx}

When the signers and fields are already set up on a template in TurboDocx, an integration only needs to say who fills each role.

1. In TurboDocx, upload the PDF as a template, add a role for each signer (for example "Client" and "Countersigner"), drag each role's fields onto the page and save the signature setup. Give a role a saved signer if the same person always signs it.
2. Look up the role keys with [Get Template Signature Setup](#endpoint-8-get-template-signature-setup), or copy them from **Use via API** on the template page (shown with **Dev Mode** on).
3. Send with [Prepare for Signing](#endpoint-2-prepare-for-signing) (or [Prepare for Review](#endpoint-1-prepare-for-review)), passing `templateId` and one recipient per role you want to fill. Leave out `fields` and `signingOrder`:

```bash
curl -X POST https://api.turbodocx.com/turbosign/single/prepare-for-signing \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "x-rapiddocx-org-id: YOUR_ORGANIZATION_ID" \
  -F "templateId=TEMPLATE_ID" \
  -F "senderEmail=you@company.com" \
  -F 'recipients=[{"role":"client","name":"Jane Doe","email":"jane@client.com"}]'
```

Here `client` is filled by Jane Doe and `countersigner`, left out, goes to the signer saved on the template. See [Sending with signer roles](#sending-with-signer-roles) for the full rules and errors. The SDKs support the same flow; see [Send a template set up in TurboDocx](https://docs.turbodocx.com/docs/SDKs/turbosign.md#send-a-template-set-up-in-turbodocx) in the TurboSign SDK guide.

---

## Reminders & Expiration

Two independent, opt-in features you can configure organization-wide in **E-Signature Settings → Reminders & Expiration**, or per-document via the [schedule fields](#endpoint-1-prepare-for-review) on either send endpoint.

**Both ship off by default.** A document sent without any schedule fields behaves exactly as it always has: no reminders, no expiry.

### The schedule

| Field | Default | Meaning |
| --- | --- | --- |
| `remindersEnabled` | `false` | Send reminder emails at all |
| `reminderDelay` | 3 days | Time to the **first** reminder, measured from that signer's invitation |
| `reminderInterval` | 3 days | Gap between **subsequent** reminders |
| `maxReminders` | 5 | Cap per signer, max `50`. `-1` unlimited, `0` none. **Never caps expiry warnings** |
| `expirationEnabled` | `false` | Expire the document at all |
| `expireAfter` | 120 days | How long the document stays signable, counted from **sending** |
| `expirationWarning` | 3 days | How far **before** expiry warnings start. `0` = never warn |
| `expirationWarningInterval` | 1 day | Gap between warnings once they start |

`reminderDelay` and `reminderInterval` are separate because a single interval can't express "first at 3 days, then every 5". `expirationWarning` / `expirationWarningInterval` mirror that split exactly.

### Two independent tracks

Reminders and expiry warnings run as **two separate clocks**, so a signer gets the full escalating stream — reminders keep coming even after the warning window opens. The two are coordinated so they never collide: on any given tick a due warning takes priority and restarts the reminder clock, so a signer never receives a reminder and a warning at the same moment.

Expiry warnings also still fire when reminders are **off** or **capped** — the deadline notice belongs to expiration, not to reminders.

### Expiry

When `expirationEnabled` is on, the document carries an absolute deadline:

- `expiresAt` — an ISO timestamp on the document, absent when it has no deadline.
- Once that instant passes, every signing link returns **HTTP 410** with `code: "DocumentExpired"`, and the document moves to the terminal status `expired`.
- Expiry is enforced **in real time** on every signing request, so a link stops working exactly on time regardless of background processing.

A document that was already **completed** keeps working — the deadline exists to stop signing, and signing is already finished.

### Validation

The API rejects contradictory combinations with `400` and `error: "InvalidSignatureSchedule"`, plus a machine-readable `data.code` and the conflicting `data.fields`:

| Code | Rule |
| --- | --- |
| `ExpiryWindowTooShort` | `expireAfter` must be at least one hour |
| `WarningExceedsExpiry` | `expirationWarning` must be **less than** `expireAfter` |
| `WarningIntervalTooShort` | `expirationWarningInterval` must be at least one hour when warnings are on |
| `ReminderDelayExceedsExpiry` | The first reminder must fall inside the expiry window, or it would never send |
| `ReminderIntervalExceedsExpiry` | Follow-up reminders must fit inside the window |

The canonical mistake these prevent: *"remind every 4 days"* with *"expire in 2 days"* — the document would die before the first nudge was ever due, and you'd believe reminders were on while no email ever sent.

---

## Recipients Reference

### Recipient Properties

Each recipient object in the `recipients` array should contain the following properties:

| Property     | Type           | Required | Description                                                |
| ------------ | -------------- | -------- | ---------------------------------------------------------- |
| name         | String         | Yes      | Full name of the recipient/signer                          |
| email        | String (email) | Yes      | Email address of the recipient (must be unique)            |
| signingOrder | Number         | Conditional | Order in which recipient should sign (starts at 1). Not needed on a recipient with a `role` |
| role         | String         | No       | Template signer role this recipient fills (for example `client`). Requires `templateId`. See [Sending with signer roles](#sending-with-signer-roles) |
| metadata     | Object         | No       | Optional metadata for UI customization (color, lightColor) |

### Metadata Object (Optional)

The `metadata` object allows you to customize the recipient's UI appearance:

| Property   | Type   | Description                                        | Example                |
| ---------- | ------ | -------------------------------------------------- | ---------------------- |
| color      | String | Primary color for recipient in HSL format          | `"hsl(200, 75%, 50%)"` |
| lightColor | String | Light background color for recipient in HSL format | `"hsl(200, 75%, 93%)"` |

### Example Recipients Array

```json
[
  {
    "name": "John Smith",
    "email": "john.smith@company.com",
    "signingOrder": 1
  },
  {
    "name": "Jane Doe",
    "email": "jane.doe@partner.com",
    "signingOrder": 2
  }
]
```

### With Optional Metadata

```json
[
  {
    "name": "John Smith",
    "email": "john.smith@company.com",
    "signingOrder": 1,
    "metadata": {
      "color": "hsl(200, 75%, 50%)",
      "lightColor": "hsl(200, 75%, 93%)"
    }
  }
]
```

## Field Types Reference

### Complete Field Type List

| Type         | Description                | Auto-filled | Use Case                                     |
| ------------ | -------------------------- | ----------- | -------------------------------------------- |
| `signature`  | Electronic signature field | No          | Legal signatures, agreements                 |
| `initial`    | Initial field              | No          | Document initials, paragraph acknowledgments |
| `date`       | Date picker field          | No          | Signing date, agreement date                 |
| `full_name`  | Full name field            | Yes         | Automatically fills signer's complete name   |
| `first_name` | First name field           | Yes         | Automatically fills signer's first name      |
| `last_name`  | Last name field            | Yes         | Automatically fills signer's last name       |
| `title`      | Title/job title field      | No          | Professional title or position               |
| `company`    | Company name field         | No          | Organization or company name                 |
| `email`      | Email address field        | Yes         | Signer's email address                       |
| `text`       | Generic text input field   | No          | Custom text, notes, or any other text input  |
| `checkbox`   | Checkbox field             | No          | Acknowledgments, consent, agreements         |

### Field Configuration Properties

#### Common Properties (All Field Types)

| Property        | Type    | Required | Description                                                    |
| --------------- | ------- | -------- | -------------------------------------------------------------- |
| recipientEmail  | String  | Yes      | Email address of recipient (matches email in recipients array) |
| type            | String  | Yes      | Field type (see table above)                                   |
| required        | Boolean | No       | Whether the signer must fill the field (default: `true`). Send `false` to let the signer leave it blank and still finish. Must be a JSON boolean, not the string `"false"`; `null` is treated as not set (required). Cannot be `false` on `signature` or `initial` fields. See [Optional Fields](#optional-fields) |
| defaultValue    | String  | No       | Pre-filled value for the field (max 600 characters). This is a real value that is submitted with the signature, not placeholder hint text. Stored per field, so two fields of the same type for one recipient can start with different values. Not allowed on `signature` or `initial` fields. A `checkbox` takes `"true"` or `"false"`; a `date` takes a fixed date in `MM/DD/YYYY` (omit it to use the signing date) |
| isReadonly      | Boolean | No       | Makes field non-editable (for prefilled values)                |
| backgroundColor | String  | No       | Custom background color (hex or rgba)                          |
| metadata        | Object  | No       | Optional field metadata. Carries `fieldKey` (on a controlling checkbox) and/or a `conditional` rule (on a dependent field). See [Conditional (IF/THEN) Fields](#conditional-if-then-fields). Other keys are kept with the field as sent. The TurboDocx app adds `useOwnDefaultValue: true` to a name, email, title or company field that keeps its own `defaultValue` instead of following its recipient's value for that field type. It is an editor hint only: every field is signed with its own `defaultValue` either way. |

#### Template-based Properties

| Property               | Type    | Required | Description                                                     |
| ---------------------- | ------- | -------- | --------------------------------------------------------------- |
| template.anchor        | String  | Yes      | Text anchor to find in document (e.g., `{Signature1}`)          |
| template.placement     | String  | Yes      | How to place field: "replace", "before", "after"                |
| template.size          | Object  | Yes      | Field dimensions: &#123; width: number, height: number &#125;             |
| template.offset        | Object  | No       | Position offset: &#123; x: number, y: number &#125; (default: &#123;x:0, y:0&#125;) |
| template.caseSensitive | Boolean | No       | Whether anchor search is case-sensitive (default: true)         |
| template.useRegex      | Boolean | No       | Whether to treat anchor as regex pattern (default: false)       |
| template.alignment     | String  | No       | Where the signer's entry sits: `"left"`, `"center"` or `"right"`. See [Field Alignment](#field-alignment) |

#### Coordinate-based Properties

| Property   | Type   | Required | Description                                                        |
| ---------- | ------ | -------- | ------------------------------------------------------------------ |
| page       | Number | Yes      | Page number (starts at 1)                                          |
| x          | Number | Yes      | Horizontal position from left edge (pixels)                        |
| y          | Number | Yes      | Vertical position from top edge (pixels)                           |
| width      | Number | Yes      | Field width in pixels                                              |
| height     | Number | Yes      | Field height in pixels                                             |
| pageWidth  | Number | No       | Total page width in pixels (optional, for responsive positioning)  |
| pageHeight | Number | No       | Total page height in pixels (optional, for responsive positioning) |
| alignment  | String | No       | Where the signer's entry sits: `"left"`, `"center"` or `"right"`. See [Field Alignment](#field-alignment) |

### Optional Fields {#optional-fields}

Every field is required by default: the signer cannot finish until it is filled. Set
`required: false` on a field to make it optional. The signer can then leave it blank and still
complete the document, and a blank optional field stays empty on the signed PDF.

```javascript
const fields = JSON.stringify([
  {
    recipientEmail: "john.smith@company.com",
    type: "signature",
    template: { anchor: "{Signature1}", placement: "replace", size: { width: 200, height: 80 } },
  },
  // Optional: the signer may leave this blank
  {
    recipientEmail: "john.smith@company.com",
    type: "text",
    template: { anchor: "{Notes}", placement: "replace", size: { width: 300, height: 30 } },
    required: false,
  },
]);
formData.append("fields", fields);
```

Rules:

- **Signature and initial fields are always required.** Sending `required: false` on a
  `signature` or `initial` field is rejected with `400 OptionalNotSupported`.
- **`required` must be a JSON boolean.** Any other value, including the string `"false"`, is
  rejected with `400 InvalidFieldRequired`. Omitting `required`, or sending `null`, is the same as
  `true`.
- **Each recipient needs at least one required, editable field.** A field counts when it is not
  `isReadonly` and its `required` is not `false`. A signature field always counts. A recipient
  whose fields are all optional or read-only is rejected with
  `400 NoEditableFieldsForRecipient`.
- **Date and checkbox fields are always filled on the signing page**, so making them optional does
  not change what the signer sees. It does change one check: an optional date or checkbox does not
  count as the recipient's required field, so a recipient whose only other fields are optional is
  still rejected with `400 NoEditableFieldsForRecipient`.
- **`defaultValue` is a real value, not hint text.** If you put instructions such as "Explain any
  changes here" in `defaultValue`, that text is submitted and lands on the signed PDF unless the
  signer clears it. Leave `defaultValue` empty on an optional field you want to stay blank.
- **`isReadonly` and `required: false` can be combined.** A read-only field is pre-filled and the
  signer can't change it, so it never holds up signing, whether it is required or not. A field
  that a conditional rule still hides or locks never holds up signing either.

On the signing page, an empty optional field keeps its normal label (for example "Text") and shows
a small **Optional** tag on its top-right corner. The "N of M required" progress counter leaves
optional fields out. A locked field never shows the tag: that covers a read-only field and a field
that a conditional `unlock` rule still holds locked, and the tag appears once the rule unlocks it.
A locked field shows its lock icon instead when your organization draws locked fields as grey
boxes (see [Locked Fields Rendering](https://docs.turbodocx.com/docs/TurboSign/How-to-Configure-Signature-Appearance.md)). If the
signer fills in an optional field and changes their mind, they can open it and click **Clear field**.

`required` works the same way on prepare-for-signing, prepare-for-review, and in each
`documents[].fields` array of the [Bulk API](https://docs.turbodocx.com/docs/TurboSign/API-Bulk-Signatures.md).

:::warning Behavior change for existing integrations
Earlier, the single-step endpoints accepted `required` but ignored it, so every field was treated
as required. `required: false` is now honored. If your integration already sends
`required: false`, those fields become optional for the signer. Requests that send
`required: false` on a signature or initial field, send a non-boolean `required`, or leave a
recipient with no required editable field now return `400` (`OptionalNotSupported`,
`InvalidFieldRequired`, or `NoEditableFieldsForRecipient`).
:::

### Conditional (IF/THEN) Fields {#conditional-if-then-fields}

Any field can carry an optional `metadata` object. It is used to build **conditional
(IF/THEN) relationships** between fields: a **controlling checkbox** decides whether one or
more **dependent fields** are shown or unlocked. This works with both the
**prepare-for-signing** and **prepare-for-review** single-step routes (and with the
[Bulk API](https://docs.turbodocx.com/docs/TurboSign/API-Bulk-Signatures.md), which uses the same field format).

The relationship has two halves:

1. **The controlling checkbox** gets a stable identifier via `metadata.fieldKey`. It must be
   a field of `type: "checkbox"`.
2. **Each dependent field** points back at that checkbox with a `metadata.conditional` rule
   whose `controllingFieldKey` equals the checkbox's `fieldKey`.

#### metadata Object

| Property      | Type   | Required | Description                                                                                   |
| ------------- | ------ | -------- | --------------------------------------------------------------------------------------------- |
| `fieldKey`    | String | No       | Stable identifier for a **controlling checkbox**. Referenced by dependent fields' `controllingFieldKey`. Set this on the checkbox (`type: "checkbox"`). |
| `conditional` | Object | No       | Rule placed on a **dependent field** that makes its visibility/editability depend on a controlling checkbox. See below. |

#### metadata.conditional Object

| Property              | Type   | Required | Description                                                                                             |
| --------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------- |
| `controllingFieldKey` | String | **Yes**  | The `metadata.fieldKey` of the controlling checkbox this field depends on. Must be non-empty.           |
| `operator`            | String | **Yes**  | Condition to evaluate against the checkbox: `"is_checked"` or `"is_not_checked"`.                       |
| `action`              | String | **Yes**  | What happens to this field when the condition is met: `"show"` or `"unlock"`.                           |

#### show vs. unlock

The `action` controls the dependent field's starting state and what the condition changes:

- **`show`** — the dependent field is **hidden** until the condition is met. When the checkbox
  matches the `operator`, the field appears.
- **`unlock`** — the dependent field is **visible but read-only** until the condition is met.
  When the checkbox matches the `operator`, the field becomes editable.

#### Worked Example

A recipient checks "Request changes" and a text box appears asking them to explain. The
checkbox is the controller (`fieldKey: "request_changes"`); the text field shows only when the
box is checked.

```javascript
const fields = JSON.stringify([
  // The recipient's signature. Every recipient needs at least one required field.
  {
    recipientEmail: "john.smith@company.com",
    type: "signature",
    page: 1,
    x: 100,
    y: 600,
    width: 200,
    height: 80,
  },
  // Controlling checkbox — gets a stable fieldKey
  {
    recipientEmail: "john.smith@company.com",
    type: "checkbox",
    page: 1,
    x: 100,
    y: 400,
    width: 20,
    height: 20,
    required: false,
    metadata: {
      fieldKey: "request_changes",
    },
  },
  // Dependent text field — hidden until the checkbox above is checked
  {
    recipientEmail: "john.smith@company.com",
    type: "text",
    page: 1,
    x: 130,
    y: 400,
    width: 300,
    height: 60,
    required: false,
    defaultValue: "",
    metadata: {
      conditional: {
        controllingFieldKey: "request_changes",
        operator: "is_checked",
        action: "show",
      },
    },
  },
]);
formData.append("fields", fields);
```

To instead keep the field visible but locked until the box is checked, change the dependent
field's `action` to `"unlock"`. To reveal a field when a box is **cleared** (for example, an
"I do not consent — explain why" note), use `operator: "is_not_checked"`.

#### Validation and Fail-Open Behavior

The API **validates the shape** of every `conditional` rule. A malformed rule is rejected with
HTTP **400** and the `type` **`InvalidConditionalRule`**. A rule is malformed when:

- `operator` is anything other than `"is_checked"` or `"is_not_checked"`, or
- `action` is anything other than `"show"` or `"unlock"`, or
- `controllingFieldKey` is missing or empty.

```json
{
  "message": "Field 2: metadata.conditional.operator must be one of: is_checked, is_not_checked",
  "type": "InvalidConditionalRule"
}
```

A **well-formed** rule whose `controllingFieldKey` does not match any checkbox's `fieldKey` in
the same request does **not** error — it **fails open**. The dependent field stays visible and
editable as if it had no rule, so a typo in `controllingFieldKey` silently disables the
condition rather than blocking the send. Double-check that the `controllingFieldKey` on each
dependent field exactly matches the `fieldKey` of an existing checkbox.

### Field Type Special Behaviors

**signature & initial**

- Draws a signature pad for user input
- Can be text-based or drawn
- Cryptographically signed and hashed for legal validity
- Cannot carry a `defaultValue` — sending one is rejected
- Always required: `required: false` is rejected with `400 OptionalNotSupported`

**date**

- Shows date picker interface
- Format: MM/DD/YYYY (US) or DD/MM/YYYY (configurable)
- Fills automatically with the date the recipient signs
- To pin a specific date instead, set `defaultValue` to that date in `MM/DD/YYYY` format (e.g. `"12/31/2026"`). Omit `defaultValue` (or send `""`) to keep the signing-date behavior
- `defaultValue` must be a **real calendar date** in `MM/DD/YYYY` — a non-existent date such as `"02/31/2026"` (or any malformed value) is rejected with `400 InvalidDateValue`. There is no `"today"` keyword
- A date field cannot be `isReadonly` — a pinned date still shows to the signer, it is not locked
- Always filled on the signing page, so `required: false` does not change what the signer sees, but an optional date does not count as the recipient's required field

**full_name, first_name, last_name, email**

- Auto-populated from recipient profile
- Can be overridden by recipient if needed
- Useful for legal compliance and form filling

**text**

- Single-line text input by default
- Supports defaultValue for prefilled content
- Set `required: false` to let the signer leave it blank (see [Optional Fields](#optional-fields))
- Use for titles, company names, custom fields

**checkbox**

- Boolean true/false value
- Useful for acknowledgments and consent
- Can have label text next to checkbox
- Always has a value (checked or unchecked) when the signer finishes, so `required: false` does not change what the signer sees, but an optional checkbox does not count as the recipient's required field

## Field Positioning Methods

TurboSign supports two methods for positioning signature fields in your documents.

### Method 1: Template-based Positioning (Recommended)

Uses text anchors in your PDF as placeholders. TurboSign searches for these anchors and places fields accordingly.

#### Advantages

✅ Easy to update field positions (just edit the PDF)
✅ No need to measure exact coordinates
✅ Works across different page sizes
✅ More maintainable for non-technical users
✅ Handles document variations gracefully

#### How it Works

1. **Add anchor text to your PDF**: Place text like `{Signature1}`, `{Date1}`, `{Initial1}` where you want fields
2. **Configure fields with anchor references**: Tell TurboSign what to search for
3. **TurboSign finds and replaces**: Anchors are found and replaced with interactive fields

#### Anchor Configuration Example

```json
{
  "recipientEmail": "john.smith@company.com",
  "type": "signature",
  "template": {
    "anchor": "{Signature1}",
    "placement": "replace",
    "size": { "width": 200, "height": 80 },
    "offset": { "x": 0, "y": 0 },
    "caseSensitive": true,
    "useRegex": false
  },
  "required": true
}
```

#### Placement Options

- **replace**: Removes the anchor text and places the field in its position
- **before**: Places field before the anchor text (anchor remains visible)
- **after**: Places field after the anchor text (anchor remains visible)

#### Field Alignment {#field-alignment}

Use `alignment` to choose where the signer's entry (a typed or drawn signature, initials, a date, or any text value) sits inside its field: `"left"`, `"center"` or `"right"`.

- **Anchor fields:** set it on the template as `template.alignment`. With `"placement": "replace"`, it also pins the field to that side of the anchor text: `"left"` starts the field where the anchor starts, `"right"` ends it where the anchor ends.
- **Coordinate fields:** set it at the top level of the field as `alignment`.
- **Leave it out (or send `null`)** to keep the standard placement. Existing integrations don't change.
- Any other value is rejected with a `400` error: `alignment must be one of: left, center, right`.
- Checkbox fields ignore it.

```json
{
  "recipientEmail": "master@shipping.com",
  "type": "signature",
  "template": {
    "anchor": "{MasterSignature}",
    "placement": "replace",
    "size": { "width": 180, "height": 40 },
    "alignment": "left"
  }
}
```

The signing page shows the entry with the same alignment, and the signed PDF matches it.

#### Offset Usage

Offset allows fine-tuning field position relative to the anchor:

- `x`: Positive moves right, negative moves left (pixels)
- `y`: Positive moves down, negative moves up (pixels)

```json
{
  "anchor": "{Signature1}",
  "placement": "replace",
  "size": { "width": 200, "height": 80 },
  "offset": { "x": 10, "y": -5 } // 10px right, 5px up from anchor
}
```

### Method 2: Coordinate-based Positioning

Uses exact pixel coordinates to position fields on specific pages. Best for precise control or when anchors aren't feasible.

#### Advantages

✅ Pixel-perfect precision
✅ Works with PDFs that can't be edited
✅ Programmatically generated positions
✅ Useful for form-filling scenarios
✅ Consistent placement across documents

#### How it Works

1. **Measure exact x,y coordinates** in your PDF (using PDF editor or viewer)
2. **Provide page number, coordinates, and dimensions**
3. **TurboSign places fields at exact positions**

#### Coordinate Configuration Example

```json
{
  "recipientEmail": "john.smith@company.com",
  "type": "signature",
  "page": 1,
  "x": 100,
  "y": 200,
  "width": 200,
  "height": 80,
  "pageWidth": 612,
  "pageHeight": 792,
  "required": true
}
```

#### Coordinate System Reference

- **Origin (0,0)**: Top-left corner of the page
- **X-axis**: Increases from left to right
- **Y-axis**: Increases from top to bottom
- **Standard US Letter**: 612 x 792 pixels (8.5" x 11" at 72 DPI)
- **Standard A4**: 595 x 842 pixels (210mm x 297mm at 72 DPI)

#### Coordinate Validation

Fields must stay within page boundaries:

- `x ≥ 0`
- `y ≥ 0`
- `x + width ≤ pageWidth`
- `y + height ≤ pageHeight`

#### Measuring Coordinates

**Adobe Acrobat Pro**:

1. View → Show/Hide → Rulers & Grids → Rulers
2. Hover over location to see coordinates

**Browser Developer Tools**:

1. Open PDF in browser
2. Right-click → Inspect
3. Use element inspector to measure positions

**PDF Editing Software**:

- Use built-in coordinate display
- Draw rectangles to measure dimensions

#### Quick Coordinate Example

Position a signature field at bottom-right of a US Letter page:

```json
{
  "recipientEmail": "john@example.com",
  "type": "signature",
  "page": 1,
  "x": 362, // 612 - 250 = 362 (right aligned with 50px margin)
  "y": 662, // 792 - 130 = 662 (bottom aligned with 50px margin)
  "width": 200,
  "height": 80,
  "pageWidth": 612,
  "pageHeight": 792
}
```

## Best Practices

### Workflow Selection

**When You Need Field Verification**:

- ✅ Use `prepare-for-review` to get preview URLs
- ✅ Verify field placement in browser before sending
- ✅ Manually trigger sending after review
- ✅ Useful for new document templates or complex field layouts

**When Field Placement Is Verified**:

- ✅ Use `prepare-for-signing` to send immediately
- ✅ Implement webhook handlers for completion notifications
- ✅ Use proper error handling and retry logic
- ✅ Monitor API rate limits
- ✅ Log all document IDs for tracking

**General Tips**:

- ✅ Use deliverableId or templateId to avoid repeated uploads
- ✅ Test with your own email addresses first
- ✅ Both endpoints are production-ready

### Security

- **Never expose API tokens**: Store tokens securely in environment variables or secrets management
- **Use HTTPS only**: All API calls must use HTTPS in production (API enforces this)
- **Validate inputs**: Always validate recipient emails and document names before submission
- **Implement rate limiting**: Respect API rate limits to avoid throttling
- **Rotate tokens regularly**: Generate new API tokens periodically
- **Use webhook signatures**: Verify webhook payloads using HMAC signatures
- **Sanitize user inputs**: Validate and sanitize all user-provided data

### Error Handling

- **Check HTTP status codes**: Always verify response status before processing
- **Handle timeouts**: Implement retry logic with exponential backoff for network failures
- **Log API responses**: Keep detailed logs for debugging and monitoring
- **Validate responses**: Check response structure before accessing data
- **Graceful degradation**: Have fallback behavior for API failures
- **User-friendly errors**: Display helpful error messages to end users

### Performance

**File Upload Optimization**:

- Compress PDFs when possible (aim for &lt;5MB)
- Use fileLink for files already in cloud storage (S3, GCS, etc.)
- Use deliverableId/templateId to reference existing documents
- Avoid uploading the same document multiple times

**API Efficiency**:

- Single-step endpoints reduce API calls from 3 to 1 (3x faster)
- Batch multiple documents in parallel requests when possible
- Use connection pooling for multiple requests
- Implement exponential backoff for retries
- Cache responses when appropriate

**Network Optimization**:

- Use CDN for document hosting when using fileLink
- Enable gzip compression for API requests
- Minimize payload sizes by only including required fields

### Document Preparation

**Text Anchors (Template-based)**:

- Use consistent anchor naming: `{FieldType}{Number}` (e.g., `{Signature1}`, `{Date1}`)
- Place anchors exactly where you want fields
- Use unique anchors (avoid duplicates)
- Test anchor placement with prepare-for-review first
- Document your anchor naming convention

**Coordinate-based**:

- Verify coordinates work across different PDF viewers
- Account for page margins and headers/footers
- Use standard page sizes when possible
- Test on actual page dimensions (don't assume)
- Validate boundaries before submission

**Document Validation**:

- Ensure PDFs are not password-protected or corrupted
- Verify all pages are readable
- Test with actual documents before production
- Keep backup copies of source documents

### JSON String Formatting

⚠️ **Critical**: Recipients and fields must be valid JSON strings when added to form-data.

**Correct**:

```javascript
const recipients = JSON.stringify([
  { name: "John", email: "john@example.com", signingOrder: 1 },
]);
formData.append("recipients", recipients);
```

**Incorrect**:

```javascript
// Don't send object/array directly!
formData.append("recipients", recipientsArray); // ❌ Wrong
formData.append("recipients", "[{...}]"); // ❌ Wrong (string literal, not stringified)
```

**Python Example**:

```python
import json
recipients = json.dumps([
    {"name": "John", "email": "john@example.com", "signingOrder": 1}
])
form_data['recipients'] = recipients
```

**C# Example**:

```csharp
using System.Text.Json;
var recipients = JsonSerializer.Serialize(new[] {
    new { name = "John", email = "john@example.com", signingOrder = 1 }
});
formData.Add(new StringContent(recipients), "recipients");
```

## Error Handling & Troubleshooting

### Common HTTP Status Codes

| Status Code | Description           | Solution                                      |
| ----------- | --------------------- | --------------------------------------------- |
| `200`       | Success               | Request completed successfully                |
| `400`       | Bad Request           | Check request body format and required fields |
| `401`       | Unauthorized          | Verify API token and headers                  |
| `403`       | Forbidden             | Check organization ID and permissions         |
| `404`       | Not Found             | Verify endpoint URLs are correct              |
| `422`       | Unprocessable Entity  | Validate field values and constraints         |
| `429`       | Too Many Requests     | Implement rate limiting and retry logic       |
| `500`       | Internal Server Error | Contact support if persistent                 |

### Error Codes for Sending and Updating Recipients {#error-codes-for-sending-and-updating-recipients}

`POST /turbosign/single/prepare-for-review`, `POST /turbosign/single/prepare-for-signing` and `PATCH /turbosign/documents/{id}/update-with-recipients` return signature errors with a specific HTTP status and a machine-readable code. The body includes `error` (human-readable text) and `code` (the machine-readable value). It also repeats them as `message` and `type`, and may include a `data` object with details, so read the fields you need and ignore the rest:

```json
{
  "message": "This organization does not allow changing the identity-verification method. Omit otpChannel or set it to 'email'.",
  "type": "OtpOverrideNotAllowed",
  "error": "This organization does not allow changing the identity-verification method. Omit otpChannel or set it to 'email'.",
  "code": "OtpOverrideNotAllowed"
}
```

| Status | `code` | Meaning |
| ------ | ------ | ------- |
| `400` | Any `code` not listed below, for example `OtpPhoneRequired`, `OtpPhoneInvalid`, `IdentityConfigInvalid`, `DuplicateExternalId` | The request is invalid. Fix the input and retry. |
| `402` | `OtpNotEntitled`, `SmsOtpLimitExceeded` | The plan does not include this verification, or the SMS allowance is used up. |
| `403` | `ExternalIdvNotAllowed`, `IdentityOverrideNotAllowed`, `OtpOverrideNotAllowed`, `EmbeddedSigningNotEnabled`, `SmsOtpNotEnabled` | The organization has not turned on the feature or mode the request uses, or has locked the passcode method. |
| `404` | `DocumentNotFound`, `RecipientNotFound` | The document or recipient does not exist, was deleted, or belongs to another organization. On `update-with-recipients`, a document ID that does not exist returns `404` with only `{ "error": "Document not found" }`. |
| `409` | `DocumentNotSignable`, `RecipientAlreadySigned`, `RecipientNotInTurn`, `SmsProviderNotConfigured`, `RecipientRequiresSingleUseUrl` | The document or recipient is not in a state that allows the request, or the organization has no SMS provider saved. |
| `410` | `SigningUrlNotRedeemable` | A single-use signing URL was already used or has expired. |

None of these signature errors return a `5xx` status. A `500` from these endpoints means an unexpected server problem, not a problem with your request.

:::note Changed in v1.132.0
`DocumentNotFound` and `RecipientNotFound` returned `400` from these endpoints before v1.132.0. Since v1.132.0 they return `404`. If your integration checks for `400` to detect a missing document or recipient, check for `404` or read `code` instead.
:::

For what each identity and passcode code means and how to fix it, see [Embedded signing errors](https://docs.turbodocx.com/docs/TurboSign/embedded-signing/reference.md#errors).

### Common Issues

#### JSON String Formatting Errors

**Symptoms**: 400 Bad Request with message "Invalid JSON in recipients/fields"

**Solutions**:

- ✅ Verify JSON.stringify() or equivalent is used for recipients, fields, ccEmails
- ✅ Check JSON is valid using JSONLint or similar validator
- ✅ Ensure proper escaping of quotes in JSON strings
- ✅ Test with minimal example first (1 recipient, 1 field)

**Example Error Response**:

```json
{
  "error": "Invalid JSON string in recipients field",
  "code": "JSONParseError",
  "details": "Unexpected token at position 45"
}
```

**Debug Steps**:

1. Log the JSON string before sending
2. Validate JSON with online validator
3. Check for special characters or unescaped quotes
4. Test with hardcoded valid JSON first

#### File Source Errors

**Symptoms**: 400 Bad Request with message about file source

**Solutions**:

- ✅ Provide exactly ONE of: file, deliverableId, templateId, fileId, fileLink
- ✅ Verify UUIDs are valid format (8-4-4-4-12 characters)
- ✅ Check file upload isn't corrupted or empty
- ✅ Ensure fileLink is accessible (not behind auth)

**Example Error Response**:

```json
{
  "error": "Must provide exactly one file source",
  "code": "InvalidFileSource"
}
```

#### Recipients/Fields Mismatch

**Symptoms**: 400 Bad Request about missing recipient or email mismatch

**Solutions**:

- ✅ Verify recipientEmail in fields matches email in recipients array **exactly**
- ✅ Check for typos in email addresses
- ✅ Ensure all fields reference valid recipients
- ✅ Email matching is case-sensitive

**Example**:

```javascript
// Recipients array
[{ email: "john.smith@company.com", ... }]

// Fields array - must match exactly
[{ recipientEmail: "john.smith@company.com", ... }]  // ✅ Correct
[{ recipientEmail: "John.Smith@company.com", ... }]  // ❌ Wrong (case mismatch)
```

#### Optional Field Errors

**Symptoms**: 400 Bad Request with code `OptionalNotSupported`, `InvalidFieldRequired`, or
`NoEditableFieldsForRecipient`

| Code                           | Cause                                                                                   | Fix                                                                  |
| ------------------------------ | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `OptionalNotSupported`         | `required: false` on a `signature` or `initial` field                                    | Remove `required` from that field, or set it to `true`               |
| `InvalidFieldRequired`         | `required` is not a boolean or `null` (for example the string `"false"`)                 | Send a JSON boolean: `true` or `false`                               |
| `NoEditableFieldsForRecipient` | A recipient has only optional or read-only fields                                        | Give that recipient at least one required, editable field, such as a signature |

**Example Error Response**:

```json
{
  "error": "Field 1: SIGNATURE and INITIAL field types cannot be optional. Field type \"signature\" must be completed by the signer.",
  "code": "OptionalNotSupported"
}
```

See [Optional Fields](#optional-fields) for the full rules.

#### Authentication Failures

**Symptoms**: 401 Unauthorized responses

**Solutions**:

- ✅ Verify API token is correct and not expired
- ✅ Check that `x-rapiddocx-org-id` header matches your organization
- ✅ Ensure Bearer token format: `Bearer YOUR_TOKEN` (with space)
- ✅ Confirm token has necessary permissions

**Example Correct Headers**:

```http
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
x-rapiddocx-org-id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
```

#### Document Upload Failures

**Symptoms**: Upload returns error or times out

**Solutions**:

- ✅ Verify PDF file is not corrupted or password-protected
- ✅ Check file size is under maximum limit (typically 10MB)
- ✅ Ensure file is actually a PDF (check MIME type)
- ✅ Verify network connection and try again
- ✅ For fileLink, ensure URL is accessible

#### Field Positioning Problems

**Symptoms**: Signature fields appear in wrong locations or not at all

**Template-based Solutions**:

- ✅ Verify anchor text exists in the PDF document
- ✅ Check anchor text matches exactly (case-sensitive by default)
- ✅ Test with `caseSensitive: false` if having matching issues
- ✅ Try different placement options (replace, before, after)
- ✅ Use prepare-for-review to visually verify placement

**Coordinate-based Solutions**:

- ✅ Verify page dimensions match your PDF's actual size
- ✅ Check that x,y coordinates are within page boundaries
- ✅ Ensure coordinates account for any PDF margins or headers
- ✅ Test with different page numbers if multi-page document
- ✅ Validate that `x + width ≤ pageWidth` and `y + height ≤ pageHeight`

#### Webhook Integration Issues

**Symptoms**: Not receiving completion notifications

**Solutions**:

- ✅ Verify webhook URLs are accessible and return 200 OK
- ✅ Check webhook configuration in organization settings
- ✅ Review webhook delivery history for error details
- ✅ Test webhook endpoints with external tools (webhook.site, ngrok)
- ✅ Implement HMAC signature verification

### Debugging Tips

1. **Test with prepare-for-review first**: Visual confirmation before sending emails
2. **Use preview URLs**: Verify field placement and document appearance
3. **Check response documentId**: Save this for tracking and debugging
4. **Enable request logging**: Log all requests and responses with timestamps
5. **Test with minimal payloads**: Start simple (1 recipient, 1 field), add complexity incrementally
6. **Validate JSON before sending**: Use JSON validators to check format
7. **Use Postman/Insomnia**: Test manually before writing code
8. **Check API status page**: Verify TurboDocx services are operational
9. **Review error messages carefully**: Error responses include specific details
10. **Monitor rate limits**: Track API usage to avoid throttling

### Example Debug Request

```bash
# Test with curl to isolate issues
curl -X POST https://api.turbodocx.com/turbosign/single/prepare-for-review \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "x-rapiddocx-org-id: YOUR_ORG_ID" \
  -F "file=@document.pdf" \
  -F "documentName=Test Document" \
  -F "senderEmail=you@yourcompany.com" \
  -F 'recipients=[{"name":"Test User","email":"test@example.com","signingOrder":1}]' \
  -F 'fields=[{"recipientEmail":"test@example.com","type":"signature","page":1,"x":100,"y":200,"width":200,"height":80,"pageWidth":612,"pageHeight":792}]' \
  -v
```

## Next Steps

### Webhooks - The Next Logical Step

Now that you've integrated the single-step signing flow, the next step is setting up webhooks to receive real-time notifications when documents are signed. This eliminates the need for polling and provides instant updates about document status changes.

📖 **[Learn how to configure Webhooks →](https://docs.turbodocx.com/docs/TurboSign/Webhooks.md)**

### Related Documentation

- [TurboSign Setup Guide](https://docs.turbodocx.com/docs/TurboSign/Setting-up-TurboSign.md)
- [Webhook Configuration](https://docs.turbodocx.com/docs/TurboSign/Webhooks.md)
- [API Authentication](https://docs.turbodocx.com/docs/API/turbodocx-api-documentation.md)

## Support

Need help with your integration?

- **Discord Community**: [Join our Discord server](https://discord.gg/NYKwz4BcpX) for real-time support and discussions
- **Documentation**: [https://docs.turbodocx.com](https://docs.turbodocx.com)

---

Ready to get started? Follow the guide above to integrate TurboSign single-step API into your application and start collecting electronic signatures programmatically with a single API call!
