API reference
The SignSecure API is organised around REST. It has predictable, resource-oriented URLs, takes JSON request bodies, returns JSON responses, and uses standard HTTP verbs, status codes and authentication.
The API covers the whole life of an envelope: create it, upload documents, choose who signs and where, send it, chase it, and find out the moment it is signed. People, billing and saved templates are managed in the app. The same rules apply either way: an API key cannot send more than the account's plan allows, and cannot see anything outside its own organisation.
New here? Start with the quickstart, which sends a real document for signature in five calls.
Base URL
https://www.signsecure.org/v1
The API is on the same host as the app, over HTTPS only. Plain HTTP requests are redirected, and a redirected request is not one you want to send your key in, so always use the address above.
{
"base_url": "https://www.signsecure.org/v1",
"version": "v1",
"format": "JSON"
}Authentication
The API authenticates requests with your secret API key. Send it in the Authorization header as a bearer token. If you prefer HTTP Basic auth, use the key as the username and leave the password empty, which is what curl -u ss_live_...: does.
Keys start with ss_live_ and are 51 characters long. An admin creates them under Developers in the app. The full key is shown once, when it is made. Only a hash of it is stored, so if you lose it, revoke it and make another.
A key acts as the admin who created it, with the rights of a Creator: it can create, send and read envelopes, but it cannot manage people, roles or billing. If that person leaves the organisation, their keys stop working at once.
Your API key can send documents in your organisation's name. Keep it on your server, in a secret store or an environment variable. Never put it in a browser, a mobile app or a public repository.
Requests without a key, with a key that has been revoked, or with something that is not a key at all, fail with 401.
Plans
The API is included on the Business plan. A request from an account that is not on Business returns 402 with the code plan_required, and that is checked on every request, so cancelling a subscription stops the API with it.
curl https://www.signsecure.org/v1/account \
-H "Authorization: Bearer $SIGNSECURE_API_KEY"const res = await fetch('https://www.signsecure.org/v1/account', {
headers: { Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}` },
});
const account = await res.json();import os
import requests
account = requests.get(
"https://www.signsecure.org/v1/account",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
).json()Errors
SignSecure uses conventional HTTP status codes. Codes in the 2xx range mean success. Codes in the 4xx range mean the request failed because of what was sent, and the error says which part. Codes in the 5xx range mean something went wrong on our side.
Every error has the same shape. type is the broad category and is safe to branch on. code, when present, is a short machine-readable reason. message is written for a person and may change. param names the parameter at fault, using bracket notation for nested ones, such as recipients[1][email].
HTTP status codes
| Code | Meaning | |
|---|---|---|
200 | OK | Everything worked as expected. |
400 | Bad Request | The request was unacceptable, often because a parameter is missing, unknown or invalid. |
401 | Unauthorized | No valid API key was provided. |
402 | Payment Required | The request needs something the plan does not include, or a plan limit has been reached. |
403 | Forbidden | The key is valid but its role cannot do this. |
404 | Not Found | The resource does not exist, or belongs to another organisation. |
409 | Conflict | The envelope is in a state that does not allow this, such as editing one that has been sent. |
413 | Payload Too Large | The file or the request body is over the limit. |
415 | Unsupported Media Type | The uploaded file is not a type SignSecure can read. |
429 | Too Many Requests | Too many requests too quickly. Back off and retry. |
500, 503 | Server errors | Something went wrong on our side. These are rare. |
Error types
| Type | Description |
|---|---|
invalid_request_error | The request has a problem: a parameter, a missing resource, or an envelope in the wrong state. |
authentication_error | The API key is missing, invalid or revoked, or its owner has left the organisation. |
permission_error | The key is valid but its role does not allow the action. |
billing_error | The plan does not include this, or a plan limit has been reached. |
idempotency_error | An Idempotency-Key was reused with a different request, or is still in use. |
rate_limit_error | Too many requests. The response includes a Retry-After header when we know how long to wait. |
api_error | Anything else. Quote the request_id if you contact support. |
Attributes
typestringThe category of error. One of the types above.
codenullable stringA short, stable reason, such as
parameter_missing,resource_missingorinvalid_state.messagestringA human-readable explanation. Show it to a developer, not to an end user.
paramnullable stringThe parameter the error relates to, if any.
request_idstringThe id of this request, also sent as the
Request-Idheader.
{
"error": {
"type": "invalid_request_error",
"code": "parameter_missing",
"message": "Missing required parameter: fields.",
"param": "fields",
"request_id": "req_0d2f4b6a8c1e3a5b7d9f0c"
}
}Idempotent requests
Networks fail. A request can time out after the server has acted on it, and retrying a "create envelope" or a "send" blindly can do it twice. Idempotency keys make a retry safe.
Send an Idempotency-Key header with any POST. The first request runs normally and SignSecure remembers what it produced for 24 hours. A retry with the same key does not run again: it gets the same envelope, document or endpoint back, as it is now, with the header Idempotent-Replayed: true. An error is replayed exactly as it was first returned.
Use a value that is unique to the operation, such as a UUID, or your own record id plus what you are doing with it. Reusing a key with a different method, path or body is an idempotency_error, because that is almost always a bug. If the first request is still running, a retry gets 409 and should wait a moment.
Responses with a 5xx or 429 status are not remembered, so retrying with the same key runs the request again. A request that never finished frees its key after a minute. A webhook endpoint's signing secret is never replayed; it is only in the first response. GET, PUT and DELETE requests are idempotent already and ignore the header.
curl https://www.signsecure.org/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/send \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-H "Idempotency-Key: 006Qy00000AbCdE-send" \
-X POSTconst res = await fetch('https://www.signsecure.org/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
'Idempotency-Key': '006Qy00000AbCdE-send',
},
});import os
import requests
res = requests.post(
"https://www.signsecure.org/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/send",
headers={
"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}",
"Idempotency-Key": "006Qy00000AbCdE-send",
},
)Metadata
Envelopes have a metadata parameter for your own key-value pairs. It is how an integration finds its way back from an envelope to the record it came from: put your CRM record id in it when you create the envelope, and it arrives in every webhook about that envelope.
Metadata can be up to 20 keys. Keys are up to 40 characters, and values are strings of up to 500 characters. Updating metadata merges with what is there; set a key to an empty string to remove it. Unlike the rest of an envelope, metadata can be changed after it has been sent, because it is your bookkeeping and not part of what anyone signs.
SignSecure does not read metadata or show it to recipients. Do not put anything sensitive in it, such as bank details or passwords.
{
"id": "env_3f9a1c7e2b8d4a6f9c0e1b",
"object": "envelope",
"metadata": {
"crm_record": "006Qy00000AbCdE",
"deal_stage": "contract"
},
"…": "…"
}Pagination
List endpoints return results newest first, a page at a time. Every list has the same shape: data holds the page, and has_more says whether there is another.
To get the next page, pass the id of the last item you received as starting_after. The cursor is an id rather than a page number, so nothing is skipped or repeated if new envelopes arrive while you are paging.
Parameters
limitintegeroptionalHow many objects to return, from 1 to 100. Defaults to 10.
starting_afterstringoptionalA cursor for pagination: the id of the last object on the previous page. For example, if your last call ended with
env_foo, passstarting_after=env_footo get the page after it.
List object
objectstringAlways
list.dataarrayThe objects on this page.
has_morebooleanWhether there are more objects after this page.
urlstringThe path this list came from.
{
"object": "list",
"url": "/v1/envelopes",
"has_more": true,
"data": [
"{ …envelope… }",
"{ …envelope… }"
]
}Request IDs
Every response, success or error, has a Request-Id header, and every error body repeats it as request_id. If you need to ask us about a request, quote its id and we can find exactly what happened.
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Request-Id: req_0d2f4b6a8c1e3a5b7d9f0cEnvelope allowance
Business includes an allowance of envelopes, shared across the account: 100 per seat a year on yearly billing, or 10 per seat a month on monthly. The free plan includes 5 a month.
An envelope counts once, when it is sent, however many documents and signers are in it. Drafts are free, and voiding a sent envelope does not give it back.
Past the allowance, sending on Business never stops. Each extra envelope is £2.00, added to the next invoice and billed monthly in arrears, and the envelope's audit trail records it. On the free plan, a send past the allowance fails with 402 and the code plan_limit.
The account shows how much of the allowance is used, so an integration can warn someone before they go over.
Rate limits
Limits keep one integration from crowding out everyone else, and stop a leaked key from doing much before you notice it. A request over a limit fails with 429 and the type rate_limit_error. Wait, then retry with exponential backoff.
Every signing email goes out from SignSecure's own domain, so the email limit counts emails, not envelopes: a parallel envelope to 20 people spends 20.
If a limit gets in the way of something legitimate, email support and tell us what you are building.
| Limit | Value |
|---|---|
| Requests | 300 a minute per account, across all its keys. |
| Uploads | 30 documents a minute per account, each up to 8 MB. |
| Sending | 500 envelopes an hour per account through the API. |
| Signing emails | 1,000 an hour per account, counting every email a send, a reminder or a correction causes, from the app or the API. |
| Reminders | One reminder an hour per envelope through the API. |
| Corrections | Three a day per recipient. |
| Recipients | 50 per envelope. |
| Fields | 500 per envelope. |
| Request bodies | 1 MB of JSON, or 12 MB for a base64 document upload. |
| Webhook endpoints | 10 per account. |
| API keys | 20 per account. |
Plan limits still apply through the API. The identity check add-on is metered per signer, and sending an envelope that asks for more identity checks than the account has left fails with 402.
Envelopes
An envelope is one request for signatures: the documents, the people who need to sign them, and where each person signs. You build it as a draft, send it, and it moves through its statuses until everyone has signed or it is stopped.
A draft can be changed freely. Once an envelope is sent, its documents, recipients and fields are fixed: they are what the signers are agreeing to.
The envelope object
Attributes
idstringUnique identifier for the envelope.
objectstringAlways
envelope.statusenumWhere the envelope is in its life.
draftBeing prepared. Nothing has been sent.sentOut for signature.completedEveryone has signed. The signed PDF is ready.declinedA signer declined, so nobody else is asked to sign.voidedYou withdrew it. Outstanding links stopped working.expiredThe signing links ran out before everyone signed.subjectstringWhat the envelope is. Recipients see it as the email subject.
messagenullable stringA note to recipients, shown in the email and above the document.
routing_modeenumThe order people are asked to sign in.
sequentialOne routing order at a time. Each person is emailed when it is their turn.parallelEvery signer is emailed at once.sender_emailnullable stringThe person the envelope is sent as. For an API key, the admin who created it.
created_viaenumapiorapp.metadataobjectYour key-value pairs. See Metadata.
createdtimestampWhen the envelope was created, in Unix seconds.
sent_atnullable timestampWhen it was sent.
completed_atnullable timestampWhen the last signer finished.
expires_atnullable timestampWhen the signing links stop working.
voided_atnullable timestampWhen it was voided.
void_reasonnullable stringThe reason given when it was voided.
documentsarray of documentsThe documents, in the order they were uploaded.
recipientsarray of recipientsThe recipients, in routing order.
fieldsnullable array of fieldsThe fields placed on the documents.
nullin lists and in events; retrieve the envelope to get them.
{
"id": "env_3f9a1c7e2b8d4a6f9c0e1b",
"object": "envelope",
"status": "draft",
"subject": "Order form, Northwind Traders",
"message": "Please sign by Friday.",
"routing_mode": "sequential",
"sender_email": "sales@yourcompany.com",
"created_via": "api",
"metadata": {
"crm_record": "006Qy00000AbCdE"
},
"created": 1791576000,
"sent_at": null,
"completed_at": null,
"expires_at": null,
"voided_at": null,
"void_reason": null,
"documents": [
{
"id": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"object": "document",
"name": "order-form.pdf",
"page_count": 3,
"size": 184320,
"sha256": "9f2c6e1a7b4d8c3f0e5a9b2d6c1f4e8a3b7d0c5f9e2a6b1d4c8f3e7a0b5d9c2f",
"signed_sha256": null,
"converted_from": null,
"created": 1791576060
}
],
"recipients": [
{
"id": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"object": "recipient",
"name": "Amara Okafor",
"email": "amara.okafor@example.com",
"role": "signer",
"routing_order": 1,
"status": "created",
"identity_required": false,
"identity_status": null,
"sent_at": null,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
},
{
"id": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"object": "recipient",
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"role": "signer",
"routing_order": 2,
"status": "created",
"identity_required": false,
"identity_status": null,
"sent_at": null,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": [
{
"id": "fld_5a1c3e7b9d2f4a6c8e0b1d",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.78,
"width": 0.3,
"height": 0.06,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
},
{
"id": "fld_9e3b5d7f1a2c4e6b8d0f3a",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "date",
"page_index": 2,
"x": 0.5,
"y": 0.79,
"width": 0.2,
"height": 0.04,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
},
{
"id": "fld_0c4e6a8b2d1f3b5c7e9a4f",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.88,
"width": 0.3,
"height": 0.06,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
}
]
}Create an envelope
Creates a draft envelope. You can add the recipients in the same call, then upload documents and place fields before you send it.
Parameters
subjectstringoptionalWhat the envelope is, up to 240 characters. Recipients see it as the email subject.
Defaults to
Untitled envelope, which is replaced by the first document's file name when you upload one.messagestringoptionalA note to recipients, up to 4,000 characters.
routing_modeenumoptionalThe order people are asked to sign in. Defaults to
sequential.sequentialOne routing order at a time.parallelEvery signer at once.metadataobjectoptionalYour key-value pairs. See Metadata.
recipientsarrayoptionalThe people on the envelope. The same as setting recipients afterwards. If any recipient is invalid, nothing is created.
Child parameters
namestringRequiredTheir full name, up to 160 characters.
emailstringRequiredTheir email address. Each address can appear once per envelope.
roleenumoptionalDefaults to
signer.signerSigns. Needs at least one field before the envelope can be sent.copySigns nothing, and gets the completed document when everyone has signed.routing_orderintegeroptionalWhen they sign under
sequentialrouting, from 1 to 999. People with the same number are asked together. Defaults to their position in the list.identity_requiredbooleanoptionalAsk this signer to prove who they are with a photo ID before signing. Uses one identity check from the account's allowance when the envelope is sent. Signers only.
Returns
Returns the envelope object, with status set to draft.
curl https://www.signsecure.org/v1/v1/envelopes \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subject": "Order form, Northwind Traders",
"message": "Please sign by Friday.",
"metadata": {
"crm_record": "006Qy00000AbCdE"
},
"recipients": [
{
"name": "Amara Okafor",
"email": "amara.okafor@example.com"
},
{
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"routing_order": 2
}
]
}'const res = await fetch('https://www.signsecure.org/v1/v1/envelopes', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
subject: 'Order form, Northwind Traders',
message: 'Please sign by Friday.',
metadata: {
crm_record: '006Qy00000AbCdE'
},
recipients: [
{
name: 'Amara Okafor',
email: 'amara.okafor@example.com'
},
{
name: 'Ben Hughes',
email: 'ben.hughes@example.com',
routing_order: 2
}
]
}),
});
const envelope = await res.json();import os
import requests
res = requests.post(
"https://www.signsecure.org/v1/v1/envelopes",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
json={
"subject": "Order form, Northwind Traders",
"message": "Please sign by Friday.",
"metadata": {
"crm_record": "006Qy00000AbCdE"
},
"recipients": [
{
"name": "Amara Okafor",
"email": "amara.okafor@example.com"
},
{
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"routing_order": 2
}
]
},
)
envelope = res.json(){
"id": "env_3f9a1c7e2b8d4a6f9c0e1b",
"object": "envelope",
"status": "draft",
"subject": "Order form, Northwind Traders",
"message": "Please sign by Friday.",
"routing_mode": "sequential",
"sender_email": "sales@yourcompany.com",
"created_via": "api",
"metadata": {
"crm_record": "006Qy00000AbCdE"
},
"created": 1791576000,
"sent_at": null,
"completed_at": null,
"expires_at": null,
"voided_at": null,
"void_reason": null,
"documents": [],
"recipients": [
{
"id": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"object": "recipient",
"name": "Amara Okafor",
"email": "amara.okafor@example.com",
"role": "signer",
"routing_order": 1,
"status": "created",
"identity_required": false,
"identity_status": null,
"sent_at": null,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
},
{
"id": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"object": "recipient",
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"role": "signer",
"routing_order": 2,
"status": "created",
"identity_required": false,
"identity_status": null,
"sent_at": null,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": []
}Retrieve an envelope
Retrieves an envelope, with its documents, recipients and fields.
Parameters
No parameters.
Returns
Returns the envelope object if the id is valid. Returns 404 if it is not, including when the envelope belongs to another organisation.
curl https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b
-H "Authorization: Bearer $SIGNSECURE_API_KEY"const res = await fetch('https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b', {
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
},
});
const envelope = await res.json();import os
import requests
res = requests.get(
"https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
)
envelope = res.json(){
"id": "env_3f9a1c7e2b8d4a6f9c0e1b",
"object": "envelope",
"status": "sent",
"subject": "Order form, Northwind Traders",
"message": "Please sign by Friday.",
"routing_mode": "sequential",
"sender_email": "sales@yourcompany.com",
"created_via": "api",
"metadata": {
"crm_record": "006Qy00000AbCdE"
},
"created": 1791576000,
"sent_at": 1791576300,
"completed_at": null,
"expires_at": 1792785900,
"voided_at": null,
"void_reason": null,
"documents": [
{
"id": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"object": "document",
"name": "order-form.pdf",
"page_count": 3,
"size": 184320,
"sha256": "9f2c6e1a7b4d8c3f0e5a9b2d6c1f4e8a3b7d0c5f9e2a6b1d4c8f3e7a0b5d9c2f",
"signed_sha256": null,
"converted_from": null,
"created": 1791576060
}
],
"recipients": [
{
"id": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"object": "recipient",
"name": "Amara Okafor",
"email": "amara.okafor@example.com",
"role": "signer",
"routing_order": 1,
"status": "sent",
"identity_required": false,
"identity_status": null,
"sent_at": 1791576300,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
},
{
"id": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"object": "recipient",
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"role": "signer",
"routing_order": 2,
"status": "created",
"identity_required": false,
"identity_status": null,
"sent_at": null,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": [
{
"id": "fld_5a1c3e7b9d2f4a6c8e0b1d",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.78,
"width": 0.3,
"height": 0.06,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
},
{
"id": "fld_9e3b5d7f1a2c4e6b8d0f3a",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "date",
"page_index": 2,
"x": 0.5,
"y": 0.79,
"width": 0.2,
"height": 0.04,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
},
{
"id": "fld_0c4e6a8b2d1f3b5c7e9a4f",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.88,
"width": 0.3,
"height": 0.06,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
}
]
}Update an envelope
Updates an envelope. Parameters you leave out are unchanged.
subject, message and routing_mode can only be changed while the envelope is a draft. metadata can be changed at any time.
Parameters
subjectstringoptionalUp to 240 characters. Cannot be empty.
messagestringoptionalUp to 4,000 characters. An empty string removes it.
routing_modeenumoptionalsequentialorparallel.metadataobjectoptionalMerged with the existing metadata. Set a key to an empty string to remove it.
Returns
Returns the updated envelope object. Returns 409 with the code invalid_state if you change anything but metadata on an envelope that has been sent.
curl https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"metadata": {
"deal_stage": "contract"
}
}'const res = await fetch('https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
metadata: {
deal_stage: 'contract'
}
}),
});
const envelope = await res.json();import os
import requests
res = requests.post(
"https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
json={
"metadata": {
"deal_stage": "contract"
}
},
)
envelope = res.json(){
"id": "env_3f9a1c7e2b8d4a6f9c0e1b",
"object": "envelope",
"status": "sent",
"subject": "Order form, Northwind Traders",
"message": "Please sign by Friday.",
"routing_mode": "sequential",
"sender_email": "sales@yourcompany.com",
"created_via": "api",
"metadata": {
"crm_record": "006Qy00000AbCdE",
"deal_stage": "contract"
},
"created": 1791576000,
"sent_at": 1791576300,
"completed_at": null,
"expires_at": 1792785900,
"voided_at": null,
"void_reason": null,
"documents": [
{
"id": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"object": "document",
"name": "order-form.pdf",
"page_count": 3,
"size": 184320,
"sha256": "9f2c6e1a7b4d8c3f0e5a9b2d6c1f4e8a3b7d0c5f9e2a6b1d4c8f3e7a0b5d9c2f",
"signed_sha256": null,
"converted_from": null,
"created": 1791576060
}
],
"recipients": [
{
"id": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"object": "recipient",
"name": "Amara Okafor",
"email": "amara.okafor@example.com",
"role": "signer",
"routing_order": 1,
"status": "sent",
"identity_required": false,
"identity_status": null,
"sent_at": 1791576300,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
},
{
"id": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"object": "recipient",
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"role": "signer",
"routing_order": 2,
"status": "created",
"identity_required": false,
"identity_status": null,
"sent_at": null,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": [
{
"id": "fld_5a1c3e7b9d2f4a6c8e0b1d",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.78,
"width": 0.3,
"height": 0.06,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
},
{
"id": "fld_9e3b5d7f1a2c4e6b8d0f3a",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "date",
"page_index": 2,
"x": 0.5,
"y": 0.79,
"width": 0.2,
"height": 0.04,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
},
{
"id": "fld_0c4e6a8b2d1f3b5c7e9a4f",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.88,
"width": 0.3,
"height": 0.06,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
}
]
}List all envelopes
Returns your organisation's envelopes, newest first. Each one includes its documents and recipients. fields is null in lists, because an envelope can have thousands; retrieve the envelope to get them.
Parameters
statusenumoptionalOnly envelopes with this status:
draft,sent,completed,declined,voidedorexpired.limitintegeroptionalFrom 1 to 100. Defaults to 10.
starting_afterstringoptionalA cursor for pagination.
Returns
A list object whose data holds up to limit envelopes. If there are none, data is empty.
curl https://www.signsecure.org/v1/v1/envelopes?status=sent&limit=3
-H "Authorization: Bearer $SIGNSECURE_API_KEY"const res = await fetch('https://www.signsecure.org/v1/v1/envelopes?status=sent&limit=3', {
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
},
});
const envelopes = await res.json();import os
import requests
res = requests.get(
"https://www.signsecure.org/v1/v1/envelopes",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
params={
"status": "sent",
"limit": 3
},
)
envelopes = res.json(){
"object": "list",
"url": "/v1/envelopes",
"has_more": false,
"data": [
{
"id": "env_3f9a1c7e2b8d4a6f9c0e1b",
"object": "envelope",
"status": "sent",
"subject": "Order form, Northwind Traders",
"message": "Please sign by Friday.",
"routing_mode": "sequential",
"sender_email": "sales@yourcompany.com",
"created_via": "api",
"metadata": {
"crm_record": "006Qy00000AbCdE"
},
"created": 1791576000,
"sent_at": 1791576300,
"completed_at": null,
"expires_at": 1792785900,
"voided_at": null,
"void_reason": null,
"documents": [
{
"id": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"object": "document",
"name": "order-form.pdf",
"page_count": 3,
"size": 184320,
"sha256": "9f2c6e1a7b4d8c3f0e5a9b2d6c1f4e8a3b7d0c5f9e2a6b1d4c8f3e7a0b5d9c2f",
"signed_sha256": null,
"converted_from": null,
"created": 1791576060
}
],
"recipients": [
{
"id": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"object": "recipient",
"name": "Amara Okafor",
"email": "amara.okafor@example.com",
"role": "signer",
"routing_order": 1,
"status": "sent",
"identity_required": false,
"identity_status": null,
"sent_at": 1791576300,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
},
{
"id": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"object": "recipient",
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"role": "signer",
"routing_order": 2,
"status": "created",
"identity_required": false,
"identity_status": null,
"sent_at": null,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": null
}
]
}Send an envelope
Sends a draft envelope for signature. Each signer whose turn it is gets an email with their own signing link. Under sequential routing, the next people are emailed as each routing order finishes.
An envelope can be sent when it has at least one document, at least one signer, and at least one field for every signer.
Parameters
expires_in_daysintegeroptionalHow long the signing links work:
7,14,30,60or90days. Defaults to30. Links never last forever, because a link is all a signer needs to sign.
Returns
Returns the envelope object with status set to sent. Past the envelope allowance on Business it still sends, and is billed. Returns 400 if the envelope is not ready to send, 402 if a plan limit stops it, such as the free plan's five a month or the identity check allowance, and 429 if the account has used its hour's signing emails.
curl https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/send \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"expires_in_days": 14
}'const res = await fetch('https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
expires_in_days: 14
}),
});
const envelope = await res.json();import os
import requests
res = requests.post(
"https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/send",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
json={
"expires_in_days": 14
},
)
envelope = res.json(){
"id": "env_3f9a1c7e2b8d4a6f9c0e1b",
"object": "envelope",
"status": "sent",
"subject": "Order form, Northwind Traders",
"message": "Please sign by Friday.",
"routing_mode": "sequential",
"sender_email": "sales@yourcompany.com",
"created_via": "api",
"metadata": {
"crm_record": "006Qy00000AbCdE"
},
"created": 1791576000,
"sent_at": 1791576300,
"completed_at": null,
"expires_at": 1792785900,
"voided_at": null,
"void_reason": null,
"documents": [
{
"id": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"object": "document",
"name": "order-form.pdf",
"page_count": 3,
"size": 184320,
"sha256": "9f2c6e1a7b4d8c3f0e5a9b2d6c1f4e8a3b7d0c5f9e2a6b1d4c8f3e7a0b5d9c2f",
"signed_sha256": null,
"converted_from": null,
"created": 1791576060
}
],
"recipients": [
{
"id": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"object": "recipient",
"name": "Amara Okafor",
"email": "amara.okafor@example.com",
"role": "signer",
"routing_order": 1,
"status": "sent",
"identity_required": false,
"identity_status": null,
"sent_at": 1791576300,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
},
{
"id": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"object": "recipient",
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"role": "signer",
"routing_order": 2,
"status": "created",
"identity_required": false,
"identity_status": null,
"sent_at": null,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": [
{
"id": "fld_5a1c3e7b9d2f4a6c8e0b1d",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.78,
"width": 0.3,
"height": 0.06,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
},
{
"id": "fld_9e3b5d7f1a2c4e6b8d0f3a",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "date",
"page_index": 2,
"x": 0.5,
"y": 0.79,
"width": 0.2,
"height": 0.04,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
},
{
"id": "fld_0c4e6a8b2d1f3b5c7e9a4f",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.88,
"width": 0.3,
"height": 0.06,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
}
]
}Remind recipients
Emails a reminder to every signer whose turn it is and who has not signed yet. Each reminder carries a fresh link with the envelope's existing expiry, so a reminder never extends how long a link lives.
SignSecure also reminds people on its own: three days after a link is sent, and up to three times. You only need this endpoint to chase sooner.
Parameters
No parameters.
Returns
Returns the envelope object. Returns 409 if the envelope is not out for signature, and 429 if it was reminded in the last hour.
curl -X POST https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/remind
-H "Authorization: Bearer $SIGNSECURE_API_KEY"const res = await fetch('https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/remind', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
},
});
const envelope = await res.json();import os
import requests
res = requests.post(
"https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/remind",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
)
envelope = res.json(){
"id": "env_3f9a1c7e2b8d4a6f9c0e1b",
"object": "envelope",
"status": "sent",
"subject": "Order form, Northwind Traders",
"message": "Please sign by Friday.",
"routing_mode": "sequential",
"sender_email": "sales@yourcompany.com",
"created_via": "api",
"metadata": {
"crm_record": "006Qy00000AbCdE"
},
"created": 1791576000,
"sent_at": 1791576300,
"completed_at": null,
"expires_at": 1792785900,
"voided_at": null,
"void_reason": null,
"documents": [
{
"id": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"object": "document",
"name": "order-form.pdf",
"page_count": 3,
"size": 184320,
"sha256": "9f2c6e1a7b4d8c3f0e5a9b2d6c1f4e8a3b7d0c5f9e2a6b1d4c8f3e7a0b5d9c2f",
"signed_sha256": null,
"converted_from": null,
"created": 1791576060
}
],
"recipients": [
{
"id": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"object": "recipient",
"name": "Amara Okafor",
"email": "amara.okafor@example.com",
"role": "signer",
"routing_order": 1,
"status": "sent",
"identity_required": false,
"identity_status": null,
"sent_at": 1791576300,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
},
{
"id": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"object": "recipient",
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"role": "signer",
"routing_order": 2,
"status": "created",
"identity_required": false,
"identity_status": null,
"sent_at": null,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": [
{
"id": "fld_5a1c3e7b9d2f4a6c8e0b1d",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.78,
"width": 0.3,
"height": 0.06,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
},
{
"id": "fld_9e3b5d7f1a2c4e6b8d0f3a",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "date",
"page_index": 2,
"x": 0.5,
"y": 0.79,
"width": 0.2,
"height": 0.04,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
},
{
"id": "fld_0c4e6a8b2d1f3b5c7e9a4f",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.88,
"width": 0.3,
"height": 0.06,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
}
]
}Void an envelope
Stops an envelope that is out for signature. Every outstanding link stops working at once, and the people who had not signed are emailed to say it was withdrawn.
Voiding is not deleting. The envelope, its audit trail and any signatures already collected stay as they are.
Parameters
reasonstringoptionalWhy, up to 500 characters. Included in the email to recipients.
Returns
Returns the envelope object with status set to voided. Returns 409 for a draft, which has nothing to withdraw, and for a completed envelope.
curl https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/void \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "Pricing changed; a new version is coming."
}'const res = await fetch('https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/void', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
reason: 'Pricing changed; a new version is coming.'
}),
});
const envelope = await res.json();import os
import requests
res = requests.post(
"https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/void",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
json={
"reason": "Pricing changed; a new version is coming."
},
)
envelope = res.json(){
"id": "env_3f9a1c7e2b8d4a6f9c0e1b",
"object": "envelope",
"status": "voided",
"subject": "Order form, Northwind Traders",
"message": "Please sign by Friday.",
"routing_mode": "sequential",
"sender_email": "sales@yourcompany.com",
"created_via": "api",
"metadata": {
"crm_record": "006Qy00000AbCdE"
},
"created": 1791576000,
"sent_at": 1791576300,
"completed_at": null,
"expires_at": 1792785900,
"voided_at": 1791579600,
"void_reason": "Pricing changed; a new version is coming.",
"documents": [
{
"id": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"object": "document",
"name": "order-form.pdf",
"page_count": 3,
"size": 184320,
"sha256": "9f2c6e1a7b4d8c3f0e5a9b2d6c1f4e8a3b7d0c5f9e2a6b1d4c8f3e7a0b5d9c2f",
"signed_sha256": null,
"converted_from": null,
"created": 1791576060
}
],
"recipients": [
{
"id": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"object": "recipient",
"name": "Amara Okafor",
"email": "amara.okafor@example.com",
"role": "signer",
"routing_order": 1,
"status": "sent",
"identity_required": false,
"identity_status": null,
"sent_at": 1791576300,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
},
{
"id": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"object": "recipient",
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"role": "signer",
"routing_order": 2,
"status": "created",
"identity_required": false,
"identity_status": null,
"sent_at": null,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": [
{
"id": "fld_5a1c3e7b9d2f4a6c8e0b1d",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.78,
"width": 0.3,
"height": 0.06,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
},
{
"id": "fld_9e3b5d7f1a2c4e6b8d0f3a",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "date",
"page_index": 2,
"x": 0.5,
"y": 0.79,
"width": 0.2,
"height": 0.04,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
},
{
"id": "fld_0c4e6a8b2d1f3b5c7e9a4f",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.88,
"width": 0.3,
"height": 0.06,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
}
]
}Retrieve the audit trail
Returns everything that has happened to an envelope, in order: who created and sent it, who opened it and from where, who signed, and what was produced.
Each entry carries the hash of the one before it, so the trail is a chain. intact is true when every link checks out and nothing has been removed from the end. The data on each entry depends on its type.
Parameters
No parameters.
Returns
Returns an audit trail object.
curl https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/audit_trail
-H "Authorization: Bearer $SIGNSECURE_API_KEY"const res = await fetch('https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/audit_trail', {
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
},
});
const trail = await res.json();import os
import requests
res = requests.get(
"https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/audit_trail",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
)
trail = res.json(){
"object": "audit_trail",
"envelope": "env_3f9a1c7e2b8d4a6f9c0e1b",
"intact": true,
"entries": [
{
"sequence": 1,
"type": "envelope.created",
"actor_email": "sales@yourcompany.com",
"ip": "203.0.113.24",
"user_agent": "node",
"data": {
"via": "api",
"apiKey": "ss_live_Ab3x"
},
"created": 1791576000,
"hash": "c1d9…",
"previous_hash": "genesis"
},
{
"sequence": 9,
"type": "recipient.signed",
"actor_email": "amara.okafor@example.com",
"ip": "198.51.100.7",
"user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X)",
"data": {
"recipient": "Amara Okafor",
"stateDigest": "7e0a…",
"fields": 2
},
"created": 1791577620,
"hash": "5f2b…",
"previous_hash": "0a7e…"
}
]
}Documents
A document is a file on an envelope: the thing people sign. Upload a PDF, a Word .docx, or a PNG or JPEG. Word documents and images are converted to PDF when they are uploaded, and the original is kept.
When the last signer finishes, SignSecure writes every value into the PDF and produces the signed version, with its SHA-256 recorded so anyone can check later that it has not changed.
The document object
Attributes
idstringUnique identifier for the document.
objectstringAlways
document.namestringThe file name. A converted file is named with
.pdf.page_countintegerHow many pages it has.
sizeintegerSize of the PDF in bytes.
sha256stringSHA-256 of the PDF as uploaded or converted.
signed_sha256nullable stringSHA-256 of the signed PDF, once there is one.
converted_fromnullable stringThe original file name, if it was converted from Word or an image.
createdtimestampWhen it was uploaded.
{
"id": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"object": "document",
"name": "order-form.pdf",
"page_count": 3,
"size": 184320,
"sha256": "9f2c6e1a7b4d8c3f0e5a9b2d6c1f4e8a3b7d0c5f9e2a6b1d4c8f3e7a0b5d9c2f",
"signed_sha256": "4b8e2f6a0d3c7b1e5f9a2d6c0b4e8f3a7d1c5b9e2f6a0c4d8b3e7f1a5c9d2b6e",
"converted_from": null,
"created": 1791576060
}Upload a document
Adds a document to a draft envelope. Send it as multipart/form-data with the file in a part called file.
If building a multipart body is awkward on your platform, send JSON instead, with the file base64-encoded in content. Salesforce Apex is the usual reason.
Parameters
filefilerequired for multipartThe file, up to 8 MB: a PDF, a Word
.docx, or a PNG or JPEG.namestringrequired for JSONThe file name, including its extension, which decides how it is read.
contentstringrequired for JSONThe file, base64-encoded.
Returns
Returns the document object. Returns 413 if the file is too large, 415 if it is not a type SignSecure can read, and 409 if the envelope has been sent.
curl https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/documents \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-F file=@order-form.pdfimport { openAsBlob } from 'node:fs';
const form = new FormData();
form.append('file', await openAsBlob('order-form.pdf'), 'order-form.pdf');
const res = await fetch('https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/documents', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
},
body: form,
});
const document = await res.json();import os
import requests
res = requests.post(
"https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/documents",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
files={"file": open("order-form.pdf", "rb")},
)
document = res.json(){
"id": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"object": "document",
"name": "order-form.pdf",
"page_count": 3,
"size": 184320,
"sha256": "9f2c6e1a7b4d8c3f0e5a9b2d6c1f4e8a3b7d0c5f9e2a6b1d4c8f3e7a0b5d9c2f",
"signed_sha256": null,
"converted_from": null,
"created": 1791576060
}Download a document
Downloads a document's file. By default you get the signed PDF, which exists once everyone has signed.
Parameters
versionenumoptionalWhich file. Defaults to
signed.signedThe completed PDF, with every value written in.originalThe PDF as it was uploaded or converted, before anyone signed.sourceThe original Word file or image, for a document that was converted.
Returns
Returns the file itself, not JSON. Returns 409 with the code invalid_state if you ask for signed before the envelope is completed.
curl https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/documents/doc_1b4d7f0a3c6e9b2d5f8a1c/content \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-o order-form-signed.pdfimport { writeFile } from 'node:fs/promises';
const res = await fetch('https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/documents/doc_1b4d7f0a3c6e9b2d5f8a1c/content', {
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
},
});
await writeFile('order-form-signed.pdf', Buffer.from(await res.arrayBuffer()));import os
import requests
res = requests.get(
"https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/documents/doc_1b4d7f0a3c6e9b2d5f8a1c/content",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
)
open("order-form-signed.pdf", "wb").write(res.content)HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="order-form.pdf"
%PDF-1.7 …Delete a document
Removes a document from a draft envelope, along with any fields placed on it.
Parameters
No parameters.
Returns
Returns an object with deleted set to true. Returns 409 if the envelope has been sent.
curl -X DELETE https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/documents/doc_1b4d7f0a3c6e9b2d5f8a1c
-H "Authorization: Bearer $SIGNSECURE_API_KEY"const res = await fetch('https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/documents/doc_1b4d7f0a3c6e9b2d5f8a1c', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
},
});
const deleted = await res.json();import os
import requests
res = requests.delete(
"https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/documents/doc_1b4d7f0a3c6e9b2d5f8a1c",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
)
deleted = res.json(){
"id": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"object": "document",
"deleted": true
}Recipients
A recipient is a person on an envelope. A signer signs; a copy recipient signs nothing and gets the finished document.
The recipient object
Attributes
idstringUnique identifier for the recipient.
objectstringAlways
recipient.namestringTheir full name.
emailstringTheir email address, in lower case.
roleenumsignerorcopy.routing_orderintegerWhen they sign under
sequentialrouting.statusenumWhere this person is.
createdNot contacted yet.sentEmailed a signing link.viewedOpened the document.signedFinished signing.declinedDeclined to sign.identity_requiredbooleanWhether they must prove who they are before signing.
identity_statusnullable enumThe identity check, if one was asked for:
requires_input,processing,verifiedorcanceled.sent_atnullable timestampWhen they were last emailed a link.
viewed_atnullable timestampWhen they first opened it.
signed_atnullable timestampWhen they signed.
declined_atnullable timestampWhen they declined.
decline_reasonnullable stringWhat they said when they declined, if anything.
{
"id": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"object": "recipient",
"name": "Amara Okafor",
"email": "amara.okafor@example.com",
"role": "signer",
"routing_order": 1,
"status": "signed",
"identity_required": false,
"identity_status": null,
"sent_at": 1791576300,
"viewed_at": 1791577500,
"signed_at": 1791577620,
"declined_at": null,
"decline_reason": null
}Set the recipients
Replaces the recipients on a draft envelope with the list you send. Anyone not in the list is removed.
Setting the recipients removes every field already placed, because each field belongs to a person and the people have changed. Set recipients first, then fields.
Parameters
recipientsarrayRequiredThe people on the envelope, at least one of them a signer, at most 50.
Child parameters
namestringRequiredTheir full name.
emailstringRequiredTheir email address.
roleenumoptionalsignerorcopy. Defaults tosigner.routing_orderintegeroptionalFrom 1 to 999. Defaults to their position in the list.
identity_requiredbooleanoptionalAsk this signer for photo ID before signing.
routing_modeenumoptionalsequentialorparallel. Leave it out to keep the current one.
Returns
Returns the envelope object. Returns 409 if the envelope has been sent.
curl -X PUT https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/recipients \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipients": [
{
"name": "Amara Okafor",
"email": "amara.okafor@example.com"
},
{
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"routing_order": 2
},
{
"name": "Finance team",
"email": "finance@yourcompany.com",
"role": "copy"
}
]
}'const res = await fetch('https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/recipients', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
recipients: [
{
name: 'Amara Okafor',
email: 'amara.okafor@example.com'
},
{
name: 'Ben Hughes',
email: 'ben.hughes@example.com',
routing_order: 2
},
{
name: 'Finance team',
email: 'finance@yourcompany.com',
role: 'copy'
}
]
}),
});
const envelope = await res.json();import os
import requests
res = requests.put(
"https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/recipients",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
json={
"recipients": [
{
"name": "Amara Okafor",
"email": "amara.okafor@example.com"
},
{
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"routing_order": 2
},
{
"name": "Finance team",
"email": "finance@yourcompany.com",
"role": "copy"
}
]
},
)
envelope = res.json(){
"id": "env_3f9a1c7e2b8d4a6f9c0e1b",
"object": "envelope",
"status": "draft",
"subject": "Order form, Northwind Traders",
"message": "Please sign by Friday.",
"routing_mode": "sequential",
"sender_email": "sales@yourcompany.com",
"created_via": "api",
"metadata": {
"crm_record": "006Qy00000AbCdE"
},
"created": 1791576000,
"sent_at": null,
"completed_at": null,
"expires_at": null,
"voided_at": null,
"void_reason": null,
"documents": [],
"recipients": [
{
"id": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"object": "recipient",
"name": "Amara Okafor",
"email": "amara.okafor@example.com",
"role": "signer",
"routing_order": 1,
"status": "created",
"identity_required": false,
"identity_status": null,
"sent_at": null,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
},
{
"id": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"object": "recipient",
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"role": "signer",
"routing_order": 2,
"status": "created",
"identity_required": false,
"identity_status": null,
"sent_at": null,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": []
}Correct a recipient
Fixes the name or email of someone on an envelope that is already out for signature, and sends them a new link. The old link stops working first, so a link that went to a mistyped address cannot be used.
You cannot correct someone who has already signed or declined, and each person can be corrected three times a day. Correcting someone's email address means they verify their identity again, if the envelope asks for it.
Parameters
namestringoptionalTheir corrected name.
emailstringoptionalTheir corrected email address.
Returns
Returns the envelope object. Returns 409 if the envelope is not out for signature or the person has signed or declined, and 429 once they have been corrected three times today.
curl https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/recipients/rcp_7c2e9b1a4f6d8e0c3b5a7d \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "amara@example.com"
}'const res = await fetch('https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/recipients/rcp_7c2e9b1a4f6d8e0c3b5a7d', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'amara@example.com'
}),
});
const envelope = await res.json();import os
import requests
res = requests.post(
"https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/recipients/rcp_7c2e9b1a4f6d8e0c3b5a7d",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
json={
"email": "amara@example.com"
},
)
envelope = res.json()Fields
A field is a place on a document where one signer does something: signs, initials, ticks a box, or types a value. Every signer needs at least one field before an envelope can be sent.
Coordinates. A field's position is given as fractions of the page, from 0 to 1, measured from the top-left corner. x: 0.5, y: 0.25 is halfway across and a quarter of the way down, whatever the page size. To convert from PDF points, divide by the page width or height; remember PDF points are usually measured from the bottom, so y = 1 - (points_from_bottom + height_in_points) / page_height.
page_index counts from 0 within the document.
The field object
Attributes
idstringUnique identifier for the field.
objectstringAlways
field.documentstringThe id of the document it is on.
recipientstringThe id of the signer who fills it in.
typeenumWhat kind of field.
signatureA drawn or typed signature.initialsDrawn or typed initials.dateThe date they sign, filled in for them.nameTheir name, filled in for them and editable.emailTheir email address, filled in for them and editable.textAnything they type, up to 500 characters.checkboxA box they tick. The value isXwhen ticked.page_indexintegerThe page, counting from
0.xnumberDistance from the left edge, as a fraction of the page width.
ynumberDistance from the top edge, as a fraction of the page height.
widthnumberAs a fraction of the page width.
heightnumberAs a fraction of the page height.
requiredbooleanWhether the signer must fill it in to finish.
labelnullable stringA hint shown to the signer.
valuenullable stringWhat was entered. Always
nullforsignatureandinitials, whose value is an image: download the signed PDF to see it.filledbooleanWhether it has been filled in.
filled_atnullable timestampWhen it was filled in.
{
"id": "fld_9e3b5d7f1a2c4e6b8d0f3a",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "date",
"page_index": 2,
"x": 0.5,
"y": 0.79,
"width": 0.2,
"height": 0.04,
"required": true,
"label": null,
"value": "8 October 2026",
"filled": true,
"filled_at": 1791577620
}Set the fields
Replaces every field on a draft envelope with the list you send. Upload the documents and set the recipients first: each field names one of each.
Parameters
fieldsarrayRequiredUp to 500 fields.
Child parameters
documentstringRequiredThe id of a document on this envelope.
recipientstringRequiredThe id of a signer on this envelope.
copyrecipients cannot have fields.typeenumRequiredsignature,initials,date,name,email,textorcheckbox.page_indexintegerRequiredThe page, counting from
0.xnumberRequiredFrom the left edge,
0to1.ynumberRequiredFrom the top edge,
0to1.widthnumberRequiredGreater than
0.x + widthcannot pass1.heightnumberRequiredGreater than
0.y + heightcannot pass1.requiredbooleanoptionalDefaults to
true.labelstringoptionalA hint for the signer, up to 120 characters.
Returns
Returns the envelope object, with its fields. Returns 400 if a field is off the page or names a document or signer not on the envelope.
curl -X PUT https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/fields \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fields": [
{
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.78,
"width": 0.3,
"height": 0.06
},
{
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "date",
"page_index": 2,
"x": 0.5,
"y": 0.79,
"width": 0.2,
"height": 0.04
},
{
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.88,
"width": 0.3,
"height": 0.06
}
]
}'const res = await fetch('https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/fields', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
fields: [
{
document: 'doc_1b4d7f0a3c6e9b2d5f8a1c',
recipient: 'rcp_7c2e9b1a4f6d8e0c3b5a7d',
type: 'signature',
page_index: 2,
x: 0.12,
y: 0.78,
width: 0.3,
height: 0.06
},
{
document: 'doc_1b4d7f0a3c6e9b2d5f8a1c',
recipient: 'rcp_7c2e9b1a4f6d8e0c3b5a7d',
type: 'date',
page_index: 2,
x: 0.5,
y: 0.79,
width: 0.2,
height: 0.04
},
{
document: 'doc_1b4d7f0a3c6e9b2d5f8a1c',
recipient: 'rcp_2d8f4b6a0c1e3f5a7b9c2e',
type: 'signature',
page_index: 2,
x: 0.12,
y: 0.88,
width: 0.3,
height: 0.06
}
]
}),
});
const envelope = await res.json();import os
import requests
res = requests.put(
"https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/fields",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
json={
"fields": [
{
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.78,
"width": 0.3,
"height": 0.06
},
{
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "date",
"page_index": 2,
"x": 0.5,
"y": 0.79,
"width": 0.2,
"height": 0.04
},
{
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.88,
"width": 0.3,
"height": 0.06
}
]
},
)
envelope = res.json(){
"id": "env_3f9a1c7e2b8d4a6f9c0e1b",
"object": "envelope",
"status": "draft",
"subject": "Order form, Northwind Traders",
"message": "Please sign by Friday.",
"routing_mode": "sequential",
"sender_email": "sales@yourcompany.com",
"created_via": "api",
"metadata": {
"crm_record": "006Qy00000AbCdE"
},
"created": 1791576000,
"sent_at": null,
"completed_at": null,
"expires_at": null,
"voided_at": null,
"void_reason": null,
"documents": [
{
"id": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"object": "document",
"name": "order-form.pdf",
"page_count": 3,
"size": 184320,
"sha256": "9f2c6e1a7b4d8c3f0e5a9b2d6c1f4e8a3b7d0c5f9e2a6b1d4c8f3e7a0b5d9c2f",
"signed_sha256": null,
"converted_from": null,
"created": 1791576060
}
],
"recipients": [
{
"id": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"object": "recipient",
"name": "Amara Okafor",
"email": "amara.okafor@example.com",
"role": "signer",
"routing_order": 1,
"status": "created",
"identity_required": false,
"identity_status": null,
"sent_at": null,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
},
{
"id": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"object": "recipient",
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"role": "signer",
"routing_order": 2,
"status": "created",
"identity_required": false,
"identity_status": null,
"sent_at": null,
"viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": [
{
"id": "fld_5a1c3e7b9d2f4a6c8e0b1d",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.78,
"width": 0.3,
"height": 0.06,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
},
{
"id": "fld_9e3b5d7f1a2c4e6b8d0f3a",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"type": "date",
"page_index": 2,
"x": 0.5,
"y": 0.79,
"width": 0.2,
"height": 0.04,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
},
{
"id": "fld_0c4e6a8b2d1f3b5c7e9a4f",
"object": "field",
"document": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"recipient": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"type": "signature",
"page_index": 2,
"x": 0.12,
"y": 0.88,
"width": 0.3,
"height": 0.06,
"required": true,
"label": null,
"value": null,
"filled": false,
"filled_at": null
}
]
}Apply a template
Lays a saved template's fields onto a draft envelope for one signer. That signer's existing fields are replaced; everyone else's are kept, so you can apply the same template once per signer.
Template pages are counted across all the envelope's documents in order, the way the app shows them. Fields for pages the envelope does not have are skipped.
Parameters
templatestringRequiredThe id of a template.
recipientstringoptionalThe id of the signer the fields are for. Defaults to the first signer.
Returns
Returns the envelope object. Returns 409 if none of the template's fields fit the envelope's pages.
curl https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/apply_template \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template": "tpl_6b8d0f2a4c1e3a5b7d9f0c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d"
}'const res = await fetch('https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/apply_template', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
template: 'tpl_6b8d0f2a4c1e3a5b7d9f0c',
recipient: 'rcp_7c2e9b1a4f6d8e0c3b5a7d'
}),
});
const envelope = await res.json();import os
import requests
res = requests.post(
"https://www.signsecure.org/v1/v1/envelopes/env_3f9a1c7e2b8d4a6f9c0e1b/apply_template",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
json={
"template": "tpl_6b8d0f2a4c1e3a5b7d9f0c",
"recipient": "rcp_7c2e9b1a4f6d8e0c3b5a7d"
},
)
envelope = res.json()Templates
A template is a saved layout of fields, with no document and no people in it, so the same layout can be laid onto next week's copy of the same form. Templates are saved in the app, from any envelope you have placed fields on.
The template object
Attributes
idstringUnique identifier for the template.
objectstringAlways
template.namestringThe name it was saved under.
field_countintegerHow many fields it places.
page_countintegerHow many pages the envelope it was saved from had.
updatedtimestampWhen it was last saved.
{
"id": "tpl_6b8d0f2a4c1e3a5b7d9f0c",
"object": "template",
"name": "Standard order form",
"field_count": 4,
"page_count": 3,
"updated": 1791489600
}List all templates
Returns every template in your organisation, most recently saved first.
Parameters
No parameters.
Returns
A list object of templates. has_more is always false.
curl https://www.signsecure.org/v1/v1/templates
-H "Authorization: Bearer $SIGNSECURE_API_KEY"const res = await fetch('https://www.signsecure.org/v1/v1/templates', {
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
},
});
const templates = await res.json();import os
import requests
res = requests.get(
"https://www.signsecure.org/v1/v1/templates",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
)
templates = res.json(){
"object": "list",
"url": "/v1/templates",
"has_more": false,
"data": [
{
"id": "tpl_6b8d0f2a4c1e3a5b7d9f0c",
"object": "template",
"name": "Standard order form",
"field_count": 4,
"page_count": 3,
"updated": 1791489600
}
]
}Account
Your organisation, its plan, its seats, and how much of the envelope allowance it has used. Useful for showing someone which account they have connected, and how close they are to paying for extra envelopes.
The account object
Attributes
idstringUnique identifier for the organisation.
objectstringAlways
account.namestringThe organisation's name.
planenumbusinessorfree.billing_intervalnullable enumyearormonthon Business,nullon the free plan.subscription_statusnullable stringThe subscription's status, such as
activeorpast_due.seatsintegerSeats paid for. Every Admin and Creator takes one; Viewers and signers are free. The free plan has one.
seats_in_useintegerSeats taken, counting invitations still open for a seat.
envelopesobjectThe envelope allowance for the current period.
Child attributes
includedintegerEnvelopes the plan includes this period: 100 per seat a year on yearly billing, 10 per seat a month on monthly, 5 a month on the free plan.
usedintegerEnvelopes sent since
period_start.period_starttimestampWhen the period began: the subscription's period on Business, the calendar month on the free plan.
overage_countintegerEnvelopes sent past the allowance this period.
overage_amountintegerWhat those cost, in pence, on the next invoice.
overage_unit_amountnullable integerThe price of each envelope past the allowance, in pence.
nullon the free plan, which stops at its allowance instead.currencystringAlways
gbp.
identity_checksobjectallowanceandusedfor the identity check add-on this billing period.api_keyobjectThe key making this request: its
prefix, who itacts_as, and itsrole.
{
"id": "org_1f3a5c7e9b0d2f4a6c8e1b",
"object": "account",
"name": "Your Company Ltd",
"plan": "business",
"billing_interval": "year",
"subscription_status": "active",
"seats": 5,
"seats_in_use": 4,
"envelopes": {
"included": 500,
"used": 212,
"period_start": 1781208000,
"overage_count": 0,
"overage_amount": 0,
"overage_unit_amount": 200,
"currency": "gbp"
},
"identity_checks": {
"allowance": 10,
"used": 3
},
"api_key": {
"prefix": "ss_live_Ab3x",
"acts_as": "sales@yourcompany.com",
"role": "creator"
}
}Retrieve the account
Retrieves the organisation the API key belongs to.
Parameters
No parameters.
Returns
Returns the account object.
curl https://www.signsecure.org/v1/v1/account
-H "Authorization: Bearer $SIGNSECURE_API_KEY"const res = await fetch('https://www.signsecure.org/v1/v1/account', {
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
},
});
const account = await res.json();import os
import requests
res = requests.get(
"https://www.signsecure.org/v1/v1/account",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
)
account = res.json(){
"id": "org_1f3a5c7e9b0d2f4a6c8e1b",
"object": "account",
"name": "Your Company Ltd",
"plan": "business",
"billing_interval": "year",
"subscription_status": "active",
"seats": 5,
"seats_in_use": 4,
"envelopes": {
"included": 500,
"used": 212,
"period_start": 1781208000,
"overage_count": 0,
"overage_amount": 0,
"overage_unit_amount": 200,
"currency": "gbp"
},
"identity_checks": {
"allowance": 10,
"used": 3
},
"api_key": {
"prefix": "ss_live_Ab3x",
"acts_as": "sales@yourcompany.com",
"role": "creator"
}
}Events
An event is a record of something that happened to an envelope. Events are what webhooks send, and you can also list them, which is how an integration catches up after missing a delivery.
Events are kept for 30 days. Each one holds the envelope as it was at that moment, in data.object, without its fields, which can run to hundreds; retrieve the envelope if you need them. Events about one person, recipient.viewed and recipient.signed, also carry that person in data.recipient. envelope.declined carries the person who declined.
The event object
Attributes
idstringUnique identifier for the event. Use it to ignore a delivery you have already handled.
objectstringAlways
event.typestringWhat happened. See types of events.
createdtimestampWhen it happened.
dataobjectobjectis the envelope at that moment.recipient, on recipient events, is the person it was about.
{
"id": "evt_4e6a8c0b2d3f5a7c9e1b2d",
"object": "event",
"type": "envelope.completed",
"created": 1791583201,
"data": {
"object": {
"id": "env_3f9a1c7e2b8d4a6f9c0e1b",
"object": "envelope",
"status": "completed",
"subject": "Order form, Northwind Traders",
"message": "Please sign by Friday.",
"routing_mode": "sequential",
"sender_email": "sales@yourcompany.com",
"created_via": "api",
"metadata": {
"crm_record": "006Qy00000AbCdE"
},
"created": 1791576000,
"sent_at": 1791576300,
"completed_at": 1791583200,
"expires_at": 1792785900,
"voided_at": null,
"void_reason": null,
"documents": [
{
"id": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"object": "document",
"name": "order-form.pdf",
"page_count": 3,
"size": 184320,
"sha256": "9f2c6e1a7b4d8c3f0e5a9b2d6c1f4e8a3b7d0c5f9e2a6b1d4c8f3e7a0b5d9c2f",
"signed_sha256": "4b8e2f6a0d3c7b1e5f9a2d6c0b4e8f3a7d1c5b9e2f6a0c4d8b3e7f1a5c9d2b6e",
"converted_from": null,
"created": 1791576060
}
],
"recipients": [
{
"id": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"object": "recipient",
"name": "Amara Okafor",
"email": "amara.okafor@example.com",
"role": "signer",
"routing_order": 1,
"status": "signed",
"identity_required": false,
"identity_status": null,
"sent_at": 1791576300,
"viewed_at": 1791577500,
"signed_at": 1791577620,
"declined_at": null,
"decline_reason": null
},
{
"id": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"object": "recipient",
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"role": "signer",
"routing_order": 2,
"status": "signed",
"identity_required": false,
"identity_status": null,
"sent_at": 1791577620,
"viewed_at": 1791582900,
"signed_at": 1791583200,
"declined_at": null,
"decline_reason": null
}
],
"fields": null
}
}
}Types of events
This is every event type SignSecure sends. New types may be added; ignore any you do not recognise.
| Type | Occurs when |
|---|---|
envelope.sent | An envelope was sent for signature. |
recipient.viewed | A signer opened the document for the first time. |
recipient.signed | A signer finished signing. Others may still need to. |
envelope.completed | Everyone has signed. The signed PDF is ready to download. |
envelope.declined | A signer declined. Nobody else will be asked to sign. |
envelope.voided | The envelope was voided. |
envelope.expired | The signing links ran out before everyone signed. |
{
"id": "evt_8c0e2a4b6d1f3a5c7e9b0d2f",
"object": "event",
"type": "recipient.signed",
"created": 1791577621,
"data": {
"object": "…the envelope…",
"recipient": {
"id": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"object": "recipient",
"name": "Amara Okafor",
"email": "amara.okafor@example.com",
"role": "signer",
"routing_order": 1,
"status": "signed",
"identity_required": false,
"identity_status": null,
"sent_at": 1791576300,
"viewed_at": 1791577500,
"signed_at": 1791577620,
"declined_at": null,
"decline_reason": null
}
}
}List all events
Returns events from the last 30 days, newest first.
Parameters
typestringoptionalOnly events of this type.
envelopestringoptionalOnly events about this envelope.
limitintegeroptionalFrom 1 to 100. Defaults to 10.
starting_afterstringoptionalA cursor for pagination.
Returns
A list object of events.
curl https://www.signsecure.org/v1/v1/events?type=envelope.completed&limit=10
-H "Authorization: Bearer $SIGNSECURE_API_KEY"const res = await fetch('https://www.signsecure.org/v1/v1/events?type=envelope.completed&limit=10', {
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
},
});
const events = await res.json();import os
import requests
res = requests.get(
"https://www.signsecure.org/v1/v1/events",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
params={
"type": "envelope.completed",
"limit": 10
},
)
events = res.json(){
"object": "list",
"url": "/v1/events",
"has_more": false,
"data": [
{
"id": "evt_4e6a8c0b2d3f5a7c9e1b2d",
"object": "event",
"type": "envelope.completed",
"created": 1791583201,
"data": {
"object": {
"id": "env_3f9a1c7e2b8d4a6f9c0e1b",
"object": "envelope",
"status": "completed",
"subject": "Order form, Northwind Traders",
"message": "Please sign by Friday.",
"routing_mode": "sequential",
"sender_email": "sales@yourcompany.com",
"created_via": "api",
"metadata": {
"crm_record": "006Qy00000AbCdE"
},
"created": 1791576000,
"sent_at": 1791576300,
"completed_at": 1791583200,
"expires_at": 1792785900,
"voided_at": null,
"void_reason": null,
"documents": [
{
"id": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"object": "document",
"name": "order-form.pdf",
"page_count": 3,
"size": 184320,
"sha256": "9f2c6e1a7b4d8c3f0e5a9b2d6c1f4e8a3b7d0c5f9e2a6b1d4c8f3e7a0b5d9c2f",
"signed_sha256": "4b8e2f6a0d3c7b1e5f9a2d6c0b4e8f3a7d1c5b9e2f6a0c4d8b3e7f1a5c9d2b6e",
"converted_from": null,
"created": 1791576060
}
],
"recipients": [
{
"id": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"object": "recipient",
"name": "Amara Okafor",
"email": "amara.okafor@example.com",
"role": "signer",
"routing_order": 1,
"status": "signed",
"identity_required": false,
"identity_status": null,
"sent_at": 1791576300,
"viewed_at": 1791577500,
"signed_at": 1791577620,
"declined_at": null,
"decline_reason": null
},
{
"id": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"object": "recipient",
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"role": "signer",
"routing_order": 2,
"status": "signed",
"identity_required": false,
"identity_status": null,
"sent_at": 1791577620,
"viewed_at": 1791582900,
"signed_at": 1791583200,
"declined_at": null,
"decline_reason": null
}
],
"fields": null
}
}
}
]
}Retrieve an event
Retrieves an event from the last 30 days.
Parameters
No parameters.
Returns
Returns the event object.
curl https://www.signsecure.org/v1/v1/events/evt_4e6a8c0b2d3f5a7c9e1b2d
-H "Authorization: Bearer $SIGNSECURE_API_KEY"const res = await fetch('https://www.signsecure.org/v1/v1/events/evt_4e6a8c0b2d3f5a7c9e1b2d', {
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
},
});
const event = await res.json();import os
import requests
res = requests.get(
"https://www.signsecure.org/v1/v1/events/evt_4e6a8c0b2d3f5a7c9e1b2d",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
)
event = res.json(){
"id": "evt_4e6a8c0b2d3f5a7c9e1b2d",
"object": "event",
"type": "envelope.completed",
"created": 1791583201,
"data": {
"object": {
"id": "env_3f9a1c7e2b8d4a6f9c0e1b",
"object": "envelope",
"status": "completed",
"subject": "Order form, Northwind Traders",
"message": "Please sign by Friday.",
"routing_mode": "sequential",
"sender_email": "sales@yourcompany.com",
"created_via": "api",
"metadata": {
"crm_record": "006Qy00000AbCdE"
},
"created": 1791576000,
"sent_at": 1791576300,
"completed_at": 1791583200,
"expires_at": 1792785900,
"voided_at": null,
"void_reason": null,
"documents": [
{
"id": "doc_1b4d7f0a3c6e9b2d5f8a1c",
"object": "document",
"name": "order-form.pdf",
"page_count": 3,
"size": 184320,
"sha256": "9f2c6e1a7b4d8c3f0e5a9b2d6c1f4e8a3b7d0c5f9e2a6b1d4c8f3e7a0b5d9c2f",
"signed_sha256": "4b8e2f6a0d3c7b1e5f9a2d6c0b4e8f3a7d1c5b9e2f6a0c4d8b3e7f1a5c9d2b6e",
"converted_from": null,
"created": 1791576060
}
],
"recipients": [
{
"id": "rcp_7c2e9b1a4f6d8e0c3b5a7d",
"object": "recipient",
"name": "Amara Okafor",
"email": "amara.okafor@example.com",
"role": "signer",
"routing_order": 1,
"status": "signed",
"identity_required": false,
"identity_status": null,
"sent_at": 1791576300,
"viewed_at": 1791577500,
"signed_at": 1791577620,
"declined_at": null,
"decline_reason": null
},
{
"id": "rcp_2d8f4b6a0c1e3f5a7b9c2e",
"object": "recipient",
"name": "Ben Hughes",
"email": "ben.hughes@example.com",
"role": "signer",
"routing_order": 2,
"status": "signed",
"identity_required": false,
"identity_status": null,
"sent_at": 1791577620,
"viewed_at": 1791582900,
"signed_at": 1791583200,
"declined_at": null,
"decline_reason": null
}
],
"fields": null
}
}
}Webhook endpoints
A webhook endpoint is a URL on your server that SignSecure sends events to as they happen. The webhooks guide covers receiving them and checking their signatures. You can also manage endpoints under Developers in the app.
An endpoint receives every envelope in the organisation, so managing them needs a key created by someone who is an admin now. A key whose creator has since become a Creator or Viewer gets 403.
The webhook endpoint object
Attributes
idstringUnique identifier for the endpoint.
objectstringAlways
webhook_endpoint.urlstringWhere events are sent.
enabled_eventsarray of stringsThe event types sent here, or
["*"]for all of them.descriptionnullable stringYour note about it.
statusenumenabled, ordisabledif you turned it off or it failed for several days in a row.createdtimestampWhen it was created.
last_attempt_atnullable timestampWhen a delivery was last attempted.
last_statusnullable integerThe HTTP status your server returned to that attempt, or
nullif it did not answer.secretstringThe signing secret. Only returned when the endpoint is created.
{
"id": "we_8a0c2e4b6d1f3a5c7e9b0d",
"object": "webhook_endpoint",
"url": "https://hooks.yourcompany.com/signsecure",
"enabled_events": [
"envelope.completed",
"envelope.declined"
],
"description": "CRM sync",
"status": "enabled",
"created": 1791576000,
"last_attempt_at": null,
"last_status": null
}Create a webhook endpoint
Creates an endpoint. The response includes its signing secret, which is shown this once. Store it with your other secrets: you need it to check that each delivery came from SignSecure.
Parameters
urlstringRequiredAn
httpsURL with a public hostname, on port 443 or 8443. IP addresses and private hostnames are refused.enabled_eventsarray of stringsRequiredThe event types to send, or
["*"]for all of them.descriptionstringoptionalA note for yourself, up to 200 characters.
Returns
Returns the webhook endpoint object, including secret.
curl https://www.signsecure.org/v1/v1/webhook_endpoints \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.yourcompany.com/signsecure",
"enabled_events": [
"envelope.completed",
"envelope.declined"
],
"description": "CRM sync"
}'const res = await fetch('https://www.signsecure.org/v1/v1/webhook_endpoints', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://hooks.yourcompany.com/signsecure',
enabled_events: [
'envelope.completed',
'envelope.declined'
],
description: 'CRM sync'
}),
});
const endpoint = await res.json();import os
import requests
res = requests.post(
"https://www.signsecure.org/v1/v1/webhook_endpoints",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
json={
"url": "https://hooks.yourcompany.com/signsecure",
"enabled_events": [
"envelope.completed",
"envelope.declined"
],
"description": "CRM sync"
},
)
endpoint = res.json(){
"id": "we_8a0c2e4b6d1f3a5c7e9b0d",
"object": "webhook_endpoint",
"url": "https://hooks.yourcompany.com/signsecure",
"enabled_events": [
"envelope.completed",
"envelope.declined"
],
"description": "CRM sync",
"status": "enabled",
"created": 1791576000,
"last_attempt_at": null,
"last_status": null,
"secret": "whsec_Xq3vK8mN2pR5tW9yB4cF7hJ0lS6dG1aE3uZ8iO2nV5k"
}Retrieve a webhook endpoint
Retrieves an endpoint. The secret is not included.
Parameters
No parameters.
Returns
Returns the webhook endpoint object.
curl https://www.signsecure.org/v1/v1/webhook_endpoints/we_8a0c2e4b6d1f3a5c7e9b0d
-H "Authorization: Bearer $SIGNSECURE_API_KEY"const res = await fetch('https://www.signsecure.org/v1/v1/webhook_endpoints/we_8a0c2e4b6d1f3a5c7e9b0d', {
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
},
});
const endpoint = await res.json();import os
import requests
res = requests.get(
"https://www.signsecure.org/v1/v1/webhook_endpoints/we_8a0c2e4b6d1f3a5c7e9b0d",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
)
endpoint = res.json(){
"id": "we_8a0c2e4b6d1f3a5c7e9b0d",
"object": "webhook_endpoint",
"url": "https://hooks.yourcompany.com/signsecure",
"enabled_events": [
"envelope.completed",
"envelope.declined"
],
"description": "CRM sync",
"status": "enabled",
"created": 1791576000,
"last_attempt_at": null,
"last_status": null
}Update a webhook endpoint
Updates an endpoint. Parameters you leave out are unchanged.
Parameters
urlstringoptionalA new URL, with the same rules as when creating one.
enabled_eventsarray of stringsoptionalReplaces the list of event types.
descriptionstringoptionalA note for yourself.
disabledbooleanoptionaltruestops deliveries.falseturns the endpoint back on, including one that was switched off after repeated failures; if it fails ten more times in a row it is switched off again.
Returns
Returns the webhook endpoint object.
curl https://www.signsecure.org/v1/v1/webhook_endpoints/we_8a0c2e4b6d1f3a5c7e9b0d \
-H "Authorization: Bearer $SIGNSECURE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"enabled_events": [
"*"
]
}'const res = await fetch('https://www.signsecure.org/v1/v1/webhook_endpoints/we_8a0c2e4b6d1f3a5c7e9b0d', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled_events: [
'*'
]
}),
});
const endpoint = await res.json();import os
import requests
res = requests.post(
"https://www.signsecure.org/v1/v1/webhook_endpoints/we_8a0c2e4b6d1f3a5c7e9b0d",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
json={
"enabled_events": [
"*"
]
},
)
endpoint = res.json(){
"id": "we_8a0c2e4b6d1f3a5c7e9b0d",
"object": "webhook_endpoint",
"url": "https://hooks.yourcompany.com/signsecure",
"enabled_events": [
"*"
],
"description": "CRM sync",
"status": "enabled",
"created": 1791576000,
"last_attempt_at": null,
"last_status": null
}List all webhook endpoints
Returns every endpoint on the account.
Parameters
No parameters.
Returns
A list object of webhook endpoints.
curl https://www.signsecure.org/v1/v1/webhook_endpoints
-H "Authorization: Bearer $SIGNSECURE_API_KEY"const res = await fetch('https://www.signsecure.org/v1/v1/webhook_endpoints', {
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
},
});
const endpoints = await res.json();import os
import requests
res = requests.get(
"https://www.signsecure.org/v1/v1/webhook_endpoints",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
)
endpoints = res.json(){
"object": "list",
"url": "/v1/webhook_endpoints",
"has_more": false,
"data": [
{
"id": "we_8a0c2e4b6d1f3a5c7e9b0d",
"object": "webhook_endpoint",
"url": "https://hooks.yourcompany.com/signsecure",
"enabled_events": [
"envelope.completed",
"envelope.declined"
],
"description": "CRM sync",
"status": "enabled",
"created": 1791576000,
"last_attempt_at": null,
"last_status": null
}
]
}Delete a webhook endpoint
Deletes an endpoint. Deliveries to it stop straight away.
Parameters
No parameters.
Returns
Returns an object with deleted set to true.
curl -X DELETE https://www.signsecure.org/v1/v1/webhook_endpoints/we_8a0c2e4b6d1f3a5c7e9b0d
-H "Authorization: Bearer $SIGNSECURE_API_KEY"const res = await fetch('https://www.signsecure.org/v1/v1/webhook_endpoints/we_8a0c2e4b6d1f3a5c7e9b0d', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.SIGNSECURE_API_KEY}`,
},
});
const deleted = await res.json();import os
import requests
res = requests.delete(
"https://www.signsecure.org/v1/v1/webhook_endpoints/we_8a0c2e4b6d1f3a5c7e9b0d",
headers={"Authorization": f"Bearer {os.environ['SIGNSECURE_API_KEY']}"},
)
deleted = res.json(){
"id": "we_8a0c2e4b6d1f3a5c7e9b0d",
"object": "webhook_endpoint",
"deleted": true
}