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"
],
"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. | |
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"
],
"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.
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, page number out of bounds, date fields in multi-signer requests, conflicting signer info, 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. |
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. | |
429 |
Rate limit exceeded. | |
422 |
Validation Error |
200 Response example
{
"created_at": "2025-01-15T10:00:00Z",
"description": "Service agreement Q1 2025",
"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:00Z",
"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. | |
429 |
Rate limit exceeded. | |
422 |
Validation Error |
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. | |
429 |
Rate limit exceeded. | |
422 |
Validation Error |
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. | |
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. |
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. |
Used by: get-request-status
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
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): request body 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"
}
]
}
}
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 |
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 |
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.