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.json

The 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 /v1 with their zod-derived request bodies, every documented error code per status, and the Idempotency-Key / Idempotent-Replayed / Retry-After / RateLimit-* headers — including PATCH /signing-requests/{id} for correcting a signer's phone.
  • bearer security scheme covering wsk_live_… keys and legacy lsk_live_… keys.
  • One shared ApiMetadata schema — the flat metadata object 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 — plus MetadataInvalid, the 400 invalid_metadata body with its top-level problems[].
  • Me.capabilities on GET /me: the booleans to feature-detect embedded signing, templates, webhooks, SMS verification, the IP allowlist and metadata, plus Me.sms_allowance — see Feature detection.
  • The SMS contract: sms_gate and reference on every signer and recipient input, one SignerReadback merged into every signer of the create, instantiate, confirm and GET /signing-requests/{id} responses, and SmsAllowanceExhausted, the 402 sms_allowance_exhausted body — see SMS verification.
  • warnings[] (ApiWarning: unknown_field / deprecated_field) on every JSON success body of the create and confirm calls.
  • Hooks: enabled on every Hook, the include_disabled query parameter on GET /hooks, and POST /hooks/{id}/rotate with its HookSecretRotated response.
  • A webhooks section 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 second v1= entry for 24 hours after a secret rotation), and the signed message ${t}.${rawBody} — under the top-level x-webhook-signature, whose rotation block spells out the overlap window.
  • Signer identity: SignerCompany and SignerJobTitle, the optional company and job_title of a signer or recipient, read back on SignerReadback and DocumentSigner; SigningRequestEventSigner, the one signer block of signing_request.signed, document.completed and the signer events.
  • Strict objects (additionalProperties: false): the signer of POST /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), where phone is marked deprecated in favour of phone_e164.
  • The field-key rule on FieldDefinition.key (pattern ^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$ with maxLength: 64 — lowercase snake_case parts joined by single dots, each starting with a letter), and the value rules on FieldValues: 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) or YYYY-MM-DD, a time field takes HH:MM, HH.MM or h:MM AM/PM, and anything else is 422 template_input_invalid. How each date or time prints is on TemplateInputField as read-only date_format, time_format and lang.
  • The Enterprise gate: /templates/{id}/generate, /templates/{id}/instantiate and /documents/{id}/confirm carry a 402 enterprise-required response.
  • The two retired endpoints (POST /documents, POST /embedded/sessions) marked deprecated with their stable 410.

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-client

Any 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} means https://api.wesign.now/v1/documents/{id}.
  • Error responses reference one Error schema ({ error, code, meta? }); the response description lists the code values that status can carry. The 400 on the three create endpoints is a oneOf of Error and MetadataInvalid, because invalid_metadata carries a top-level problems: [{ path, message }] array (the same placement template_input_invalid uses) rather than meta. 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) are application/pdf.
  • POST /templates/{id}/generate and POST /templates/{id}/instantiate return different shapes depending on validate_only and review; the 200 and 201 responses spell each one out.