07 · TEMPLATE LINKS

Shareable and embeddable template links

Publish one URL that anyone can open, fill and sign. Each visit becomes its own document, and the link can be embedded in your app.

All documentation

Everything else in this documentation assumes you know who is signing. A template link inverts that: you publish one URL, and whoever opens it fills in their own details and signs. Each visit becomes its own document. It is the right shape for a vendor NDA on your supplier page, a waiver on a booking confirmation, or a consent form you hand out at scale.

One role is open, the rest are named
This is not an all-roles-open form. Exactly one signer role is left for the visitor; every other signer is named when you create the link, and they are emailed in order after the visitor signs. Trying to create a link with zero or two open roles returns 400 exactly_one_open_role_required.

Publish a link

Read the template first so you have its role ids - GET /v1/templates/{id} returns them. Then mark one open and assign the rest.

bash
curl https://api.documentesign.com/v1/templates/tpl_4c8a.../links \
  -H "Authorization: Bearer $ESIGN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Supplier NDA - website",
    "headline": "Sign our mutual NDA",
    "signers": [
      { "placeholder_id": "ph_1", "is_open": true },
      { "placeholder_id": "ph_2", "is_open": false,
        "name": "Dana Poole", "email": "dana@acme.com" }
    ],
    "max_uses": 500
  }'
201 Created
{
  "id": "lnk_2f8d...",
  "slug": "k4m9xa",
  "url": "https://app.documentesign.com/start/k4m9xa",
  "template_id": "tpl_4c8a...",
  "use_count": 0,
  "max_uses": 500,
  "enabled": true,
  "signers": [
    { "placeholder_id": "ph_1", "role": "Supplier", "is_open": true,
      "name": null, "email": null },
    { "placeholder_id": "ph_2", "role": "Counsel", "is_open": false,
      "name": "Dana Poole", "email": "dana@acme.com" }
  ],
  "created_at": "2026-09-01T10:00:00.000Z"
}

Hand out url and you are done. It keeps working until you disable it, it expires, or it runs out of uses.

Embedding one

The url above is a full page, and it is deliberately not frameable - loading it in an iframe is refused by the browser. To embed the link in your own site, mint a signed URL for it:

bash
curl https://api.documentesign.com/v1/template-links/lnk_2f8d.../embed-link \
  -H "Authorization: Bearer $ESIGN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "expires_in": 900 }'
200 OK
{
  "url": "https://app.documentesign.com/start/embed/eyJhbGciOi...",
  "expires_at": "2026-09-01T10:15:00.000Z"
}

Drop that into an iframe. The visitor fills the open role and signs without ever leaving your page - the signing step stays in the frame too, so there is no jarring hand-off at the end.

javascript
<iframe
  src={embedUrl}
  title="Sign the agreement"
  style={{ width: "100%", height: 780, border: 0 }}
/>
Two things to set up first
Your origin must be on the workspace embed allowlist (Settings, Developer, Embedding) or minting returns 409 embedding_not_configured. And the envelope is short-lived by design, so mint it when you render the page rather than storing it. Rotating the link's slug does not invalidate an outstanding embed - the envelope carries the link id, not the slug.

Controlling a link

  • expires_at and max_usesboth stop a link on their own. A visitor arriving after either gets a plain "no longer available" page rather than an error.
  • Rotate (POST /template-links/{id}/rotate) issues a new slug and kills the old one. Use it when a link leaks.
  • Delete removes the link permanently. Documents already created from it are unaffected.

Tracking what comes through

Every visit produces an ordinary document, so nothing new is needed to follow it. Subscribe to webhooks and you will get document.sent when a visitor starts, then document.completed when everyone has signed. Read use_count on the link for a running total.

Sandbox works the same way
A link created with a sk_test_ key produces sandbox documents: watermarked, nobody emailed, nothing counted. Build the embed against a test link before you publish a real one.

What is not supported

  • No prefill on the link. Neither you nor the visitor can supply field values through a template link today. If you need values filled in advance, you know who the signer is, so use a template send with field_values instead.
  • reCAPTCHA always applies to the visitor form, embedded or not. An embedded link is no less public than a shared one.