OpenAPI spec
The machine-readable v1 contract — import it into Postman, Insomnia or a client generator.
Every v1 endpoint, request schema, response shape, status code and webhook event is described in one OpenAPI 3.1 document:
https://www.wesign.now/openapi.jsonThe same file is served on the letssign.now alias host. It declares
both API hosts as servers — https://api.wesign.now/v1 (canonical) and
https://api.letssign.now/v1 (permanent alias) — so a generated client
can target either.
info.version is the date of the release the file describes
(2026-10-01 today). Each release's spec is also kept, frozen, at
/openapi/<date>.json, so you can diff two releases or pin a generator to
one; what changed between them is in the changelog and in
/api-changelog.json. See
Versioning.
What is in it
- All 24 paths under
/v1with their zod-derived request bodies, every documented errorcodeper status, and theIdempotency-Key/Idempotent-Replayed/Retry-After/RateLimit-*headers — includingPATCH /signing-requests/{id}for correcting a signer's phone. bearersecurity scheme coveringwsk_live_…keys and legacylsk_live_…keys.- One shared
ApiMetadataschema — the flatmetadataobject every create endpoint accepts (POST /signing-requests,POST /templates/{id}/instantiate,POST /templates/{id}/generate) and every create response,GET /documents/{id}and the webhook envelope echo — plusMetadataInvalid, the400 invalid_metadatabody with its top-levelproblems[]. Me.capabilitiesonGET /me: the booleans to feature-detect embedded signing, templates, webhooks, SMS verification, the IP allowlist andmetadata, plusMe.sms_allowance— see Feature detection.- The SMS contract:
sms_gateandreferenceon every signer and recipient input, oneSignerReadbackmerged into every signer of the create, instantiate, confirm andGET /signing-requests/{id}responses, andSmsAllowanceExhausted, the402 sms_allowance_exhaustedbody — see SMS verification. warnings[](ApiWarning:unknown_field/deprecated_field) on every JSON success body of the create and confirm calls.- Hooks:
enabledon everyHook, theinclude_disabledquery parameter onGET /hooks, andPOST /hooks/{id}/rotatewith itsHookSecretRotatedresponse. - A
webhookssection with one entry per event (signing_request.sent,.viewed,.signed,.declined,.withdrawn,.expired,.sms_sent,.sms_failed,.sms_verified,document.completed,template_instance.staged,template_instance.confirmed,template_instance.discarded), the delivery envelope, and the signature scheme — both header families,t=…,v1=…(with a secondv1=entry for 24 hours after a secret rotation), and the signed message${t}.${rawBody}— under the top-levelx-webhook-signature, whoserotationblock spells out the overlap window. - Signer identity:
SignerCompanyandSignerJobTitle, the optionalcompanyandjob_titleof a signer or recipient, read back onSignerReadbackandDocumentSigner;SigningRequestEventSigner, the onesignerblock ofsigning_request.signed,document.completedand the signer events. - Strict objects (
additionalProperties: false): the signer ofPOST /signing-requests(400 unknown_signer_field), the instantiate recipient (slot,email,name,phone_e164,require_sms_verification,sms_gate,reference,company,job_title) and the confirm recipient (400 unknown_recipient_field), wherephoneis markeddeprecatedin favour ofphone_e164. - The field-key rule on
FieldDefinition.key(pattern^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$withmaxLength: 64— lowercase snake_case parts joined by single dots, each starting with a letter), and the value rules onFieldValues: every scalar is a string, a boolean field takes"true"/"false","yes"/"no"or a UI-locale label, a date field takes a day-first date (DD.MM.YYYY,DD/MM/YYYY,DD-MM-YYYY,DD MM YYYY) orYYYY-MM-DD, a time field takesHH:MM,HH.MMorh:MM AM/PM, and anything else is422 template_input_invalid. How each date or time prints is onTemplateInputFieldas read-onlydate_format,time_formatandlang. - The Enterprise gate:
/templates/{id}/generate,/templates/{id}/instantiateand/documents/{id}/confirmcarry a402 enterprise-requiredresponse. - The two retired endpoints (
POST /documents,POST /embedded/sessions) markeddeprecatedwith their stable410.
Import it
Import → Link, paste https://www.wesign.now/openapi.json, then
set the collection variable baseUrl to https://api.wesign.now/v1
and add your key under Authorization → Bearer Token.
Create → Import → URL, paste the spec URL. Pick the canonical server when prompted and set the bearer token on the environment.
npx @openapitools/openapi-generator-cli generate \
-i https://www.wesign.now/openapi.json \
-g typescript-fetch \
-o ./wesign-clientAny generator target works (python, go, java, csharp, …). The
spec uses 3.1.0 features — webhooks, type arrays for nullables,
const — so use a generator release from 2023 or later.
Point any hosted viewer at the URL, e.g.
https://redocly.github.io/redoc/?url=https://www.wesign.now/openapi.json.
The spec is written by hand from the route handlers' zod schemas
and response builders — it is not generated at build time. A unit test
(tests/openapi.test.ts, run on every build) guards it against drift:
every route directory must have a path, every documented method must be
exported by its route file, every status: literal in a handler must be
documented, Enterprise-gated routes must carry a 402, and the webhook
entries must match the event catalogue exactly.
Reading the spec by hand
- Paths are relative to the server URL, so
/documents/{id}meanshttps://api.wesign.now/v1/documents/{id}. - Error responses reference one
Errorschema ({ error, code, meta? }); the response description lists thecodevalues that status can carry. The400on the three create endpoints is aoneOfofErrorandMetadataInvalid, becauseinvalid_metadatacarries a top-levelproblems: [{ path, message }]array (the same placementtemplate_input_invaliduses) rather thanmeta. See Errors for the prose version. - Binary responses (
/documents/{id}/signed,/documents/{id}/pdf,/documents/{id}/audit-trail, and the default mode of/templates/{id}/generate) areapplication/pdf. POST /templates/{id}/generateandPOST /templates/{id}/instantiatereturn different shapes depending onvalidate_onlyandreview; the200and201responses spell each one out.
