curl --request GET \
--url https://app.vumasign.com/api/v1/envelopes/{envelopeId} \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.vumasign.com/api/v1/envelopes/{envelopeId}"
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}', 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}"
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-0000000000e5",
"status": "sent",
"template_id": "01960000-0000-4000-8000-0000000007e1",
"title": "Employment contract",
"created_at": "2026-09-02T09:00:00.000Z",
"sent_at": "2026-09-02T09:05:00.000Z",
"expires_at": null,
"recipients": [
{
"id": "01960000-0000-4000-8000-00000000005e",
"role": "Employee",
"name": "Thandi Mokoena",
"email": "thandi@example.test",
"routing_type": "sign",
"status": "sent",
"invitation_delivered": true,
"embedded": false,
"external_ref": 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>"
}
}One envelope, as it stands right now.
⚠️ THIS IS WHAT EVERY WEBHOOK BODY’S uri POINTS AT, AND WHY THAT FIELD EXISTS. A webhook payload is a snapshot of a moment; this is the resource. Anything you are about to do because of an event — release goods, bill somebody, advance a workflow — should be decided from a read here rather than from the body you were posted, because the body cannot have changed since it was written and the envelope can.
It is a safe GET: it writes nothing, mints nothing and takes no Idempotency-Key. Poll it if you must, but the webhook is the signal and this is the confirmation.
⚠️ invitation_delivered IS ANSWERED HERE FROM THE EVIDENCE, not left null. A recipient the provider has reported delivery for reads true weeks later, and a recipient nobody was ever meant to email — a cc, an embedded signer, a position a sequential envelope is still holding back — reads null. Those two are different facts and this endpoint keeps them different.
⚠️ AND THIS IS THE ENDPOINT TO RE-READ IT FROM, BECAUSE THE TWO ENDPOINTS MEAN DIFFERENT THINGS BY true AND THAT IS DELIBERATE. Here, true means THE PROVIDER REPORTED A DELIVERY, on the provider’s clock; an invitation the provider has merely accepted and not yet reported on reads false. POST .../send answers true for that same recipient — it is built from the answers the provider gave while the send was running, and acceptance is the only thing anybody knows at that moment. Its own description says so, and says it is the one place true means acceptance. So a recipient can read true from the send and false here seconds later, and neither answer is wrong: deciding anything real from the send response is deciding from a snapshot taken before the fact existed. Re-read here.
⚠️ FOUR SITUATIONS ANSWER WITH THE SAME 404: no such envelope anywhere; one belonging to another organisation; a draft that has since been deleted; and an id that is not a uuid. “It exists but is not yours” is itself the secret, and the handler could not tell the first two apart if it wanted to — the read runs inside a transaction already scoped to the key’s tenant.
A vsk_test_ key may read. Reading emails nobody.
curl --request GET \
--url https://app.vumasign.com/api/v1/envelopes/{envelopeId} \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.vumasign.com/api/v1/envelopes/{envelopeId}"
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}', 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}"
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-0000000000e5",
"status": "sent",
"template_id": "01960000-0000-4000-8000-0000000007e1",
"title": "Employment contract",
"created_at": "2026-09-02T09:00:00.000Z",
"sent_at": "2026-09-02T09:05:00.000Z",
"expires_at": null,
"recipients": [
{
"id": "01960000-0000-4000-8000-00000000005e",
"role": "Employee",
"name": "Thandi Mokoena",
"email": "thandi@example.test",
"routing_type": "sign",
"status": "sent",
"invitation_delivered": true,
"embedded": false,
"external_ref": 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.
Path Parameters
The envelope’s id, as returned by POST /api/v1/envelopes or carried as envelope_id on every webhook event.
An envelope id.
"01960000-0000-4000-8000-0000000000e5"
Response
The envelope, with its recipients in routing order.
The envelope.
The envelope’s id. A bare uuid.
draft or sent from this endpoint. Later states arrive as people act.
The template this was made from, or null when there was none — an envelope created by POST /api/v1/envelopes/one-off carries the document itself and never had a template. Branch on null, never on the empty string.
What the signers see naming the document. Defaults to the template’s name.
ISO 8601, UTC.
ISO 8601, UTC. Null while it is a draft.
ISO 8601, UTC. Null when this envelope has no deadline, which is the default. Read back from the stored value rather than echoed, so an offset you sent comes back as the same instant in UTC.
In routing order.
Show child attributes
Show child attributes