curl --request GET \
--url https://app.vumasign.com/api/v1/envelopes/{envelopeId}/documents \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.vumasign.com/api/v1/envelopes/{envelopeId}/documents"
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/envelopes/{envelopeId}/documents', 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/envelopes/{envelopeId}/documents"
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))
}{
"status": "sent",
"documents": [
{
"position": 1,
"sealed": false,
"byte_size": 851,
"url": "/documents/specimen-payload.specimen-signature"
}
]
}{
"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>"
}
}Get an envelope’s document, and its certificate once sealed.
THE BYTES GET /api/v1/envelopes/{envelopeId} COULD NOT REACH. That endpoint answers with status and recipients; this is where the contract itself lives — the one route an integrator who embedded signing and received envelope.completed needs and, until now, did not have.
⚠️ CALL IT ON envelope.sealed, NOT ON envelope.completed. The two are about a minute apart: completion is the last signature, and the executed document does not exist at that instant — sealing is dispatched by the transaction that completes the envelope and finishes afterwards. Calling here on envelope.completed is a race you will usually lose, and it answers sealed: false honestly when you do. envelope.sealed fires when every document is sealed and fetchable; envelope.sealing_failed is the other end of it and means sealing gave up, so no later call answers true on its own. ⚠️ IT IS STILL NOT THE END: our support can requeue a failed seal, and one that succeeds delivers envelope.sealed and flips this endpoint to true afterwards.
⚠️ url IS SHORT-LIVED AND MUST BE FETCHED, NOT STORED. It is minted fresh on every call — a few minutes of validity, never the same value twice — so keep the envelope id and the position, and come back to this endpoint for a new url whenever you next need the bytes.
⚠️ sealed: false MEANS “THIS IS THE DOCUMENT AS ORIGINALLY SENT” — READ status TO KNOW WHAT HAPPENS NEXT. Sealing runs asynchronously, so for a draft, sent, partially_signed or freshly completed envelope false usually means the certificate has not landed yet and a later read will flip it — envelope.sealed is the moment it does. For a voided, declined or expired envelope it means never — none of those statuses ever completes, and polling this endpoint waits forever for a seal that is not coming. The original is still returned rather than an empty list either way, honestly marked unsealed.
position is 1-based, matching what a sender is shown — “Document 2 of 3” — and ordinarily there is exactly one.
⚠️ documents:read, NOT envelopes:read. A key that may read an envelope’s status is not thereby a key that may fetch what it holds; wiring a status dashboard needs only the first.
⚠️ A vsk_test_ KEY MAY READ THE STATUS OF ANY ENVELOPE IN ITS ORGANISATION BUT MAY ONLY DOWNLOAD THE DOCUMENTS OF ONE IT CREATED — test_key_cannot_read_documents otherwise. That argument (GET /api/v1/envelopes/{envelopeId}’s own rule) is about status and recipients, and it does not transfer to the bytes of a real, signed contract.
⚠️ FOUR SITUATIONS ANSWER WITH THE SAME 404, the disclosure rule this namespace uses everywhere: no such envelope anywhere; one belonging to another organisation; a deleted draft; and an id that is not a uuid. “It exists but is not yours” is itself the secret.
curl --request GET \
--url https://app.vumasign.com/api/v1/envelopes/{envelopeId}/documents \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.vumasign.com/api/v1/envelopes/{envelopeId}/documents"
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/envelopes/{envelopeId}/documents', 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/envelopes/{envelopeId}/documents"
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))
}{
"status": "sent",
"documents": [
{
"position": 1,
"sealed": false,
"byte_size": 851,
"url": "/documents/specimen-payload.specimen-signature"
}
]
}{
"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 envelope’s id, as returned by POST /api/v1/envelopes.
An envelope id.
"01960000-0000-4000-8000-0000000000e5"
Response
The envelope’s status and documents, in position order.
The status and documents.
The envelope’s status, exactly as GET /api/v1/envelopes/{envelopeId} reports it. ⚠️ READ THIS BEFORE ACTING ON sealed: false BELOW — it is the only way to tell "the seal has not landed yet" (draft, sent, partially_signed) from "it never will" (voided, declined, expired) apart, and the two calls for different behaviour: poll again, or stop.
⚠️ AND completed IS THE ONE STATUS THIS FIELD CANNOT DECIDE. A completed envelope with sealed: false is either a seal that is a minute away or one that has permanently failed, and the status reads the same either way for ever. The events are what tell them apart: envelope.sealed says the bytes are here, envelope.sealing_failed says sealing gave up and nothing further is being tried. ⚠️ THE SECOND OF THOSE IS NOT THE LAST WORD — our support can requeue a failed seal, and one that succeeds delivers envelope.sealed afterwards, so an envelope you saw fail may still turn sealed: true. Subscribe rather than poll.
Ordinarily one entry. More than one only for an envelope built from a multi-document template or one-off pack.
Show child attributes
Show child attributes