curl --request POST \
--url https://app.vumasign.com/api/v1/envelopes/{envelopeId}/void \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"reason": "Superseded by a corrected offer"
}
'import requests
url = "https://app.vumasign.com/api/v1/envelopes/{envelopeId}/void"
payload = { "reason": "Superseded by a corrected offer" }
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({reason: 'Superseded by a corrected offer'})
};
fetch('https://app.vumasign.com/api/v1/envelopes/{envelopeId}/void', 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/envelopes/{envelopeId}/void"
payload := strings.NewReader("{\n \"reason\": \"Superseded by a corrected offer\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
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))
}{
"id": "01960000-0000-4000-8000-0000000000e5",
"status": "voided",
"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": null,
"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>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}Withdraw a sent envelope.
STOPS AN ENVELOPE THAT IS OUT FOR SIGNATURE. Every outstanding signing link is revoked, the envelope moves to voided, and the reason is kept on the record and in the audit trail.
⚠️ IT DOES NOT DELETE ANYTHING. Signatures already collected stay exactly where they are and the evidence chain is untouched — voiding is a statement about what happens NEXT, not an erasure of what happened. A signer who follows an old link is told the envelope was withdrawn rather than being shown a document they can no longer act on.
200, not 201. Nothing was created; the envelope existed before this call.
Which envelopes can be voided
ONLY sent AND partially_signed — an envelope that is out for signature. Anything else is envelope_not_sent:
- a draft has asked nobody to do anything, so there is nothing to withdraw. Simply never send it.
- a completed envelope is executed. Voiding it would claim a signed agreement was withdrawn after the fact, which is not true and not this endpoint’s to say.
- an envelope that is already
voided,declinedorexpiredhas stopped once already.
The idempotency
⚠️ Idempotency-Key IS REQUIRED, and the reason is sharper here than for most endpoints. Without one, a timeout is unresolvable in the worst way: you retry, the envelope IS already voided, and you are refused — so a successful void and a failed one look identical from the outside. With a key the retry replays the original 200 and the envelope it names.
THE REASON IS NOT PART OF THE KEY’S FINGERPRINT. Two voids of the same envelope under one key with different reasons are the same act; the first reason is the one recorded. A DIFFERENT envelope under a used key is idempotency_key_reused.
curl --request POST \
--url https://app.vumasign.com/api/v1/envelopes/{envelopeId}/void \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"reason": "Superseded by a corrected offer"
}
'import requests
url = "https://app.vumasign.com/api/v1/envelopes/{envelopeId}/void"
payload = { "reason": "Superseded by a corrected offer" }
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({reason: 'Superseded by a corrected offer'})
};
fetch('https://app.vumasign.com/api/v1/envelopes/{envelopeId}/void', 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/envelopes/{envelopeId}/void"
payload := strings.NewReader("{\n \"reason\": \"Superseded by a corrected offer\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
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))
}{
"id": "01960000-0000-4000-8000-0000000000e5",
"status": "voided",
"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": null,
"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>"
}
}{
"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
Any string that identifies this request; a UUID is the usual choice, and at most 255 characters. ⚠️ REQUIRED — see the note above on why a void without one cannot be retried safely. The caller’s own request identifier.
"01960000-0000-4000-8000-00000000ffff"
Path Parameters
The envelope to withdraw, as returned when it was created. An envelope id.
"01960000-0000-4000-8000-0000000000e5"
Body
Why this envelope is being withdrawn.
Why this envelope is being withdrawn.
WHY, IN WORDS SOMEBODY WILL READ MONTHS LATER. Required, and kept on the envelope record and in the audit event — it is the answer to “why is this contract withdrawn”, asked by somebody who was not there. At most 500 characters.
500"Superseded by a corrected offer"
Response
The envelope, now voided, with the reason it carries. Its recipients are returned as they stand — a recipient who had already signed still reads signed, because they did.
The envelope, as it now stands.
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