E-Signing API
Send documents for signature straight from your own systems — exchange an API key for a token, create envelopes from templates, manage contacts, and retrieve signing links over a simple REST API.
The E-Signing API lets your application do what your team does in the app: turn a template into a signature request, send it, and follow it through to a signed, audit-trailed PDF — without anyone opening BunnyDoc.
It's a small, focused REST API. JSON in, JSON out, one bearer token, and the exact same permissions your team already has in the app.
Base URL
Every endpoint lives under https://api2.bunnydoc.com/v1. All paths on these pages are relative
to that base.
How authentication works
Access is a two-step flow, and it's the first thing you'll build. Your long-lived API key is sent once to get a short-lived access token; every other request carries only the token, so the key never travels again.
Create an API key
In the app, open Settings → API Keys (owner or admin only) and create a key. It looks like
bd_live_… and is shown once, at creation — copy it somewhere safe. You can't retrieve it
afterwards, only rotate it.
Exchange the key for an access token
Send your key to POST /v1/auth/token. You get back a
bearer token that's valid for one hour.
Call the API with the token
Send Authorization: Bearer <token> on every other request. Cache the token and reuse it until it
expires — don't mint a new one per call.
Keep your API key server-side
An API key acts on behalf of your whole company. Never ship it in a browser, a mobile app, or any client you don't control. If a key is exposed, rotate it from Settings → API Keys — rotating (or revoking) invalidates its outstanding tokens immediately.
Start here
Get an access token
The first call every integration makes — exchange your API key for a bearer token.
Send from a template
Create an envelope from one of your templates and send it, in a single call.
Endpoints at a glance
Each request is authorised by both the scope on your key and the permission of the user who owns it — a key can never do more than that person can in the app.
| Do this | Endpoint | Scope |
|---|---|---|
| Exchange an API key for a token | POST /v1/auth/token | — |
| List your templates | GET /v1/templates | templates:read |
| Get one template and its roles | GET /v1/templates/{id} | templates:read |
| List contacts | GET /v1/contacts | contacts:read |
| Create a contact | POST /v1/contacts | contacts:write |
| Create an envelope from a template and send it | POST /v1/envelopes/from-template | envelopes:send |
| Retrieve signing links for a sent envelope | GET /v1/envelopes/{id}/signing-links | envelopes:send |
| List the events you can subscribe to | GET /v1/webhooks/events | webhooks:read |
| List your webhook subscriptions | GET /v1/webhooks | webhooks:read |
| Subscribe an endpoint to events | POST /v1/webhooks | webhooks:write |
| Unsubscribe an endpoint | DELETE /v1/webhooks/{id} | webhooks:write |
Want to be notified when a document is signed, declined or voided instead of polling? Use
webhooks — and take the finished PDF straight from the
envelope.document_ready
payload.
Conventions
- JSON in, JSON out — send and expect
application/json. - Scoped to your company — every resource belongs to one company, and requests never cross company boundaries.
- Same permissions as the app — a key can only do what its owner's role allows. See Users & roles.
- Scopes narrow access — the scopes on a key (
templates:read,contacts:read,contacts:write,envelopes:send,webhooks:read,webhooks:write) can only ever restrict what its owner could already do, never extend it.
Pagination
List endpoints take page (from 1) and per_page (up to 100, default 25), and return a
pagination object:
{
"data": [ /* … */ ],
"pagination": { "page": 1, "per_page": 25, "total": 134, "total_pages": 6 }
}Rate limits
Requests are throttled per API key (the token endpoint is per IP). Exceeding a limit returns
429 rate_limited — back off and retry.
| Limit | Scope |
|---|---|
| 20 token exchanges / 15 min | per IP |
| 100 sends / 15 min | per API key |
| 1000 other requests / 15 min | per API key |
Errors
Every error has the same shape — branch on error.code, never on the message text. See the full
error reference.
{ "error": { "code": "unknown_signer_role", "message": "…", "details": { } } }