curl --request GET \
--url https://app.vumasign.com/api/v1/templates \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.vumasign.com/api/v1/templates"
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', 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"
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))
}{
"data": [
{
"id": "01960000-0000-4000-8000-0000000007e1",
"name": "Employment contract",
"created_at": "2026-09-01T08:30:00.000Z"
}
],
"has_more": false,
"next_cursor": null
}{
"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>"
}
}List live templates, newest first.
Walked by keyset cursor rather than by offset. ⚠️ OFFSET WOULD BE WRONG HERE, not merely unfashionable: this list is ordered newest first, so a template created between page 1 and page 2 shifts every row down — the caller receives one row twice and never sees another. The thing an integration does between two pages is upload a template.
Archived templates are never returned, by either endpoint.
curl --request GET \
--url https://app.vumasign.com/api/v1/templates \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.vumasign.com/api/v1/templates"
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', 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"
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))
}{
"data": [
{
"id": "01960000-0000-4000-8000-0000000007e1",
"name": "Employment contract",
"created_at": "2026-09-01T08:30:00.000Z"
}
],
"has_more": false,
"next_cursor": null
}{
"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.
Query Parameters
⚠️ A VALUE OUTSIDE THE RANGE IS REFUSED, NOT CLAMPED. Clamping looks friendlier and produces a silent defect: a caller who asks for 1000 and receives 100 has a working integration that reads their whole account in ten times the requests they budgeted, and nothing anywhere says so. How many templates to return.
1 <= x <= 100The next_cursor from the previous page. Omit it to start at the newest template. It is the last item’s id in clear — not an opaque blob, because encoding a uuid to signal opacity is a costume that makes every support conversation start with a decoding step.
A template id to resume after.
Response
A page of templates, newest first, with the cursor for the next.
The page.
This page’s templates.
Show child attributes
Show child attributes
⚠️ THE ANSWER, rather than something to infer. A caller inferring "more" from a full page loops one extra time on every list whose size is an exact multiple of limit.
Pass this as cursor for the next page. Null exactly when has_more is false — the two are computed from one expression, so a true with a null cursor cannot happen.