API reference
Version 1.5.0 of the OpenAPI 3.1.0 file, rendered when this site was built.
Status. The SignWren API is opening in stages and may not be on your account yet. If Settings shows an API keys section, you can make sandbox keys now; live keys come with the Pro and Business plans. SignWren is an electronic signature app for small businesses; it is not a law firm and does not give legal advice.
What does the API cover?
The SignWren API lets your code create envelopes from PDFs or account templates, send them for signature, follow their status and download the finished files. It calls the same service layer as the SignWren web app, so the same rules apply: plan limits, the excluded-document warning, the signer's consent step and the audit trail.
Nothing in the API can sign for a person. Every signature is made by the signer in the signing ceremony.
Authentication. Send Authorization: Bearer <key>. Keys are made by an owner or admin in Settings, shown once, and start with sw_live_ (live) or sw_test_ (sandbox). A sandbox key sees and makes sandbox envelopes only, and a live key live envelopes only. Live keys need a plan that includes the API (Pro or Business); sandbox keys work on every plan, Free included. A key acts as the member who made it, with that member's current role, plus the key's scopes. Cookies are never accepted on /v1.
OAuth 2.0 for apps. An app that works for other people's SignWren accounts (an automation tool, an MCP client, a partner's product) uses SignWren's OAuth 2.0 authorization server instead of asking for a key: the authorization code grant with PKCE (S256 only, for every client), refresh tokens that rotate on every use, and the API key scopes. The person sees a consent screen that names the app, the organization and each scope, and picks live or sandbox (live needs a plan with the API). Send the access token (swo_at_..., one hour) as Authorization: Bearer <token>; it acts as the person who allowed the app, with their current role, plus the scopes they allowed. Presenting a refresh token a second time revokes the whole grant. Redirect URIs are registered with SignWren and compared exactly (any port for an http://127.0.0.1 redirect of a native app). Endpoints, on the app's host: GET /oauth/authorize (the consent screen), POST /api/oauth/token, POST /api/oauth/revoke (RFC 7009), and metadata at /.well-known/oauth-authorization-server (RFC 8414). The authorization response carries iss (RFC 9207). Apps are registered by SignWren; there is no self-service registration yet. People see and remove apps under Settings, Connected apps.
Ids. Every id has a type prefix: env_ envelopes, doc_ documents, rcp_ recipients, fld_ fields, tpl_ templates, whk_ webhook endpoints, key_ API keys, aex_ audit exports, and sev_ / eev_ audit log events.
IP allowlists. When an organization turns on its IP allowlist (Settings, Business plan), its API keys and the access tokens of apps connected to it work only from the addresses on the list. A request from anywhere else is 403 IP_NOT_ALLOWED, whatever the key or token.
Field positions are PDF points (1/72 inch) with the origin at the top-left corner of the page as displayed; y grows downward. Pages are numbered from 1.
Errors always have the same body (see Error). Another organization's resource, or one in the other environment, is 404 NOT_FOUND, never 403.
Idempotency. Every POST accepts an Idempotency-Key header (1 to 255 printable ASCII characters). The first answer for a key is saved for 24 hours and replayed (with Idempotent-Replayed: true) when the same key is sent again by the same API key. The same key with a different request is 422 IDEMPOTENCY_MISMATCH; a second request while the first is still running is 409 IDEMPOTENCY_IN_PROGRESS and can be retried.
Rate limits. Proposed: 25 requests a second per live key and 10 per sandbox key, with a short burst, and a ceiling per organization. Every answer carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset; a refused request is 429 RATE_LIMITED with Retry-After.
Pagination. List endpoints take limit (1 to 100, default 25) and starting_after (the id of the last item you have) and return { "object": "list", "data": [...], "has_more": true|false }, newest first.
Versioning. Additive changes (new optional fields, new endpoints, new event types) ship on /v1 without notice in the version; removing or renaming a field, changing a type or a default needs /v2. Every change is listed in the changelog the day it ships.
Hosts in the spec:
https://api.signwren.com/v1: Proposed public host (not live yet).https://app.signwren.com/api/v1: The app's own host.
The machine-readable file is served by the API at /v1/openapi.json without a key.
Which endpoints are there?
38 operations, grouped as in the spec. Each lists the scope its key needs.
- Documents (1)
- Envelopes (8)
- Recipients (3)
- Fields (3)
- Downloads (4)
- Templates (3)
- Webhooks (9)
- Embedded (2)
- Spec (1)
- Account (1)
- Audit log (3)
- Webhook events
- Objects (72)
What is in Documents?
Upload a document
POST/v1/documents
Upload one PDF, Word (.docx), JPEG or PNG file as multipart form data in the file field. The same checks as the web app apply: the type is read from the file's bytes, files can be up to 25 MB and 100 pages, Word files with macros are refused, and a PDF that already carries digital signatures is flagged (pre_signed). The document waits up to 24 hours to be put in an envelope with document_ids on POST /envelopes, and can be used once. Word files and images are converted to PDF after they are put in an envelope. Scope: envelopes:write.
Request body (multipart/form-data): object
| Field | Type | Notes |
|---|---|---|
file required | file | The file. Its name is kept as the document's file name. |
Answer 201: The stored document, ready to be put in an envelope. UploadedDocument
Example answer
{
"id": "doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788",
"object": "document",
"filename": "lease-14-alder-court.pdf",
"kind": "pdf",
"status": "ready",
"page_count": 2,
"pages": [
{
"width": 612,
"height": 792
},
{
"width": 612,
"height": 792
}
],
"sha256": "9b74c9897bac770ffc029102a200c5de7d4e6bdfa0e3f1d4b9a2f0c1e8d7c6b5",
"size_bytes": 48213,
"pre_signed": false,
"warnings": [],
"envelope_id": null,
"created_at": "2026-10-01T14:05:00.000Z",
"expires_at": "2026-10-02T14:05:00.000Z"
}What is in Envelopes?
List envelopes
GET/v1/envelopes
Envelopes the key's member can see, newest first. Filter by status, by an external reference to find "the envelope for this record", or by one metadata pair (metadata_key and metadata_value together, an exact match). Scope: envelopes:read.
| Parameter | In | Type | Notes |
|---|---|---|---|
limit | query | integer | |
starting_after | query | EnvelopeId | Id of the last envelope you already have. |
status | query | EnvelopeStatus | |
updated_since | query | string | Only envelopes that changed (any event: sent, viewed, signed, completed, voided and so on) at or after this time, as an RFC 3339 date-time with a time zone. For sync jobs that can't receive webhooks: keep the time you started the last run and pass it, minus a minute or two. Each item's updated_at says when it last changed. |
external_ref.system | query | string | Required with the other external_ref. filters. |
external_ref.object_type | query | string | |
external_ref.object_id | query | string | |
metadata_key | query | string | A metadata key. Required with metadata_value; envelopes whose metadata has this exact pair. |
metadata_value | query | string | The value for metadata_key, matched exactly. |
Answer 200: One page of envelopes. EnvelopeList
Example answer
{
"object": "list",
"has_more": false,
"data": [
{
"id": "env_7d1e0c2b9a8f4e3d8c7b6a5f4e3d2c1b",
"object": "envelope",
"environment": "sandbox",
"title": "Lease, 14 Alder Court",
"status": "sent",
"status_label": "Sent",
"metadata": {
"crm_deal": "D-1042"
},
"created_at": "2026-10-01T14:05:00.000Z",
"updated_at": "2026-10-01T14:20:31.000Z",
"sent_at": "2026-10-01T14:07:12.000Z",
"completed_at": null,
"expires_at": "2026-10-31T14:07:12.000Z",
"recipients": [
{
"id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"name": "Priya Raman",
"email": "priya@example.com",
"role": "signer",
"status": "notified",
"display_status": "Emailed"
}
]
}
]
}Create a draft envelope
POST/v1/envelopes
Create a draft from documents uploaded with POST /documents, optionally with its recipients, fields, metadata and external references in the same call. Fields name their recipient by recipient_index (the position in this request's recipients) and their document by id. To start from an account template, use POST /templates/{template_id}/envelopes. Scope: envelopes:write.
Request body (application/json): CreateEnvelopeRequest
Answer 201: The draft, with its documents, recipients and fields. Envelope
Example request body
{
"title": "Lease, 14 Alder Court",
"message": "Hi Priya, here is the lease we talked about.",
"document_ids": [
"doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788"
],
"recipients": [
{
"name": "Priya Raman",
"email": "priya@example.com",
"role": "signer"
}
],
"fields": [
{
"document_id": "doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788",
"recipient_index": 0,
"type": "signature",
"page": 2,
"x": 72,
"y": 600,
"width": 180,
"height": 40
}
],
"metadata": {
"crm_deal": "D-1042"
},
"external_refs": [
{
"system": "servicenow",
"object_type": "sn_hr_core_case",
"object_id": "9d385017c611228701d22104cc95c371"
}
]
}Example answer
{
"id": "env_7d1e0c2b9a8f4e3d8c7b6a5f4e3d2c1b",
"object": "envelope",
"environment": "sandbox",
"title": "Lease, 14 Alder Court",
"message": "Hi Priya, here is the lease we talked about.",
"status": "draft",
"status_label": "Draft",
"signing_order": "parallel",
"expiry_days": 30,
"expires_at": null,
"reminders": {
"enabled": true,
"interval_days": 3,
"max_count": 3
},
"metadata": {
"crm_deal": "D-1042"
},
"external_refs": [
{
"system": "servicenow",
"object_type": "sn_hr_core_case",
"object_id": "9d385017c611228701d22104cc95c371",
"url": null
}
],
"warnings": [],
"template": null,
"documents": [
{
"id": "doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788",
"object": "document",
"position": 0,
"filename": "lease-14-alder-court.pdf",
"source_type": "pdf",
"status": "ready",
"page_count": 2,
"pages": [
{
"width": 612,
"height": 792
},
{
"width": 612,
"height": 792
}
],
"sha256": "9b74c9897bac770ffc029102a200c5de7d4e6bdfa0e3f1d4b9a2f0c1e8d7c6b5",
"size_bytes": 48213,
"pre_signed": false,
"failure_reason": null
}
],
"recipients": [
{
"id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"object": "recipient",
"name": "Priya Raman",
"email": "priya@example.com",
"role": "signer",
"routing_order": 1,
"status": "pending",
"display_status": "Waiting",
"signing_method": "email",
"email_code": false,
"sms_phone": null,
"has_access_code": false,
"template_role": null,
"embedded": false,
"client_user_id": null,
"signature_meaning": null,
"notified_at": null,
"first_viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": [
{
"id": "fld_5e4d3c2b1a0948f7a6b5c4d3e2f1a0b9",
"object": "field",
"document_id": "doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788",
"recipient_id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"type": "signature",
"page": 2,
"x": 72,
"y": 600,
"width": 180,
"height": 40,
"required": true,
"label": null,
"placeholder": null,
"max_length": null,
"filled": false
}
],
"created_at": "2026-10-01T14:05:00.000Z",
"sent_at": null,
"completed_at": null,
"voided_at": null,
"void_reason": null,
"declined_at": null,
"expired_at": null,
"final_sha256": null,
"certificate_sha256": null,
"signature_level": null
}Get an envelope
GET/v1/envelopes/{envelope_id}
Status, recipients with their status, documents and fields. Scope envelopes:read.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId |
Answer 200: The envelope. Envelope
Example answer
{
"id": "env_7d1e0c2b9a8f4e3d8c7b6a5f4e3d2c1b",
"object": "envelope",
"environment": "sandbox",
"title": "Lease, 14 Alder Court",
"message": "Hi Priya, here is the lease we talked about.",
"status": "draft",
"status_label": "Draft",
"signing_order": "parallel",
"expiry_days": 30,
"expires_at": null,
"reminders": {
"enabled": true,
"interval_days": 3,
"max_count": 3
},
"metadata": {
"crm_deal": "D-1042"
},
"external_refs": [
{
"system": "servicenow",
"object_type": "sn_hr_core_case",
"object_id": "9d385017c611228701d22104cc95c371",
"url": null
}
],
"warnings": [],
"template": null,
"documents": [
{
"id": "doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788",
"object": "document",
"position": 0,
"filename": "lease-14-alder-court.pdf",
"source_type": "pdf",
"status": "ready",
"page_count": 2,
"pages": [
{
"width": 612,
"height": 792
},
{
"width": 612,
"height": 792
}
],
"sha256": "9b74c9897bac770ffc029102a200c5de7d4e6bdfa0e3f1d4b9a2f0c1e8d7c6b5",
"size_bytes": 48213,
"pre_signed": false,
"failure_reason": null
}
],
"recipients": [
{
"id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"object": "recipient",
"name": "Priya Raman",
"email": "priya@example.com",
"role": "signer",
"routing_order": 1,
"status": "pending",
"display_status": "Waiting",
"signing_method": "email",
"email_code": false,
"sms_phone": null,
"has_access_code": false,
"template_role": null,
"embedded": false,
"client_user_id": null,
"signature_meaning": null,
"notified_at": null,
"first_viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": [
{
"id": "fld_5e4d3c2b1a0948f7a6b5c4d3e2f1a0b9",
"object": "field",
"document_id": "doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788",
"recipient_id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"type": "signature",
"page": 2,
"x": 72,
"y": 600,
"width": 180,
"height": 40,
"required": true,
"label": null,
"placeholder": null,
"max_length": null,
"filled": false
}
],
"created_at": "2026-10-01T14:05:00.000Z",
"sent_at": null,
"completed_at": null,
"voided_at": null,
"void_reason": null,
"declined_at": null,
"expired_at": null,
"final_sha256": null,
"certificate_sha256": null,
"signature_level": null
}Change a draft, or its metadata
PATCH/v1/envelopes/{envelope_id}
Title, message, signing order, expiry and reminders can change while the envelope is a draft (409 ENVELOPE_LOCKED after sending). metadata can change at any time; it replaces the whole set. Scope: envelopes:write.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId |
Request body (application/json): UpdateEnvelopeRequest
Answer 200: The envelope after the change. Envelope
Example request body
{
"title": "Lease, 14 Alder Court (revised)",
"reminders": {
"enabled": true,
"interval_days": 2,
"max_count": 3
}
}Example answer
{
"id": "env_7d1e0c2b9a8f4e3d8c7b6a5f4e3d2c1b",
"object": "envelope",
"environment": "sandbox",
"title": "Lease, 14 Alder Court",
"message": "Hi Priya, here is the lease we talked about.",
"status": "draft",
"status_label": "Draft",
"signing_order": "parallel",
"expiry_days": 30,
"expires_at": null,
"reminders": {
"enabled": true,
"interval_days": 3,
"max_count": 3
},
"metadata": {
"crm_deal": "D-1042"
},
"external_refs": [
{
"system": "servicenow",
"object_type": "sn_hr_core_case",
"object_id": "9d385017c611228701d22104cc95c371",
"url": null
}
],
"warnings": [],
"template": null,
"documents": [
{
"id": "doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788",
"object": "document",
"position": 0,
"filename": "lease-14-alder-court.pdf",
"source_type": "pdf",
"status": "ready",
"page_count": 2,
"pages": [
{
"width": 612,
"height": 792
},
{
"width": 612,
"height": 792
}
],
"sha256": "9b74c9897bac770ffc029102a200c5de7d4e6bdfa0e3f1d4b9a2f0c1e8d7c6b5",
"size_bytes": 48213,
"pre_signed": false,
"failure_reason": null
}
],
"recipients": [
{
"id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"object": "recipient",
"name": "Priya Raman",
"email": "priya@example.com",
"role": "signer",
"routing_order": 1,
"status": "pending",
"display_status": "Waiting",
"signing_method": "email",
"email_code": false,
"sms_phone": null,
"has_access_code": false,
"template_role": null,
"embedded": false,
"client_user_id": null,
"signature_meaning": null,
"notified_at": null,
"first_viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": [
{
"id": "fld_5e4d3c2b1a0948f7a6b5c4d3e2f1a0b9",
"object": "field",
"document_id": "doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788",
"recipient_id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"type": "signature",
"page": 2,
"x": 72,
"y": 600,
"width": 180,
"height": 40,
"required": true,
"label": null,
"placeholder": null,
"max_length": null,
"filled": false
}
],
"created_at": "2026-10-01T14:05:00.000Z",
"sent_at": null,
"completed_at": null,
"voided_at": null,
"void_reason": null,
"declined_at": null,
"expired_at": null,
"final_sha256": null,
"certificate_sha256": null,
"signature_level": null
}Send a draft for signature
POST/v1/envelopes/{envelope_id}/send
Sends exactly as the web app does. The sender's business details must be set in Settings first (422 SEND_BLOCKED names what is missing). If the document looks like a kind that electronic signature laws exclude, the answer is 422 WARNING_UNACKNOWLEDGED with the categories, unless acknowledge_warnings lists them; each acknowledgement is recorded in the audit trail with the key's name. A PDF with existing digital signatures needs confirm_pre_signed: true. Sending twice is refused by the envelope's state (409 INVALID_STATE), with or without an idempotency key. Scope envelopes:write.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId |
Request body (application/json, optional): SendRequest
Answer 200: The envelope, now sent. Envelope
Example request body
{
"acknowledge_warnings": [
"EXC_WILL"
]
}Example answer
{
"id": "env_7d1e0c2b9a8f4e3d8c7b6a5f4e3d2c1b",
"object": "envelope",
"environment": "sandbox",
"title": "Lease, 14 Alder Court",
"message": "Hi Priya, here is the lease we talked about.",
"status": "draft",
"status_label": "Draft",
"signing_order": "parallel",
"expiry_days": 30,
"expires_at": null,
"reminders": {
"enabled": true,
"interval_days": 3,
"max_count": 3
},
"metadata": {
"crm_deal": "D-1042"
},
"external_refs": [
{
"system": "servicenow",
"object_type": "sn_hr_core_case",
"object_id": "9d385017c611228701d22104cc95c371",
"url": null
}
],
"warnings": [],
"template": null,
"documents": [
{
"id": "doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788",
"object": "document",
"position": 0,
"filename": "lease-14-alder-court.pdf",
"source_type": "pdf",
"status": "ready",
"page_count": 2,
"pages": [
{
"width": 612,
"height": 792
},
{
"width": 612,
"height": 792
}
],
"sha256": "9b74c9897bac770ffc029102a200c5de7d4e6bdfa0e3f1d4b9a2f0c1e8d7c6b5",
"size_bytes": 48213,
"pre_signed": false,
"failure_reason": null
}
],
"recipients": [
{
"id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"object": "recipient",
"name": "Priya Raman",
"email": "priya@example.com",
"role": "signer",
"routing_order": 1,
"status": "pending",
"display_status": "Waiting",
"signing_method": "email",
"email_code": false,
"sms_phone": null,
"has_access_code": false,
"template_role": null,
"embedded": false,
"client_user_id": null,
"signature_meaning": null,
"notified_at": null,
"first_viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": [
{
"id": "fld_5e4d3c2b1a0948f7a6b5c4d3e2f1a0b9",
"object": "field",
"document_id": "doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788",
"recipient_id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"type": "signature",
"page": 2,
"x": 72,
"y": 600,
"width": 180,
"height": 40,
"required": true,
"label": null,
"placeholder": null,
"max_length": null,
"filled": false
}
],
"created_at": "2026-10-01T14:05:00.000Z",
"sent_at": null,
"completed_at": null,
"voided_at": null,
"void_reason": null,
"declined_at": null,
"expired_at": null,
"final_sha256": null,
"certificate_sha256": null,
"signature_level": null
}Remind a recipient now
POST/v1/envelopes/{envelope_id}/remind
Emails a reminder to a signer whose turn it is. Once every 24 hours per recipient (429 RATE_LIMITED with details.retry_at otherwise). Scope envelopes:write.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId |
Request body (application/json): RemindRequest
Answer 200: The reminder is queued. Recipient
Example request body
{
"recipient_id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9"
}Example answer
{
"id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"object": "recipient",
"name": "Priya Raman",
"email": "priya@example.com",
"role": "signer",
"routing_order": 1,
"status": "pending",
"display_status": "Waiting",
"signing_method": "email",
"email_code": false,
"sms_phone": null,
"has_access_code": false,
"template_role": null,
"embedded": false,
"client_user_id": null,
"signature_meaning": null,
"notified_at": null,
"first_viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}Void an envelope that is out for signature
POST/v1/envelopes/{envelope_id}/void
Signing links stop working and signers who were emailed get a short notice with the reason. Scope envelopes:write.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId |
Request body (application/json): VoidRequest
Answer 200: The envelope, now voided. Envelope
Example request body
{
"reason": "The rent amount was wrong. A corrected lease is on its way."
}Example answer
{
"id": "env_7d1e0c2b9a8f4e3d8c7b6a5f4e3d2c1b",
"object": "envelope",
"environment": "sandbox",
"title": "Lease, 14 Alder Court",
"message": "Hi Priya, here is the lease we talked about.",
"status": "draft",
"status_label": "Draft",
"signing_order": "parallel",
"expiry_days": 30,
"expires_at": null,
"reminders": {
"enabled": true,
"interval_days": 3,
"max_count": 3
},
"metadata": {
"crm_deal": "D-1042"
},
"external_refs": [
{
"system": "servicenow",
"object_type": "sn_hr_core_case",
"object_id": "9d385017c611228701d22104cc95c371",
"url": null
}
],
"warnings": [],
"template": null,
"documents": [
{
"id": "doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788",
"object": "document",
"position": 0,
"filename": "lease-14-alder-court.pdf",
"source_type": "pdf",
"status": "ready",
"page_count": 2,
"pages": [
{
"width": 612,
"height": 792
},
{
"width": 612,
"height": 792
}
],
"sha256": "9b74c9897bac770ffc029102a200c5de7d4e6bdfa0e3f1d4b9a2f0c1e8d7c6b5",
"size_bytes": 48213,
"pre_signed": false,
"failure_reason": null
}
],
"recipients": [
{
"id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"object": "recipient",
"name": "Priya Raman",
"email": "priya@example.com",
"role": "signer",
"routing_order": 1,
"status": "pending",
"display_status": "Waiting",
"signing_method": "email",
"email_code": false,
"sms_phone": null,
"has_access_code": false,
"template_role": null,
"embedded": false,
"client_user_id": null,
"signature_meaning": null,
"notified_at": null,
"first_viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": [
{
"id": "fld_5e4d3c2b1a0948f7a6b5c4d3e2f1a0b9",
"object": "field",
"document_id": "doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788",
"recipient_id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"type": "signature",
"page": 2,
"x": 72,
"y": 600,
"width": 180,
"height": 40,
"required": true,
"label": null,
"placeholder": null,
"max_length": null,
"filled": false
}
],
"created_at": "2026-10-01T14:05:00.000Z",
"sent_at": null,
"completed_at": null,
"voided_at": null,
"void_reason": null,
"declined_at": null,
"expired_at": null,
"final_sha256": null,
"certificate_sha256": null,
"signature_level": null
}The audit trail
GET/v1/envelopes/{envelope_id}/events
Every audit event in order, with the result of recomputing the envelope's hash chain. The whole trail is returned in one page. Scope envelopes:read.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId |
Answer 200: The audit trail. EventList
Example answer
{
"object": "list",
"has_more": false,
"chain": {
"valid": true,
"length": 2,
"head_hash": "4f0c3d2b1a09887766554433221100ffeeddccbbaa99887766554433221100ff",
"broken_at_seq": null,
"reason": null
},
"data": [
{
"seq": 1,
"type": "envelope.created",
"description": "Draft created",
"occurred_at": "2026-10-01T14:05:00.000Z",
"actor_type": "user",
"recipient_id": null,
"ip_address": "203.0.113.7",
"user_agent": "my-crm/2.1",
"details": {
"apiKey": {
"id": "2c9e1d4b-7a3f-4e8d-9b6c-5a4f3e2d1c0b",
"name": "CRM sync"
}
}
},
{
"seq": 2,
"type": "document.uploaded",
"description": "Added lease-14-alder-court.pdf",
"occurred_at": "2026-10-01T14:05:01.000Z",
"actor_type": "user",
"recipient_id": null,
"ip_address": "203.0.113.7",
"user_agent": "my-crm/2.1",
"details": {
"filename": "lease-14-alder-court.pdf"
}
}
]
}What is in Recipients?
Add a recipient to a draft
POST/v1/envelopes/{envelope_id}/recipients
Same limits as the editor (up to 10 recipients). Scope envelopes:write.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId |
Request body (application/json): RecipientInput
Answer 201: The new recipient. Recipient
Example request body
{
"name": "Tom Brennan",
"email": "tom@example.com",
"role": "signer",
"routing_order": 2
}Example answer
{
"id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"object": "recipient",
"name": "Priya Raman",
"email": "priya@example.com",
"role": "signer",
"routing_order": 1,
"status": "pending",
"display_status": "Waiting",
"signing_method": "email",
"email_code": false,
"sms_phone": null,
"has_access_code": false,
"template_role": null,
"embedded": false,
"client_user_id": null,
"signature_meaning": null,
"notified_at": null,
"first_viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}Change a recipient on a draft
PATCH/v1/envelopes/{envelope_id}/recipients/{recipient_id}
Only the properties sent change. Scope envelopes:write.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId | |
recipient_id required | path | RecipientId |
Request body (application/json): RecipientPatch
Answer 200: The recipient after the change. Recipient
Example request body
{
"email": "priya.raman@example.com"
}Example answer
{
"id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"object": "recipient",
"name": "Priya Raman",
"email": "priya@example.com",
"role": "signer",
"routing_order": 1,
"status": "pending",
"display_status": "Waiting",
"signing_method": "email",
"email_code": false,
"sms_phone": null,
"has_access_code": false,
"template_role": null,
"embedded": false,
"client_user_id": null,
"signature_meaning": null,
"notified_at": null,
"first_viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}Remove a recipient from a draft
DELETE/v1/envelopes/{envelope_id}/recipients/{recipient_id}
Their fields are removed with them. Scope envelopes:write.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId | |
recipient_id required | path | RecipientId |
Answer 200: Removed. Deleted
Example answer
{
"id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"object": "recipient",
"deleted": true
}What is in Fields?
Add a field to a draft
POST/v1/envelopes/{envelope_id}/fields
Positions are PDF points from the top-left of the page as displayed. Same rules as the editor: minimum sizes, inside the page, at most 500 fields, signature fields always required. After sending, 409 ENVELOPE_LOCKED. Scope envelopes:write.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId |
Request body (application/json): FieldInput
Answer 201: The new field. Field
Example request body
{
"document_id": "doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788",
"recipient_id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"type": "date_signed",
"page": 2,
"x": 300,
"y": 650,
"width": 110,
"height": 18
}Example answer
{
"id": "fld_5e4d3c2b1a0948f7a6b5c4d3e2f1a0b9",
"object": "field",
"document_id": "doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788",
"recipient_id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"type": "date_signed",
"page": 2,
"x": 300,
"y": 650,
"width": 110,
"height": 18,
"required": true,
"label": null,
"placeholder": null,
"max_length": null,
"filled": false
}Move, resize or change a field on a draft
PATCH/v1/envelopes/{envelope_id}/fields/{field_id}
Only the properties sent change. Scope envelopes:write.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId | |
field_id required | path | FieldId |
Request body (application/json): FieldPatch
Answer 200: The field after the change. Field
Example request body
{
"x": 320,
"y": 655
}Example answer
{
"id": "fld_5e4d3c2b1a0948f7a6b5c4d3e2f1a0b9",
"object": "field",
"document_id": "doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788",
"recipient_id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"type": "date_signed",
"page": 2,
"x": 300,
"y": 650,
"width": 110,
"height": 18,
"required": true,
"label": null,
"placeholder": null,
"max_length": null,
"filled": false
}Remove a field from a draft
DELETE/v1/envelopes/{envelope_id}/fields/{field_id}
Scope envelopes:write.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId | |
field_id required | path | FieldId |
Answer 200: Removed. Deleted
Example answer
{
"id": "fld_5e4d3c2b1a0948f7a6b5c4d3e2f1a0b9",
"object": "field",
"deleted": true
}What is in Downloads?
Download an original document
GET/v1/envelopes/{envelope_id}/documents/{document_id}
The document as it was prepared for signing, at any status. Each download is recorded in the audit trail. Scope envelopes:read.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId | |
document_id required | path | DocumentId |
Answer 200: The PDF, byte for byte as stored. file
Download the signed, sealed PDF
GET/v1/envelopes/{envelope_id}/documents/combined
The finished PDF of a completed envelope, byte for byte as stored, with ETag and Repr-Digest set from its SHA-256. Once a year SignWren adds a fresh document timestamp to stored sealed PDFs so they stay checkable for longer; after that this answers with the newest copy, so its SHA-256 differs from the envelope's final_sha256 (the original). Signatures and content are the same, and both copies pass the public check page. 409 INVALID_STATE until the envelope is completed. Each download is recorded in the audit trail. Scope envelopes:read.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId |
Answer 200: The PDF, byte for byte as stored. file
Download the certificate of completion
GET/v1/envelopes/{envelope_id}/certificate
Same rules as the combined PDF. Scope envelopes:read.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId |
Answer 200: The PDF, byte for byte as stored. file
Get a short-lived download link for a finished file
POST/v1/envelopes/{envelope_id}/file_links
A link to the signed, sealed PDF (signed_pdf) or the certificate of completion (certificate) of a completed envelope that works without a key until it expires (5 to 60 minutes, 60 by default). For tools that fetch files by URL, such as no-code automation apps. Anyone who has the link can download the file until then, so pass it only to the system that needs it. A signed_pdf link is for the newest sealed copy, the same file as the combined download, and sha256 is that file's hash. Each download through the link is recorded in the audit trail. 409 INVALID_STATE until the envelope is completed. Scope envelopes:read.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId |
Request body (application/json, optional): CreateFileLinkRequest
Answer 201: The link. FileLink
Example request body
{
"file": "signed_pdf",
"expires_in_minutes": 60
}Example answer
{
"object": "file_link",
"envelope_id": "env_7d1e0c2b9a8f4e3d8c7b6a5f4e3d2c1b",
"file": "signed_pdf",
"url": "https://app.signwren.com/api/files/eyJ2IjoxLCJlIjoiN2QxZTBjMmIifQ.c2lnbmF0dXJlLWV4YW1wbGU",
"filename": "Lease, 14 Alder Court (signed).pdf",
"sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"expires_at": "2026-10-01T15:05:00.000Z"
}What is in Templates?
List account templates
GET/v1/templates
Account templates the key's member can see, newest first, with their roles and sender fields. Scope templates:read.
| Parameter | In | Type | Notes |
|---|---|---|---|
limit | query | integer | |
starting_after | query | TemplateId | Id of the last template you already have. |
Answer 200: One page of templates. TemplateList
Example answer
{
"object": "list",
"has_more": false,
"data": [
{
"id": "tpl_1c2d3e4f5a6b47c8d9e0f1a2b3c4d5e6",
"object": "template",
"name": "Residential lease",
"visibility": "org",
"current_version": 3,
"signing_order": "sequential",
"message": "Please review and sign the lease.",
"expiry_days": 30,
"roles": [
{
"id": "tenant",
"name": "Tenant",
"kind": "signer",
"routing_order": 1,
"optional": false
},
{
"id": "landlord",
"name": "Landlord",
"kind": "signer",
"routing_order": 2,
"optional": false
}
],
"sender_fields": [
{
"id": "monthly_rent",
"label": "Monthly rent",
"fill": "prefilled",
"required": true,
"default_value": null
}
],
"merge_fields": [
"tenant.full_name"
],
"merge_field_mappings": [
{
"name": "tenant.full_name",
"target_kind": "placeholder",
"target": "[Tenant's full legal name]",
"required": true
}
],
"documents": [
{
"filename": "lease.pdf",
"page_count": 4
}
],
"created_at": "2026-09-20T10:00:00.000Z",
"updated_at": "2026-09-28T16:30:00.000Z"
}
]
}Get an account template
GET/v1/templates/{template_id}
The current version's roles, sender fields and settings. Scope templates:read.
| Parameter | In | Type | Notes |
|---|---|---|---|
template_id required | path | TemplateId |
Answer 200: The template. Template
Example answer
{
"id": "tpl_1c2d3e4f5a6b47c8d9e0f1a2b3c4d5e6",
"object": "template",
"name": "Residential lease",
"visibility": "org",
"current_version": 3,
"signing_order": "sequential",
"message": "Please review and sign the lease.",
"expiry_days": 30,
"roles": [
{
"id": "tenant",
"name": "Tenant",
"kind": "signer",
"routing_order": 1,
"optional": false
},
{
"id": "landlord",
"name": "Landlord",
"kind": "signer",
"routing_order": 2,
"optional": false
}
],
"sender_fields": [
{
"id": "monthly_rent",
"label": "Monthly rent",
"fill": "prefilled",
"required": true,
"default_value": null
}
],
"merge_fields": [
"tenant.full_name"
],
"merge_field_mappings": [
{
"name": "tenant.full_name",
"target_kind": "placeholder",
"target": "[Tenant's full legal name]",
"required": true
}
],
"documents": [
{
"filename": "lease.pdf",
"page_count": 4
}
],
"created_at": "2026-09-20T10:00:00.000Z",
"updated_at": "2026-09-28T16:30:00.000Z"
}Create (and optionally send) an envelope from a template
POST/v1/templates/{template_id}/envelopes
A draft from the template's current version: one person per role, values for sender fields (drawn into the document) and prefilled fields (shown to the signer, who can change them). With send: true the draft is sent in the same request, with the same rules as POST /envelopes/{envelope_id}/send; if sending is refused, the draft stays and the error names it in details.envelope_id. Scopes templates:read and envelopes:write.
Merge fields: merge_fields fills the template's mapped placeholders (the text is laid out again with the values) and mapped sender or prefilled fields. The answer's merge lists what was applied, ignored and missing. A required merge field without a value keeps the envelope a draft; with send: true that is 422 MERGE_FIELDS_MISSING with details.envelope_id and details.missing_merge_fields. merge_from_record: true also reads values from the record in the first external_refs entry through the account's connection to that system (503 INTEGRATIONS_NOT_CONFIGURED when the server has no connections set up).
| Parameter | In | Type | Notes |
|---|---|---|---|
template_id required | path | TemplateId |
Request body (application/json): CreateFromTemplateRequest
Answer 201: The new envelope (sent when `send` was true). Envelope
Example request body
{
"recipients": [
{
"role_id": "tenant",
"name": "Priya Raman",
"email": "priya@example.com"
},
{
"role_id": "landlord",
"name": "Dana Whitfield",
"email": "dana@example.com"
}
],
"prefill": {
"monthly_rent": "1,450"
},
"merge_fields": {
"tenant.full_name": "Priya Raman"
},
"send": true,
"metadata": {
"crm_deal": "D-1042"
}
}Example answer
{
"id": "env_7d1e0c2b9a8f4e3d8c7b6a5f4e3d2c1b",
"object": "envelope",
"environment": "sandbox",
"title": "Lease, 14 Alder Court",
"message": "Hi Priya, here is the lease we talked about.",
"status": "draft",
"status_label": "Draft",
"signing_order": "parallel",
"expiry_days": 30,
"expires_at": null,
"reminders": {
"enabled": true,
"interval_days": 3,
"max_count": 3
},
"metadata": {
"crm_deal": "D-1042"
},
"external_refs": [
{
"system": "servicenow",
"object_type": "sn_hr_core_case",
"object_id": "9d385017c611228701d22104cc95c371",
"url": null
}
],
"warnings": [],
"template": null,
"documents": [
{
"id": "doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788",
"object": "document",
"position": 0,
"filename": "lease-14-alder-court.pdf",
"source_type": "pdf",
"status": "ready",
"page_count": 2,
"pages": [
{
"width": 612,
"height": 792
},
{
"width": 612,
"height": 792
}
],
"sha256": "9b74c9897bac770ffc029102a200c5de7d4e6bdfa0e3f1d4b9a2f0c1e8d7c6b5",
"size_bytes": 48213,
"pre_signed": false,
"failure_reason": null
}
],
"recipients": [
{
"id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"object": "recipient",
"name": "Priya Raman",
"email": "priya@example.com",
"role": "signer",
"routing_order": 1,
"status": "pending",
"display_status": "Waiting",
"signing_method": "email",
"email_code": false,
"sms_phone": null,
"has_access_code": false,
"template_role": null,
"embedded": false,
"client_user_id": null,
"signature_meaning": null,
"notified_at": null,
"first_viewed_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": [
{
"id": "fld_5e4d3c2b1a0948f7a6b5c4d3e2f1a0b9",
"object": "field",
"document_id": "doc_3f1c2a9e8b7d4c6fa1e2d3c4b5a69788",
"recipient_id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"type": "signature",
"page": 2,
"x": 72,
"y": 600,
"width": 180,
"height": 40,
"required": true,
"label": null,
"placeholder": null,
"max_length": null,
"filled": false
}
],
"created_at": "2026-10-01T14:05:00.000Z",
"sent_at": null,
"completed_at": null,
"voided_at": null,
"void_reason": null,
"declined_at": null,
"expired_at": null,
"final_sha256": null,
"certificate_sha256": null,
"signature_level": null
}What is in Webhooks?
Signed HTTPS notifications when envelopes change (P3-HOOK).
List webhook endpoints
GET/v1/webhook-endpoints
The endpoints of the key's environment (a sandbox key sees sandbox endpoints only), newest first, always as one page. Secrets are never included. An app connected with OAuth sees only the endpoints it made itself (app names it); keys and the web app see every endpoint, the apps' ones included. Scope: webhooks:manage.
Answer 200: The endpoints. WebhookEndpointList
Example answer
{
"object": "list",
"has_more": false,
"data": [
{
"id": "whk_4b3a2f1e0d9c48b7a6f5e4d3c2b1a090",
"object": "webhook_endpoint",
"url": "https://example.com/signwren/webhook",
"description": "CRM sync",
"events": [
"envelope.completed",
"recipient.signed"
],
"environment": "sandbox",
"livemode": false,
"status": "enabled",
"disabled_reason": null,
"disabled_at": null,
"secret_hint": "whsec_...Q2xR",
"rotation_ends_at": null,
"app": null,
"created_at": "2026-10-01T14:05:00.000Z",
"updated_at": "2026-10-01T14:05:00.000Z"
}
]
}Add a webhook endpoint
POST/v1/webhook-endpoints
Subscribe an https:// URL to envelope events in the key's environment. The URL must resolve only to public addresses (private, loopback, link-local and cloud metadata ranges are refused, here and again before every delivery) and redirects are never followed. The answer carries the signing secret once; it is never shown again (rotate it if you lose it). Live endpoints need a plan that includes the API; sandbox endpoints work on every plan. Scope: webhooks:manage.
Endpoints made by an app. When an app connected with OAuth makes an endpoint (an automation tool subscribing to an event, for example), the endpoint belongs to that connection: it carries app, only that connection can see and change it, and SignWren deletes it when the connection ends (someone removes the app in Settings, the app revokes its token, or the person who allowed it leaves the account) or when the receiver answers a delivery with 410 Gone. Each environment has room for 16 endpoints made with keys or in the app, and each app connection for 50 of its own.
Request body (application/json): CreateWebhookEndpointRequest
Answer 201: The endpoint, with its signing secret (shown this once). WebhookEndpointWithSecret
Example request body
{
"url": "https://example.com/signwren/webhook",
"events": [
"envelope.completed",
"recipient.signed"
],
"description": "CRM sync"
}Example answer
{
"id": "whk_4b3a2f1e0d9c48b7a6f5e4d3c2b1a090",
"object": "webhook_endpoint",
"url": "https://example.com/signwren/webhook",
"description": "CRM sync",
"events": [
"envelope.completed",
"recipient.signed"
],
"environment": "sandbox",
"livemode": false,
"status": "enabled",
"disabled_reason": null,
"disabled_at": null,
"secret_hint": "whsec_...Q2xR",
"rotation_ends_at": null,
"app": null,
"created_at": "2026-10-01T14:05:00.000Z",
"updated_at": "2026-10-01T14:05:00.000Z",
"secret": "whsec_ZXhhbXBsZSBvbmx5LCBub3QgYSByZWFsIHdlYmhvb2s"
}Get a webhook endpoint
GET/v1/webhook-endpoints/{webhook_id}
One endpoint of the key's environment. Scope webhooks:manage.
| Parameter | In | Type | Notes |
|---|---|---|---|
webhook_id required | path | WebhookEndpointId |
Answer 200: The endpoint. WebhookEndpoint
Example answer
{
"id": "whk_4b3a2f1e0d9c48b7a6f5e4d3c2b1a090",
"object": "webhook_endpoint",
"url": "https://example.com/signwren/webhook",
"description": "CRM sync",
"events": [
"envelope.completed",
"recipient.signed"
],
"environment": "sandbox",
"livemode": false,
"status": "enabled",
"disabled_reason": null,
"disabled_at": null,
"secret_hint": "whsec_...Q2xR",
"rotation_ends_at": null,
"app": null,
"created_at": "2026-10-01T14:05:00.000Z",
"updated_at": "2026-10-01T14:05:00.000Z"
}Change a webhook endpoint, or turn it off and on
PATCH/v1/webhook-endpoints/{webhook_id}
Change the URL (checked again), the events or the description, or set enabled. An endpoint that failed every delivery for 3 days is turned off by SignWren (status: disabled, disabled_reason: failing); enabled: true turns it back on. Scope: webhooks:manage.
| Parameter | In | Type | Notes |
|---|---|---|---|
webhook_id required | path | WebhookEndpointId |
Request body (application/json): UpdateWebhookEndpointRequest
Answer 200: The endpoint after the change. WebhookEndpoint
Example request body
{
"enabled": true
}Example answer
{
"id": "whk_4b3a2f1e0d9c48b7a6f5e4d3c2b1a090",
"object": "webhook_endpoint",
"url": "https://example.com/signwren/webhook",
"description": "CRM sync",
"events": [
"envelope.completed",
"recipient.signed"
],
"environment": "sandbox",
"livemode": false,
"status": "enabled",
"disabled_reason": null,
"disabled_at": null,
"secret_hint": "whsec_...Q2xR",
"rotation_ends_at": null,
"app": null,
"created_at": "2026-10-01T14:05:00.000Z",
"updated_at": "2026-10-01T14:05:00.000Z"
}Delete a webhook endpoint
DELETE/v1/webhook-endpoints/{webhook_id}
The endpoint and its delivery log are deleted. Scope webhooks:manage.
| Parameter | In | Type | Notes |
|---|---|---|---|
webhook_id required | path | WebhookEndpointId |
Answer 200: Deleted. WebhookEndpointDeleted
Example answer
{
"id": "whk_4b3a2f1e0d9c48b7a6f5e4d3c2b1a090",
"object": "webhook_endpoint",
"deleted": true
}Rotate an endpoint's signing secret
POST/v1/webhook-endpoints/{webhook_id}/rotate-secret
Makes a new secret and returns it once. For 24 hours the old secret signs as well, so each delivery carries two v1= signatures and receivers can switch over without dropping events. expire_now: true stops the old secret at once. Scope: webhooks:manage.
| Parameter | In | Type | Notes |
|---|---|---|---|
webhook_id required | path | WebhookEndpointId |
Request body (application/json, optional): RotateWebhookSecretRequest
Answer 200: The endpoint with its new secret (shown this once). WebhookEndpointWithSecret
Example request body
{
"expire_now": false
}Example answer
{
"id": "whk_4b3a2f1e0d9c48b7a6f5e4d3c2b1a090",
"object": "webhook_endpoint",
"url": "https://example.com/signwren/webhook",
"description": "CRM sync",
"events": [
"envelope.completed",
"recipient.signed"
],
"environment": "sandbox",
"livemode": false,
"status": "enabled",
"disabled_reason": null,
"disabled_at": null,
"secret_hint": "whsec_...Q2xR",
"rotation_ends_at": "2026-10-02T14:05:00.000Z",
"app": null,
"created_at": "2026-10-01T14:05:00.000Z",
"updated_at": "2026-10-01T14:05:00.000Z",
"secret": "whsec_YW5vdGhlciBleGFtcGxlLCBub3QgYSByZWFsIHNlY3J"
}Send a test event
POST/v1/webhook-endpoints/{webhook_id}/test
Queues one webhook.test event to this endpoint; it goes out within seconds and appears in the delivery log. A test event is tried once. The endpoint must be on (409 otherwise). Scope: webhooks:manage.
| Parameter | In | Type | Notes |
|---|---|---|---|
webhook_id required | path | WebhookEndpointId |
Answer 202: The queued delivery. WebhookDelivery
Example answer
{
"id": "whd_9a8b7c6d5e4f40312a3b4c5d6e7f8091",
"object": "webhook_delivery",
"webhook_endpoint_id": "whk_4b3a2f1e0d9c48b7a6f5e4d3c2b1a090",
"event_id": "evt_0b9d6a512f0c4d559b2f7a0d6c1e2f30",
"event_type": "webhook.test",
"environment": "sandbox",
"livemode": false,
"status": "pending",
"attempts": 0,
"next_attempt_at": null,
"last_status_code": 200,
"last_error": null,
"delivered_at": "2026-10-01T14:20:31.000Z",
"is_test": true,
"resend_of": null,
"created_at": "2026-10-01T14:20:00.000Z",
"attempt_log": []
}The delivery log
GET/v1/webhook-endpoints/{webhook_id}/deliveries
Deliveries to this endpoint, newest first, each with every attempt: time, status code, how long it took and the first 1 KB of the response body. Deliveries are kept for 30 days. Scope: webhooks:manage.
| Parameter | In | Type | Notes |
|---|---|---|---|
webhook_id required | path | WebhookEndpointId | |
limit | query | integer | |
starting_after | query | WebhookDeliveryId | Id of the last delivery you already have. |
Answer 200: One page of deliveries. WebhookDeliveryList
Example answer
{
"object": "list",
"has_more": false,
"data": [
{
"id": "whd_9a8b7c6d5e4f40312a3b4c5d6e7f8091",
"object": "webhook_delivery",
"webhook_endpoint_id": "whk_4b3a2f1e0d9c48b7a6f5e4d3c2b1a090",
"event_id": "evt_0b9d6a512f0c4d559b2f7a0d6c1e2f30",
"event_type": "envelope.completed",
"environment": "sandbox",
"livemode": false,
"status": "delivered",
"attempts": 2,
"next_attempt_at": null,
"last_status_code": 200,
"last_error": null,
"delivered_at": "2026-10-01T14:20:31.000Z",
"is_test": false,
"resend_of": null,
"created_at": "2026-10-01T14:20:00.000Z",
"attempt_log": [
{
"attempt": 1,
"at": "2026-10-01T14:20:00.400Z",
"duration_ms": 10004,
"status_code": null,
"error": "timeout: no complete answer within 10 s",
"response_excerpt": null,
"manual": false
},
{
"attempt": 2,
"at": "2026-10-01T14:20:30.900Z",
"duration_ms": 182,
"status_code": 200,
"error": null,
"response_excerpt": "ok",
"manual": false
}
]
}
]
}Resend a delivery
POST/v1/webhook-endpoints/{webhook_id}/deliveries/{delivery_id}/resend
Sends a finished delivery again, with the same event id and body, as a new entry in the log. Not while it is still being retried, and not to an endpoint that is off (409). Scope: webhooks:manage.
| Parameter | In | Type | Notes |
|---|---|---|---|
webhook_id required | path | WebhookEndpointId | |
delivery_id required | path | WebhookDeliveryId |
Answer 202: The new, queued delivery. WebhookDelivery
Example answer
{
"id": "whd_0f1e2d3c4b5a49687f6e5d4c3b2a1908",
"object": "webhook_delivery",
"webhook_endpoint_id": "whk_4b3a2f1e0d9c48b7a6f5e4d3c2b1a090",
"event_id": "evt_0b9d6a512f0c4d559b2f7a0d6c1e2f30",
"event_type": "envelope.completed",
"environment": "sandbox",
"livemode": false,
"status": "pending",
"attempts": 0,
"next_attempt_at": null,
"last_status_code": 200,
"last_error": null,
"delivered_at": "2026-10-01T14:20:31.000Z",
"is_test": false,
"resend_of": "whd_9a8b7c6d5e4f40312a3b4c5d6e7f8091",
"created_at": "2026-10-01T14:20:00.000Z",
"attempt_log": []
}What is in Embedded?
Signing and sending inside your own app (P3-EMB). Add the sites your app runs on in Settings, under Embedding (separately for live and sandbox). Then ask for a short-lived URL and either put it in an iframe on one of those sites or send the person to it.
Get a signing URL for an embedded signer
POST/v1/envelopes/{envelope_id}/recipients/{recipient_id}/signing_url
For a recipient added with embedded: true on a sent envelope, when it is their turn. The URL must be opened within 5 minutes and then works for the browser that opened it only. Asking again stops earlier embedded URLs for the same signer. frame_origin picks one of the sites allowed in Settings (needed when more than one is allowed); return_url must be on one of them. Errors: 409 INVALID_STATE (not sent, finished, or not an embedded signer), 403 NOT_YOUR_TURN, 422 VALIDATION with details.reason EMBED_ORIGINS_REQUIRED, EMBED_ORIGIN_NOT_ALLOWED, EMBED_ORIGIN_REQUIRED or RETURN_URL_NOT_ALLOWED. Scope envelopes:write.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId | |
recipient_id required | path | RecipientId |
Request body (application/json, optional): EmbedUrlRequest
Answer 201: The signing URL. SigningUrl
Example request body
{
"frame_origin": "https://app.example.com",
"return_url": "https://app.example.com/leases/4471"
}Example answer
{
"object": "signing_url",
"url": "https://app.signwren.com/embedded/sign/q3Jr9xWbVv0nF2yQe1Lk8sPz4tUa7cMd6hGi5oRj0bE",
"expires_at": "2026-10-01T15:05:00.000Z",
"envelope_id": "env_7d1e0c2b9a8f4e3d8c7b6a5f4e3d2c1b",
"recipient_id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"frame_origin": "https://app.example.com",
"return_url": "https://app.example.com/leases/4471",
"environment": "sandbox"
}Get a URL to the field editor for a draft
POST/v1/envelopes/{envelope_id}/sender_view_url
A URL to place fields on this draft and send it, inside your app. It must be opened within 5 minutes, works for the browser that opened it only (for an hour), reaches this one draft and nothing else, and acts as this API key, or as the app and the person behind an OAuth access token (checked again on every request, so revoking the app ends the view). Pressing Send posts signwren:sent to the parent window. frame_origin must be one of the sites allowed in Settings; only SignWren's own Outlook and Gmail add-ins may name SignWren's own address instead. Errors: 409 ENVELOPE_LOCKED (already sent), 422 VALIDATION as for signing URLs. Scope envelopes:write.
| Parameter | In | Type | Notes |
|---|---|---|---|
envelope_id required | path | EnvelopeId |
Request body (application/json, optional): EmbedUrlRequest
Answer 201: The sending view URL. SenderViewUrl
Example request body
{
"frame_origin": "https://app.example.com"
}Example answer
{
"object": "sender_view_url",
"url": "https://app.signwren.com/embedded/send/Yk2p8QwLx4vN0aT7rB1sC9dE3fG6hJ5mZ2uV8iO1qW4",
"expires_at": "2026-10-01T15:05:00.000Z",
"envelope_id": "env_7d1e0c2b9a8f4e3d8c7b6a5f4e3d2c1b",
"frame_origin": "https://app.example.com",
"return_url": null,
"environment": "live"
}What is in Spec?
This specification as JSON
GET/v1/openapi.json
Answer 200: The OpenAPI document. object
Example answer
{
"openapi": "3.1.0",
"info": {
"title": "SignWren API",
"version": "1.0.0"
}
}What is in Account?
The organization, environment and person a key or access token acts for.
Who this key or access token acts for
GET/v1/me
The organization, the environment and the person a key or OAuth access token acts for, and its scopes. Apps call it to check that a connection works and to label it, for example "Priya Raman, Wren Lane Lettings (sandbox)". Any valid key or access token may call it; it needs no scope.
Answer 200: The key or token and who it acts for. Me
Example answer
{
"object": "me",
"environment": "sandbox",
"livemode": false,
"credential": {
"type": "oauth",
"name": "Zapier",
"scopes": [
"envelopes:read",
"envelopes:write",
"templates:read",
"webhooks:manage"
]
},
"organization": {
"name": "Wren Lane Lettings",
"region": "us"
},
"user": {
"name": "Priya Raman",
"email": "priya@example.com",
"role": "owner"
}
}What is in Audit log?
The organization's security log and every envelope audit event, for security reviews and your own archive (P4-AUD). Owners and admins only (the key or token acts with its member's current role), Business plan, scope audit:read, and live keys or tokens only: sandbox credentials get 403 LIVE_KEY_REQUIRED.
List audit log events
GET/v1/audit-events
Security events or envelope audit events in a date range, newest first. from and to take a date (2026-10-01, a whole UTC day; to includes that day) or an RFC 3339 date-time; a range can cover up to 366 days. Envelope pages also carry chains: the result of recomputing the hash chain of each envelope on the page. Scope audit:read; owners and admins; Business plan; live only.
| Parameter | In | Type | Notes |
|---|---|---|---|
source required | query | "security" | "envelope" | security for the security log, envelope for envelope audit events. |
from required | query | string | Start of the range (included). |
to required | query | string | End of the range. A date includes that whole day; a date-time is excluded. |
limit | query | integer | |
starting_after | query | string | Id of the last event you already have (sev_... or eev_..., matching source). |
Answer 200: One page of events. AuditLogEventList
Example answer
{
"object": "list",
"source": "envelope",
"has_more": false,
"data": [
{
"id": "eev_4812",
"object": "audit_event",
"source": "envelope",
"type": "recipient.signed",
"occurred_at": "2026-09-14T15:02:11.000Z",
"envelope_id": "env_7d1e0c2b9a8f4e3d8c7b6a5f4e3d2c1b",
"environment": "live",
"seq": 6,
"actor_type": "recipient",
"actor_id": null,
"recipient_id": "rcp_0a1b2c3d4e5f40718293a4b5c6d7e8f9",
"ip_address": "203.0.113.7",
"user_agent": "Mozilla/5.0",
"details": {},
"prev_hash": "4f0c3d2b1a09887766554433221100ffeeddccbbaa99887766554433221100ff",
"hash": "9b74c9897bac770ffc029102a200c5de7d4e6bdfa0e3f1d4b9a2f0c1e8d7c6b5"
}
],
"chains": [
{
"envelope_id": "env_7d1e0c2b9a8f4e3d8c7b6a5f4e3d2c1b",
"environment": "live",
"valid": true,
"length": 6,
"head_hash": "9b74c9897bac770ffc029102a200c5de7d4e6bdfa0e3f1d4b9a2f0c1e8d7c6b5",
"broken_at_seq": null,
"reason": null
}
]
}Prepare an audit log export
POST/v1/audit-exports
Queues a ZIP of the security log and envelope audit events for a date range (up to 366 days), with manifest.json holding the SHA-256 of each file. Poll GET /audit-exports/{export_id}; when status is ready, download it from download_url. Finished exports are kept for 7 days. At most 5 exports can be waiting at once (429 RATE_LIMITED). Scope audit:read; owners and admins; Business plan; live only.
Request body (application/json): CreateAuditExportRequest
Answer 202: The export, queued. AuditExport
Example request body
{
"from": "2026-01-01",
"to": "2026-09-30",
"format": "csv"
}Example answer
{
"id": "aex_5a4b3c2d1e0f49a8b7c6d5e4f3a2b1c0",
"object": "audit_export",
"status": "queued",
"format": "csv",
"from": "2026-01-01T00:00:00.000Z",
"to": "2026-10-01T00:00:00.000Z",
"created_at": "2026-10-02T09:00:00.000Z",
"completed_at": null,
"expires_at": null,
"filename": "signwren-audit-alder-property-2026-01-01-to-2026-09-30.zip",
"sha256": null,
"bytes": null,
"download_url": null,
"download_url_expires_at": null,
"manifest": null,
"error": null
}Get an audit log export
GET/v1/audit-exports/{export_id}
The export's status. When it is ready, download_url is a link to the ZIP that works without a key for 60 minutes (ask again for a fresh one), sha256 and bytes describe the ZIP, and manifest is the manifest inside it. After 7 days the status is expired and the files are gone. Scope audit:read; owners and admins; live only.
| Parameter | In | Type | Notes |
|---|---|---|---|
export_id required | path | AuditExportId |
Answer 200: The export. AuditExport
Example answer
{
"id": "aex_5a4b3c2d1e0f49a8b7c6d5e4f3a2b1c0",
"object": "audit_export",
"status": "ready",
"format": "csv",
"from": "2026-01-01T00:00:00.000Z",
"to": "2026-10-01T00:00:00.000Z",
"created_at": "2026-10-02T09:00:00.000Z",
"completed_at": "2026-10-02T09:01:12.000Z",
"expires_at": "2026-10-09T09:01:12.000Z",
"filename": "signwren-audit-alder-property-2026-01-01-to-2026-09-30.zip",
"sha256": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
"bytes": 482113,
"download_url": "https://app.signwren.com/api/files/eyJ2IjoxLCJlIjoiNWE0YiJ9.c2lnbmF0dXJl",
"download_url_expires_at": "2026-10-02T10:05:00.000Z",
"manifest": {
"object": "audit_export_manifest",
"version": 1,
"files": [
{
"name": "security_events.csv",
"sha256": "3f79bb7b435b05321651daefd374cdc681dc06faa65e374e38337b88ca046dea",
"bytes": 90211,
"rows": 412
}
]
},
"error": null
}What does SignWren send to webhook endpoints?
SignWren posts these to your endpoints; the webhooks page explains how to verify them.
An envelope event delivered to your endpoint
SignWren POSTs this to each enabled endpoint of the envelope's environment that subscribed to the event type. Answer with any 2xx within 10 seconds. Anything else, a timeout or a redirect is retried after 30 s, 2 min, 10 min, 30 min, 1 h, then every 6 h, for up to 3 days.
| Header | Example |
|---|---|
SignWren-Signature | t=1790812805,v1=9f180999dd3c5224b30dfc773b297c7ca5110b8949528fb6e24216ee199d5100 |
SignWren-Event-Id | |
SignWren-Event-Type |
Body: WebhookEvent
Example body
{
"id": "evt_0b9d6a512f0c4d559b2f7a0d6c1e2f30",
"type": "recipient.signed",
"created": 1790812805,
"environment": "sandbox",
"livemode": false,
"data": {
"envelope": {
"id": "7d1e0c2b-9a8f-4e3d-8c7b-6a5f4e3d2c1b",
"status": "sent",
"metadata": {
"crm_deal": "D-1042"
}
},
"recipient": {
"id": "0a1b2c3d-4e5f-4071-8293-a4b5c6d7e8f9",
"status": "signed"
}
}
}What do the objects look like?
Every object the API sends or accepts, as named in the spec. Fields marked required are always present in answers (possibly as null when the type allows it).
EnvelopeId
Type: string matching ^env_[0-9a-f]{32}$
DocumentId
Type: string matching ^doc_[0-9a-f]{32}$
RecipientId
Type: string matching ^rcp_[0-9a-f]{32}$
FieldId
Type: string matching ^fld_[0-9a-f]{32}$
TemplateId
Type: string matching ^tpl_[0-9a-f]{32}$
AuditExportId
Type: string matching ^aex_[0-9a-f]{32}$
Timestamp
Type: string
NullableTimestamp
Type: string | null
Sha256
Type: string matching ^[0-9a-f]{64}$
Error
| Field | Type | Notes |
|---|---|---|
error required | object |
Deleted
| Field | Type | Notes |
|---|---|---|
id required | string | |
object required | "recipient" | "field" | |
deleted required | true |
EnvelopeStatus
completing is shown as "Finishing up" (the last signature is in and the PDF is being sealed); delayed means finishing failed three times and SignWren is looking at it.
Type: "draft" | "sent" | "completing" | "delayed" | "completed" | "declined" | "voided" | "expired"
Environment
Type: "live" | "sandbox"
PageSize
| Field | Type | Notes |
|---|---|---|
width required | number | |
height required | number |
Warning
| Field | Type | Notes |
|---|---|---|
category required | string | |
matched_terms required | string[] | |
acknowledged | boolean |
UploadedDocument
| Field | Type | Notes |
|---|---|---|
id required | DocumentId | |
object required | "document" | |
filename required | string | |
kind required | "pdf" | "docx" | "image" | |
status required | "ready" | "attached" | ready to be put in an envelope, attached once it is in one. |
page_count required | integer | null | Null for Word files and images until they are converted. |
pages required | PageSize[] | null | |
sha256 required | Sha256 | |
size_bytes required | integer | |
pre_signed required | boolean | |
warnings required | Warning[] | |
envelope_id required | EnvelopeId | null | |
created_at required | Timestamp | |
expires_at required | Timestamp |
EnvelopeDocument
| Field | Type | Notes |
|---|---|---|
id required | DocumentId | |
object required | "document" | |
position required | integer | |
filename required | string | |
source_type required | "pdf" | "docx" | "image" | "template" | |
status required | "processing" | "ready" | "failed" | |
page_count required | integer | null | |
pages required | PageSize[] | null | |
sha256 required | Sha256 | null | |
size_bytes required | integer | |
pre_signed required | boolean | |
failure_reason required | string | null |
Recipient
| Field | Type | Notes |
|---|---|---|
id required | RecipientId | |
object required | "recipient" | |
name required | string | |
email required | string | |
role required | "signer" | "cc" | |
routing_order required | integer | |
status required | "pending" | "notified" | "viewed" | "consented" | "signed" | "declined" | "withdrew_consent" | |
display_status required | "Waiting" | "Emailed" | "Bounced" | "Viewed" | "Signed" | "Declined" | "Asked for paper" | The label the dashboard shows. |
signing_method required | "email" | "in_person" | |
email_code required | boolean | |
sms_phone required | string | null | |
has_access_code required | boolean | |
template_role required | string | null | |
embedded required | boolean | Signs inside your app from a signing URL; SignWren sends them no signing email. |
client_user_id required | string | null | Your own id for an embedded signer. |
signature_meaning required | string | null | The meaning of this signer's signature ("Reviewed"), when the organization uses signature meaning (Business plan). The signer sees it before signing and confirms who they are at the moment of signing; their printed name, the date and time and the meaning are drawn next to the signature and repeated on the certificate. A set of features for your own 21 CFR Part 11 program: SignWren doesn't certify or validate Part 11 compliance. |
notified_at required | NullableTimestamp | |
first_viewed_at required | NullableTimestamp | |
signed_at required | NullableTimestamp | |
declined_at required | NullableTimestamp | |
decline_reason required | string | null |
RecipientSummary
| Field | Type | Notes |
|---|---|---|
id required | RecipientId | |
name required | string | |
email required | string | |
role required | "signer" | "cc" | |
status required | string | |
display_status required | string |
Field
| Field | Type | Notes |
|---|---|---|
id required | FieldId | |
object required | "field" | |
document_id required | DocumentId | |
recipient_id required | RecipientId | |
type required | FieldType | |
page required | integer | |
x required | number | PDF points from the left edge. |
y required | number | PDF points from the top edge (top-left origin). |
width required | number | |
height required | number | |
required required | boolean | |
label required | string | null | |
placeholder required | string | null | |
max_length required | integer | null | |
filled required | boolean |
FieldType
Type: "signature" | "initials" | "date_signed" | "name" | "text" | "checkbox"
ExternalRef
| Field | Type | Notes |
|---|---|---|
system required | string | |
object_type required | string | |
object_id required | string | |
url | string | null |
Metadata
Up to 50 string pairs (keys up to 40 characters, values up to 500). Returned on every read.
Type: object
Reminders
| Field | Type | Notes |
|---|---|---|
enabled required | boolean | |
interval_days required | integer | |
max_count required | integer |
Envelope
| Field | Type | Notes |
|---|---|---|
id required | EnvelopeId | |
object required | "envelope" | |
environment required | Environment | |
title required | string | |
message required | string | null | |
status required | EnvelopeStatus | |
status_label required | string | |
signing_order required | "parallel" | "sequential" | |
expiry_days required | integer | |
expires_at required | NullableTimestamp | |
reminders required | Reminders | |
metadata required | Metadata | |
external_refs required | ExternalRef[] | |
warnings required | Warning[] | |
template required | object | null | |
documents required | EnvelopeDocument[] | |
recipients required | Recipient[] | |
fields required | Field[] | |
created_at required | Timestamp | |
sent_at required | NullableTimestamp | |
completed_at required | NullableTimestamp | |
voided_at required | NullableTimestamp | |
void_reason required | string | null | |
declined_at required | NullableTimestamp | |
expired_at required | NullableTimestamp | |
final_sha256 required | Sha256 | null | SHA-256 of the sealed PDF as completed, the hash in envelope.completed. It doesn't change. After a yearly timestamp refresh the combined download is a newer copy with its own hash (see its ETag). |
certificate_sha256 required | Sha256 | null | |
signature_level required | "simple" | null | The kind of signatures on a completed envelope, null before it completes. Always simple: SignWren signatures are electronic signatures under Article 3(10) of the EU eIDAS Regulation (Regulation (EU) No 910/2014), often called simple electronic signatures. They are not advanced or qualified electronic signatures, and SignWren's seal on the file is SignWren's, not a signature by any signer. General information, not legal advice. |
merge | MergeResult |
EnvelopeListItem
| Field | Type | Notes |
|---|---|---|
id required | EnvelopeId | |
object required | "envelope" | |
environment required | Environment | |
title required | string | |
status required | EnvelopeStatus | |
status_label required | string | |
metadata required | Metadata | |
created_at required | Timestamp | |
updated_at required | Timestamp | When the envelope last changed (any event). Filter with updated_since. |
sent_at required | NullableTimestamp | |
completed_at required | NullableTimestamp | |
expires_at required | NullableTimestamp | |
recipients required | RecipientSummary[] |
EnvelopeList
| Field | Type | Notes |
|---|---|---|
object required | "list" | |
has_more required | boolean | |
data required | EnvelopeListItem[] |
AuditEvent
| Field | Type | Notes |
|---|---|---|
seq required | integer | |
type required | string | |
description required | string | |
occurred_at required | Timestamp | |
actor_type required | "user" | "recipient" | "system" | |
recipient_id required | RecipientId | null | |
ip_address required | string | null | |
user_agent required | string | null | |
details required | object |
EventList
| Field | Type | Notes |
|---|---|---|
object required | "list" | |
has_more required | false | |
data required | AuditEvent[] | |
chain required | object | The result of recomputing the hash chain over the events. |
SecurityLogEvent
One row of the organization's security log (sign-in and admin events recorded with its id).
| Field | Type | Notes |
|---|---|---|
id required | string | |
object required | "audit_event" | |
source required | "security" | |
type required | string | For example role_changed, api_key.created, ip_allowlist.changed. |
occurred_at required | Timestamp | |
user_id required | string | null | |
email required | string | null | |
ip_address required | string | null | |
user_agent required | string | null | |
metadata required | object |
EnvelopeLogEvent
One envelope audit event with its place in the envelope's hash chain.
| Field | Type | Notes |
|---|---|---|
id required | string | |
object required | "audit_event" | |
source required | "envelope" | |
type required | string | |
occurred_at required | Timestamp | |
envelope_id required | EnvelopeId | |
environment required | Environment | |
seq required | integer | |
actor_type required | "user" | "recipient" | "system" | |
actor_id required | string | null | |
recipient_id required | RecipientId | null | |
ip_address required | string | null | |
user_agent required | string | null | |
details required | object | |
prev_hash required | Sha256 | |
hash required | Sha256 |
ChainResult
| Field | Type | Notes |
|---|---|---|
envelope_id required | EnvelopeId | |
environment required | Environment | |
valid required | boolean | |
length required | integer | Events in the envelope's whole chain. |
head_hash required | string | null | |
broken_at_seq required | integer | null | |
reason required | "sequence_gap" | "prev_hash_mismatch" | "hash_mismatch" | "wrong_envelope" | null |
AuditLogEventList
| Field | Type | Notes |
|---|---|---|
object required | "list" | |
source required | "security" | "envelope" | |
has_more required | boolean | |
data required | SecurityLogEvent | EnvelopeLogEvent[] | |
chains required | ChainResult[] | For envelope pages, each envelope on the page and the result of recomputing its chain. Empty for security pages. |
CreateAuditExportRequest
| Field | Type | Notes |
|---|---|---|
from required | string | A date (whole UTC day) or an RFC 3339 date-time. Included. |
to required | string | A date (that whole day is included) or an RFC 3339 date-time (excluded). |
format | "csv" | "jsonl" |
AuditExport
| Field | Type | Notes |
|---|---|---|
id required | AuditExportId | |
object required | "audit_export" | |
status required | "queued" | "running" | "ready" | "failed" | "expired" | |
format required | "csv" | "jsonl" | |
from required | Timestamp | |
to required | Timestamp | |
created_at required | Timestamp | |
completed_at required | NullableTimestamp | |
expires_at required | NullableTimestamp | |
filename required | string | |
sha256 required | string | null | SHA-256 of the whole ZIP. |
bytes required | integer | null | |
download_url required | string | null | Works without a key for 60 minutes. Ask again for a fresh one. |
download_url_expires_at required | NullableTimestamp | |
manifest required | object | null | The manifest.json inside the ZIP, once it is ready. |
error required | string | null |
TemplateRole
| Field | Type | Notes |
|---|---|---|
id required | string | |
name required | string | |
kind required | "signer" | "cc" | |
routing_order required | integer | |
optional required | boolean |
TemplateSenderField
| Field | Type | Notes |
|---|---|---|
id required | string | |
label required | string | null | |
fill required | "sender" | "prefilled" | sender: drawn into the document when the envelope is made. prefilled: shown to the signer, who can change it. |
required required | boolean | |
default_value required | string | null |
Template
| Field | Type | Notes |
|---|---|---|
id required | TemplateId | |
object required | "template" | |
name required | string | |
visibility required | "private" | "team" | "org" | |
current_version required | integer | |
signing_order required | "parallel" | "sequential" | |
message required | string | null | |
expiry_days required | integer | |
roles required | TemplateRole[] | |
sender_fields required | TemplateSenderField[] | |
merge_fields required | string[] | The template's merge field names, such as employee.first_name (written {{employee.first_name}} in the app), sorted. Send values for them in merge_fields when you create an envelope. |
merge_field_mappings required | MergeFieldMapping[] | What each merge field fills, set up by the template's owner in the app. |
documents required | object[] | |
created_at required | Timestamp | |
updated_at required | Timestamp |
MergeFieldMapping
| Field | Type | Notes |
|---|---|---|
name required | string | |
target_kind required | "placeholder" | "field" | A bracketed placeholder in the template's text, or one of its sender or prefilled fields. |
target required | string | The placeholder with its brackets, or the template field id. |
required required | boolean | Without a value the envelope stays a draft (P3-INT-03.3). |
MergeResult
Only on the answer to POST /templates/{template_id}/envelopes: which merge fields were filled, which names in merge_fields the template doesn't use (ignored), and which required ones had no value (the envelope is then a draft).
| Field | Type | Notes |
|---|---|---|
applied required | string[] | |
ignored required | string[] | |
missing required | string[] |
TemplateList
| Field | Type | Notes |
|---|---|---|
object required | "list" | |
has_more required | boolean | |
data required | Template[] |
RecipientInput
| Field | Type | Notes |
|---|---|---|
name required | string | |
email | string | Required unless signing_method is in_person. |
role required | "signer" | "cc" | |
routing_order | integer | Used when the envelope's signing order is sequential. |
signing_method | "email" | "in_person" | |
access_code | string | null | A code you give the signer yourself. Stored hashed and never returned. |
email_code | boolean | Ask for a code sent to the signer's email (plan feature). |
sms_phone | string | null | Mobile number for a text message code (plan feature; allowed countries only). |
embedded | boolean | Sign inside your app from POST .../signing_url instead of an emailed link. Signers only, not with signing_method: in_person. Needs client_user_id. |
client_user_id | string | null | Your own id for this person. Shown on the certificate for an embedded signer. |
signature_meaning | string | null | One of the organization's signature meanings (Settings, Signature meaning; Business plan). Only for a signer who is a member of the organization and signs from their own emailed link (not in person, not embedded); otherwise 422 VALIDATION with param: signature_meaning. null removes it. |
RecipientPatch
| Field | Type | Notes |
|---|---|---|
name | string | |
email | string | |
role | "signer" | "cc" | |
routing_order | integer | |
signing_method | "email" | "in_person" | |
access_code | string | null | |
email_code | boolean | |
sms_phone | string | null | |
embedded | boolean | |
client_user_id | string | null | |
signature_meaning | string | null |
EmbedUrlRequest
| Field | Type | Notes |
|---|---|---|
frame_origin | string | One of the sites allowed in Settings. Needed when more than one is allowed. |
return_url | string | Where to send the person afterwards. Its origin must be an allowed site. |
SigningUrl
| Field | Type | Notes |
|---|---|---|
object required | "signing_url" | |
url required | string | Open within 5 minutes; works for one browser. |
expires_at required | Timestamp | |
envelope_id required | EnvelopeId | |
recipient_id required | RecipientId | |
frame_origin required | string | |
return_url required | string | null | |
environment required | Environment |
SenderViewUrl
| Field | Type | Notes |
|---|---|---|
object required | "sender_view_url" | |
url required | string | Open within 5 minutes; works for one browser. |
expires_at required | Timestamp | |
envelope_id required | EnvelopeId | |
frame_origin required | string | |
return_url required | string | null | |
environment required | Environment |
FieldInput
| Field | Type | Notes |
|---|---|---|
document_id required | DocumentId | |
recipient_id required | RecipientId | |
type required | FieldType | |
page required | integer | |
x required | number | |
y required | number | |
width required | number | |
height required | number | |
required | boolean | |
label | string | null | |
placeholder | string | null | |
max_length | integer | null |
FieldPatch
| Field | Type | Notes |
|---|---|---|
recipient_id | RecipientId | |
type | FieldType | |
page | integer | |
x | number | |
y | number | |
width | number | |
height | number | |
required | boolean | |
label | string | null | |
placeholder | string | null | |
max_length | integer | null |
NewEnvelopeField
| Field | Type | Notes |
|---|---|---|
document_id required | DocumentId | |
recipient_index required | integer | Position of the recipient in this request's recipients. |
type required | FieldType | |
page required | integer | |
x required | number | |
y required | number | |
width required | number | |
height required | number | |
required | boolean | |
label | string | null | |
placeholder | string | null | |
max_length | integer | null |
CreateEnvelopeRequest
| Field | Type | Notes |
|---|---|---|
title required | string | |
message | string | null | |
signing_order | "parallel" | "sequential" | |
expiry_days | integer | |
reminders | Reminders | |
document_ids | DocumentId[] | |
recipients | RecipientInput[] | |
fields | NewEnvelopeField[] | |
metadata | Metadata | |
external_refs | ExternalRef[] |
UpdateEnvelopeRequest
| Field | Type | Notes |
|---|---|---|
title | string | |
message | string | null | |
signing_order | "parallel" | "sequential" | |
expiry_days | integer | |
reminders | Reminders | |
metadata | Metadata |
SendRequest
| Field | Type | Notes |
|---|---|---|
acknowledge_warnings | string[] | Warning categories you have read and accept (from warnings or the WARNING_UNACKNOWLEDGED error). |
confirm_pre_signed | boolean |
RemindRequest
| Field | Type | Notes |
|---|---|---|
recipient_id required | RecipientId |
VoidRequest
| Field | Type | Notes |
|---|---|---|
reason required | string |
CreateFromTemplateRequest
| Field | Type | Notes |
|---|---|---|
recipients required | object[] | |
prefill | object | Values for sender and prefilled fields, by template field id. |
title | string | |
message | string | |
confirm_duplicate_emails | boolean | |
metadata | Metadata | |
external_refs | ExternalRef[] | |
send | boolean | |
acknowledge_warnings | string[] | |
merge_fields | object | Values for the template's merge fields, by name (employee.first_name, or {{employee.first_name}}). Mapped placeholders are filled before the PDF is made; names the template doesn't use are ignored and listed in merge.ignored. Up to 100 names, each value up to 500 characters (as one line of plain text). |
merge_from_record | boolean | Also read values from the record in the first external_refs entry, through the account's connection to that system. Values in merge_fields win. |
Me
| Field | Type | Notes |
|---|---|---|
object required | "me" | |
environment required | Environment | |
livemode required | boolean | |
credential required | object | |
organization required | object | |
user required | object | The member the key or token acts as (who made the key, or who allowed the app). |
CreateFileLinkRequest
| Field | Type | Notes |
|---|---|---|
file | "signed_pdf" | "certificate" | |
expires_in_minutes | integer |
FileLink
| Field | Type | Notes |
|---|---|---|
object required | "file_link" | |
envelope_id required | EnvelopeId | |
file required | "signed_pdf" | "certificate" | |
url required | string | Works without a key until expires_at. |
filename required | string | |
sha256 required | Sha256 | |
expires_at required | Timestamp |
WebhookEndpointId
Type: string matching ^whk_[0-9a-f]{32}$
WebhookDeliveryId
Type: string matching ^whd_[0-9a-f]{32}$
WebhookEventType
Type: "envelope.created" | "envelope.sent" | "recipient.viewed" | "recipient.signed" | "recipient.declined" | "recipient.withdrew_consent" | "envelope.completing" | "envelope.completed" | "envelope.declined" | "envelope.voided" | "envelope.expired" | "envelope.deleted"
WebhookEndpoint
| Field | Type | Notes |
|---|---|---|
id required | WebhookEndpointId | |
object required | "webhook_endpoint" | |
url required | string | |
description required | string | |
events required | string[] | Event types, or ["*"] for all of them. |
environment required | Environment | |
livemode required | boolean | |
status required | "enabled" | "disabled" | |
disabled_reason required | "manual" | "failing" | null | failing: every delivery failed for 3 days and SignWren turned it off. |
disabled_at required | NullableTimestamp | |
secret_hint required | string | The secret's prefix and last four characters. |
rotation_ends_at required | NullableTimestamp | |
app required | object | null | Set when an app connected with OAuth made this endpoint: the endpoint belongs to that connection and is deleted when the connection ends. Null for endpoints made with a key or in the app. |
created_at required | Timestamp | |
updated_at required | Timestamp |
WebhookEndpointWithSecret
| Field | Type | Notes |
|---|---|---|
id required | WebhookEndpointId | |
object required | "webhook_endpoint" | |
url required | string | |
description required | string | |
events required | string[] | Event types, or ["*"] for all of them. |
environment required | Environment | |
livemode required | boolean | |
status required | "enabled" | "disabled" | |
disabled_reason required | "manual" | "failing" | null | failing: every delivery failed for 3 days and SignWren turned it off. |
disabled_at required | NullableTimestamp | |
secret_hint required | string | The secret's prefix and last four characters. |
rotation_ends_at required | NullableTimestamp | |
app required | object | null | Set when an app connected with OAuth made this endpoint: the endpoint belongs to that connection and is deleted when the connection ends. Null for endpoints made with a key or in the app. |
created_at required | Timestamp | |
updated_at required | Timestamp | |
secret required | string | The signing secret. Shown in this answer only. |
WebhookEndpointList
| Field | Type | Notes |
|---|---|---|
object required | "list" | |
data required | WebhookEndpoint[] | |
has_more required | boolean |
WebhookEndpointDeleted
| Field | Type | Notes |
|---|---|---|
id required | WebhookEndpointId | |
object required | "webhook_endpoint" | |
deleted required | true |
CreateWebhookEndpointRequest
| Field | Type | Notes |
|---|---|---|
url required | string | https:// on port 443 or a port above 1023, resolving to public addresses only. |
events required | WebhookEventType | "*"[] | |
description | string | |
environment | Environment | Optional; must match the key's environment if given. |
UpdateWebhookEndpointRequest
| Field | Type | Notes |
|---|---|---|
url | string | |
events | WebhookEventType | "*"[] | |
description | string | |
enabled | boolean |
RotateWebhookSecretRequest
| Field | Type | Notes |
|---|---|---|
expire_now | boolean |
WebhookAttempt
| Field | Type | Notes |
|---|---|---|
attempt required | integer | |
at required | Timestamp | |
duration_ms required | integer | |
status_code required | integer | null | |
error required | string | null | Why it failed (timeout, connection, refused_address, redirect, http_status), or null. |
response_excerpt required | string | null | The first 1 KB of the response body. |
manual required | boolean | A test event or a resend. |
WebhookDelivery
| Field | Type | Notes |
|---|---|---|
id required | WebhookDeliveryId | |
object required | "webhook_delivery" | |
webhook_endpoint_id required | WebhookEndpointId | |
event_id required | string | The event's id |
event_type required | string | |
environment required | Environment | |
livemode required | boolean | |
status required | "pending" | "delivered" | "failed" | failed means retries stopped (3 days, or a test or resend that failed once). |
attempts required | integer | |
next_attempt_at required | NullableTimestamp | |
last_status_code required | integer | null | |
last_error required | string | null | |
delivered_at required | NullableTimestamp | |
is_test required | boolean | |
resend_of required | WebhookDeliveryId | null | |
created_at required | Timestamp | |
attempt_log required | WebhookAttempt[] |
WebhookDeliveryList
| Field | Type | Notes |
|---|---|---|
object required | "list" | |
data required | WebhookDelivery[] | |
has_more required | boolean |
WebhookEvent
The JSON body of every delivery. Verify the SignWren-Signature header first: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of <t>.<raw body> with the endpoint's secret (during a rotation there are two v1 values; accept if either matches). Refuse a t more than 5 minutes from your clock. Drop events whose id you have already handled.
| Field | Type | Notes |
|---|---|---|
id required | string | |
type required | WebhookEventType | "webhook.test" | |
created required | integer | Unix seconds when it happened. |
environment required | Environment | |
livemode required | boolean | False for sandbox envelopes. |
data required | object |
Last checked against the API spec on .