Versioning
What may change inside /v1 and how you hear about it — notice periods, deprecation signals, dated spec snapshots and the machine-readable changelog.
/v1 is the only API version. This page says what we change inside it
without asking, what we announce first and how far ahead, and where you
read it — by a human on the changelog, by a machine in
api-changelog.json and in the
dated spec snapshots.
What never changes
- The path and the host.
https://api.wesign.now/v1(Base URL). - Keys.
wsk_live_…keys, andlsk_live_…keys minted before September 2026, are accepted forever. - Webhook signing. The
X-WeSign-*headers and the schemet=<unix seconds>,v1=<hex>over${t}.${rawBody}with one secret (twov1=entries only during a 24-hour rotation window). - Error codes. A
codestring keeps its meaning. The humanerrormessage may be reworded at any time — never parse it.
Additive changes — no notice
These ship to /v1 with a changelog entry and no notice period:
- new endpoints, and new optional request fields;
- new fields in response bodies and webhook payloads;
- new webhook event types — delivered at once to every hook whose
eventslist is empty,callback_urlhooks included; - new error codes, new values of a response enum (a new
sms_failed.reason, say), new warning codes.
So your client must ignore response fields it does not know, answer 2xx
to webhook events it does not handle, and fall back on the HTTP status for
an error code it does not know.
Breaking changes — announced
A change is breaking when a request that worked can now fail, or a field you read changes meaning or disappears. Such a change is:
- announced in the changelog, flagged
breaking: trueinapi-changelog.json, with the day it takes effect (effective) — at least 90 days after the announcement; - signalled on the way: in the meantime, every response to a request the
change would affect carries a
warnings[]entry naming the field and the date (an answer without a JSON body, such as thegeneratePDF stream, carries a response header instead).
One kind of change does not get the 90 days: refusing input that never
had any effect. An unknown key used to be dropped silently, so the call
ran with a default you never chose. Where that silent drop could weaken a
signature — an unknown key in a signer or recipient object, which may be a
misspelt SMS factor — the refusal took effect at once (2026-09-24). Where it
could not, it gets the normal notice (98 days) and a warning on every
affected response: unknown top-level keys are named in warnings[] from
2026-09-24 — on the generate PDF stream, which has no JSON body, in the
X-Unknown-Fields response header — and refused with 400 unknown_field
from 2026-12-31, 00:00 UTC. Unknown keys inside an explicit field
(fields[0].type) or a signer's role_label object are named by their path
from 2026-10-15 and refused the same way from 2027-01-31, 00:00 UTC. Both
refusals answer { error, code: "unknown_field", meta: { fields, accepted } }
before anything is created; on create and instantiate, a retry with the same
Idempotency-Key of a call that succeeded before the day still replays its
answer. The same rule made four more refusals immediate on
2026-09-24, each of a value that used to be ignored: a malformed metadata,
a send_emails that is not a boolean, a multipart filename over 200
characters, and a text anchor marker whose field key has a dot or 42–64
characters (it used to be skipped; it is now placed, so its role is
checked).
The 2026-09-24 release also refuses up front, with
402 sms_allowance_exhausted, a call whose SMS signers the monthly allowance
cannot cover. Such a call used to succeed and leave the signer unable to
sign; the refusal moves an existing failure to the moment you can act on it.
It is flagged breaking all the same.
Revision 2026-10-15 announces three refusals for one day, 2027-01-31,
00:00 UTC (108 days' notice), each named in warnings[] on every
affected response until then: unknown keys inside an explicit field or a
role_label object (above); an explicit signature or initials box whose
caption would cross the page's bottom edge (caption_outside_page, then
422 field_out_of_bounds); and an upload that already carries a digital
signature, which preparing it removes (existing_signature_removed, then
422 pdf_already_signed). They start at that moment, without a deploy.
A fix that makes the API do what the documentation always said (a date field
now rejects 2026-02-30) ships as a fix; when a call that passed can now
fail, the changelog flags it breaking too.
Anything that cannot be done this way — a change of shape we cannot announce
field by field — would ship as /v2, and /v1 would keep working for at
least 12 months after /v2 is generally available.
Deprecated fields
A deprecated spelling keeps working until its sunset date, at least 90 days after the deprecation was announced. Every response to a request that used it carries a warning:
{
"warnings": [
{
"field": "recipients[0].phone",
"code": "deprecated_field",
"message": "\"phone\" is a deprecated alias of \"phone_e164\" and is accepted until 2026-12-31. Send phone_e164."
}
]
}We will also send the standard Deprecation and Sunset HTTP headers on
those responses. They are not sent yet — today the deprecated_field
warning is the signal; the changelog will say when the headers ship.
In effect now:
| What | Where | Instead | Announced | Until |
|---|---|---|---|---|
recipients[].phone | POST /v1/documents/{id}/confirm | phone_e164 | 2026-09-24 | 2026-12-31 — after that phone is refused like any unknown key (400 unknown_recipient_field) |
| Unknown top-level keys, ignored with a warning | create, instantiate, generate, confirm | send only documented keys | 2026-09-24 | 2026-12-31, 00:00 UTC — from then 400 unknown_field (meta.fields, meta.accepted) |
Unknown keys inside fields[] or role_label, ignored with a warning (fields[0].type) | POST /v1/signing-requests | send only documented keys | 2026-10-15 | 2027-01-31, 00:00 UTC — from then 400 unknown_field (meta.fields, meta.accepted) |
An explicit signature or initials box whose caption would cross the page's bottom edge, created with a warning (caption_outside_page) | POST /v1/signing-requests (placement: "explicit") | keep y + h ≤ 1 − D / H (explicit placement) | 2026-10-15 | 2027-01-31, 00:00 UTC — from then 422 field_out_of_bounds (meta.reason: "caption_outside_page") |
An upload that already carries a digital signature, accepted with a warning (existing_signature_removed) | POST /v1/signing-requests | send the document before anyone signs it digitally | 2026-10-15 | 2027-01-31, 00:00 UTC — from then 422 pdf_already_signed (meta.signatures) |
The legacy path POST /api/templates/{id}/instantiate | the web host (www.wesign.now) | POST https://api.wesign.now/v1/templates/{id}/instantiate | 2026-06-21, when the /v1 path shipped | No sunset date yet. It runs the same code as the /v1 route and answers identically, with no deprecated_field warning and no deprecation header. A sunset will be announced at least 90 days ahead, like any breaking change. |
Already retired, each with a stable answer rather than a 404:
POST /v1/documents (410 placement_retired), placement: "manual"
(400 placement_retired) and POST /v1/embedded/sessions
(410 embedded_sessions_retired), all on 2026-07-31.
Dated spec snapshots
https://www.wesign.now/openapi.jsonis always the current release. Itsinfo.versionis that release's date.- Each release's spec is also frozen at
https://www.wesign.now/openapi/<date>.jsonand never edited again —openapi/2026-09-24.jsonis the first. Releases before it have no snapshot. - Diff two snapshots to see exactly what a release changed in the contract; pin a client generator to one to build against a fixed contract.
The machine-readable changelog
https://www.wesign.now/api-changelog.json lists every release, newest
first — the same releases as the changelog page, one
entry per date:
{
"format": 1,
"current_openapi": "/openapi/2026-09-24.json",
"entries": [
{
"date": "2026-09-24",
"title": "…",
"openapi": "/openapi/2026-09-24.json", // null before snapshots existed
"changes": [
{
"id": "confirm-phone-e164", // stable within the file
"type": "deprecated", // added | changed | deprecated | removed | fixed | docs
"breaking": false,
"sunset": "2026-12-31", // deprecations only; scheduled changes carry "effective" instead
"endpoints": ["POST /v1/documents/{id}/confirm"],
"summary": "…",
"action": "Rename phone to phone_e164 before 2026-12-31." // null = nothing to do
}
]
}
]
}effective and sunset appear only on changes that have them. Poll it
daily, or on deploy of your integration, and alert on any change with
breaking: true or an action.
No sandbox yet
Every API key is a live key: every call creates real documents, sends real emails and real SMS codes, and counts against the monthly document cap and the SMS allowance. There are no test keys.
Until the sandbox exists:
- use
validate_only: trueoninstantiateandgenerate— it runs the full check (oninstantiatethe SMS allowance included) and creates nothing; - stage with
review: trueoninstantiateorgenerate, inspect the document, andPOST /v1/documents/{id}/discardit — nothing is sent; - send to addresses and mobile numbers you control;
- use
send_emails: falseto keep invitations out of real inboxes, also for a sequential signer when their turn comes (the SMS code is still sent when someone opens the link and asks for it; automatic reminders and completion emails still go out).
Planned for 2026-11-30: a sandbox whose emails and SMS are simulated and visible through the API instead of delivered.
How verification works
What /verify actually does when you drop a PDF — hash lookup, PAdES validation, eIDAS QES detection, the lot.
Recipes
End-to-end integration patterns — Portant, a two-signer template, invitations from your own email provider (Resend, Postmark, SendGrid, Amazon SES), Zapier, agent-generated PDFs, CRM sync.
