Create and send an envelope
From one of your templates, filling each role with a real recipient — or ad hoc from a document you upload. Both modes place the fields in the request itself.
Developers
A REST API that creates and sends envelopes, tracks them, voids them and hands back the sealed PDF — with HMAC-signed webhooks so your CRM, ERP or internal tooling knows the moment a document is viewed, signed or completed, without anyone checking a dashboard.
Scope
Enough to run signing end to end from your own systems: create the envelope, place the fields, send it, watch it, and collect the finished document. The one thing worth planning around is that there is no sandbox — every key is a live key.
From one of your templates, filling each role with a real recipient — or ad hoc from a document you upload. Both modes place the fields in the request itself.
x and y as a percentage of the page, width and height in pixels against the dimensions the documents endpoint reports. Every signer needs at least one field.
List envelopes with cursor pagination and filters, or fetch one envelope with per-recipient state, roles, sequence and timestamps.
Cancel before completion. Anyone who still owed an action is emailed, and signatures already collected are kept as a record.
The ordered audit trail for a completed envelope, and the final sealed document once it exists. Aadhaar envelopes carry the eSign provider's seal rather than ours.
HMAC-signed events pushed to your endpoint on all seven envelope events, with endpoint management and secret rotation through the API.
Stated openly so you can plan around it rather than discover it mid-build.
Quickstart
In the Accordsign app go to Account → API keys and create one. Only the account owner can do this. The key is shown once and stored as a hash, so copy it then — it cannot be recovered. Up to five live keys per account, and revoking one takes effect immediately.
No key needed for this one.
curl https://api.accordsign.app/v1/health
{"status":"ok"}
Authenticate with a bearer token. Watch the X-RateLimit-*
headers on the response — they are on every response, not just refusals.
curl "https://api.accordsign.app/v1/envelopes?limit=5" \
-H "Authorization: Bearer as_live_YOUR_KEY"
Returns { data: [...], next_cursor, has_more }. Filter with
status, from_date and
to_date; page with cursor.
limit accepts 1–100 and defaults to 25.
curl https://api.accordsign.app/v1/envelopes/ENVELOPE_ID \
-H "Authorization: Bearer as_live_YOUR_KEY"
You get the envelope plus a recipients[] array with each
person's role, status, sequence and last event. An envelope belonging to another account
returns 404 rather than 403,
so ids cannot be probed.
Register one HTTPS endpoint per account, either from the app or with
POST /v1/webhooks. Store the
whsec_ secret it returns — it is shown at
registration and at rotation, and never again. Then send a document
from the web app and watch
envelope.viewed →
envelope.signed →
envelope.completed arrive.
Webhooks
| Event | Fires when | Recipient |
|---|---|---|
| envelope.sent | An envelope created through the API was dispatched. Envelopes sent from the Accordsign app do not emit this event yet. | no |
| envelope.viewed | A recipient opens the signing link for the first time. Once per recipient. | yes |
| envelope.signed | A recipient completes their part. Once per recipient. | yes |
| envelope.declined | A recipient declines. | yes |
| envelope.voided | The sender revoked the envelope before it completed. Terminal. | no |
| envelope.completed | The last required signature landed and the final document exists. | no |
| envelope.expired | The envelope passed its expiry without completing. | no |
Every delivery carries X-AccordSign-Signature: t=<unix>,v1=<hex>.
The signature is HMAC-SHA256 of
"{t}.{raw body}", keyed with your endpoint secret,
lowercase hex. Verify against the raw bytes — re-serialising
the JSON changes them. Timestamps outside a five-minute tolerance are rejected in both directions.
import hashlib, hmac, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(int(time.time()) - int(parts["t"])) > 300:
return False
expected = hmac.new(
secret.encode(),
f'{parts["t"]}.'.encode() + raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, parts["v1"])
Return any 2xx within ten seconds once you have durably
accepted the event. Anything else counts as a failed attempt. We retry ten times on a fixed
schedule — immediately, then +1m, +5m, +15m, +30m, +1h, +2h, +4h, +8h, +8h — about 24 hours end
to end. Returning 410 Gone stops retries for that delivery
at once. When the budget is exhausted the endpoint is disabled and the account owner is emailed;
re-enabling does not replay missed events, so reconcile with
the envelopes list.
Delivery is at-least-once and order is not guaranteed. De-duplicate on the event
id, which is stable for a given transition. Endpoints must
be absolute HTTPS URLs resolving to a public address.
Rate limits
X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds until reset) appear on every response.429 with Retry-After. It is a hard block, not a throttle: nothing is queued or slowed, and every request is refused until the minute rolls over.
One wrinkle worth knowing while it lasts. Errors are shaped
{ "error": { "code": "...", "message": "..." } }, but
401, 403 and
429 currently serialise those two keys capitalised
(Code, Message) while
other statuses use lowercase. Match the key case-insensitively and you will be right either way.
We are fixing it.
The OpenAPI document is public and authoritative — no key required to read it. API access is available on Growth and Enterprise plans.