curl --request GET \
--url https://app.vumasign.com/api/v1/documents/{documentId}/pages/{pageNumber} \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.vumasign.com/api/v1/documents/{documentId}/pages/{pageNumber}"
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/documents/{documentId}/pages/{pageNumber}', 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/documents/{documentId}/pages/{pageNumber}"
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))
}"<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 page of a document, as a PDF, to look at.
THE PAGE ITSELF. GET /api/v1/templates/{templateId} tells you where every field is; this is the picture those coordinates are measured against. Fetch it, render it, and every Field.rect on this page lands on its box.
⚠️ IT ANSWERS application/pdf, NOT AN IMAGE, AND THAT IS DELIBERATE. The page is served as a one-page PDF carrying the original page’s vectors, fonts, images and field boxes. Every model that accepts a screenshot accepts this too, and rasterises it at the resolution IT wants rather than one we picked — too low and the OCR-derived labels this feature exists to disambiguate are unreadable, too high and it is a render nobody asked for. You also keep the text layer, which a PNG would have thrown away.
It is one page rather than the whole document because the cost of the answer should not grow with the length of the paperwork: page 4 of a nine-page application and page 4 of a two-hundred-page one are the same response. There is deliberately no whole-document download.
⚠️ THE PAGE IS NOT FILLABLE. Its field boxes DRAW exactly as they do in the original — the widget annotations come across — but the form itself does not, so this is a picture of the page and not a copy of the form. Values are supplied through values on POST /api/v1/envelopes.
⚠️ FOUR SITUATIONS ANSWER WITH THE SAME 404: no such document anywhere; a document belonging to another organisation; an id that is not a uuid; and a pageNumber that is not a page number. A fifth is specific — “this document has 6 pages” — and only because it is reached after the document has been found under YOUR key, which is the point at which a refusal is allowed to say something.
Never cached, by anything: Cache-Control: private, no-store. This is one organisation’s confidential paperwork selected by a request header, and a shared cache keyed on the URL would hand it to the next caller of the same path.
curl --request GET \
--url https://app.vumasign.com/api/v1/documents/{documentId}/pages/{pageNumber} \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.vumasign.com/api/v1/documents/{documentId}/pages/{pageNumber}"
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/documents/{documentId}/pages/{pageNumber}', 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/documents/{documentId}/pages/{pageNumber}"
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))
}"<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
A documents[].id from GET /api/v1/templates/{templateId}. ⚠️ It is the DOCUMENT’s id, not the template’s — a template’s pack may hold several, and Field.document_id says which one a box is on.
A document id.
"01960000-0000-4000-8000-00000000d0c5"
1-indexed, WITHIN this document. The same number as Field.page and Page.page. ⚠️ There is one spelling of each page: 01, 1.0 and 1 are refused rather than accepted as 1, so that one page does not answer to five URLs.
Which page of the document.
x >= 14
Response
The page, as a one-page PDF. Content-Disposition: inline; filename="page-N.pdf" — the filename names the page and nothing about the customer.
The PDF bytes. ⚠️ NOT JSON — this is the one operation in this API whose success body is not a resource. Its refusals still are: every non-2xx is the same Error envelope in application/json.