API Reference¶
Language Notice
This page is only available in English.
Introduction¶
The VoidSign API lets you create signature requests, track their status, and download signed documents and audit trails programmatically. It follows REST conventions: resource-oriented URLs, standard HTTP methods, and JSON-encoded request and response bodies. Use it to integrate e-signature workflows directly into your application.
Authentication
Every request must include a valid API key in the X-VoidSign-Key header. You can create and manage your API keys from your VoidSign Dashboard.
Content-Type
All request and response bodies are encoded as JSON (application/json) unless explicitly stated otherwise.
Data Types¶
The API uses standard JSON types. Some string fields carry a format qualifier shown in parentheses in the schema tables:
| Format | Description | Example |
|---|---|---|
date-time |
ISO 8601 timestamp in UTC. Always includes a UTC offset. | 2025-01-15T14:30:00+00:00 |
email |
RFC 5322 email address. | jean.dupont@example.com |
Errors¶
All errors follow a unified format with a top-level error object containing code and message fields. Some errors also include a details array. See the Errors Table for the full list of error codes and examples.
Rate Limits¶
The API enforces a token-bucket rate limit of 60 requests per minute per API key. Every response includes standard RateLimit-* headers. When the limit is exceeded, the API returns a 429 error with a Retry-After header. For full details, see the Rate Limit Headers & Policy.
Update Policy¶
The API evolves in non-breaking ways for existing integrations. For details on what this means and how to stay informed about improvements, see the API Evolution Policy.
OpenAPI Specification¶
Non-contractual specification
This file reflects the current state of the API and may change at any time as part of the non-breaking evolution policy. New endpoints, fields, and error codes can be added without notice. Do not treat it as a frozen contract. See the API Evolution Policy for details.
Getting Started¶
For a step-by-step walkthrough of creating your first signature request, choose your integration mode on Integration Modes and follow either the mail-flow tutorial or the embed-flow tutorial.
Create request¶
POST /api/v1/requests
Upload one or more PDF documents (up to 5) with positioned fields and send signature invitations to the specified signers. Each signer receives an email with a link to sign the documents.
curl
curl -X POST \
"https://api.voidsign.com/api/v1/requests" \
-H "X-VoidSign-Key: vs_live_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"auth_method": "mail",
"description": "Service agreement Q1 2025",
"documents": [
{
"base64": "JVBERi0xLjQg...",
"fields": [
{
"box_coordinate_system": "CS-ULP-GD",
"box_height_ratio": 0.1,
"box_width_ratio": 0.3,
"box_x_ratio": 0.15,
"box_y_ratio": 0.7,
"page_number": 0,
"signer_ref": 1,
"type": "signature"
}
]
}
],
"invitation_mode": "voidsign_mail",
"locales": [
"fr-FR"
],
"metadata": {
"crm_contact": "cust_8842",
"order_id": "A-123"
},
"signers": [
{
"email": "jean.dupont@example.com",
"first_name": "Jean",
"last_name": "Dupont",
"ref": 1,
"reminder_days": [
3,
7
]
}
]
}'
Request body¶
Schema: CreateRequestBody
| Field | Type | Required | Description |
|---|---|---|---|
documents |
array[ApiDocument] | Required | List of documents (1–5). Each document carries its own base64-encoded PDF and its own fields; document order is the zero-based document index. The combined decoded size of all documents must not exceed 5 MB. |
signers |
array[ApiSigner] | Required | List of signers (1–24). Each signer receives an email invitation. |
description |
string | null |
Optional description for the signature request. | |
metadata |
object[string] | null |
Free-form dictionary you own, echoed back on reads and in webhook payloads so you can correlate a VoidSign request with a record in your own system. Flat: string keys, string values only (numbers and booleans are rejected rather than coerced). At most 10 keys; keys at most 64 characters, values at most 256. Do not store personal data here. | |
auth_method |
"mail" |
Required | Channel used to deliver the one-time code the signer enters before confirming their signature. "mail" is the only value supported today; "sms" is planned. Independent of invitation_mode (which controls how the signer is invited). |
invitation_mode |
string |
Required | How participants receive their signing invitation. Required. "voidsign_mail": VoidSign sends an invitation email. "caller_embed": no invitation email is sent; the response includes a per-signer embed block with an iframe_url and expires_at so the integrator can drop the signing UI straight into their own iframe. Compatible with every supported auth_method. |
locales |
array["fr-FR"] |
Required | Languages available in the signing interface. Currently only ["fr-FR"] (French) is supported. |
locale_default |
"fr-FR" | null |
Default language for the signing interface. Required when locales contains more than one entry. Must be a value present in locales. |
Request Body Example
{
"auth_method": "mail",
"description": "Service agreement Q1 2025",
"documents": [
{
"base64": "JVBERi0xLjQg...",
"fields": [
{
"box_coordinate_system": "CS-ULP-GD",
"box_height_ratio": 0.1,
"box_width_ratio": 0.3,
"box_x_ratio": 0.15,
"box_y_ratio": 0.7,
"page_number": 0,
"signer_ref": 1,
"type": "signature"
}
]
}
],
"invitation_mode": "voidsign_mail",
"locales": [
"fr-FR"
],
"metadata": {
"crm_contact": "cust_8842",
"order_id": "A-123"
},
"signers": [
{
"email": "jean.dupont@example.com",
"first_name": "Jean",
"last_name": "Dupont",
"ref": 1,
"reminder_days": [
3,
7
]
}
]
}
Explicit over implicit
Some parameters like auth_method, invitation_mode, and locales currently accept only one value.
They are mandatory by design: when new options become available (e.g., SMS authentication,
self-managed invitations, additional locales), existing integrations will continue working without
breaking changes. We prefer requiring these parameters early to ensure explicit behavior rather
than implicit behavior.
Vos métadonnées (metadata)¶
Le champ optionnel metadata est un dictionnaire qui vous appartient : VoidSign le stocke, vous le restitue à la lecture et le renvoie dans les payloads de webhook, sans jamais l'interpréter. C'est là que vous inscrivez la correspondance entre une demande VoidSign et l'enregistrement équivalent dans votre propre système : l'identifiant de commande, la référence CRM, le numéro de dossier. Vos webhooks deviennent alors auto-suffisants : plus besoin d'un GET de rattrapage pour savoir de quel dossier il s'agit.
Deux lectures, deux instants
Un payload de webhook porte les métadonnées telles qu'elles étaient au moment où VoidSign a émis l'événement ; GET /api/v1/requests/{request_id} renvoie les métadonnées courantes. Les deux peuvent donc différer si vous avez appelé PATCH entre-temps, et c'est voulu : un événement déjà émis ne change plus, y compris lors d'un réessai de livraison. Pour les événements adossés à un e-mail, l'émission peut suivre l'envoi de l'e-mail de quelques minutes à une heure ; un PATCH dans cette fenêtre est donc inclus dans l'événement, bien que le timestamp de celui-ci précède le PATCH.
Le format est volontairement étroit :
| Contrainte | Valeur |
|---|---|
| Nombre de clés | 10 au maximum |
| Longueur d'une clé | 1 à 64 caractères |
| Longueur d'une valeur | 256 caractères au maximum |
| Type des valeurs | chaîne de caractères uniquement |
| Structure | plat, sans tableau ni objet imbriqué |
Les valeurs ne sont jamais converties
{"order_id": 123} renvoie une erreur 422 de code invalid_request : le nombre 123 n'est pas converti en "123". Il en va de même pour les booléens. Ce refus est délibéré : une conversion silencieuse modifierait votre donnée à votre insu. Sérialisez vous-même vos valeurs avant l'envoi : {"order_id": "123"}.
En cas de dépassement d'une limite, la réponse 422 porte un tableau details dont le champ field désigne la clé fautive.
N'y stockez aucune donnée personnelle
Les métadonnées ne sont pas chiffrées à votre usage exclusif, elles circulent en clair dans le corps de vos webhooks et sont visibles par nos équipes support. N'y placez ni nom, ni adresse e-mail, ni numéro de téléphone, ni aucune autre donnée à caractère personnel : utilisez un identifiant opaque qui ne prend son sens que dans votre propre système.
Responses¶
| Status | Description | Schema |
|---|---|---|
201 |
Successful Response | CreateRequestResponse |
400 |
Bad request (anchor tag not found in PDF, duplicate anchor tag in PDF). | |
401 |
Missing or invalid API key. | |
403 |
API key does not have permission for this operation. | |
422 |
Validation error (invalid base64, invalid PDF, duplicate signer refs, unknown signer_ref in field, PDF too large, PDF with fillable form fields other than empty signature fields, page number out of bounds, date fields in multi-signer requests, conflicting signer info, two signers with the same name, telephone not yet supported). | |
429 |
Rate limit exceeded. | |
500 |
Internal server error (company not found). |
201 Response example
{
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"signers": [
{
"email": "jean.dupont@example.com",
"embed": {
"expires_at": "2026-05-25T14:00:00+00:00",
"iframe_url": "https://app.voidsign.com/sign/embed/eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
},
"signer_id": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
"status": "pending"
}
],
"status": "created"
}
Get request status¶
GET /api/v1/requests/{request_id}
Retrieve the current status of a signature request, including per-signer details and timestamps.
curl
Parameters¶
| Name | In | Type | Required | Description |
|---|---|---|---|---|
request_id |
path | string |
Required | Unique identifier of the signature request. |
expand |
query | array[string] |
Optional related data to include. Repeat the parameter or comma-separate: expand=events&expand=emails or expand=events,emails. email, mail and mails are accepted as aliases of emails; the response always uses the canonical emails field. |
Inclure les événements et les e-mails (expand)¶
Par défaut, cet endpoint renvoie un résumé : statut de la demande, horodatages et tableau des signataires. Le paramètre de requête optionnel expand permet d'y ajouter, à la demande, la chronologie des événements de la demande et la liste des e-mails que VoidSign a envoyés en votre nom. C'est le moyen, au moment du polling, de répondre à « l'invitation est-elle réellement partie, et quand ? » sans quitter l'API.
Deux syntaxes sont acceptées, indifféremment :
GET /api/v1/requests/{request_id}?expand=events&expand=emails
GET /api/v1/requests/{request_id}?expand=events,emails
Les segments vides sont ignorés et les doublons sont dédupliqués. La comparaison est sensible à la casse : ?expand=EVENTS renvoie une erreur 422 de code invalid_request, avec un details[].field valant expand. L'appel ne consomme qu'un seul jeton de limite de débit, que expand soit utilisé ou non.
null n'est pas []
Les champs events et emails sont toujours présents dans la réponse. null signifie « non demandé » ; un tableau vide signifie « demandé, mais il n'y a rien à montrer », ce qui peut arriver à emails mais jamais à events, qui porte au minimum l'événement created de la demande. Le schéma de réponse est donc unique et votre client n'a aucun branchement à faire.
Les champs event_type et email_type sont des énumérations fermées : les valeurs possibles (14 pour event_type, 6 pour email_type) sont énumérées dans le schéma de la réponse, et l'ajout d'une valeur fera l'objet d'une évolution annoncée de l'API. La liste des valeurs est décrite dans Événements de la demande.
Trois des valeurs de event_type (created, signer_signed et completed) décrivent l'état de la demande plutôt qu'un envoi : elles n'ont ni email_id ni link_expires_at, et leur occurred_at est respectivement le created_at de la demande, le signed_at du signataire et le signed_at de la demande.
Le champ email_id sert de clé de jointure entre les deux tableaux : un événement adossé à un e-mail porte l'email_id de la ligne correspondante de emails, et son occurred_at est égal au sent_at de cet e-mail. Seuls les e-mails effectivement remis au fournisseur d'envoi apparaissent dans emails ; un envoi en échec n'apparaît ni dans emails ni dans events. L'e-mail de code à usage unique (OTP) de signature n'y figure pas : il ne génère aucun événement de demande, et ce tableau est construit à partir des événements.
null n'est pas {}¶
Le champ metadata est toujours présent dans la réponse, et il n'est pas conditionné par expand : il accompagne systématiquement la demande.
| Valeur renvoyée | Signification |
|---|---|
null |
Aucune métadonnée n'a jamais été définie sur cette demande |
{} |
Des métadonnées ont été définies puis explicitement vidées |
{ ... } |
Vos clés, telles que vous les avez envoyées |
Cette distinction est contractuelle et se retrouve à l'écriture : PATCH accepte {} (qui vide le dictionnaire) mais refuse null, qui reste réservé à l'état « jamais défini ».
Responses¶
| Status | Description | Schema |
|---|---|---|
200 |
Successful Response | GetRequestResponse |
401 |
Missing or invalid API key. | |
403 |
Signature request belongs to a different company. | |
404 |
Signature request not found. | |
422 |
Validation error (unknown expand value). |
|
429 |
Rate limit exceeded. |
200 Response example
{
"created_at": "2025-01-15T10:00:00+00:00",
"description": "Service agreement Q1 2025",
"emails": [
{
"email_id": "c3d4e5f6-a7b8-9012-cdef-234567890abc",
"email_type": "invite",
"sent_at": "2025-01-15T10:00:03+00:00",
"to_signer_id": "b1c2d3e4-f5a6-7890-abcd-ef1234567890"
}
],
"events": [
{
"email_id": "c3d4e5f6-a7b8-9012-cdef-234567890abc",
"event_type": "invite_sent",
"link_expires_at": "2025-02-14T10:00:03+00:00",
"occurred_at": "2025-01-15T10:00:03+00:00",
"signer_id": "b1c2d3e4-f5a6-7890-abcd-ef1234567890"
}
],
"metadata": {
"crm_contact": "cust_8842",
"order_id": "A-123"
},
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"signed_at": null,
"signers": [
{
"email": "jean.dupont@example.com",
"first_name": "Jean",
"last_name": "Dupont",
"signed_at": "2025-01-15T14:30:00+00:00",
"signer_id": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
"status": "signed"
}
],
"status": "created"
}
Update request¶
PATCH /api/v1/requests/{request_id}
Update the customer-owned fields of a signature request: description and metadata. Only the fields present in the body are touched, and metadata is replaced wholesale. Allowed in any status, including completed and cancelled requests — these fields are yours, and are not part of the signed artefact.
curl
Parameters¶
| Name | In | Type | Required | Description |
|---|---|---|---|---|
request_id |
path | string |
Required | Unique identifier of the signature request. |
Request body¶
Schema: PatchRequestBody
| Field | Type | Required | Description |
|---|---|---|---|
description |
string | null |
New description. Omit the field to leave it unchanged; send null to clear it. |
|
metadata |
object[string] | null |
Free-form dictionary you own, echoed back on reads and in webhook payloads so you can correlate a VoidSign request with a record in your own system. Flat: string keys, string values only (numbers and booleans are rejected rather than coerced). At most 10 keys; keys at most 64 characters, values at most 256. Do not store personal data here. Replaces the whole dictionary: send {} to clear it. Omit the field to leave it unchanged. null is rejected — it is the never set state and is not writable. |
Request Body Example
Ce qui est modifié, et ce qui ne l'est pas¶
Cet endpoint ne touche qu'aux deux champs qui vous appartiennent : description et metadata. Il ne modifie ni les documents, ni les signataires, ni le statut de la demande.
La règle tient en une phrase : un champ absent du corps de la requête n'est pas modifié. L'absence et la valeur null sont donc deux choses différentes, et le tableau ci-dessous est l'intégralité du contrat :
| Corps envoyé | Effet |
|---|---|
{"metadata": {"a": "1"}} |
Remplace tout le dictionnaire ; description inchangée |
{"metadata": {}} |
Vide le dictionnaire (la lecture renverra {}, pas null) |
{"metadata": null} |
Erreur 422 : null n'est pas une valeur inscriptible |
{"description": "Avenant"} |
Modifie le libellé ; metadata inchangées |
{"description": null} |
Efface le libellé ; metadata inchangées |
{} |
Erreur 422 : au moins un des deux champs doit être présent |
metadata est remplacé, jamais fusionné
Une clé absente du dictionnaire envoyé disparaît. Pour ajouter une clé à des métadonnées existantes, lisez-les d'abord (GET), fusionnez côté client, puis renvoyez le dictionnaire complet.
Modifiable à tout moment, y compris après signature
PATCH est accepté quel que soit le statut de la demande, signée ou annulée comprises. C'est possible précisément parce que ces deux champs ne font pas partie de l'artefact signé : ils n'apparaissent ni dans le PDF signé, ni dans le dossier de preuve. Les modifier n'altère donc en rien la valeur probante de la signature.
Sans effet sur les webhooks déjà émis
Un PATCH ne réécrit pas le passé : un événement déjà émis conserve les métadonnées qu'il portait au moment de son émission, réessais de livraison compris ; seuls les événements émis après le PATCH portent la nouvelle valeur. Deux conséquences concrètes : un webhook reçu après votre PATCH peut encore porter l'ancienne valeur s'il a été émis avant celui-ci ; et pour les trois événements adossés à un e-mail, un webhook émis après votre PATCH porte la nouvelle valeur même si son timestamp (l'envoi de l'e-mail) précède le PATCH. Voir Événements webhook.
La réponse a exactement la même forme que celle de Get request status, avec les tableaux events et emails toujours à null (cet endpoint n'accepte pas de paramètre expand).
Responses¶
| Status | Description | Schema |
|---|---|---|
200 |
Successful Response | GetRequestResponse |
401 |
Missing or invalid API key. | |
403 |
Signature request belongs to a different company. | |
404 |
Signature request not found. | |
422 |
Validation error (empty body, metadata: null, or metadata limits exceeded). |
|
429 |
Rate limit exceeded. |
200 Response example
{
"created_at": "2025-01-15T10:00:00+00:00",
"description": "Service agreement Q1 2025",
"emails": [
{
"email_id": "c3d4e5f6-a7b8-9012-cdef-234567890abc",
"email_type": "invite",
"sent_at": "2025-01-15T10:00:03+00:00",
"to_signer_id": "b1c2d3e4-f5a6-7890-abcd-ef1234567890"
}
],
"events": [
{
"email_id": "c3d4e5f6-a7b8-9012-cdef-234567890abc",
"event_type": "invite_sent",
"link_expires_at": "2025-02-14T10:00:03+00:00",
"occurred_at": "2025-01-15T10:00:03+00:00",
"signer_id": "b1c2d3e4-f5a6-7890-abcd-ef1234567890"
}
],
"metadata": {
"crm_contact": "cust_8842",
"order_id": "A-123"
},
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"signed_at": null,
"signers": [
{
"email": "jean.dupont@example.com",
"first_name": "Jean",
"last_name": "Dupont",
"signed_at": "2025-01-15T14:30:00+00:00",
"signer_id": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
"status": "signed"
}
],
"status": "created"
}
Refresh iframe token¶
POST /api/v1/requests/{request_id}/signers/{signer_id}/iframe_token
Mint a fresh sign_api token for a signer in a request that was created with invitation_mode="caller_embed". Previously-minted tokens are not invalidated; each one lives until its own JWT exp claim. Use this to resume a signing session hours/days after request creation or to recover from an expired token without recreating the request.
curl
Parameters¶
| Name | In | Type | Required | Description |
|---|---|---|---|---|
request_id |
path | string |
Required | Unique identifier of the signature request. |
signer_id |
path | string |
Required | Canonical identifier of the signer. |
Responses¶
| Status | Description | Schema |
|---|---|---|
201 |
Successful Response | RefreshIframeTokenResponse |
400 |
The session is not refreshable: the signature request has already been fully signed (signature_request_already_signed), the signer has already signed (signer_already_signed), the signer has been cancelled (signer_already_cancelled), the signer's turn has not started (signer_not_active_yet), or the signature request was cancelled (signature_request_cancelled). |
|
401 |
Missing or invalid API key. | |
403 |
Signature request belongs to a different company. | |
404 |
Signature request or signer not found. | |
422 |
The request was not created with invitation_mode="caller_embed" (iframe_token_not_supported). |
|
429 |
Rate limit exceeded. |
201 Response example
Download signed PDF¶
GET /api/v1/requests/{request_id}/downloads/signed
Download the final signed document(s). The request must be fully signed (all signers have completed). The response payload depends on how many documents the request holds: a single-document request streams the lone signed PDF (application/pdf); a multi-document request streams a ZIP archive (application/zip) with one signed PDF entry per document. Inspect the Content-Type header to distinguish the two.
curl
Parameters¶
| Name | In | Type | Required | Description |
|---|---|---|---|---|
request_id |
path | string |
Required | Unique identifier of the signature request. |
Responses¶
| Status | Description | Schema |
|---|---|---|
200 |
The signed document(s). application/pdf when the request holds a single document; application/zip (one signed PDF per document) when it holds multiple documents. |
|
400 |
Signature request is not fully signed yet. | |
401 |
Missing or invalid API key. | |
403 |
Signature request belongs to a different company. | |
404 |
Signature request or signed file not found. | |
422 |
Validation error. | |
429 |
Rate limit exceeded. |
200 Response
The response body contains the signed document(s). A single-document request returns the lone signed PDF (application/pdf); a multi-document request returns a ZIP archive (application/zip) with one signed PDF per document. Inspect the Content-Type header to distinguish the two. Only the multi-document ZIP response sets a Content-Disposition header with a suggested filename; the single-PDF response does not, so choose your own filename in that case.
400 Response example
Download audit trail¶
GET /api/v1/requests/{request_id}/downloads/audit-trail/{format}
Download the audit trail for a fully signed request. Available in JSON or PDF format.
curl
Parameters¶
| Name | In | Type | Required | Description |
|---|---|---|---|---|
request_id |
path | string |
Required | Unique identifier of the signature request. |
format |
path | string |
Required | Output format: json or pdf. |
Responses¶
| Status | Description | Schema |
|---|---|---|
200 |
Successful Response | |
400 |
Signature request is not fully signed yet. | |
401 |
Missing or invalid API key. | |
403 |
Signature request belongs to a different company. | |
404 |
Signature request not found. | |
422 |
Validation error (format must be json or pdf). |
|
429 |
Rate limit exceeded. |
200 Response
The response body contains the audit trail file. Format depends on the format path parameter: application/json for JSON, application/pdf for PDF. Only the JSON response sets a Content-Disposition header with a suggested filename; the PDF response does not.
400 Response example
Annex¶
Schemas¶
CreateRequestBody¶
| Field | Type | Required | Description |
|---|---|---|---|
documents |
array[ApiDocument] | Required | List of documents (1–5). Each document carries its own base64-encoded PDF and its own fields; document order is the zero-based document index. The combined decoded size of all documents must not exceed 5 MB. |
signers |
array[ApiSigner] | Required | List of signers (1–24). Each signer receives an email invitation. |
description |
string | null |
Optional description for the signature request. | |
metadata |
object[string] | null |
Free-form dictionary you own, echoed back on reads and in webhook payloads so you can correlate a VoidSign request with a record in your own system. Flat: string keys, string values only (numbers and booleans are rejected rather than coerced). At most 10 keys; keys at most 64 characters, values at most 256. Do not store personal data here. | |
auth_method |
"mail" |
Required | Channel used to deliver the one-time code the signer enters before confirming their signature. "mail" is the only value supported today; "sms" is planned. Independent of invitation_mode (which controls how the signer is invited). |
invitation_mode |
string |
Required | How participants receive their signing invitation. Required. "voidsign_mail": VoidSign sends an invitation email. "caller_embed": no invitation email is sent; the response includes a per-signer embed block with an iframe_url and expires_at so the integrator can drop the signing UI straight into their own iframe. Compatible with every supported auth_method. |
locales |
array["fr-FR"] |
Required | Languages available in the signing interface. Currently only ["fr-FR"] (French) is supported. |
locale_default |
"fr-FR" | null |
Default language for the signing interface. Required when locales contains more than one entry. Must be a value present in locales. |
Used by: create-request
ApiDocument¶
| Field | Type | Required | Description |
|---|---|---|---|
base64 |
string |
Required | Base64-encoded PDF content. The combined decoded size of all documents in the request must not exceed 5 MB. |
fields |
array[ApiSfieldSignature | ApiSfieldSignatureACPGD | ApiSfieldText | ApiSfieldParaph | ApiSfieldDate] | Required | List of fields (signature, paraph, text, date) to place on the document. |
Used by: create-request
ApiSfieldSignature¶
Signature field positioned using the CS-ULP-GD coordinate system (upper-left point + given dimensions).
| Field | Type | Required | Description |
|---|---|---|---|
type |
"signature" |
Required | Field type. Must be signature. |
box_coordinate_system |
"CS-ULP-GD" |
Required | Coordinate system. Must be CS-ULP-GD (upper-left point + given dimensions). |
box_x_ratio |
number (0–1) |
Required | X position of the upper-left corner as a ratio (0.0-1.0) of page width. |
box_y_ratio |
number (0–1) |
Required | Y position of the upper-left corner as a ratio (0.0-1.0) of page height. |
box_width_ratio |
number (0–1) |
Required | Width of the signature box as a ratio (0.0-1.0) of page width. |
box_height_ratio |
number (0–1) |
Required | Height of the signature box as a ratio (0.0-1.0) of page height. |
page_number |
integer | "all" |
Required | Zero-based page number, or "all" to place on every page. |
signer_ref |
integer |
Required | Reference number of the signer who must sign this field. Must match a signer ref in the request. |
Used by: create-request
ApiSfieldSignatureACPGD¶
| Field | Type | Required | Description |
|---|---|---|---|
type |
"signature" |
Required | Field type. Must be signature. |
box_coordinate_system |
"CS-ACP-GD" |
Required | Coordinate system. Must be CS-ACP-GD (anchor center point + given dimensions). |
box_center_anchor_tag |
string |
Required | Name of the hidden text anchor embedded in the PDF. Must start with VS_ANCH_. |
box_width_ratio |
number |
Required | Width of the signature box as a ratio (0.0-1.0) of page width. |
box_height_ratio |
number |
Required | Height of the signature box as a ratio (0.0-1.0) of page height. |
signer_ref |
integer |
Required | Reference number of the signer who must sign this field. Must match a signer ref in the request. |
Used by: create-request
ApiSfieldText¶
Static text field positioned using the CS-CP-AD coordinate system (center point + auto-derived dimensions).
| Field | Type | Required | Description |
|---|---|---|---|
type |
"text" |
Required | Field type. Must be text. |
box_coordinate_system |
"CS-CP-AD" |
Required | Coordinate system. Must be CS-CP-AD (center point + auto-derived dimensions). |
box_center_x_ratio |
number (0–1) |
Required | X position of the center point as a ratio (0.0-1.0) of page width. |
box_center_y_ratio |
number (0–1) |
Required | Y position of the center point as a ratio (0.0-1.0) of page height. |
page_number |
integer |
Required | Zero-based page number where the field is placed. |
text |
string |
Required | Text content to display in the field. |
font_size |
number |
Required | Font size in points. |
Used by: create-request
ApiSfieldParaph¶
Paraph (initials) field positioned using the CS-CP-AD coordinate system (center point + auto-derived dimensions).
| Field | Type | Required | Description |
|---|---|---|---|
type |
"paraph" |
Required | Field type. Must be paraph. |
box_coordinate_system |
"CS-CP-AD" |
Required | Coordinate system. Must be CS-CP-AD (center point + auto-derived dimensions). |
box_center_x_ratio |
number (0–1) |
Required | X position of the center point as a ratio (0.0-1.0) of page width. |
box_center_y_ratio |
number (0–1) |
Required | Y position of the center point as a ratio (0.0-1.0) of page height. |
page_number |
integer | "all" |
Required | Zero-based page number, or "all" to place on every page. |
signer_ref |
integer |
Required | Reference number of the signer who must initial this field. Must match a signer ref in the request. |
font_size |
number |
Required | Font size in points. |
Used by: create-request
ApiSfieldDate¶
Automatic date field positioned using the CS-CP-AD coordinate system (center point + auto-derived dimensions).
| Field | Type | Required | Description |
|---|---|---|---|
type |
"date" |
Required | Field type. Must be date. |
box_coordinate_system |
"CS-CP-AD" |
Required | Coordinate system. Must be CS-CP-AD (center point + auto-derived dimensions). |
box_center_x_ratio |
number (0–1) |
Required | X position of the center point as a ratio (0.0-1.0) of page width. |
box_center_y_ratio |
number (0–1) |
Required | Y position of the center point as a ratio (0.0-1.0) of page height. |
page_number |
integer | "all" |
Required | Zero-based page number, or "all" to place on every page. |
font_size |
number |
Required | Font size in points. |
Used by: create-request
ApiSigner¶
| Field | Type | Required | Description |
|---|---|---|---|
ref |
integer |
Required | Reference number for this signer within the request. Used by signature/paraph fields to indicate which signer they belong to. |
email |
string (email) |
Required | Email address of the signer. An invitation will be sent to this address. |
first_name |
string |
Required | First name of the signer. |
last_name |
string |
Required | Last name of the signer. |
telephone |
string | null |
Personal mobile phone number in E.164 format. Only French mobile numbers are accepted (+336… or +337…, e.g. +33612345678). Reserved for future use; providing a value will return a 422 error. | |
reminder_days |
array[integer] | null |
Days after creation to send reminder emails. Example: [3, 7] sends reminders on day 3 and day 7. |
Used by: create-request
CreateRequestResponse¶
| Field | Type | Required | Description |
|---|---|---|---|
request_id |
string |
Required | Unique identifier for the created signature request. |
status |
"created" |
Required | Status of the request. Always created on success. |
signers |
array[SignerStatusResponse] | Required | List of signers with their initial status. |
Used by: create-request
SignerStatusResponse¶
| Field | Type | Required | Description |
|---|---|---|---|
signer_id |
string |
Required | Canonical identifier of the signer (use this when calling per-signer endpoints). |
email |
string (email) |
Required | Email address of the signer. |
status |
string |
Required | Current signing status. |
embed |
SignerEmbedTokenResponse | null | Present only when the request was created with invitation_mode="caller_embed". Carries the initial iframe_url and its expiry — refresh via POST /requests/{request_id}/signers/{signer_id}/iframe_token. |
Used by: create-request
SignerEmbedTokenResponse¶
| Field | Type | Required | Description |
|---|---|---|---|
iframe_url |
string |
Required | Ready-to-embed signing URL bound to this (request_id, signer_id) pair. Drop it straight into <iframe src="../...">. The URL wraps a short-lived JWT (~2 hours) and uses the /sign/embed/ prefix that nginx routes through the iframe-deliverability gate. |
expires_at |
string (date-time) |
Required | ISO 8601 UTC timestamp when the underlying token (and therefore the iframe_url) expires. |
Used by: create-request
GetRequestResponse¶
| Field | Type | Required | Description |
|---|---|---|---|
request_id |
string |
Required | Unique identifier of the signature request. |
status |
string |
Required | Overall request status. |
description |
string | null |
Required | Description provided when creating the request. |
metadata |
object[string] | null |
Required | Your metadata dictionary. Always present, and NOT behind expand: null means never set, {} means explicitly set to empty. |
created_at |
string (date-time) | null |
Required | ISO 8601 UTC timestamp when the request was created. |
signed_at |
string (date-time) | null |
Required | ISO 8601 UTC timestamp when all signers completed, or null. |
signers |
array[GetRequestSignerResponse] | Required | List of signers with their detailed status. |
events |
array[GetRequestEventResponse] | null | Present when expand=events was requested; null otherwise. Never empty when requested: every request carries at least its created event. |
|
emails |
array[GetRequestEmailResponse] | null | Present when expand=emails was requested; null otherwise. Empty list when no email has been sent for the request yet. |
Used by: get-request-status, patch-request
GetRequestSignerResponse¶
| Field | Type | Required | Description |
|---|---|---|---|
signer_id |
string |
Required | Canonical identifier of the signer (use this when calling per-signer endpoints). |
email |
string (email) |
Required | Email address of the signer. |
first_name |
string |
Required | First name of the signer. |
last_name |
string |
Required | Last name of the signer. |
status |
string |
Required | Signing status. |
signed_at |
string (date-time) | null |
Required | ISO 8601 UTC timestamp when the signer signed, or null if not yet signed. |
Used by: get-request-status, patch-request
GetRequestEventResponse¶
| Field | Type | Required | Description |
|---|---|---|---|
event_type |
string |
Required | Type of the event. |
occurred_at |
string (date-time) |
Required | ISO 8601 UTC timestamp of the event. For email-backed events this is the moment the email was accepted by the delivery provider — the same instant reported by the matching webhook. For created, signer_signed and completed it is, respectively, the request's creation time, that signer's signed_at and the request's signed_at. |
signer_id |
string | null |
Required | Signer this event relates to, or null for request-level events, which includes created and completed. |
email_id |
string | null |
Required | Identifier of the email backing this event, or null when the event is not email-backed. Join key into the emails array. |
link_expires_at |
string (date-time) | null |
Required | ISO 8601 UTC timestamp when the signing link carried by this event expires, or null for events that carry no link. |
Used by: get-request-status, patch-request
GetRequestEmailResponse¶
| Field | Type | Required | Description |
|---|---|---|---|
email_id |
string |
Required | Unique identifier of the email. Matches email_id on the events it backs. |
email_type |
string |
Required | Type of the email. |
to_signer_id |
string | null |
Required | Signer this email was addressed to, or null when the recipient is not a signer (completion and partial-signature notices go to your team). |
sent_at |
string (date-time) |
Required | ISO 8601 UTC timestamp when the email was accepted by the delivery provider. |
Used by: get-request-status, patch-request
PatchRequestBody¶
| Field | Type | Required | Description |
|---|---|---|---|
description |
string | null |
New description. Omit the field to leave it unchanged; send null to clear it. |
|
metadata |
object[string] | null |
Free-form dictionary you own, echoed back on reads and in webhook payloads so you can correlate a VoidSign request with a record in your own system. Flat: string keys, string values only (numbers and booleans are rejected rather than coerced). At most 10 keys; keys at most 64 characters, values at most 256. Do not store personal data here. Replaces the whole dictionary: send {} to clear it. Omit the field to leave it unchanged. null is rejected — it is the never set state and is not writable. |
Used by: patch-request
RefreshIframeTokenResponse¶
| Field | Type | Required | Description |
|---|---|---|---|
iframe_url |
string |
Required | Freshly minted ready-to-embed signing URL (~2 hour lifetime). Drop it straight into <iframe src="../..."> to resume the signing session. |
expires_at |
string (date-time) |
Required | ISO 8601 UTC timestamp when the new iframe_url expires. |
Used by: refresh-iframe-token
Errors Table¶
All API errors share a consistent JSON envelope:
Request errors (400/404): business-logic and resource errors:
{
"error": {
"code": "signature_request_not_completed",
"message": "Signature request is not fully signed yet"
}
}
Authentication errors (401/403): missing, invalid, or unauthorized API key:
Validation errors (422): a request body or a query parameter failed validation. Both share the invalid_request code, but the message differs by source.
A request body that failed schema validation:
{
"error": {
"code": "invalid_request",
"message": "Request body failed validation",
"details": [
{
"field": "signers[0].email",
"message": "value is not a valid email address: An email address must have an @-sign."
}
]
}
}
A query parameter that failed validation:
{
"error": {
"code": "invalid_request",
"message": "Invalid request",
"details": [
{
"field": "expand",
"message": "unknown expand value 'bogus'. Allowed values are: events, emails"
}
]
}
}
Server errors (500): unexpected internal failures:
400: Bad Request¶
| Code | Default message |
|---|---|
anchor_tag_not_found |
Anchor tag was not found in the PDF document |
anchor_tag_duplicate |
Anchor tag appears multiple times in the PDF document |
signer_already_signed |
This signer has already signed |
signer_already_cancelled |
This signer has been cancelled |
signer_not_active_yet |
This signer's turn has not yet started |
signature_request_not_completed |
Signature request is not fully signed yet |
signature_request_already_signed |
Signature request has already been fully signed |
signature_request_cancelled |
Signature request has been cancelled |
401: Unauthorized¶
| Code | Default message |
|---|---|
missing_api_key |
Missing API key |
invalid_api_key |
Invalid API key |
api_key_revoked |
API key has been revoked |
403: Forbidden¶
| Code | Default message |
|---|---|
api_feature_disabled |
API feature is not enabled for this company |
not_owned_resource |
Signature request belongs to a different company |
billing_wall_blocked |
This action is blocked by the billing wall; configure billing/payment or contact support |
404: Not Found¶
| Code | Default message |
|---|---|
signature_request_not_found |
Signature request not found |
signed_pdf_not_found |
Signed PDF not found |
422: Validation Error¶
| Code | Default message |
|---|---|
invalid_request |
Invalid request |
invalid_base64 |
Invalid base64 encoding for document |
invalid_pdf |
Document is not a valid PDF |
pdf_too_large |
PDF exceeds maximum allowed size |
pdf_has_form_fields |
PDF contains fillable form fields |
pdf_has_signatures |
PDF already carries an electronic signature. Pre-signed documents are not accepted through the API by default; to accept them, enable the option "Documents déjà signés via l'API" in your VoidSign settings (Paramètres > Champs): the existing signatures are then removed at upload and recorded in the audit trail |
duplicate_signer_refs |
Duplicate signer refs |
duplicate_signer_email |
The same signer cannot appear more than once in a request |
invalid_signer_refs |
Signer refs are out of valid range |
conflicting_signer_info |
Same signer email appears multiple times with different contact information |
signer_info_mismatch |
A signer with this email already exists with different contact information |
invalid_signer_name |
Signer first_name and last_name must be non-empty and at most 255 characters |
duplicate_signer_name |
Two signers share the same name. Same-name signers are not supported through the API yet; create this request from the VoidSign platform or contact support |
telephone_not_supported |
The telephone field is not yet supported |
unknown_signer_ref |
Field references an unknown signer ref |
signer_missing_signature |
Signer has no associated signature field |
date_field_multi_signer |
Date fields are not supported in multi-signer requests |
page_number_out_of_bounds |
Field page number exceeds document page count |
invalid_locale_default |
locale_default is required when locales contains more than one entry, and must be a value present in locales |
invalid_reminder_days |
reminder_days must be a list of distinct, strictly positive integers |
iframe_token_not_supported |
The iframe-token endpoint is only available for signers in a request created with invitation_mode="caller_embed" |
500: Server Error¶
| Code | Default message |
|---|---|
company_not_found |
Company not found for this API key |
internal_server_error |
Internal server error |
What's Next¶
Now that you're familiar with the API surface, here are some useful next steps:
- Integration Modes: compare the mail and embed flows, then jump into the mail-flow tutorial or the embed-flow tutorial.
- Set Up Webhooks: receive real-time notifications when signature requests change status.
- API Evolution Policy: stay informed about new endpoints, fields, and improvements.
API Updates
The VoidSign API evolves in non-breaking ways. Your existing integration will keep working as we add new capabilities. See the API Evolution Policy to learn more.