Developers

ToroDocs API reference

Send documents for signature, track their status, retrieve the sealed PDF, and receive real-time events — all over a clean REST API.

Introduction

The ToroDocs REST API lets you create and send envelopes from templates, poll their status, download the sealed PDF, and subscribe to webhook events — the same pipeline that powers the dashboard. All responses are JSON.

  • Base URL: https://torodocs.com/api/v1
  • Version: the API is versioned in the path (/v1).
  • Enterprise-only: API access requires an Enterprise plan. See plans →

Authentication

Authenticate every request with a team API token in the Authorization header:

Authorization: Bearer <your-api-token>

Create a token on the Developers page (Dashboard → Developers). Tokens are minted on your team and act with your team's data and document allowance — a token is the whole team's access, so keep it secret. The plaintext token is shown once at creation.

Two scopes:

  • read — list & read templates, envelopes, recipients, status, and download sealed PDFs.
  • read-write — everything read can do, plus create/send envelopes and manage webhooks.

Conventions

Response envelope

Success responses wrap the result in data (list endpoints add meta):

{ "data": { … } }
{ "data": [ … ], "meta": { "current_page": 1, "per_page": 25, "total": 3, "last_page": 1 } }

Errors use a consistent shape:

{ "error": { "code": "not_found", "message": "…", "details": { … } } }

Pagination

List endpoints accept ?page and ?per_page (default 25, max 100; out of range → 422). The meta object carries current_page, per_page, total, and last_page.

Rate limiting

Requests are limited to 120 per minute per token. Responses carry X-RateLimit-Limit and X-RateLimit-Remaining; exceeding the limit returns 429 with a Retry-After header.

Idempotency

Send an Idempotency-Key header on POST /envelopes to make a retry safe. Replaying the same key with the same body returns the original envelope (no second send or charge). Reusing a key with a different body returns 409 idempotency_conflict.

The field key

Every template field has a stable, developer-facing name: its field key (key). Read a template's fields via GET /templates/{id} to discover the keys, then prefill values by key when you send (POST /envelopes). Prefilled values are locked for the signer and stamped onto the final sealed PDF.

Signature, initials, and checkbox fields have key: null — they are the signer's action and can't be prefilled. Text/date/number/etc. fields carry a real key like "candidate_name".

Quickstart — send your first envelope

  1. Create an Enterprise read-write API key on the Developers page.
  2. GET /templates/{id} to see each role's fields and their keys.
  3. POST /envelopes with a recipient per role, optionally prefilling fields by key.
  4. Redirect your user to the returned signing_url, or wait for the envelope.sent / envelope.completed webhooks.

GET /me

GET /me read

The authenticated team, its document usage, and the current token.

curl https://torodocs.com/api/v1/me \
  -H "Authorization: Bearer $TORODOCS_TOKEN"
{
  "data": {
    "id": 42,
    "name": "Acme Inc",
    "plan": "enterprise",
    "documents": { "used_this_period": 3, "allowance": null, "remaining": null },
    "token": { "name": "Zapier integration", "abilities": ["read", "write"] }
  }
}

allowance/remaining are null on Enterprise (unlimited).

GET /templates

GET /templates read

Paginated list of your team's templates, newest first. Query: ?page, ?per_page.

curl "https://torodocs.com/api/v1/templates?per_page=25" \
  -H "Authorization: Bearer $TORODOCS_TOKEN"
{
  "data": [
    { "id": 10, "name": "Offer Letter", "description": "Employment offer",
      "page_count": 3, "created_at": "2026-07-24T20:32:51.000000Z" }
  ],
  "meta": { "current_page": 1, "per_page": 25, "total": 1, "last_page": 1 }
}

GET /templates/{id}

GET /templates/{id} read

Full template with roles grouped by signer_slot; each role lists its fields with their key. This is the contract for sending — a recipient is required for every signer slot the template uses.

{
  "data": {
    "id": 10, "name": "Offer Letter", "description": "Employment offer",
    "page_count": 3, "sequential": true,
    "default_title": "Your offer", "default_message": null,
    "created_at": "2026-07-24T20:32:51.000000Z",
    "roles": [
      { "signer_slot": 1, "label": "Candidate", "default_email": null, "routing_order": 1,
        "fields": [
          { "key": "candidate_name", "type": "text", "label": "Candidate Name", "required": true, "page": 1 },
          { "key": "start_date", "type": "date", "label": "Start Date", "required": true, "page": 1 },
          { "key": null, "type": "signature", "label": "Candidate Signature", "required": true, "page": 3 }
        ] },
      { "signer_slot": 2, "label": "HR", "default_email": "hr@example.com", "routing_order": 2,
        "fields": [ { "key": null, "type": "signature", "label": "HR Signature", "required": true, "page": 3 } ] }
    ]
  }
}

GET /envelopes

GET /envelopes read

Paginated list, newest first. Filters: ?status= (draft, sent, in_progress, completed, voided, declined, expired) and ?template_id=.

curl "https://torodocs.com/api/v1/envelopes?status=completed" \
  -H "Authorization: Bearer $TORODOCS_TOKEN"
{
  "data": [
    { "id": 137, "title": "Please sign your offer", "status": "sent",
      "recipient_count": 2, "is_sealed": false, "from_template": true, "template_id": 10,
      "sent_at": "2026-07-24T20:00:00.000000Z", "completed_at": null,
      "created_at": "2026-07-24T20:00:00.000000Z" }
  ],
  "meta": { "current_page": 1, "per_page": 25, "total": 1, "last_page": 1 }
}

GET /envelopes/{id}

GET /envelopes/{id} read

Full envelope with recipients, document, and — once completed — the sealed block (SHA-256 + download URL).

{
  "data": {
    "id": 138, "title": "Please sign your offer", "status": "completed",
    "sequential": false, "recipient_count": 1, "is_sealed": true,
    "from_template": true, "template_id": 10,
    "sent_at": "2026-07-19T20:32:51.000000Z", "completed_at": "2026-07-21T20:32:51.000000Z",
    "created_at": "2026-07-19T20:32:51.000000Z",
    "document": { "title": "Offer Letter", "page_count": 3 },
    "recipients": [
      { "id": 139, "name": "Jordan Lee", "email": "jordan@example.com",
        "signer_slot": 1, "routing_order": 1, "status": "signed",
        "signed_at": "2026-07-21T20:32:51.000000Z", "viewed_at": "2026-07-20T20:32:51.000000Z",
        "signing_url": null }
    ],
    "sealed": {
      "sha256": "92e2f0976f47f01125d5ab20f0ed7cfe36c1de94faac6835dc65ef6887825ea1",
      "sealed_at": "2026-07-21T20:32:51.000000Z", "bytes": 10951,
      "download_url": "https://torodocs.com/api/v1/envelopes/138/download"
    }
  }
}

GET /envelopes/{id}/recipients

GET /envelopes/{id}/recipients read

Recipient statuses for polling. Each recipient who can still act carries a live signing_url (for a sequential envelope, only the current signer's URL is live — everyone else is null).

{
  "data": [
    { "id": 139, "name": "Jordan Lee", "email": "jordan@example.com",
      "signer_slot": 1, "routing_order": 1, "status": "sent",
      "signed_at": null, "viewed_at": null,
      "signing_url": "https://torodocs.com/sign/AbCd…40-char-token" }
  ]
}

GET /envelopes/{id}/download

GET /envelopes/{id}/download read

Streams the sealed PDF (application/pdf). Returns 409 not_ready until the envelope is completed & sealed.

curl -L "https://torodocs.com/api/v1/envelopes/138/download" \
  -H "Authorization: Bearer $TORODOCS_TOKEN" -o signed.pdf

POST /envelopes

POST /envelopes read-write

Create and send an envelope from a template — the main write endpoint. Provide one recipient per signer slot the template uses, and optionally prefill fields by key. Optional Idempotency-Key header.

Body: template_id (required), recipients[] (required; each signer_slot, name, email, optional routing_order and fields), plus optional title, message, sequential, expires_in_days (1–365), reminders{enabled,interval_days}.

curl -X POST https://torodocs.com/api/v1/envelopes \
  -H "Authorization: Bearer $TORODOCS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-2481" \
  -d '{
    "template_id": 10,
    "title": "Please sign your offer",
    "sequential": true,
    "expires_in_days": 14,
    "reminders": { "enabled": true, "interval_days": 3 },
    "recipients": [
      { "signer_slot": 1, "name": "Jordan Lee", "email": "jordan@example.com",
        "routing_order": 1,
        "fields": { "candidate_name": "Jordan Lee", "start_date": "2026-09-01" } },
      { "signer_slot": 2, "name": "HR Team", "email": "hr@acme.com", "routing_order": 2 }
    ]
  }'

Returns 201 with the full envelope (same shape as GET /envelopes/{id}). Each recipient who can act carries a signing_url; for a sequential envelope only order-1 is live at send time.

Webhook endpoints (API)

GET /webhooks read-write
POST /webhooks read-write
DELETE /webhooks/{id} read-write
POST /webhooks/{id}/test read-write

Manage endpoints programmatically. POST accepts url (https only) and optional events (defaults to ["*"]); the signing secret is returned once. /test queues a sample webhook.test delivery.

curl -X POST https://torodocs.com/api/v1/webhooks \
  -H "Authorization: Bearer $TORODOCS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/webhooks/torodocs", "events": ["*"] }'
{
  "data": {
    "id": 7, "url": "https://example.com/webhooks/torodocs",
    "events": ["*"], "active": true, "consecutive_failures": 0,
    "last_success_at": null, "last_failure_at": null,
    "created_at": "2026-07-24T22:48:30.000000Z",
    "secret": "whsec_…store-this-now"
  }
}

Webhooks

Subscribe endpoints to events and ToroDocs will POST a signed JSON payload as documents move through their lifecycle. Event types:

EventFires when…
envelope.sentan envelope is sent
recipient.vieweda recipient opens the signing page (first view)
recipient.signeda recipient completes signing
envelope.completedall recipients have signed
envelope.sealedthe sealed PDF is ready (payload carries sealed.sha256 + download_url)
envelope.declineda recipient declines
envelope.expiredan envelope passes its expiry
envelope.voidedthe sender voids the envelope

Payloaddata.envelope matches GET /envelopes/{id}; recipient-scoped events also include data.recipient:

{
  "id": "689dcf24-1167-4154-a4e4-6fd55a02a84b",
  "type": "envelope.sealed",
  "created_at": "2026-07-24T22:48:53.212200Z",
  "data": {
    "envelope": { "id": 138, "status": "completed", "is_sealed": true,
                  "sealed": { "sha256": "92e2f097…", "download_url": "https://torodocs.com/api/v1/envelopes/138/download", "bytes": 10951, "sealed_at": "…" },
                  "recipients": [ … ], "document": { … } }
  }
}

Headers on each delivery:

Content-Type: application/json
X-ToroDocs-Event: envelope.sealed
X-ToroDocs-Delivery: 1837          # unique per delivery — use it to dedupe
X-ToroDocs-Signature: t=1784933335,v1=4648fe1c3bfced…

Verifying signatures

The X-ToroDocs-Signature header is t=<unix_ts>,v1=<hex>, where v1 = HMAC-SHA256(endpoint_secret, "<t>.<raw_request_body>"). Recompute it over the raw body and compare in constant time. Optionally reject if now − t exceeds a tolerance to guard against replay.

// Node.js (Express — use express.raw so you get the exact bytes)
const crypto = require('crypto');
function verify(secret, header, rawBody) {
  const [tp, vp] = header.split(',');            // "t=...", "v1=..."
  const t = tp.split('=')[1], v1 = vp.split('=')[1];
  const expected = crypto.createHmac('sha256', secret)
                         .update(`${t}.${rawBody}`).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}
// PHP
function torodocs_verify(string $secret, string $header, string $rawBody): bool {
    [$tp, $vp] = explode(',', $header);
    $t  = explode('=', $tp)[1];
    $v1 = explode('=', $vp)[1];
    $expected = hash_hmac('sha256', $t.'.'.$rawBody, $secret);
    return hash_equals($expected, $v1);
}

Retries & reliability. A non-2xx or timeout is retried with exponential backoff (roughly 10s, 30s, 120s … up to ~30m over 6 attempts). After 15 consecutive failed deliveries an endpoint is auto-disabled (re-enable it on the Developers page). Deliveries may repeat on retry — dedupe using X-ToroDocs-Delivery.

Errors

Errors return the { "error": { "code", "message" } } envelope (validation adds details).

HTTPcodeMeaning
401unauthenticatedMissing or invalid token.
403api_not_enabledTeam is not on Enterprise.
403insufficient_scopeToken lacks the required ability (read/write).
404not_foundResource doesn't exist or isn't in your team.
405method_not_allowedWrong HTTP method for the path.
409not_readySealed PDF requested before the envelope is sealed.
409idempotency_conflictIdempotency-Key reused with a different body.
422validation_failedInvalid request data (see details).
422invalid_urlWebhook URL rejected (must be https; no private/reserved addresses).
429rate_limitedToo many requests — retry after Retry-After.
500server_errorSomething went wrong on our side.

OpenAPI

Import the machine-readable spec into Postman or Insomnia: /docs/openapi.json.


Questions? Contact us or visit the help center.