BunnyDoc
APIE-Signing API

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. See Create and manage API keys for scopes, expiry and rotation.

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, revoke it from Settings → API Keys and mint a new one — revoking invalidates its outstanding tokens immediately, where rotating leaves them valid for the rest of their hour.

Build it in this order

A complete integration is five calls. Follow them in this order and you'll hit the gotchas in the cheapest possible place — a 404 on a template you can read, not a 422 on a send you meant to deliver.

Find the template you want to send

GET /v1/templates to locate it, then GET /v1/templates/{id} to read its roles. This second call is not optional in practice: it tells you which role names to fill and which are locked.

Decide who sends it

Omit sender_email to send as the key's owner, or call GET /v1/users to let your UI pick a colleague. Sending as someone else uses their permissions, not the key owner's.

Send it

POST /v1/envelopes/from-template. This creates and sends in one call; if the send fails, nothing is left behind. Store the returned envelope_id.

Subscribe to what happens next

POST /v1/webhooks — the API has no envelope-status endpoint by design. Take the finished PDF from envelope.document_ready.

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 thisEndpointScope
Exchange an API key for a tokenPOST /v1/auth/token—
List your templatesGET /v1/templatestemplates:read
Get one template and its rolesGET /v1/templates/{id}templates:read
List contactsGET /v1/contactscontacts:read
Create a contactPOST /v1/contactscontacts:write
Create an envelope from a template and send itPOST /v1/envelopes/from-templateenvelopes:send
Retrieve signing links for a sent envelopeGET /v1/envelopes/{id}/signing-linksenvelopes:send
List company membersGET /v1/usersusers:read
List the events you can subscribe toGET /v1/webhooks/eventswebhooks:read
List your webhook subscriptionsGET /v1/webhookswebhooks:read
Subscribe an endpoint to eventsPOST /v1/webhookswebhooks:write
Unsubscribe an endpointDELETE /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, users:read) 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 }
}

Quotas

Reads are free of charge against your plan. Two things are metered:

  • Sending draws on a separate API signature requests allowance — one unit per envelope, no matter how many recipients it has. This is not the same pool as envelopes sent from the app. When it's exhausted, sends return 402 quota_exceeded and nothing is delivered.
  • Creating a contact consumes your plan's ordinary contact allowance, returning 403 quota_exceeded when full. You don't need to create contacts to send to people, so this is easy to avoid.

Check both under Settings → Billing → Resources.

Rate limits

Requests are throttled per API key (the token endpoint is per IP). Exceeding a limit returns 429 rate_limited with a standard Retry-After header, in seconds — back off for that long and retry.

LimitScope
20 token exchanges / 15 minper IP
100 sends / 15 minper API key
1000 other requests / 15 minper API key

Counting per key (not per IP) means a shared egress address — an integration platform, a NAT — never makes one customer's traffic count against another's.

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": { },
    "reference": "0190f3a2-7b6c-7e21-9a3f-2c9b1d4e5f60"
  }
}

reference appears on the errors we log server-side (5xx and 422). Keep it in your own logs — quoting it to support locates the exact failure in one lookup.

On this page