> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vumasign.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Templates and one-off envelopes

> A document you send again is a template. A document that exists once is a one-off envelope.

export const valueNotLockableCode = "value_not_lockable";

export const templateHasNoFieldsCode = "template_has_no_fields";

export const routingCc = "cc";

export const recipientRoleUnknownCode = "recipient_role_unknown";

export const recipientRoleMissingCode = "recipient_role_missing";

export const recipientRoleDuplicatedCode = "recipient_role_duplicated";

export const idempotencyKeyReusedCode = "idempotency_key_reused";

export const forbiddenCode = "forbidden";

There are two ways to get a document in front of the people who sign it, and the question that
chooses between them is whether you will send **this document** again.

* **A template** is a library entry: a kind of document — an employment contract, a membership
  form — whose fields are placed once and which you send many times.
  [`POST /api/v1/templates`](/api-reference/create-template) creates it, and every envelope after
  that is [`POST /api/v1/envelopes`](/api-reference/create-envelope) with its `template_id` and the
  people this copy is for — or [`POST /api/v1/envelopes/batches`](/api-reference/create-envelope-batch)
  for many copies at once.
* **A one-off envelope** is a document that exists once, for one set of people — a Letter of
  Authority, an offer of employment generated for one candidate.
  [`POST /api/v1/envelopes/one-off`](/api-reference/create-one-off-envelope) takes the document
  and the people in a single request and creates **no template**: the envelope's `template_id`
  comes back `null`.

⚠️ **Do not register a template per send.** A template stays in your library and in
`GET /api/v1/templates` until somebody archives it in the dashboard, and this API has no operation
that deletes one. An integration that creates a template for every offer letter leaves
one behind for every offer letter. If the document is generated fresh each time, it is a one-off.

## Choosing

| | From a template | One-off |
| - | - | - |
| You send each time | a `template_id` and the people | the documents and the people |
| Fields come from | [text tags](/concepts/text-tags), or [detection](/concepts/field-detection) when the document has none — and the editor afterwards | text tags only; nothing is detected |
| Roles | declared once, in the template's `roles` | each recipient's `role` is its slot; there is no separate list |
| Sign, approve or copy | per role, in the template's `routing_types` | per recipient, in its `routing_type` |
| Documents | one per template created through the API; a template made on the Templates screen may hold several | several in one request |
| Prefilled and locked answers | `values`, which may be `locked` on the field types that take a lock | none from you: there is no `values` array, and no answer can be locked |
| Subjects — the people a document is about | `subjects` on the template, and `subject(...)` in a tag | none: a `subject(...)` tag is refused |
| The envelope's name | the template's name | your `title`, which is required |
| Brand | `brand_id`, or the organisation's default | always the organisation's default: there is no `brand_id` |
| Who it is sent by | the member who issued the API key | `sender` may name another member of your organisation |
| Many at once | [`POST /api/v1/envelopes/batches`](/api-reference/create-envelope-batch) | one envelope per request |
| Your plan | creating a template needs a plan that includes templates, and is refused <code>{forbiddenCode}</code> otherwise | no plan check beyond the API itself |
| What is left behind | the template, until it is archived | the envelope and nothing else |

Choose **a template** when the document is the same every time and only the people and a few
answers change: it is placed once, reviewed once in the editor, and each envelope is a small
request that names the people and what to prefill. Choose **a one-off** when your own system
generates the document — conditional clauses, a page count that varies — so there is nothing
stable to reuse, and the document can carry its own fields as text tags.

## The limits

| Bound | Limit | Where |
| - | - | - |
| Documents on one one-off envelope | 10 | `POST /api/v1/envelopes/one-off` |
| Document bytes in one request, every document in it together | about 6 MB | `POST /api/v1/templates`, `POST /api/v1/envelopes/one-off` |
| Roles on a template | 6 | `POST /api/v1/templates` |
| Recipients on one envelope | 100 | all three ways to create an envelope |
| Prefilled values on one envelope | 500 | `POST /api/v1/envelopes`, each batch row |
| Rows in one batch | 100 | `POST /api/v1/envelopes/batches` |
| Recipients across one batch | 1000 | `POST /api/v1/envelopes/batches` |

The document bound is on the **whole request**, not on each document: a document travels
base64-encoded inside the JSON body, and every document in a one-off request shares one body.
Documents larger than that go through the upload screen instead, which sends the file itself.

## From a template

Create the template once. `file` is the whole document, base64-encoded — the value below is a
truncated stub, not a document — and `roles` names who signs it, in order.

```json POST /api/v1/templates theme={null}
{
  "file": "JVBERi0xLjcK",
  "name": "Employment contract",
  "roles": ["Employee", "Employer"],
  "routing_types": ["sign", "sign"]
}
```

The answer carries the template's `id` and the fields that were placed, each with its own `id`
and, where the document named one, the `data_key` a prefilled value is addressed to. Then each envelope names the people by the template's role
names, and may prefill what your own records already know, and lock some of it:

```json POST /api/v1/envelopes theme={null}
{
  "template_id": "3f0c1a52-8d4e-4b7a-9f61-2c5e7d9a0b13",
  "recipients": [
    { "role": "Employee", "name": "Thandi Mokoena", "email": "thandi@example.test" },
    { "role": "Employer", "name": "Sipho Nkosi", "email": "sipho@example.test" }
  ],
  "values": [
    { "subject": null, "key": "employee_id", "value": "E-1042", "locked": true }
  ],
  "send": true
}
```

The envelope uses the template's documents as they are: nothing is uploaded again. `send`
defaults to `false`, which creates a draft and emails nobody — send it later with
[`POST /api/v1/envelopes/{envelopeId}/send`](/api-reference/send-envelope). Every role that has a
field needs a recipient who is not a copy, or the request is refused <code>{recipientRoleMissingCode}</code>;
a role the template declares but places no field for needs nobody. A name the template does
not declare is refused <code>{recipientRoleUnknownCode}</code>; [Roles and recipients](/concepts/roles-and-recipients) has the rules.

Not every field takes a value, and not every field that takes one can be locked. A job title or
a company belongs to whoever fills the role, not to the box, so `locked` on one is refused
<code>{valueNotLockableCode}</code>: send the value without `locked`, or set `job_title` and
`company` on the recipient, which is where those fields are filled from. What a value does to
each field `type`, in a batch row too:

| Field `type` | A value in `values` | With `locked: true` |
| - | - | - |
| `text`, `date`, `checkbox`, `dropdown` | prefilled | prefilled and locked |
| `signer_title`, `signer_company` | prefilled | refused `value_not_lockable` |
| `signature`, `initial`, `attachment`, `date_signed`, `signer_name`, `signer_email` | refused `value_not_writable` | refused `value_not_writable` |

### Many envelopes from one template

A batch is the bulk path, and it exists only from a template. Each row is one envelope — its
recipients and its values — and `send`, `expires_at`, `brand_id` and `completion_delivery` apply
to every row.

```json POST /api/v1/envelopes/batches theme={null}
{
  "template_id": "3f0c1a52-8d4e-4b7a-9f61-2c5e7d9a0b13",
  "rows": [
    {
      "recipients": [
        { "role": "Employee", "name": "Thandi Mokoena", "email": "thandi@example.test" },
        { "role": "Employer", "name": "Sipho Nkosi", "email": "sipho@example.test" }
      ],
      "values": [{ "subject": null, "key": "employee_id", "value": "E-1042" }]
    },
    {
      "recipients": [
        { "role": "Employee", "name": "Lwazi Dube", "email": "lwazi@example.test" },
        { "role": "Employer", "name": "Sipho Nkosi", "email": "sipho@example.test" }
      ],
      "values": [{ "subject": null, "key": "employee_id", "value": "E-1043" }]
    }
  ],
  "send": true
}
```

* A mistake in any row's own data refuses **the whole batch**, and nothing is created: a mapping
  error in one row is usually in all of them.
* Past that, sending is per row. The status line says whether every row went out; when one did
  not — most often because the plan's send allowance ran out partway — its draft is kept and
  named in the answer, and the remedy is to send those drafts rather than to rebuild the batch.
* A batch counts one unit per row against the [rate limit](/rate-limits), not one per request.
* There is no one-off batch. For many generated documents, make one one-off request each.

## One-off

The document and its people, in one request. Each recipient's `role` is the slot a text tag
names, so `<<sig:Employee>>` in the document places a signature for the recipient whose `role` is
`Employee`.

```json POST /api/v1/envelopes/one-off theme={null}
{
  "title": "Offer of employment — Thandi Mokoena",
  "documents": [{ "content": "JVBERi0xLjcK" }],
  "recipients": [
    { "role": "Employee", "name": "Thandi Mokoena", "email": "thandi@example.test" },
    { "role": "HR", "name": "Sipho Nkosi", "email": "sipho@example.test", "routing_type": "cc" }
  ],
  "send": true
}
```

`documents` is a list, in the order a signer meets them. They are stacked into one scroll on the
signing screen, and each is sealed on its own with its own certificate of completion. As on the
template path, a Word `.docx` is accepted and converted to PDF on our side, and its tags survive
the render.

### What a one-off refuses

* ⚠️ **An envelope with no tags in any of its documents is refused**, <code>{templateHasNoFieldsCode}</code>,
  and nothing is created. A one-off has no detection and no editor behind it — this API has no
  operation that places a field later — so an envelope with no field could never become
  signable. The count is over the whole request, not per document: an annexure with no tags is
  accepted as long as another document in the same request places a field.
* ⚠️ **Roles are declared inline, and only there.** A tag naming a role no recipient holds is
  refused with the tag, its page and the roles the request does declare; so is a
  `subject(...)` tag, since a one-off declares no subjects. `signerN` counts the entries in
  `recipients`, copies included.
* **One signing role, one person.** Two recipients on the same role that is not a copy are
  refused <code>{recipientRoleDuplicatedCode}</code>: both would answer every box on that role. Give each person
  a role of their own and tag each role where that person signs.
* **Every role with a field needs somebody who can fill it.** A tag for a role whose only
  recipient is a <code>{routingCc}</code> is refused <code>{recipientRoleMissingCode}</code>, because a copy is never asked to sign.
* There is no `values` array, so you prefill nothing and lock nothing.

That is not the same as the signer typing every box. On either path, some tags arrive
already filled, and the first row is never asked at all. A recipient's `job_title` and `company`
are what seed the second row.

| Tags | Filled by |
| - | - |
| `datesigned`, `name`, `email` | Vumasign, from the recipient and the moment they sign; nobody types them |
| `title`, `company` | the recipient's own details, when the request gives them: the signer starts from that value |

Every refusal [`POST /api/v1/envelopes/one-off`](/api-reference/create-one-off-envelope) declares,
with what each one means, as the reference publishes them:

| Code | HTTP | Retry? | What it means |
| - | - | - | - |
| `invalid_request` | 400 | no | The request is malformed, or breaks a rule about its own shape: a body that is not JSON or does not match the operation’s schema (a missing or mistyped property, a value out of range), a document that is not a PDF or a Word file we can read, a text tag that cannot be placed, an `Idempotency-Key` that is present but not a valid key, a query parameter outside its range, or a cursor that names nothing. The `message` says which, and for a body names the property path (`recipients[0].email`) — there is no separate `param` field. Fix the request; retrying it unchanged will fail identically. |
| `idempotency_key_required` | 400 | no | Send `Idempotency-Key: <uuid>` — this operation takes one and the request carried none. A distinct code from `invalid_request` because the remedy is to ADD a header, and an integrator reading it in a log should not have to work that out. (A key that is present but malformed is `invalid_request`, and its `message` names the header.) |
| `unauthenticated` | 401 | no | The credential was absent, unreadable, unknown, wrong or revoked — this answer is deliberately identical for all of them, so it cannot be used to probe which keys exist. Retrying the same request changes nothing. Check the key in Settings or issue a new one. |
| `send_allowance_exhausted` | 402 | no | ⚠️ 402, THE ONLY STATUS IN THIS API THAT NAMES MONEY. Not 403 (the credential is permitted; the plan is not) and not 429 (waiting does not help until the month turns). Upgrade the plan, or wait for the period to roll over. ⚠️ RETRYING IS POINTLESS BUT NOT HARMFUL — nothing was created, so there is no partial envelope to clean up. Only the Free plan (5 documents a month) is ever blocked, once that many documents have been sent in the month; every other plan is counted and never stopped, and a `vsk_test_` key never meets this at all because a sandbox envelope consumes no allowance to exhaust. |
| `forbidden` | 403 | no | The key is valid and the organisation’s plan does not grant what this operation does. Nothing about the credential needs to change, and it is deliberately MORE specific than 401 because the caller has already proved they hold the key. TWO SITUATIONS PRODUCE IT. (1) THE PLAN EXCLUDES THE OPERATION: `POST /api/v1/templates` on the Free plan, which does not include authoring templates. A template that already exists stays readable at `GET /api/v1/templates`; authoring one needs a plan that includes templates. (2) WE CANNOT ESTABLISH WHAT THE PLAN INCLUDES, on any operation, so nothing is granted. That one is ours rather than yours, an ordinary account does not meet it, and it means ask us. |
| `insufficient_scope` | 403 | no | This key is scoped and does not hold the scope this operation requires — `x-required-scope` on the operation names it, and the `WWW-Authenticate` header on the refusal repeats it (RFC 6750 §3.1). ⚠️ Retrying cannot help and neither can editing the key: scopes are fixed when a key is minted and no endpoint or screen can widen them. Issue a new key with the access it needs. A key created before scopes existed carries an empty scope list, which means EVERY capability, so this refusal cannot reach an integration that was already working. |
| `api_key_owner_removed` | 403 | no | The person who created this key has left the organisation, and everything the key creates or sends — an envelope, a batch, a template — is attributed to that person, who must still be a member. The key is not revoked and its reads still work, and so does managing webhooks; mint a new key from a current member and use that. |
| `test_key_cannot_send` | 403 | no | A `vsk_test_` key tried a send it may not make. TWO SITUATIONS PRODUCE IT. (1) `POST /api/v1/envelopes/{envelopeId}/send` on an envelope a LIVE key created: sending it would email its real recipients and spend real allowance, so send it with a live key. (2) Far more often, a recipient who would be EMAILED and whose address is not one this organisation may send rehearsals to. THE RULE IS ABOUT WHO, NOT ABOUT WHETHER: a test key may email anybody who is a MEMBER of the sending organisation, and any other address that has CONFIRMED a verification link sent to it (Settings → Test recipients). Nobody else at all. So a mixed envelope — one `"embedded": true` signer in your application and one emailed counterparty — is fully rehearsable, provided that counterparty is on the list. An `"embedded": true` recipient is never checked against it, because an embedded recipient is issued no invitation and is emailed by no code path at all. ⚠️ WHY THE LIMIT EXISTS: a sandbox send consumes no billing allowance, so an unbounded one would be an unmetered way to email strangers from a free account, over a sending domain every customer shares. The message names each address that was refused. Your options are: add and verify the address; make the recipient `"embedded": true`; create the draft with `"send": false`; or send with a live key, which may email anybody. What a test key still never does is consume billing allowance or produce a sealed artefact anybody can be held to — its documents are watermarked on every page and sealed by a certificate issued for the sandbox and for nothing else, so a PDF reader reports the signature as intact and its issuer as untrusted, and everyone who receives one is told so before they can open it. |
| `idempotency_key_reused` | 409 | no | This key was already used for a DIFFERENT request. ⚠️ The request itself may be perfectly valid — what conflicts is the key against state we already hold, which is what 409 means and why it is not a 400. Mint a new key. Do not retry with this one; it will conflict forever. |
| `request_in_progress` | 409 | Exponential from 1s, up to about 30s in total. The first request is still running. | A request carrying this key has not finished. We deliberately do not block waiting for it — that would hold a connection across a send that calls an email provider N times. Retry the identical request; when the first one lands you will get its result, replayed. |
| `brand_has_no_embed_origins` | 422 | no | The brand has no embedding origins registered, so a signing URL for it could not be framed by anything — and an embedded recipient is never emailed, so they would be unreachable. Register the domains you embed on against that brand in Settings; they must be bare origins (`https://example.com`, no trailing slash and no path). ⚠️ THE BRAND IS THE ONE THE ENVELOPE GOES OUT UNDER — the `brand_id` you sent, or the organisation’s default when you sent none. It is NOT always the default: registering an origin against the default while sending under another brand is the mistake this refusal most often means. |
| `recipient_role_missing` | 422 | no | A role that has fields on it was given nobody to fill them. Every role the template declares with work to do needs a recipient. |
| `recipient_role_duplicated` | 422 | no | Two recipients were given one role that the template does not route as `cc`. Everyone on a role is served the same fields and every one of them must sign, so both would answer the same boxes and the completed envelope could never be sealed. Give each person a role of their own; several `cc` readers may share a role. Answered by the create endpoints, naming each `recipients[i].role` that holds the role, and by the send endpoint for a draft saved before this was refused. |
| `cannot_be_drawn` | 422 | no | A value, a name or a title contains something the sealer cannot draw into the PDF. Asked BEFORE the send rather than discovered after everybody has signed. The message names which string. |
| `template_has_no_fields` | 422 | no | There are no fields, so the envelope would ask nobody to do anything. From a template — creating an envelope, a batch, or sending a draft made from one — place at least one field on the template in the editor; a template made from a document with text tags arrives with them already placed. On `POST /api/v1/envelopes/one-off`, which has no template and no editor, fields come only from text tags in the documents you send, and this is refused only when none of them places a field — an untagged annexure beside a tagged document is fine. Add a tag such as `<<sig:Employee>>`, naming a recipient’s `role`, to any of them and send it again. Nothing is detected there, and the body has no property for placing a field. A batch refuses every row for this, because every row shares the template. |
| `rate_limited` | 429 | Exponential from 1s, doubling, with jitter. Honour `Retry-After` when present. | This key has spent its minute. The limit is per KEY and per minute, and it follows your PLAN rather than the kind of key — 600 requests a minute on every paid plan, 60 on the free plan, and the sandbox gets the same number as production so that an integration which passes in rehearsal passes live. Every successful authenticated response, and this one, carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds), so a client can slow down before it is made to. TWO OPERATIONS ALSO ANSWER IT FOR A REASON OF THEIR OWN. `POST /api/v1/envelopes/batches` costs one request per row and is refused WHOLE when its rows do not fit what is left of the key’s minute — `RateLimit-Remaining` says how many would. `GET /api/v1/envelopes/{envelopeId}/documents/{position}/draft` has its own per-envelope and per-organisation ceilings on renders each minute, and the `message` says which one refused; there `Retry-After` is that ceiling’s, while the `RateLimit-*` headers still describe the key. `Retry-After` on this refusal is at most 60 and is read from the stored window rather than computed, so two of our instances refusing the same key quote the same instant. ⚠️ A REFUSED REQUEST STILL COUNTS: hammering a limit you have already exceeded pushes the counter higher rather than holding it. Honour `Retry-After`. If one key genuinely needs more throughput, issue a second one in Settings — the ceiling is a fairness and blast-radius control, not a commercial meter. |
| `internal_error` | 500 | Exponential from 1s, at most three attempts, then stop and alert a human. | Ours, not yours. ⚠️ RETRY WITH THE SAME `Idempotency-Key` YOU SENT THE FIRST TIME, on any operation that takes one — that is what makes a retry safe here, and sending a NEW key would create a second envelope or send the same one twice. The safe GETs in this document can be retried freely. (This entry used to say every operation here was a GET; the write endpoints have since shipped, and the reasoning moved with them.) |
| `service_unavailable` | 503 | Exponential from 5s, doubling, for a few minutes. Nothing was created. | Ours, and briefly. Something this operation depends on was not answering. Two services can produce it and which one it was depends on the operation: on a create, the service that renders a Word document to a PDF, so it is reachable only by sending a `.docx` and never by sending one that is already a PDF; on `GET .../documents/{position}/draft`, the service that renders the draft itself. ⚠️ THE DISTINCTION FROM 400 IS THE WHOLE POINT: your request is fine and your document is fine, so do not go and re-save it. On a create, nothing was created and no allowance was spent. On an operation that takes an `Idempotency-Key`, retry the identical request with the SAME one — a completed key replays, so a retry can never make a second envelope or a second template even if the first attempt got further than this answer suggests. A new key could. ⚠️ ON THE DRAFT READ THE ATTEMPT WAS ALREADY PAID FOR: the render is charged to the envelope’s and the organisation’s per-minute draft ceilings before the renderer is asked, and a render that then fails is not refunded. There is no key to reuse, because there is nothing to replay — so retry on the backoff above rather than at once, or an outage of ours becomes a `rate_limited` of yours. |

## Retrying safely

Every request on this page requires an `Idempotency-Key` — one key per request you intend, reused
only to retry that same request. A retry with the same key and the same body returns the first
answer and creates nothing; a batch replay returns the same batch, failed rows and all. A different
body under a used key is refused <code>{idempotencyKeyReusedCode}</code>. Reordering `recipients` is a different
body, because that order is the routing order. How long a key is remembered, and what to do past
it, is in [Retrying a request safely](/errors#retrying-a-request-safely).
