curl --request POST \
--url https://app.vumasign.com/api/v1/templates/{templateId}/preview \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{}'import requests
url = "https://app.vumasign.com/api/v1/templates/{templateId}/preview"
payload = {}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({})
};
fetch('https://app.vumasign.com/api/v1/templates/{templateId}/preview', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.vumasign.com/api/v1/templates/{templateId}/preview"
payload := strings.NewReader("{}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}"<string>"{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}One of a template’s documents, filled in with your values, as it would be sealed.
SEE A BINDING BEFORE YOU SEND IT. Takes the values and subjects you would send to POST /api/v1/envelopes for this template and answers with one of its documents as a PDF, every value drawn exactly as the sealed copy would draw it — a value split across printed boxes one character per cell, a date in the form the field prints, the option of a question ticked, long text wrapped in its box — and PREVIEW across every page. Nothing is stored, sent, recorded or billed, and there is no envelope and nobody to sign.
⚠️ A PREVIEW THAT RENDERS IS ONE THE SEND WOULD ACCEPT. The values are judged by the same check as POST /api/v1/templates/{templateId}/validate-values and create, and the first one create would refuse is refused here with create’s code and create’s message — ask validate-values for every entry’s verdict at once. A template_etag that is no longer current is refused template_changed, as create refuses it. The send’s own checks on the answers it would fix are asked too, of every document of the template, and refused with the send’s code and sentence: a locked, required box left empty (locked_answer_missing), or a locked answer its own box refuses (locked_answer_unusable) — the template’s own fixed answers included. It also refuses, with cannot_be_drawn, a value too long for its box even at the smallest size the sealed copy prints: create accepts that value, and the envelope would then fail to seal after every signature.
⚠️ THERE ARE NO RECIPIENTS, SO EVERY ROLE IS DRAWN AS IF IT IS ON THE ENVELOPE, held by one person — an optional role included, so you see the whole form. For the send’s checks your values stand in for the recipients: an optional role is on the envelope when one of them fills one of its boxes, and left out otherwise, as a send leaves out an optional role nobody is named for; a left-out role’s boxes are drawn but not checked, and leaving out a role whose box decides another role’s box is refused as the send refuses it. A title or company box filled from a recipient’s details is neither drawn nor checked here.
Boxes nobody gave a value stay blank, except an answer the template fixes itself. Boxes filled by signing — a signature, initials, the date of signing, the signer’s name and email — show whose they are, as Signature: Employee.
document is the 1-based position of the document in documents on GET /api/v1/templates/{templateId}, and is required when the template holds more than one.
⚠️ IT IS A RENDER, AND IT SHARES A CEILING. Each preview that renders counts against your organisation’s per-minute ceiling on renders, which draft renders (GET /api/v1/envelopes/{envelopeId}/documents/{position}/draft) count against too; past it the answer is rate_limited with Retry-After. ⚠️ EVERY REQUEST PAST THE CREDENTIAL AND BODY CHECKS IS COUNTED, before the template is read — one refused for a value or a stale etag, and a rate_limited one, included. To check values without spending the ceiling, use validate-values, which renders nothing.
⚠️ IT NEEDS documents:read AS WELL AS templates:read (x-additional-required-scopes): what it answers with is the template’s document itself, which templates:read alone never grants. A key missing either is refused insufficient_scope.
A test key and a live key get the same answer: a template has no environment. It is a POST only because the body can be large, and takes no Idempotency-Key.
curl --request POST \
--url https://app.vumasign.com/api/v1/templates/{templateId}/preview \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{}'import requests
url = "https://app.vumasign.com/api/v1/templates/{templateId}/preview"
payload = {}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({})
};
fetch('https://app.vumasign.com/api/v1/templates/{templateId}/preview', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.vumasign.com/api/v1/templates/{templateId}/preview"
payload := strings.NewReader("{}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}"<string>"{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}Authorizations
Authorization: Bearer vsk_live_…. Chosen over a bespoke X-API-KEY header because every client, proxy and log-redaction rule already knows this one. What a key may DO is its scopes — see x-scopes at the root of this document and x-required-scope on each operation. The scope list is not written here because OpenAPI reserves a requirement’s scope array for oauth2 and openIdConnect and requires it to be empty for an http scheme.
Path Parameters
The template’s id, as returned by the list endpoint.
A template id.
"01960000-0000-4000-8000-0000000007e1"
Body
The values and subjects you would send to create, and optionally template_etag and document.
The values to draw.
Optional. Values to prefill, as triples. Omitted means none. ⚠️ A signature, a date_signed, a signer_name and a signer_email may not be prefilled — they are acts, or they are written by this system.
Show child attributes
Show child attributes
Optional. WHO THIS ENVELOPE IS ABOUT, as a map from a subject key the template declares to true (on this envelope), false (not), or "ask": the signer who holds that person’s section is asked first, in a required question, and their answer decides. While it is open the person reads present: false and awaiting_signer: true on the envelope. Two cases are settled at send instead, with nobody asked: a person no recipient holds a box for is left out, and a person a recipient signs for in a role standing for the same person is included. A field addressed to a subject that is not on the envelope is hidden from the signer and not required.
A subject you do not name takes the template’s own rule: a required subject is on every envelope, and a conditional one is absent and the signer is never asked about them, whatever the template’s Ask, Include or Leave out says (that choice applies to envelopes sent from the dashboard). Omitting subjects altogether opens the same sections as naming nobody — but see the next paragraph, because the two are not the same request. A key the template does not declare is refused, and so are false and "ask" for a required subject — all invalid_request, and nothing is created.
⚠️ IT SETS WHAT THE DOCUMENT OPENS WITH, NOT WHAT IT MUST STAY. The signer can switch a conditional subject on or off on the signing page, in either direction, and every such change is recorded in the envelope’s audit history.
⚠️ NAME AS true OR "ask" EVERY conditional SUBJECT YOU PREFILL. A value for a subject you ask about is shown if the signer answers Yes, and cleared if they answer No. A value in values for a conditional subject you did not name as present or asked is handled in one of two ways, depending on whether subjects is in the request at all:
subjectssent (even{}): the subject is absent — byfalse, or by the template’s rule when unnamed — so the value contradicts the request and is refused withinvalid_request. Nothing is created.subjectsomitted: the value is accepted and stored, but the subject is absent by the template’s rule when the envelope is sent, so the prefilled section opens HIDDEN until the signer switches it on.
See Subjects.
Show child attributes
Show child attributes
{ "co_tenant": true, "surety": false }
{ "co_tenant": "ask" }
Optional. The ETag of the template as you checked it — from GET /api/v1/templates/{templateId} or the template_etag of validate-values — with or without its quotes. When it is no longer current the preview is refused with template_changed, exactly as create would refuse the send.
1 - 200Which of the template’s documents to draw: its 1-based position in documents on GET /api/v1/templates/{templateId}. Required when the template holds more than one; otherwise the only one is drawn.
x >= 1Response
The document, filled in and marked PREVIEW on every page. Content-Disposition: attachment; filename="<template name> (preview).pdf" — with (document 2 of 3) after the name for a template of several — and Cache-Control: no-store.
The PDF bytes. ⚠️ NOT JSON, like the draft render: every non-2xx is still the same Error envelope in application/json.