curl --request POST \
--url https://app.vumasign.com/api/v1/standing-authorisations/invitations \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"brand_id": "9d4f2a6b-1c3e-4d5f-8a7b-6c5d4e3f2a1b",
"legal_entity": {
"name": "NTT DATA South Africa (Pty) Ltd",
"registration_number": "1990/001234/07"
},
"grantor": {
"name": "Naledi Mokoena",
"email": "naledi@example.test",
"job_title": "HR Director"
},
"valid_until": "2027-10-13T22:00:00.000Z"
}
'import requests
url = "https://app.vumasign.com/api/v1/standing-authorisations/invitations"
payload = {
"brand_id": "9d4f2a6b-1c3e-4d5f-8a7b-6c5d4e3f2a1b",
"legal_entity": {
"name": "NTT DATA South Africa (Pty) Ltd",
"registration_number": "1990/001234/07"
},
"grantor": {
"name": "Naledi Mokoena",
"email": "naledi@example.test",
"job_title": "HR Director"
},
"valid_until": "2027-10-13T22:00:00.000Z"
}
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({
brand_id: '9d4f2a6b-1c3e-4d5f-8a7b-6c5d4e3f2a1b',
legal_entity: {name: 'NTT DATA South Africa (Pty) Ltd', registration_number: '1990/001234/07'},
grantor: {name: 'Naledi Mokoena', email: 'naledi@example.test', job_title: 'HR Director'},
valid_until: '2027-10-13T22:00:00.000Z'
})
};
fetch('https://app.vumasign.com/api/v1/standing-authorisations/invitations', 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/standing-authorisations/invitations"
payload := strings.NewReader("{\n \"brand_id\": \"9d4f2a6b-1c3e-4d5f-8a7b-6c5d4e3f2a1b\",\n \"legal_entity\": {\n \"name\": \"NTT DATA South Africa (Pty) Ltd\",\n \"registration_number\": \"1990/001234/07\"\n },\n \"grantor\": {\n \"name\": \"Naledi Mokoena\",\n \"email\": \"naledi@example.test\",\n \"job_title\": \"HR Director\"\n },\n \"valid_until\": \"2027-10-13T22:00:00.000Z\"\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": "0b9a5c1e-7d42-4f8a-9e13-5c6d7e8f9a0b",
"number": "SA-0012",
"status": "invited",
"sandbox": false,
"brand": {
"id": "9d4f2a6b-1c3e-4d5f-8a7b-6c5d4e3f2a1b",
"name": "NTT DATA"
},
"legal_entity": {
"name": "NTT DATA South Africa (Pty) Ltd",
"registration_number": "1990/001234/07"
},
"grantor": {
"name": "Naledi Mokoena",
"email": "naledi@example.test",
"job_title": "HR Director"
},
"intended_use": "Salary adjustment letters and employment confirmations",
"valid_from": "2026-10-14T22:00:00.000Z",
"valid_until": "2027-10-13T22:00:00.000Z",
"invite_expires_at": "2026-10-28T22:00:00.000Z",
"granted_at": null,
"cancellation": null,
"revocation": null,
"invalidation": null,
"reference": "ntt-signatory-2026",
"applied_count": 0,
"created_at": "2026-10-14T06:00:00.000Z"
}{
"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>"
}
}{
"error": {
"code": "unauthenticated",
"message": "<string>"
}
}Request a standing authorisation: generate the mandate and send it to the grantor.
Generates the mandate document and sends it to the grantor as a Vumasign envelope under the brand. They must enter a one-time code sent to their email address before they can open it. ⚠️ IT GRANTS NOTHING: the authorisation becomes active only when the signed mandate is sealed. Poll GET /api/v1/standing-authorisations/{authorisationId} or listen for authorisation.granted.
⚠️ authorisations:invite IS EXPLICIT-ONLY. A full-access key (an empty scope list) does not hold it; the key must name it.
Every email the grantor receives about the mandate names your organisation and the brand, never the member who minted your key, so it keeps working after that person leaves. Your members receive no email about it.
If the mandate was created but its send was refused, you get that refusal and the authorisation is left cancelled with cancellation.reason not_sent. So is one whose email to the grantor could not be delivered: that answers 503 service_unavailable, and the mandate is withdrawn. Request again with a new Idempotency-Key. A replay returns the first answer as it was given.
curl --request POST \
--url https://app.vumasign.com/api/v1/standing-authorisations/invitations \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"brand_id": "9d4f2a6b-1c3e-4d5f-8a7b-6c5d4e3f2a1b",
"legal_entity": {
"name": "NTT DATA South Africa (Pty) Ltd",
"registration_number": "1990/001234/07"
},
"grantor": {
"name": "Naledi Mokoena",
"email": "naledi@example.test",
"job_title": "HR Director"
},
"valid_until": "2027-10-13T22:00:00.000Z"
}
'import requests
url = "https://app.vumasign.com/api/v1/standing-authorisations/invitations"
payload = {
"brand_id": "9d4f2a6b-1c3e-4d5f-8a7b-6c5d4e3f2a1b",
"legal_entity": {
"name": "NTT DATA South Africa (Pty) Ltd",
"registration_number": "1990/001234/07"
},
"grantor": {
"name": "Naledi Mokoena",
"email": "naledi@example.test",
"job_title": "HR Director"
},
"valid_until": "2027-10-13T22:00:00.000Z"
}
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({
brand_id: '9d4f2a6b-1c3e-4d5f-8a7b-6c5d4e3f2a1b',
legal_entity: {name: 'NTT DATA South Africa (Pty) Ltd', registration_number: '1990/001234/07'},
grantor: {name: 'Naledi Mokoena', email: 'naledi@example.test', job_title: 'HR Director'},
valid_until: '2027-10-13T22:00:00.000Z'
})
};
fetch('https://app.vumasign.com/api/v1/standing-authorisations/invitations', 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/standing-authorisations/invitations"
payload := strings.NewReader("{\n \"brand_id\": \"9d4f2a6b-1c3e-4d5f-8a7b-6c5d4e3f2a1b\",\n \"legal_entity\": {\n \"name\": \"NTT DATA South Africa (Pty) Ltd\",\n \"registration_number\": \"1990/001234/07\"\n },\n \"grantor\": {\n \"name\": \"Naledi Mokoena\",\n \"email\": \"naledi@example.test\",\n \"job_title\": \"HR Director\"\n },\n \"valid_until\": \"2027-10-13T22:00:00.000Z\"\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": "0b9a5c1e-7d42-4f8a-9e13-5c6d7e8f9a0b",
"number": "SA-0012",
"status": "invited",
"sandbox": false,
"brand": {
"id": "9d4f2a6b-1c3e-4d5f-8a7b-6c5d4e3f2a1b",
"name": "NTT DATA"
},
"legal_entity": {
"name": "NTT DATA South Africa (Pty) Ltd",
"registration_number": "1990/001234/07"
},
"grantor": {
"name": "Naledi Mokoena",
"email": "naledi@example.test",
"job_title": "HR Director"
},
"intended_use": "Salary adjustment letters and employment confirmations",
"valid_from": "2026-10-14T22:00:00.000Z",
"valid_until": "2027-10-13T22:00:00.000Z",
"invite_expires_at": "2026-10-28T22:00:00.000Z",
"granted_at": null,
"cancellation": null,
"revocation": null,
"invalidation": null,
"reference": "ntt-signatory-2026",
"applied_count": 0,
"created_at": "2026-10-14T06:00:00.000Z"
}{
"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>"
}
}{
"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, at most 255 characters. ⚠️ REQUIRED: a retry with the same key returns the same authorisation instead of sending a second mandate. The caller’s own request identifier.
"01960000-0000-4000-8000-00000000ffff"
Body
The brand, the legal entity, the grantor and the validity.
The request.
A live brand of yours. The mandate is sent under it, and covers documents sent under it.
The legal entity the grantor signs for.
Show child attributes
Show child attributes
The person who will sign the mandate.
Show child attributes
Show child attributes
After the start, at most 12 months after it, and at least 2 days from now.
Printed on the mandate as your description, in quotation marks. Not enforced.
500At most 5 minutes in the past and 90 days ahead. Omit it to run from the grant.
Defaults to 14 days, or 1 day before valid_until if sooner. Between 1 and 30 days from now, and at least 1 day before valid_until.
Your own reference, such as a tenant id.
255Response
The authorisation, invited, with its id and number. Idempotency-Replayed says whether this request created it or is being shown an earlier one’s answer; the status is 201 either way.
The authorisation.
The authorisation’s id.
Its number in your organisation, as printed on the mandate.
"SA-0012"
invited until the signed mandate is sealed, then active. An invite ends declined, expired_unsigned or cancelled; an active one ends revoked, expired or invalidated.
invited, active, declined, expired_unsigned, cancelled, revoked, expired, invalidated Requested with a test key: it binds nobody, and applies only to sandbox envelopes.
The brand it covers, as granted.
Show child attributes
Show child attributes
The legal entity the grantor signs for.
Show child attributes
Show child attributes
The person who signs the mandate.
Show child attributes
Show child attributes
Your description, printed on the mandate as yours. Not enforced.
The start, or null for "from the grant".
The end.
When the mandate lapses unsigned.
When the signed mandate was sealed and the authorisation took effect.
Set when the request ended without a grant.
Show child attributes
Show child attributes
Set when an active authorisation was revoked.
Show child attributes
Show child attributes
Set when the brand changed under it.
Show child attributes
Show child attributes
Your own reference, as you set it.
How many documents it pre-signed.
When it was requested.