Skip to content

Signature Requests

GET /v1/signature_requests

Returns a paginated list of signature requests for your organization.

Query parameters:

ParameterTypeDescription
pageintegerPage number (default: 1)
per_pageintegerResults per page (default: 20, max: 100)

Response:

{
"signature_requests": [
{
"id": "sr_123",
"title": "Service Agreement",
"status": "sent",
"is_complete": false,
"is_declined": false,
"signing_order": "everyone",
"test_mode": false,
"metadata": {},
"files_url": "/v1/signature_requests/sr_123/files",
"form_data": {},
"created_at": "2026-03-04T10:00:00Z",
"expires_at": null,
"signers": [
{
"id": "sig_456",
"name": "Jane Smith",
"email": "jane@example.com",
"status": "pending",
"order": 0,
"signed_at": null,
"viewed_at": null
}
]
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total_pages": 1,
"total_count": 1
}
}

Signature request statuses:

StatusDescription
draftCreated but not yet sent
sentSent to signers, awaiting signatures
completedAll signers have signed
cancelledCancelled by the sender
expiredPast the expires_at date without completion

GET /v1/signature_requests/:id

Returns a single signature request with signer details.


POST /v1/signature_requests

Send a document for signature. You can either upload a PDF file (multipart/form-data) or provide markdown content in the JSON body — see Signing a markdown document below.

Parameters:

ParameterTypeDescription
filefilePDF file to sign (required unless data.content is provided)
dataJSONRequest data (required, see below)

Data fields:

FieldTypeDefaultDescription
titlestring"Untitled"Document title
contentstringMarkdown source to render as the document (alternative to file)
messagestringMessage included in signing emails
signersarrayrequiredSigner objects (see below)
fieldsarrayField placement objects (not needed when using content with inline tokens)
import_fieldsbooleanfalseAuto-detect AcroForm fields from the uploaded PDF and assign them to the first signer
signing_orderstring"everyone""everyone" or "one_at_a_time"
expires_atstringISO 8601 expiration date
test_modebooleanfalseNon-binding test mode
metadataobjectArbitrary key-value data

Signer object:

FieldTypeDescription
namestringSigner’s full name (required)
emailstringSigner’s email address (required)
phone_numberstringSigner’s phone number
orderintegerSigning order (required for one_at_a_time)
rolestringRole key used to match inline markdown tokens (e.g. signer1)

Maximum signers per document: 10 for accounts less than 30 days old, 50 for established accounts.

Field object:

FieldTypeDescription
typestringsignature, initials, date, text, or checkbox (required)
signer_indexintegerIndex into the signers array (required)
pageintegerPage number, 1-based (required)
xnumberX position in points (required)
ynumberY position in points (required)
widthnumberWidth in points (required)
heightnumberHeight in points (required)
labelstringLabel shown to signer
mapping_keystringKey for extracting the value into form_data
requiredbooleanWhether field must be filled (default: true)

Example:

Terminal window
curl -X POST https://api.visisign.app/v1/signature_requests \
-H "Authorization: Bearer vsk_your_key_here" \
-F "file=@contract.pdf" \
-F 'data={
"title": "Service Agreement",
"message": "Please review and sign this agreement.",
"signers": [
{"name": "Jane Smith", "email": "jane@example.com"}
],
"fields": [
{
"type": "signature",
"signer_index": 0,
"page": 1,
"x": 100,
"y": 650,
"width": 200,
"height": 50
}
]
}'

Error responses:

StatusDescription
402Insufficient wallet balance (API plan)
422Validation error (e.g. missing required fields, max signers per document exceeded)
429Hourly/daily send limit or recipient rate limit exceeded

Response (201):

{
"signature_request": {
"id": "sr_789",
"title": "Service Agreement",
"status": "sent",
"is_complete": false,
"is_declined": false,
"message": "Please review and sign this agreement.",
"signing_order": "everyone",
"test_mode": false,
"metadata": {},
"files_url": "/v1/signature_requests/sr_789/files",
"form_data": {},
"created_at": "2026-03-04T12:00:00Z",
"expires_at": null,
"signers": [
{
"id": "sig_101",
"name": "Jane Smith",
"email": "jane@example.com",
"status": "pending",
"order": 0,
"signed_at": null,
"viewed_at": null,
"signing_url": "https://app.visisign.app/sign/tok_abc123",
"embed_url": "https://app.visisign.app/embed/sign/tok_abc123"
}
]
}
}

The signing_url opens the standard signing page. The embed_url opens a streamlined view designed for iframe embedding — see Embedded Signing for details.


Instead of uploading a PDF, you can send raw markdown as the document source. VisiSign renders the markdown to a PDF on the server and auto-places signature fields at inline tokens.

Use application/json (no multipart/form-data required) and pass the markdown in data.content.

Inline field tokens:

Embed {{type:key}} tokens directly in the markdown body. The key is matched against each signer’s role (or, as a fallback, their name) to decide who fills the field.

TokenField typeRendered size
{{signature:key}}signature200 × 50 pt
{{initials:key}}initials150 × 50 pt
{{date:key}}date160 × 28 pt
{{text:key}}text200 × 28 pt
{{checkbox:key}}checkbox20 × 20 pt

Page breaks:

Put {{pagebreak}} on its own line to start a new PDF page. Useful for splitting long agreements into clear sections (e.g. main agreement → addendum → guaranty). Whitespace inside the braces is ignored, so {{ pagebreak }} works too.

Supported markdown: #/##/### headings, paragraphs, -/*/numbered lists, horizontal rules (---), GFM pipe tables, and inline **bold**, *italic*, __underline__. If no signer’s role matches a token’s key, the field is assigned to the first signer.

The API rejects markdown sends that produce zero {{signature:*}} fields after rendering, so you’ll get a 422 if your template has no signature anchors.

Example:

Terminal window
curl -X POST https://api.visisign.app/v1/signature_requests \
-H "Authorization: Bearer vsk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"title": "NDA",
"content": "# Mutual NDA\n\nThis agreement is between **Acme Inc** and the signer below.\n\n## Acknowledgement\n\nI have read and agree to the terms.\n\nSignature: {{signature:signer1}}\n\nDate: {{date:signer1}}\n",
"signers": [
{"name": "Jane Smith", "email": "jane@example.com", "role": "signer1"}
]
}'

The response matches the PDF-upload variant — you get back a signature_request with status: "sent" and tokenized signing URLs emailed to each signer.


POST /v1/signature_requests/send_with_template

Create a signature request from a pre-configured template.

Body parameters:

FieldTypeDefaultDescription
template_idstringrequiredTemplate ID (prefixed tmpl_)
titlestringtemplate nameDocument title
messagestringMessage for signers
signersarrayrequiredSigners mapped to template roles
expires_atstringISO 8601 expiration
test_modebooleanfalseNon-binding test mode
metadataobjectArbitrary metadata
custom_fieldsobjectPrefill fields by mapping_key (see below)

Signer object (template):

FieldTypeDescription
namestringSigner’s full name (required)
emailstringSigner’s email address (required)
rolestringTemplate role name
role_idintegerTemplate role ID

Provide either role or role_id for each signer. For roleless templates (e.g. W-9), all fields are automatically assigned to the first signer — you can omit role/role_id.

Error responses:

StatusDescription
403Templates require a Team plan

Prefilling fields with custom_fields:

Pass a custom_fields object to pre-populate field values by their mapping_key. Get available mapping keys from GET /v1/templates/:id.

{
"template_id": "tmpl_123",
"signers": [
{"name": "Jane Smith", "email": "jane@example.com"}
],
"custom_fields": {
"taxpayer_name": "Jane Smith",
"business_name": "Acme Inc",
"address": "123 Main St, Springfield, IL 62701"
}
}

Only fields with a non-null mapping_key matching a key in custom_fields are prefilled. Unmatched keys are ignored.


POST /v1/signature_requests/:id/cancel

Cancels a signature request. Only requests that haven’t been completed can be cancelled.

Response (200):

Returns the updated signature request with status: "cancelled".


POST /v1/signature_requests/:id/remind

Send a reminder email to a signer who hasn’t signed yet.

Body parameters:

FieldTypeDescription
email_addressstringEmail of the signer to remind (required)

Response (200):

{
"message": "Reminder sent"
}

Error responses:

StatusDescription
422No active envelope
404No pending signer with that email
429Reminder cooldown — already sent recently

GET /v1/signature_requests/:id/files

Returns the signed PDF. With an Authorization header, streams the file directly as an attachment. Without an Authorization header, redirects to the file URL.

Only available after all signers have completed.

Terminal window
curl https://api.visisign.app/v1/signature_requests/sr_123/files \
-H "Authorization: Bearer vsk_your_key_here" \
-o signed.pdf

Error responses:

StatusDescription
404Signed file not yet available
500Integrity error — signed file hash mismatch

The 404 response uses the standard error format:

{
"error": {
"type": "not_found",
"message": "Signed file not yet available"
}
}