curl --request GET \
--url https://app.vumasign.com/api/v1/templates/{templateId} \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.vumasign.com/api/v1/templates/{templateId}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://app.vumasign.com/api/v1/templates/{templateId}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://app.vumasign.com/api/v1/templates/{templateId}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"id": "01960000-0000-4000-8000-0000000007e1",
"name": "Employment contract",
"created_at": "2026-09-01T08:30:00.000Z",
"documents": [
{
"id": "01960000-0000-4000-8000-00000000d0c5",
"page_count": 1
}
],
"pages": [
{
"document_id": "01960000-0000-4000-8000-00000000d0c5",
"page": 1,
"width": 595.28,
"height": 841.89,
"rotation": 0
}
],
"roles": [
{
"name": "Employee",
"routing_type": "sign"
}
],
"subjects": [
{
"key": "employee",
"label": "Employee"
}
],
"questions": [],
"fields": [
{
"id": "01960000-0000-4000-8000-00000000f1e1",
"type": "text",
"label": "Full name",
"required": true,
"role": "Employee",
"document_id": "01960000-0000-4000-8000-00000000d0c5",
"page": 1,
"subject": "employee",
"data_key": "full_name",
"question_id": null,
"options": null,
"rect": {
"x": 0.1007929,
"y": 0.2107045,
"w": 0.5288973,
"h": 0.0213805
}
}
]
}{
"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 template, with every field address on it.
THE ENDPOINT THAT SHOWS THE ADDRESSES. Read it once, by hand, before writing the code that sends anything: it is where you learn which (subject, data_key) pair each box on the paper answers to, and which boxes have no address at all.
⚠️ AND IT SHOWS WHERE EVERY BOX IS. Each Field carries a rect, and pages carries the dimensions those rectangles are fractions of. Together with GET /api/v1/documents/{documentId}/pages/{pageNumber}, which serves the page itself, that is enough for a program that can SEE to match fields it could not match from labels alone — which is the case on any real form, where checklist.17 is a field name and “ID or passport number” appears four times on one page.
⚠️ FOUR SITUATIONS ANSWER WITH THE SAME 404: no such template anywhere; a template belonging to another organisation; one of yours that has been archived; and an id that is not a uuid. “This exists but is not yours” is itself the secret, and the handler could not distinguish them if it wanted to — the read runs inside a transaction already scoped to the key’s tenant.
curl --request GET \
--url https://app.vumasign.com/api/v1/templates/{templateId} \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.vumasign.com/api/v1/templates/{templateId}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://app.vumasign.com/api/v1/templates/{templateId}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://app.vumasign.com/api/v1/templates/{templateId}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"id": "01960000-0000-4000-8000-0000000007e1",
"name": "Employment contract",
"created_at": "2026-09-01T08:30:00.000Z",
"documents": [
{
"id": "01960000-0000-4000-8000-00000000d0c5",
"page_count": 1
}
],
"pages": [
{
"document_id": "01960000-0000-4000-8000-00000000d0c5",
"page": 1,
"width": 595.28,
"height": 841.89,
"rotation": 0
}
],
"roles": [
{
"name": "Employee",
"routing_type": "sign"
}
],
"subjects": [
{
"key": "employee",
"label": "Employee"
}
],
"questions": [],
"fields": [
{
"id": "01960000-0000-4000-8000-00000000f1e1",
"type": "text",
"label": "Full name",
"required": true,
"role": "Employee",
"document_id": "01960000-0000-4000-8000-00000000d0c5",
"page": 1,
"subject": "employee",
"data_key": "full_name",
"question_id": null,
"options": null,
"rect": {
"x": 0.1007929,
"y": 0.2107045,
"w": 0.5288973,
"h": 0.0213805
}
}
]
}{
"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"
Response
The template, with its documents, pages, roles and every field.
The template, in full.
The template’s id. A bare uuid, no prefix.
What the sender called it.
ISO 8601, UTC. Also the order this list is in — newest first.
The pack, in the order it is stacked.
Show child attributes
Show child attributes
⚠️ THE DENOMINATOR FOR EVERY Field.rect, and a client doing field matching cannot skip it: a rectangle of fractions is dimensionless without the page it is a fraction of. Join on (document_id, page), the same pair fields is joined by.
Every page of every document of the pack, in the pack’s order and then by page number — the order a signer scrolls.
Show child attributes
Show child attributes
The signing roles, in routing order.
Show child attributes
Show child attributes
The people whose data this template collects.
Show child attributes
Show child attributes
The tickbox questions and how many answers each takes.
Show child attributes
Show child attributes
⚠️ A FLAT LIST, NOT A MAP KEYED BY ADDRESS. Several fields sharing one (subject, data_key) pair is NORMAL — real forms ask for an ID number on the application and again on the declaration, and initials in the footer of all nine pages. A value supplied for an address means it for every box that asks.
Show child attributes
Show child attributes