curl --request PATCH \
--url https://app.vumasign.com/api/v1/templates/{templateId}/fields \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'If-Match: <if-match>' \
--data '{}'import requests
url = "https://app.vumasign.com/api/v1/templates/{templateId}/fields"
payload = {}
headers = {
"If-Match": "<if-match>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {
'If-Match': '<if-match>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({})
};
fetch('https://app.vumasign.com/api/v1/templates/{templateId}/fields', 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}/fields"
payload := strings.NewReader("{}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("If-Match", "<if-match>")
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))
}{
"warnings": [],
"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",
"kind": "party"
}
],
"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": "staff.full_name",
"question_id": null,
"options": null,
"rect": {
"x": 0.1007929,
"y": 0.2107045,
"w": 0.5288973,
"h": 0.0213805
},
"context": {
"label": "Full name",
"row": null,
"column": "Full name",
"option": null,
"section": "Employment contract",
"nearby": [
"<<text:Employee:Full name:subject(employee):key(full_name)>>",
"Full name"
],
"widget_name": null,
"tooltip": null
}
}
],
"coverage": {
"prefillable": 1,
"core": 0,
"organisation": 1,
"legacy": 0,
"unnamed": 0,
"questions": 0,
"unnamed_groups": 0,
"unnamed_options": 0,
"collisions": 0
}
}{
"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>"
}
}Rename a template’s fields and questions: names only, all or nothing.
NAME THE BOXES YOUR DATA GOES INTO, ONCE, FOR EVERY ENVELOPE AFTER. Sets a field’s data_key, label and subject, and a question’s label. Nothing else can change here: not where a field is, its type, who fills it, its options, its rules or its conditions.
⚠️ templates:name IS EXPLICIT-ONLY: a full-access key does not hold it, and the key must name it. ⚠️ LIVE KEYS ONLY, and the key’s creator must still be a member: a template has no environment, so a test key’s rename would change what live envelopes contain.
⚠️ ALL OR NOTHING, AGAINST THE ETAG YOU READ. Send If-Match; every change applies, or none does. Only envelopes CREATED afterwards use the new names — an envelope already created, sent or not, keeps the names it was created with. Every change is recorded in the template’s naming record, with the key that made it, and summarised in your organisation’s activity.
A new key follows the naming grammar — namespace.attribute or namespace.entity.attribute, snake_case, such as medical_aid.plan — and may not start with a core namespace (person, employer, employment, residential_address, postal_address, bank), which is reserved for the core vocabulary, nor with a subject key. A key a field already has may be kept whatever it says. A box in a question takes no key of its own. option_value, and a question’s own key and subject, arrive with the core vocabulary and are refused until then.
curl --request PATCH \
--url https://app.vumasign.com/api/v1/templates/{templateId}/fields \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'If-Match: <if-match>' \
--data '{}'import requests
url = "https://app.vumasign.com/api/v1/templates/{templateId}/fields"
payload = {}
headers = {
"If-Match": "<if-match>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {
'If-Match': '<if-match>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({})
};
fetch('https://app.vumasign.com/api/v1/templates/{templateId}/fields', 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}/fields"
payload := strings.NewReader("{}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("If-Match", "<if-match>")
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))
}{
"warnings": [],
"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",
"kind": "party"
}
],
"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": "staff.full_name",
"question_id": null,
"options": null,
"rect": {
"x": 0.1007929,
"y": 0.2107045,
"w": 0.5288973,
"h": 0.0213805
},
"context": {
"label": "Full name",
"row": null,
"column": "Full name",
"option": null,
"section": "Employment contract",
"nearby": [
"<<text:Employee:Full name:subject(employee):key(full_name)>>",
"Full name"
],
"widget_name": null,
"tooltip": null
}
}
],
"coverage": {
"prefillable": 1,
"core": 0,
"organisation": 1,
"legacy": 0,
"unnamed": 0,
"questions": 0,
"unnamed_groups": 0,
"unnamed_options": 0,
"collisions": 0
}
}{
"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.
Headers
The ETag of the template you read. ⚠️ REQUIRED: without it, or with *, the answer is precondition_required. When it is no longer current the answer is template_changed with the current ETag, and nothing is renamed — unless every name you sent is already the stored one, which is what a retry of a rename that succeeded looks like, and is answered 200.
An entity tag, quoted.
Path Parameters
The template’s id, as returned by the list endpoint.
A template id.
"01960000-0000-4000-8000-0000000007e1"
Body
The names to change, by field and question id.
Response
The template as it now stands, with its new ETag.
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.
What the template’s upload warned about, kept with the template: on the create response and on every read, so a template somebody uploaded on the dashboard says here what its upload said. Empty when there was nothing to say, and always present. A retry of the create’s Idempotency-Key answers with the same list.
10Show child attributes
Show child attributes
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
How much of the template your integration can address, counted by the same code as the editor’s coverage line, so the two always agree. Part of the body, so the template’s ETag changes when it does.
Show child attributes
Show child attributes