# Docuseal API Structure for Creating and Managing Templates and Submissions: Complete REST Reference

> Explore the Docuseal API structure for managing templates and submissions. Access complete REST reference and CRUD operations secured with Bearer tokens or session cookies.

- Repository: [DocuSeal/docuseal](https://github.com/docusealco/docuseal)
- Tags: api-reference
- Published: 2026-05-05

---

**Docuseal exposes a JSON-only REST API under the `/api` namespace that supports full CRUD operations for templates and submissions, secured via Bearer tokens or session cookies with CanCanCan authorization.**

Docuseal is an open-source document signing platform hosted at `docusealco/docuseal`. The **API structure for creating and managing templates and submissions** follows RESTful conventions, exposing two primary resources—`/api/templates` and `/api/submissions`—that accept JSON payloads and return serialized responses via dedicated service objects.

## Templates API Endpoints

The templates resource supports document template management, including schema definition, field configuration, and lifecycle operations.

### List and Retrieve Operations

- **List templates**: `GET /api/templates` supports pagination, search via the `q` parameter, and filtering by `archived`, `folder`, `slug`, and `external_id`. Implementation located at [`TemplatesController#index`](https://github.com/docusealco/docuseal/blob/master/app/controllers/api/templates_controller.rb#L7-L42).
- **Retrieve single template**: `GET /api/templates/:id` returns the template serialized by `Templates::SerializeForApi.call`. See [`TemplatesController#show`](https://github.com/docusealco/docuseal/blob/master/app/controllers/api/templates_controller.rb#L44-L46).

### Update and Delete Operations

- **Update template**: `PATCH /api/templates/:id` accepts name, fields, submitters, folder assignment, and archived status. Strong parameters are defined in [`TemplatesController#update`](https://github.com/docusealco/docuseal/blob/master/app/controllers/api/templates_controller.rb#L48-L74).
- **Delete template**: `DELETE /api/templates/:id` performs a soft-delete (archive) by default. Append `?permanently=true` to purge the record completely. See [`TemplatesController#destroy`](https://github.com/docusealco/docuseal/blob/master/app/controllers/api/templates_controller.rb#L77-L87).

### Clone and Nested Submissions

- **Clone template**: `POST /api/templates/:id/clone` creates a duplicate schema via [`TemplatesCloneController#create`](https://github.com/docusealco/docuseal/blob/master/app/controllers/api/templates_clone_controller.rb).
- **Template submissions**: `GET /api/templates/:id/submissions` lists all submissions for a specific template, while `POST /api/templates/:id/submissions` creates a new submission scoped to that template. These routes delegate to [`SubmissionsController`](https://github.com/docusealco/docuseal/blob/master/config/routes.rb#L39-L42).

### Template Request Payload

Templates are accepted as a **strong-parameter hash** defined in `template_params`:

```ruby

# app/controllers/api/templates_controller.rb (excerpt)

permitted_params = [
  :name,
  :external_id,
  :shared_link,
  {
    submitters: [%i[
      name uuid is_requester invite_by_uuid invite_via_field_uuid
      optional_invite_by_uuid linked_to_uuid email order
    ]],
    fields: [[:uuid, :submitter_uuid, :name, :type,
              :required, :readonly, :default_value,
              :title, :description, :prefillable,
              { preferences: {}, default_value: [], conditions: [%i[field_uuid value action operation]],
                options: [%i[value uuid]],
                validation: %i[message pattern min max step],
                areas: [%i[uuid x y w h cell_w attachment_uuid option_uuid page]] }]]
  }
]

```

Typical JSON body for template creation:

```json
{
  "template": {
    "name": "Employment Contract",
    "external_id": "emp-contract-v1",
    "fields": [
      {
        "uuid": "f1",
        "name": "Employee Name",
        "type": "text",
        "required": true
      },
      {
        "uuid": "f2",
        "name": "Start Date",
        "type": "date",
        "required": true
      }
    ],
    "submitters": [
      { "name": "Employee", "uuid": "s1", "email": "employee@example.com" },
      { "name": "HR Manager", "uuid": "s2", "email": "hr@example.com" }
    ]
  }
}

```

### Template Response Structure

`Templates::SerializeForApi.call` (located in [[`lib/templates/serialize_for_api.rb`](https://github.com/docusealco/docuseal/blob/main/lib/templates/serialize_for_api.rb)](https://github.com/docusealco/docuseal/blob/master/lib/templates/serialize_for_api.rb)) returns:

- `id`, `name`, `slug`, `fields`, `submitters`, `schema`, `author`
- `documents` array containing `id`, `uuid`, `url`, `preview_image_url`, `filename`
- `folder_name`, `application_key`, `expires_at`

## Submissions API Endpoints

The submissions resource handles document signing workflows, submitter invitations, and completion tracking.

### List and Retrieve Operations

- **List submissions**: `GET /api/submissions` supports filtering by `template_id`, `slug`, `archived`, and `template_folder`. Implementation at [`SubmissionsController#index`](https://github.com/docusealco/docuseal/blob/master/app/controllers/api/submissions_controller.rb#L12-L23).
- **Retrieve submission**: `GET /api/submissions/:id` returns submitters, documents, audit trail, and status via [`SubmissionsController#show`](https://github.com/docusealco/docuseal/blob/master/app/controllers/api/submissions_controller.rb#L36-L50).

### Create and Destroy Operations

- **Create submission**: `POST /api/submissions` is the primary endpoint for initiating signing workflows. Parameters are validated by `Params::SubmissionCreateValidator` and normalized via `Submissions::NormalizeParamUtils`. See [`SubmissionsController#create`](https://github.com/docusealco/docuseal/blob/master/app/controllers/api/submissions_controller.rb#L52-L84).
- **Delete submission**: `DELETE /api/submissions/:id` archives the record; use `?permanently=true` for hard deletion. See [`SubmissionsController#destroy`](https://github.com/docusealco/docuseal/blob/master/app/controllers/api/submissions_controller.rb#L90-L99).

### Collection Routes for Bulk Operations

The API provides specialized collection endpoints defined in [[`config/routes.rb`](https://github.com/docusealco/docuseal/blob/main/config/routes.rb)](https://github.com/docusealco/docuseal/blob/master/config/routes.rb):

- **`POST /api/submissions/init`**: Creates a submission without initial file uploads, useful for headless integrations.
- **`POST /api/submissions/emails`**: Bulk creates submissions from a list of email addresses.

Both routes utilize the same `create` action logic but handle different payload structures.

### Submission Request Payload

The controller permits the following nested attributes via `submissions_params`:

```ruby

# app/controllers/api/submissions_controller.rb (excerpt)

permitted_attrs = [
  :send_email, :send_sms, :bcc_completed, :completed_redirect_url, :reply_to, :go_to_last,
  :require_phone_2fa, :require_email_2fa, :expire_at, :name,
  {
    variables: {},
    message: %i[subject body],
    submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email,
                  :role, :completed, :phone, :application_key, :external_id,
                  :reply_to, :go_to_last, :require_phone_2fa, :require_email_2fa,
                  :order, :index, :invite_by,
                  { metadata: {}, values: {}, roles: [], readonly_fields: [], message: %i[subject body],
                    fields: [:name, :uuid, :default_value, :value, :title, :description,
                             :readonly, :required, :validation_pattern, :invalid_message,
                             { default_value: [], value: [], preferences: {}, validation: {} }] }]]
  }
]

```

Example payload creating a submission from template ID `42`:

```json
{
  "template_id": 42,
  "send_email": true,
  "expire_at": "2026-06-01T00:00:00Z",
  "submitters": [
    {
      "email": "employee@example.com",
      "name": "John Doe",
      "role": "Employee",
      "fields": [
        { "uuid": "f1", "value": "John Doe" },
        { "uuid": "f2", "value": "2023-05-01" }
      ]
    },
    {
      "email": "hr@example.com",
      "name": "HR Manager",
      "role": "Manager"
    }
  ],
  "variables": { "company": "Acme Corp" },
  "message": {
    "subject": "Please sign the employment contract",
    "body": "Hello, please review and sign the attached document."
  }
}

```

### Submission Response Structure

`Submissions::SerializeForApi.call` (located in [[`lib/submissions/serialize_for_api.rb`](https://github.com/docusealco/docuseal/blob/main/lib/submissions/serialize_for_api.rb)](https://github.com/docusealco/docuseal/blob/master/lib/submissions/serialize_for_api.rb)) returns:

- `id`, `name`, `slug`, `expire_at`, `created_at`, `updated_at`, `archived_at`
- `template` (including `id`, `name`, `external_id`, `folder_name`)
- `submitters` array with `id`, `email`, `documents`, and `status`
- `variables`, `documents` (populated after completion), `audit_log_url`, `combined_document_url`
- `status` enum: `pending`, `completed`, `declined`, or `expired`

## Authentication and Security

Both API resources are protected by **CanCanCan** authorization via `load_and_authorize_resource`. Requests require either:

- A valid **Bearer token** in the `Authorization` header
- An authenticated session cookie from a Docuseal user

Unauthorized requests return standard HTTP 401/403 responses.

## Practical Code Examples

### Create a Template

```bash
curl -X POST "https://your-docuseal-instance.com/api/templates" \
     -H "Authorization: Bearer <ACCESS_TOKEN>" \
     -H "Content-Type: application/json" \
     -d '{
        "template": {
          "name": "NDA",
          "external_id": "nda-2024",
          "fields": [
            {"uuid":"f1","name":"Company Name","type":"text","required":true},
            {"uuid":"f2","name":"Effective Date","type":"date","required":true}
          ],
          "submitters": [
            {"name":"Counterparty","uuid":"s1","email":"partner@example.com"},
            {"name":"Signer","uuid":"s2","email":"signer@example.com"}
          ]
        }
      }'

```

Response includes the template `id`, `slug`, and empty `documents` array (populated after PDF upload).

### Create a Submission

```bash
curl -X POST "https://your-docuseal-instance.com/api/submissions" \
     -H "Authorization: Bearer <TOKEN>" \
     -H "Content-Type: application/json" \
     -d '{
        "template_id": 123,
        "expire_at": "2026-06-30T00:00:00Z",
        "submitters": [
          {
            "email":"john.doe@example.com",
            "name":"John Doe",
            "role":"Employee",
            "fields":[
              {"uuid":"f1","value":"Acme Corp"},
              {"uuid":"f2","value":"2023-05-01"}
            ]
          },
          {
            "email":"hr@example.com",
            "name":"HR Manager",
            "role":"Manager"
          }
        ],
        "message": {"subject":"Please sign", "body":"Kindly sign the attached NDA."}
      }'

```

Success returns submitter signing URLs:

```json
{
  "submitters": [
    {
      "id": 321,
      "url": "https://your-docuseal-instance.com/s/321?token=abc123"
    },
    {
      "id": 322,
      "url": "https://your-docuseal-instance.com/s/322?token=def456"
    }
  ],
  "expire_at": "2026-06-30T00:00:00Z",
  "created_at": "2026-05-05T12:34:56Z"
}

```

### Retrieve Completed Submission

```bash
curl -X GET "https://your-docuseal-instance.com/api/submissions/789" \
     -H "Authorization: Bearer <TOKEN>"

```

Returns `status: "completed"`, `documents` array with download URLs, `audit_log_url`, and `combined_document_url`.

## Summary

- The **Docuseal API structure** consists of two primary REST resources: `/api/templates` and `/api/submissions`, both requiring Bearer token or session authentication.
- **Templates** support CRUD operations plus cloning (`POST /api/templates/:id/clone`) and nested submission listing, with payloads validated via `template_params` and serialized via `Templates::CreateOrUpdate` and `SerializeForApi`.
- **Submissions** support creation with complex submitter configurations, bulk email endpoints (`/api/submissions/emails`), and headless initialization (`/api/submissions/init`), using `Submissions::NormalizeParamUtils` for payload processing.
- Both resources implement **soft-delete by default** (use `?permanently=true` for hard deletion) and return JSON structured by dedicated serialization classes in [`lib/templates/serialize_for_api.rb`](https://github.com/docusealco/docuseal/blob/main/lib/templates/serialize_for_api.rb) and [`lib/submissions/serialize_for_api.rb`](https://github.com/docusealco/docuseal/blob/main/lib/submissions/serialize_for_api.rb).

## Frequently Asked Questions

### What authentication method does the Docuseal API use?

The API uses **Bearer token authentication** via the `Authorization` header or session cookies from authenticated Docuseal users. All endpoints invoke `load_and_authorize_resource` from CanCanCan to enforce resource-level permissions.

### How do I permanently delete a template or submission via the API?

By default, `DELETE /api/templates/:id` and `DELETE /api/submissions/:id` perform soft-deletion (archiving). Append `?permanently=true` to the query string to trigger hard deletion, which removes the record from the database entirely.

### What is the difference between POST /api/submissions and POST /api/submissions/init?

`POST /api/submissions` accepts full template and submitter data including initial file uploads. `POST /api/submissions/init` is a collection route shortcut that creates a submission without requiring file attachments, designed for headless integrations where documents are uploaded later.

### How are webhook events triggered in the Docuseal API?

Webhook events (such as `template.created`, `template.updated`, `submission.created`) are enqueued via `WebhookUrls.enqueue_events` calls within the controller actions. These background jobs dispatch payloads to configured webhook URLs asynchronously after database transactions commit.