> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vumasign.com/llms.txt
> Use this file to discover all available pages before exploring further.

# The core vocabulary, and your organisation’s own keys.

> WHAT A TEMPLATE’S KEYS MEAN, IN TWO LISTS. `core` is the vocabulary Vumasign publishes — people, SA ID and passport, contact, address, employment, employer and banking — with its version: map your data to it once, for every template. `organisation` is every other key your own live templates use, with its type, how many templates use it, the labels printed beside it and, for a question, its option values: map to those as they appear.

⚠️ THE CORE NEVER RENAMES OR REMOVES A KEY OR AN OPTION VALUE; a minor version adds, and a key is at most deprecated with its replacement. ⚠️ `organisation` IS DERIVED, live, from your organisation’s live templates (archived ones left out), and lists only your keys, never another organisation’s. Test and live keys alike.



## OpenAPI

````yaml /openapi.json get /api/v1/vocabulary
openapi: 3.1.0
info:
  title: Vumasign API
  version: 0.2.0
  description: >-
    The Vumasign API. South African e-signature: templates, the addresses an

    integration writes values to, an idempotent send, and webhooks.


    **This document describes what exists today and nothing else.** It is not a

    roadmap, and there is deliberately no entry here for anything that has not

    shipped — a spec you can generate a client from and then discover was

    aspirational is worse than no spec at all.


    ## Authentication

        Authorization: Bearer vsk_live_…

    One header, one step. Keys are minted in Settings. `vsk_live_` and
    `vsk_test_`

    are different keys; the prefix is stored in clear so a leaked key is
    greppable

    and traceable to an organisation, while the secret is stored only as a hash.


    ## Errors


    Every refusal the API returns deliberately, at every status, is

    `{ "error": { "code", "message" } }`, and such a response is a failure **if

    and only if** it carries an `error` key — that test is correct even for a

    client that ignores the status line. An unexpected server failure may not
    have

    this shape: treat a 5xx without an `error` key as an `internal_error`, which

    is retryable. `code` is the

    contract and is stable forever; `message` is prose for a developer reading a

    log and may be reworded in any release.


    The root of this document carries **`x-error-codes`**: the closed enum, with

    `status`, `retryable`, `backoff`, `emitted` and `guidance` for each

    code. Branch on that rather than on our prose.


    ## Rate limits


    **600 requests a minute on any paid plan,

    60 on the free plan — and a test key

    gets the same number as a live one.** Per key, not per organisation, so a
    batch

    job and an interactive integration can be given a key each and neither can

    starve the other.


    The ceiling follows the PLAN rather than the kind of key, because the plan
    is

    what says whether this is production traffic or somebody evaluating us. On
    the

    API plan it is sized so that no honest workload meets it: a thousand
    envelopes

    with a status read each is two thousand requests, which is three and a half

    minutes rather than seventeen. The sandbox deliberately gets the *same*
    number

    rather than a larger one — an integration that passes in rehearsal is then
    an

    integration that passes live, and your `429` handling is exercised where

    meeting a `429` is free.


    Every successful authenticated response carries `RateLimit-Limit`,

    `RateLimit-Remaining` and `RateLimit-Reset` (seconds until the window

    closes, at most 60). Read them on your successes and you will never need to
    be

    refused. A 429 carries them too, and additionally carries `Retry-After`,
    which

    you should honour. A 401 carries none of them; a 403 may not, depending on

    whether it was decided before the request was counted. Each operation's

    responses say which others do.


    ⚠️ **A refused request still counts.** Retrying inside the window pushes the

    counter higher rather than holding it at the ceiling — which is why the
    backoff

    in `x-error-codes` is exponential and why `Retry-After` is worth reading

    rather than guessing at.


    ⚠️ The limits are **per minute**, and the window is fixed rather than
    sliding.

    Spending a whole window at the end of one minute and a whole window at the
    start

    of the next is possible; it is bounded at two windows' worth, which is a
    burst

    rather than an incident.


    ## Webhooks


    Register an endpoint with `POST /api/v1/webhooks`; the response carries the

    signing secret, **once**. The root of this document carries
    **`x-webhooks`**:

    the body we POST, the signature recipe (including the two mistakes that make
    a

    verifier look correct and be worthless), the published retry ladder, and the

    auto-disable rule. Read that before writing a receiver.


    ## What is not here yet


    No organisation-level email exists, so an auto-disabled webhook endpoint is

    discovered by polling rather than by being told. There is no way to change
    an

    envelope after it is created and before it is sent — `PATCH
    /v1/envelopes/{id}`

    is designed and not built — so a draft is created complete or not at all.
    Both

    are stated because a gap named is a gap somebody can plan around.


    ⚠️ **The rate limit above counts authenticated requests only.** A request

    carrying no key, or a key we cannot recognise, is refused before any counter
    is

    touched — a per-key ceiling cannot defend against a caller who has not
    presented

    a key, and pretending otherwise would be a promise we could not keep. That
    half

    belongs to the edge rather than to this API.


    (This section has twice described as missing something that now exists: the

    `uri` on every webhook body, which was `null` until

    `GET /api/v1/envelopes/{envelopeId}` gave it somewhere to point, and the
    rate

    limiter, which nothing enforced until the per-key ceiling above. Both were
    named

    here as gaps first, which is the point of naming them.)
servers:
  - url: https://app.vumasign.com
security:
  - ApiKey: []
tags:
  - name: Templates
    description: >-
      A template is a document with its signing roles and field placements
      authored once. Every envelope is made from one, or from a one-off pack.
  - name: Documents
    description: >-
      The pages of a template’s documents, served as PDF so that a client which
      has to show the paper can do so without rendering it itself.
  - name: Envelopes
    description: >-
      An envelope is one sending of a document to named people. Creating one is
      not sending it: a draft emails nobody until it is sent.
  - name: Webhooks
    description: >-
      Endpoints we POST signed events to when an envelope changes, with the
      health of each endpoint and a secret you can rotate without a gap.
  - name: Standing authorisations
    description: >-
      A grantor’s signed, brand-wide authority for your organisation to apply
      their signature to documents you send under that brand. Requested here; it
      takes effect only when the grantor signs the mandate and it is sealed. See
      /concepts/standing-authorisations.
  - name: Specification
    description: >-
      This document, served without a credential so that anybody can read what
      the API does before they hold a key.
paths:
  /api/v1/vocabulary:
    get:
      tags:
        - Templates
      summary: The core vocabulary, and your organisation’s own keys.
      description: >-
        WHAT A TEMPLATE’S KEYS MEAN, IN TWO LISTS. `core` is the vocabulary
        Vumasign publishes — people, SA ID and passport, contact, address,
        employment, employer and banking — with its version: map your data to it
        once, for every template. `organisation` is every other key your own
        live templates use, with its type, how many templates use it, the labels
        printed beside it and, for a question, its option values: map to those
        as they appear.


        ⚠️ THE CORE NEVER RENAMES OR REMOVES A KEY OR AN OPTION VALUE; a minor
        version adds, and a key is at most deprecated with its replacement. ⚠️
        `organisation` IS DERIVED, live, from your organisation’s live templates
        (archived ones left out), and lists only your keys, never another
        organisation’s. Test and live keys alike.
      operationId: getVocabulary
      parameters:
        - name: If-None-Match
          in: header
          required: false
          description: >-
            The `ETag` you hold. When it is still current the answer is 304 with
            no body. A 304 still counts against the rate limit.
          schema:
            type: string
            description: One or more entity tags, or `*`.
      responses:
        '200':
          description: The core vocabulary and your organisation’s keys.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Vocabulary'
                description: The vocabulary.
              examples:
                example:
                  summary: The core, and keys of the organisation’s own
                  value:
                    core:
                      version: 1.0.0
                      subject_keys:
                        - spouse
                        - dependant_<n>
                        - beneficiary_<n>
                        - witness_<n>
                      keys:
                        - key: person.title
                          label: Title
                          type: choice
                          description: >-
                            The form of address printed before a name. Not a job
                            title (that is `employment.job_title`).
                          subject_capable: true
                          options:
                            - value: mr
                              label: Mr
                            - value: mrs
                              label: Mrs
                            - value: ms
                              label: Ms
                            - value: miss
                              label: Miss
                            - value: dr
                              label: Dr
                            - value: prof
                              label: Prof
                          since: 1.0.0
                        - key: person.first_names
                          label: First names
                          type: text
                          description: >-
                            Every given name, as on the identity document. Not
                            initials, and not a preferred or known-as name.
                          subject_capable: true
                          since: 1.0.0
                        - key: person.last_name
                          label: Surname
                          type: text
                          description: >-
                            The family name, as on the identity document. Not a
                            maiden name unless the form asks for the current
                            surname.
                          subject_capable: true
                          since: 1.0.0
                        - key: person.initials
                          label: Initials
                          type: text
                          description: >-
                            The initials of the first names, for example `J.M.`
                            Not a signature initial.
                          subject_capable: true
                          since: 1.0.0
                        - key: person.sa_id_number
                          label: South African ID number
                          type: sa_id
                          description: >-
                            The 13-digit South African identity number. Not a
                            passport number.
                          subject_capable: true
                          since: 1.0.0
                        - key: person.passport_number
                          label: Passport number
                          type: text
                          description: >-
                            The number of a passport, for a person without a
                            South African ID number. Not the ID number.
                          subject_capable: true
                          since: 1.0.0
                        - key: person.passport_country
                          label: Passport country
                          type: text
                          description: >-
                            The country that issued the passport. Not the
                            country of residence.
                          subject_capable: true
                          since: 1.0.0
                        - key: person.date_of_birth
                          label: Date of birth
                          type: date
                          description: The day the person was born. Not an age.
                          subject_capable: true
                          since: 1.0.0
                        - key: person.gender
                          label: Gender
                          type: choice
                          description: >-
                            The gender the form asks for, as the two options
                            printed on most South African forms.
                          subject_capable: true
                          options:
                            - value: male
                              label: Male
                            - value: female
                              label: Female
                          since: 1.0.0
                        - key: person.marital_status
                          label: Marital status
                          type: choice
                          description: >-
                            Whether the person is married, and how. Not the
                            spouse’s details.
                          subject_capable: true
                          options:
                            - value: single
                              label: Single
                            - value: married
                              label: Married
                            - value: divorced
                              label: Divorced
                            - value: widowed
                              label: Widowed
                            - value: life_partner
                              label: Life partner
                          since: 1.0.0
                        - key: person.relationship_to_principal
                          label: Relationship to the principal
                          type: choice
                          description: >-
                            How a subject (a spouse, dependant or beneficiary)
                            is related to the form’s principal person.
                          subject_capable: true
                          options:
                            - value: spouse
                              label: Spouse
                            - value: child
                              label: Child
                            - value: parent
                              label: Parent
                            - value: sibling
                              label: Sibling
                            - value: other
                              label: Other
                          since: 1.0.0
                        - key: person.email
                          label: Email address
                          type: email
                          description: >-
                            The person’s own email address. Not an employer’s
                            contact address (that is `employer.contact_email`).
                          subject_capable: true
                          since: 1.0.0
                        - key: person.mobile_number
                          label: Mobile number
                          type: phone
                          description: The person’s cellphone number.
                          subject_capable: true
                          since: 1.0.0
                        - key: person.home_phone
                          label: Home telephone
                          type: phone
                          description: A landline at home. Not a cellphone.
                          subject_capable: true
                          since: 1.0.0
                        - key: person.work_phone
                          label: Work telephone
                          type: phone
                          description: >-
                            A telephone number at work. Not the employer’s
                            switchboard on an employer section.
                          subject_capable: true
                          since: 1.0.0
                        - key: person.income_tax_number
                          label: Income tax number
                          type: text
                          description: >-
                            The person’s SARS income tax reference number. Not a
                            company’s PAYE reference.
                          subject_capable: true
                          since: 1.0.0
                        - key: person.home_language
                          label: Home language
                          type: text
                          description: The language the person speaks at home.
                          subject_capable: true
                          since: 1.0.0
                        - key: person.occupation
                          label: Occupation
                          type: text
                          description: >-
                            What the person does for a living, in general terms.
                            Not a job title at one employer.
                          subject_capable: true
                          since: 1.0.0
                        - key: residential_address.line_1
                          label: Residential address line 1
                          type: text
                          description: >-
                            The first line of the residential address: a street
                            number and name, a unit, or a box number.
                          subject_capable: true
                          since: 1.0.0
                        - key: residential_address.line_2
                          label: Residential address line 2
                          type: text
                          description: >-
                            The second line of the residential address, when the
                            form prints one.
                          subject_capable: true
                          since: 1.0.0
                        - key: residential_address.suburb
                          label: Suburb
                          type: text
                          description: The suburb of the residential address. Not the city.
                          subject_capable: true
                          since: 1.0.0
                        - key: residential_address.city
                          label: City or town
                          type: text
                          description: The city or town of the residential address.
                          subject_capable: true
                          since: 1.0.0
                        - key: residential_address.postal_code
                          label: Postal code
                          type: text
                          description: >-
                            The four-digit postal code of the residential
                            address.
                          subject_capable: true
                          since: 1.0.0
                        - key: residential_address.province
                          label: Province
                          type: choice
                          description: >-
                            The South African province of the residential
                            address.
                          subject_capable: true
                          options:
                            - value: eastern_cape
                              label: Eastern Cape
                            - value: free_state
                              label: Free State
                            - value: gauteng
                              label: Gauteng
                            - value: kwazulu_natal
                              label: KwaZulu-Natal
                            - value: limpopo
                              label: Limpopo
                            - value: mpumalanga
                              label: Mpumalanga
                            - value: north_west
                              label: North West
                            - value: northern_cape
                              label: Northern Cape
                            - value: western_cape
                              label: Western Cape
                          since: 1.0.0
                        - key: residential_address.country
                          label: Country
                          type: text
                          description: The country of the residential address.
                          subject_capable: true
                          since: 1.0.0
                        - key: postal_address.line_1
                          label: Postal address line 1
                          type: text
                          description: >-
                            The first line of the postal address: a street
                            number and name, a unit, or a box number.
                          subject_capable: true
                          since: 1.0.0
                        - key: postal_address.line_2
                          label: Postal address line 2
                          type: text
                          description: >-
                            The second line of the postal address, when the form
                            prints one.
                          subject_capable: true
                          since: 1.0.0
                        - key: postal_address.suburb
                          label: Suburb
                          type: text
                          description: The suburb of the postal address. Not the city.
                          subject_capable: true
                          since: 1.0.0
                        - key: postal_address.city
                          label: City or town
                          type: text
                          description: The city or town of the postal address.
                          subject_capable: true
                          since: 1.0.0
                        - key: postal_address.postal_code
                          label: Postal code
                          type: text
                          description: The four-digit postal code of the postal address.
                          subject_capable: true
                          since: 1.0.0
                        - key: postal_address.province
                          label: Province
                          type: choice
                          description: The South African province of the postal address.
                          subject_capable: true
                          options:
                            - value: eastern_cape
                              label: Eastern Cape
                            - value: free_state
                              label: Free State
                            - value: gauteng
                              label: Gauteng
                            - value: kwazulu_natal
                              label: KwaZulu-Natal
                            - value: limpopo
                              label: Limpopo
                            - value: mpumalanga
                              label: Mpumalanga
                            - value: north_west
                              label: North West
                            - value: northern_cape
                              label: Northern Cape
                            - value: western_cape
                              label: Western Cape
                          since: 1.0.0
                        - key: postal_address.country
                          label: Country
                          type: text
                          description: The country of the postal address.
                          subject_capable: true
                          since: 1.0.0
                        - key: employment.employee_number
                          label: Employee number
                          type: text
                          description: >-
                            The number the employer knows the person by. Not a
                            payroll or tax reference.
                          subject_capable: true
                          since: 1.0.0
                        - key: employment.job_title
                          label: Job title
                          type: text
                          description: >-
                            The person’s position at their employer. Not their
                            form of address (that is `person.title`).
                          subject_capable: true
                          since: 1.0.0
                        - key: employment.start_date
                          label: Employment start date
                          type: date
                          description: The day the person started with this employer.
                          subject_capable: true
                          since: 1.0.0
                        - key: employment.monthly_salary
                          label: Monthly salary
                          type: number
                          description: Gross pay a month, in rand. Not an annual figure.
                          subject_capable: true
                          since: 1.0.0
                        - key: employment.annual_salary
                          label: Annual salary
                          type: number
                          description: Gross pay a year, in rand. Not a monthly figure.
                          subject_capable: true
                          since: 1.0.0
                        - key: employer.name
                          label: Employer name
                          type: text
                          description: The employer’s registered name. Not a trading name.
                          subject_capable: false
                          since: 1.0.0
                        - key: employer.trading_name
                          label: Trading name
                          type: text
                          description: >-
                            The name the employer trades under, when it differs
                            from the registered name.
                          subject_capable: false
                          since: 1.0.0
                        - key: employer.registration_number
                          label: Company registration number
                          type: text
                          description: >-
                            The employer’s CIPC registration number. Not a PAYE
                            or VAT reference.
                          subject_capable: false
                          since: 1.0.0
                        - key: employer.paye_reference_number
                          label: PAYE reference number
                          type: text
                          description: >-
                            The employer’s SARS PAYE reference number. Not a
                            person’s income tax number.
                          subject_capable: false
                          since: 1.0.0
                        - key: employer.contact_name
                          label: Employer contact person
                          type: text
                          description: >-
                            The person at the employer a form names as its
                            contact, for example someone in HR.
                          subject_capable: false
                          since: 1.0.0
                        - key: employer.contact_email
                          label: Employer contact email
                          type: email
                          description: >-
                            The employer contact’s email address. Not the
                            employee’s.
                          subject_capable: false
                          since: 1.0.0
                        - key: employer.contact_phone
                          label: Employer contact telephone
                          type: phone
                          description: >-
                            The employer contact’s telephone number. Not the
                            employee’s.
                          subject_capable: false
                          since: 1.0.0
                        - key: bank.account_holder
                          label: Account holder
                          type: text
                          description: The name the bank account is held in.
                          subject_capable: false
                          since: 1.0.0
                        - key: bank.bank_name
                          label: Bank
                          type: text
                          description: The name of the bank.
                          subject_capable: false
                          since: 1.0.0
                        - key: bank.branch_code
                          label: Branch code
                          type: text
                          description: The bank’s branch or universal branch code.
                          subject_capable: false
                          since: 1.0.0
                        - key: bank.account_number
                          label: Account number
                          type: text
                          description: The bank account number. Not a card number.
                          subject_capable: false
                          since: 1.0.0
                        - key: bank.account_type
                          label: Account type
                          type: choice
                          description: The kind of bank account.
                          subject_capable: false
                          options:
                            - value: cheque
                              label: Cheque
                            - value: savings
                              label: Savings
                            - value: transmission
                              label: Transmission
                          since: 1.0.0
                        - key: bank.debit_order_day
                          label: Debit order day
                          type: number
                          description: >-
                            The day of the month a debit order runs, from 1 to
                            31.
                          subject_capable: false
                          since: 1.0.0
                    organisation:
                      keys:
                        - key: medical_aid.plan
                          type: choice
                          templates: 3
                          labels:
                            - Plan
                            - Benefit option
                          options:
                            - classic_comprehensive
                            - classic_priority
                            - essential_smart
                        - key: medical_aid.membership_number
                          type: text
                          templates: 3
                          labels:
                            - Membership number
          headers:
            RateLimit-Limit:
              description: >-
                How many requests this key may make per minute: 600 on any paid
                plan and 60 on the free plan, the same in both environments. Per
                key, not per organisation — a second key has its own allowance.
                ⚠️ READ IT RATHER THAN ASSUMING IT: it is the number the
                database applied to this request, so it is right even when this
                description is out of date.
              schema:
                type: integer
                description: Requests permitted in the current minute.
            RateLimit-Remaining:
              description: >-
                How many of them are left, this one already counted. Zero on the
                request that is refused, and zero on every further request of
                that minute — ⚠️ a refused request still counts.
              schema:
                type: integer
                description: Requests remaining in the current minute.
            RateLimit-Reset:
              description: >-
                Seconds until the current minute closes and the allowance
                returns. At most 60, which is what makes the published
                exponential backoff converge in about six doublings rather than
                eleven.
              schema:
                type: integer
                description: Seconds until the window resets.
            ETag:
              description: >-
                The hash of this whole body: it changes when the core is
                released and whenever your templates’ keys do. Send it as
                `If-None-Match` to read again cheaply.
              schema:
                type: string
                description: A quoted SHA-256, in hex.
                examples:
                  - '"5c1f…"'
        '304':
          description: >-
            Nothing has changed since the `ETag` you sent in `If-None-Match`. No
            body.
          headers:
            RateLimit-Limit:
              description: >-
                How many requests this key may make per minute: 600 on any paid
                plan and 60 on the free plan, the same in both environments. Per
                key, not per organisation — a second key has its own allowance.
                ⚠️ READ IT RATHER THAN ASSUMING IT: it is the number the
                database applied to this request, so it is right even when this
                description is out of date.
              schema:
                type: integer
                description: Requests permitted in the current minute.
            RateLimit-Remaining:
              description: >-
                How many of them are left, this one already counted. Zero on the
                request that is refused, and zero on every further request of
                that minute — ⚠️ a refused request still counts.
              schema:
                type: integer
                description: Requests remaining in the current minute.
            RateLimit-Reset:
              description: >-
                Seconds until the current minute closes and the allowance
                returns. At most 60, which is what makes the published
                exponential backoff converge in about six doublings rather than
                eleven.
              schema:
                type: integer
                description: Seconds until the window resets.
            ETag:
              description: >-
                The hash of this whole body: it changes when the core is
                released and whenever your templates’ keys do. Send it as
                `If-None-Match` to read again cheaply.
              schema:
                type: string
                description: A quoted SHA-256, in hex.
                examples:
                  - '"5c1f…"'
        '401':
          description: >-
            `unauthenticated` — The credential was absent, unreadable, unknown,
            wrong or revoked — this answer is deliberately identical for all of
            them, so it cannot be used to probe which keys exist. Retrying the
            same request changes nothing. Check the key in Settings or issue a
            new one.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
                description: The refusal.
        '403':
          description: >-
            `forbidden` — The key is valid and the organisation’s plan does not
            grant what this operation does. Nothing about the credential needs
            to change, and it is deliberately MORE specific than 401 because the
            caller has already proved they hold the key. TWO SITUATIONS PRODUCE
            IT. (1) THE PLAN EXCLUDES THE OPERATION: `POST /api/v1/templates` on
            the Free plan, which does not include authoring templates. A
            template that already exists stays readable at `GET
            /api/v1/templates`; authoring one needs a plan that includes
            templates. (2) WE CANNOT ESTABLISH WHAT THE PLAN INCLUDES, on any
            operation, so nothing is granted. That one is ours rather than
            yours, an ordinary account does not meet it, and it means ask us.


            `insufficient_scope` — This key is scoped and does not hold the
            scope this operation requires — `x-required-scope` on the operation
            names it, and the `WWW-Authenticate` header on the refusal repeats
            it (RFC 6750 §3.1). ⚠️ Retrying cannot help and neither can editing
            the key: scopes are fixed when a key is minted and no endpoint or
            screen can widen them. Issue a new key with the access it needs. A
            key created before scopes existed carries an empty scope list, which
            means EVERY capability, so this refusal cannot reach an integration
            that was already working.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
                description: The refusal.
        '429':
          description: >-
            `rate_limited` — This key has spent its minute. The limit is per KEY
            and per minute, and it follows your PLAN rather than the kind of key
            — 600 requests a minute on every paid plan, 60 on the free plan, and
            the sandbox gets the same number as production so that an
            integration which passes in rehearsal passes live. Every successful
            authenticated response, and this one, carries `RateLimit-Limit`,
            `RateLimit-Remaining` and `RateLimit-Reset` (seconds), so a client
            can slow down before it is made to. TWO OPERATIONS ALSO ANSWER IT
            FOR A REASON OF THEIR OWN. `POST /api/v1/envelopes/batches` costs
            one request per row and is refused WHOLE when its rows do not fit
            what is left of the key’s minute — `RateLimit-Remaining` says how
            many would. `GET
            /api/v1/envelopes/{envelopeId}/documents/{position}/draft` has its
            own per-envelope and per-organisation ceilings on renders each
            minute, and the `message` says which one refused; there
            `Retry-After` is that ceiling’s, while the `RateLimit-*` headers
            still describe the key. `Retry-After` on this refusal is at most 60
            and is read from the stored window rather than computed, so two of
            our instances refusing the same key quote the same instant. ⚠️ A
            REFUSED REQUEST STILL COUNTS: hammering a limit you have already
            exceeded pushes the counter higher rather than holding it. Honour
            `Retry-After`. If one key genuinely needs more throughput, issue a
            second one in Settings — the ceiling is a fairness and blast-radius
            control, not a commercial meter.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
                description: The refusal.
          headers:
            RateLimit-Limit:
              description: >-
                How many requests this key may make per minute: 600 on any paid
                plan and 60 on the free plan, the same in both environments. Per
                key, not per organisation — a second key has its own allowance.
                ⚠️ READ IT RATHER THAN ASSUMING IT: it is the number the
                database applied to this request, so it is right even when this
                description is out of date.
              schema:
                type: integer
                description: Requests permitted in the current minute.
            RateLimit-Remaining:
              description: >-
                How many of them are left, this one already counted. Zero on the
                request that is refused, and zero on every further request of
                that minute — ⚠️ a refused request still counts.
              schema:
                type: integer
                description: Requests remaining in the current minute.
            RateLimit-Reset:
              description: >-
                Seconds until the current minute closes and the allowance
                returns. At most 60, which is what makes the published
                exponential backoff converge in about six doublings rather than
                eleven.
              schema:
                type: integer
                description: Seconds until the window resets.
            Retry-After:
              description: >-
                Seconds to wait before retrying, from the stored end of the
                window. Always present on a 429, and never more than 60. On the
                per-key limit it equals `RateLimit-Reset`; a narrower limit,
                such as the draft render allowance, has a window of its own and
                may ask for a different wait. `x-error-codes` tells you to
                honour it; this is it.
              schema:
                type: integer
                description: Seconds to wait before retrying.
        '500':
          description: >-
            `internal_error` — Ours, not yours. ⚠️ RETRY WITH THE SAME
            `Idempotency-Key` YOU SENT THE FIRST TIME, on any operation that
            takes one — that is what makes a retry safe here, and sending a NEW
            key would create a second envelope or send the same one twice. The
            safe GETs in this document can be retried freely. (This entry used
            to say every operation here was a GET; the write endpoints have
            since shipped, and the reasoning moved with them.)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
                description: The refusal.
      security:
        - ApiKey: []
components:
  schemas:
    Vocabulary:
      type: object
      description: >-
        The core vocabulary with its version, and your organisation’s own keys
        beside it.
      required:
        - core
        - organisation
      properties:
        core:
          type: object
          description: >-
            The core vocabulary. It changes only by version: a minor version
            adds, and nothing is renamed or removed.
          required:
            - version
            - subject_keys
            - keys
          properties:
            version:
              type: string
              examples:
                - 1.0.0
              description: A semantic version.
            subject_keys:
              type: array
              items:
                type: string
                description: A subject key, or its shape (`n` from 1).
              description: >-
                The subject keys the core standardises. A key’s namespace may
                never be one.
            keys:
              type: array
              items:
                $ref: '#/components/schemas/CoreKey'
                description: One core key.
              description: Every core key, by namespace.
        organisation:
          type: object
          description: >-
            Your organisation’s own keys, from its live templates. Never another
            organisation’s.
          required:
            - keys
          properties:
            keys:
              type: array
              items:
                $ref: '#/components/schemas/OrganisationKey'
                description: One of your keys.
              description: By key.
    Error:
      type: object
      description: >-
        The body of every refusal the API returns deliberately, at every status,
        from every endpoint. Such a response is a failure if and only if it
        carries an `error` key — that test is correct even for a client that
        ignores the status line, which is a thing real clients do. An unexpected
        server failure may not have this shape: treat a 5xx without an `error`
        key as an `internal_error`, which is retryable.
      properties:
        error:
          type: object
          description: The refusal. Anything added in future is added INSIDE this object.
          properties:
            code:
              type: string
              description: >-
                A stable snake_case identifier that will never change meaning or
                spelling. This is the contract; branch on it. See
                `x-error-codes` at the root of this document for retry semantics
                per code.
              enum:
                - unauthenticated
                - forbidden
                - insufficient_scope
                - not_found
                - invalid_request
                - rate_limited
                - internal_error
                - service_unavailable
                - idempotency_key_required
                - idempotency_key_reused
                - request_in_progress
                - template_archived
                - template_unusable
                - template_has_no_fields
                - template_changed
                - precondition_required
                - field_unknown
                - naming_rule_broken
                - recipient_role_unknown
                - recipient_role_missing
                - recipient_role_duplicated
                - value_subject_unknown
                - value_address_unknown
                - value_not_writable
                - value_option_unknown
                - value_selection_out_of_bounds
                - value_not_lockable
                - locked_answer_missing
                - locked_answer_unusable
                - cannot_be_drawn
                - send_allowance_exhausted
                - test_key_cannot_send
                - test_key_cannot_read_documents
                - test_key_cannot_edit_templates
                - document_too_complex
                - test_key_cannot_mint
                - webhook_url_invalid
                - webhook_events_invalid
                - webhook_brand_unknown
                - webhook_limit_reached
                - api_key_owner_removed
                - envelope_sender_removed
                - recipient_not_embedded
                - recipient_not_yet_turn
                - recipient_cannot_sign
                - envelope_not_sent
                - envelope_not_draft
                - brand_has_no_embed_origins
                - recipient_cannot_be_embedded
                - recipient_verification_unavailable
                - recipient_mobile_unused
                - recipient_verification_not_allowed
                - batch_interrupted
                - brand_unknown
                - draft_not_available
                - standing_authorisations_not_enabled
                - authorisation_text_rejected
                - authorisation_validity_invalid
                - authorisation_not_revocable
                - authorisation_not_pending
                - authorisation_resend_limited
                - authorisation_not_renewable
                - authorisation_already_renewed
                - authorisation_unknown
                - authorisation_not_granted
                - authorisation_revoked
                - authorisation_expired
                - authorisation_not_yet_valid
                - authorisation_invalidated
                - authorisation_brand_mismatch
                - authorisation_environment_mismatch
                - authorisation_role_is_witness
                - authorisation_role_not_signing
                - authorisation_recipient_altered
                - authorisation_used_twice
                - authorisation_grantor_twice
                - authorisation_field_unsupported
                - authorisation_value_forbidden
                - authorisation_value_missing
                - authorisation_value_unlocked
                - authorisation_mark_too_small
                - authorisation_mark_unplaced
                - authorisation_without_signer
                - authorisation_mark_unavailable
                - authorisation_expires_during_batch
            message:
              type: string
              description: >-
                ⚠️ PROSE FOR A DEVELOPER READING A LOG, and NOT part of the
                contract. It may be reworded in any release; anything that
                parses it is broken by design.
          required:
            - code
            - message
      required:
        - error
    CoreKey:
      type: object
      description: >-
        One key of the core vocabulary: a cross-industry key Vumasign publishes
        and never renames or removes — at most deprecated, with its replacement.
      required:
        - key
        - label
        - type
        - description
        - subject_capable
        - since
      properties:
        key:
          type: string
          examples:
            - person.sa_id_number
          description: The canonical address, `namespace.attribute`.
        label:
          type: string
          examples:
            - South African ID number
          description: What a sender reads.
        type:
          type: string
          enum:
            - text
            - date
            - number
            - email
            - phone
            - sa_id
            - choice
          description: >-
            What it holds. A `choice` is a question (or a dropdown), and its
            options are fixed by the core.
        description:
          type: string
          description: 'One sentence: what belongs here and what does not.'
        subject_capable:
          type: boolean
          description: >-
            Whether it may be combined with a subject: `person.*`, the addresses
            and `employment.*` describe a person, and with `subject` null mean
            the form’s principal; `employer.*` and `bank.*` do not.
        options:
          type: array
          description: 'For a `choice` key: its closed options. Absent otherwise.'
          items:
            type: object
            description: One option.
            required:
              - value
              - label
            properties:
              value:
                type: string
                description: The option value a question’s value names.
              label:
                type: string
                description: What a sender reads.
        since:
          type: string
          examples:
            - 1.0.0
          description: The vocabulary version that added it.
        deprecated:
          type: object
          description: >-
            Absent, or the version that deprecated it and the key that replaces
            it.
          required:
            - since
            - replacement
          properties:
            since:
              type: string
              description: The version that deprecated it.
            replacement:
              type: string
              description: The key to use instead.
    OrganisationKey:
      type: object
      description: >-
        One key your organisation uses on its live templates that is not core:
        derived, never stored, so it is always exactly what your templates say.
      required:
        - key
        - type
        - templates
        - labels
      properties:
        key:
          type: string
          examples:
            - medical_aid.plan
          description: The key, in the naming grammar.
        type:
          type: string
          description: >-
            What it holds on your templates: a field type, or `choice` for a
            question’s key.
        templates:
          type: integer
          minimum: 1
          description: How many of your live templates use it.
        labels:
          type: array
          maxItems: 5
          items:
            type: string
            description: A label printed beside a box that carries the key.
          description: >-
            The labels PRINTED beside the boxes that carry it, as the documents
            print them — never the label a sender typed — at most 5. Empty where
            the documents’ text has not been stored.
        options:
          type: array
          items:
            type: string
            description: An option value.
          description: 'For a question’s key: the option values in use on your templates.'
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer
      description: >-
        `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.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.