Skip to content
Sign · API reference

Flaxxa Sign e-signature API and webhooks.

Drive Flaxxa Sign from your own software: a REST API with workspace keys, signed webhooks and idempotency keys.

No credit card
Free plan
Cancel anytime
flaxxa.com / product / sign / docs
cURLREST · JSON
# Flaxxa Sign · first request
curl https://flaxxa.com/api/v1/sign \
  -H "Authorization: Bearer $FLAXXA_KEY" \
  -H "Accept: application/json"

# webhooks: X-Flaxxa-Signature (HMAC-SHA256)
# retries: Idempotency-Key, 24 h
200 OKapplication/json
+ webhooks · HMAC-signed
REST
Requests per key by default
120/min
Idempotency window
24h
Quick start

How do I make my first API call?

Replace $FLAXXA_KEY with a key from your workspace. The first call returns a real id you can use for the next ones.

cURL
curl -X POST https://flaxxa.com/api/v1/sign/envelopes/send \
  -H "Authorization: Bearer flx_live_YOUR_KEY" \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{
    "title": "Service agreement — Acme Ltd",
    "external_id": "PROPOSAL-42",
    "documents":  [{ "name": "Agreement.pdf", "url": "https://your-app.com/proposals/42.pdf" }],
    "recipients": [{ "key": "customer", "name": "Asha Verma", "email": "[email protected]" }],
    "return_signing_links": true
  }'
PHP
$res = Http::withToken(env('FLAXXA_SIGN_KEY'))->acceptJson()
    ->post('https://flaxxa.com/api/v1/sign/envelopes/send', [
        'title'       => "Proposal #{$proposal->id}",
        'external_id' => "PROPOSAL-{$proposal->id}",
        'documents'   => [['name' => 'Proposal.pdf', 'base64' => base64_encode($pdfBytes)]],
        'recipients'  => [['name' => $customer->name, 'email' => $customer->email]],
        'return_signing_links' => true,
    ])->throw()->json('data');

// $res['id'], $res['status'] === 'sent', $res['signing_links'][0]['url']
Node
const res = await fetch("https://flaxxa.com/api/v1/sign/envelopes/send", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.FLAXXA_SIGN_KEY}`, Accept: "application/json", "Content-Type": "application/json" },
  body: JSON.stringify({
    title: "Proposal #42",
    external_id: "PROPOSAL-42",
    documents: [{ name: "Proposal", pages: [
      "<h1>Proposal #42</h1><p>Scope, price and terms…</p>",
      '<p>Accepted:</p><div data-type="signature-block" data-signer="Asha Verma"></div>',
    ] }],
    recipients: [{ name: "Asha Verma", email: "[email protected]" }],
  }),
});
const { data } = await res.json(); // data.id, data.status
Python
import requests, base64

r = requests.post("https://flaxxa.com/api/v1/sign/envelopes/send",
    headers={"Authorization": f"Bearer {KEY}", "Accept": "application/json"},
    json={
        "title": "Service agreement",
        "external_id": "PROPOSAL-42",
        "documents": [{"name": "Agreement.pdf", "base64": base64.b64encode(pdf_bytes).decode()}],
        "recipients": [{"name": "Asha Verma", "email": "[email protected]"}],
    })
r.raise_for_status()
envelope = r.json()["data"]
Request
POST /api/v1/sign/envelopes/send HTTP/1.1
Host: flaxxa.com
Authorization: Bearer flx_live_·············
Content-Type: application/json
Idempotency-Key: proposal-42-send

{
  "title":       "Service agreement — Acme Ltd",
  "external_id": "PROPOSAL-42",
  "documents":   [{ "name": "Agreement.pdf", "url": "https://your-app.com/proposals/42.pdf" }],
  "recipients":  [{ "key": "customer", "name": "Asha Verma", "email": "[email protected]" }],
  "return_signing_links": true
}
Response · 201
HTTP/1.1 201 Created
Content-Type: application/json

{
  "data": {
    "id": 123,
    "status": "sent",
    "external_id": "PROPOSAL-42",
    "sent_at": "2026-10-27T10:00:00+00:00",
    "expires_at": "2027-01-25T10:00:00+00:00",
    "documents":  [{ "id": 77, "name": "Agreement.pdf", "pages": 4, "status": "ready" }],
    "recipients": [{ "id": 9, "name": "Asha Verma", "email": "[email protected]", "role": "signer", "order": 1, "status": "notified" }],
    "download_path": "/api/v1/sign/envelopes/123/download",
    "signing_links": [{ "recipient_id": 9, "email": "[email protected]", "url": "https://flaxxa.com/sign/r/…" }]
  }
}
Authentication

How do I authenticate?

Every call requires Authorization: Bearer <key>. Rotate from workspace settings — old keys are revoked instantly. Server-side only; never ship a live key to the browser.

Base URLhttps://flaxxa.com/api/v1/sign
Send for signature

Send for signature API

One call creates the envelope (documents, signers, fields) and sends it. API access depends on your plan, and the key needs the sign.envelope.send scope. Documents come as a URL we fetch, base64, or HTML pages we render; a <div data-type="signature-block" data-signer="…"> in HTML becomes that signer's exact signing spot. Without fields or blocks, a signature and date box is placed at the foot of the last page per signer.

POST/envelopes/send

Create + send

Body: title, subject, message, signing_mode (sequential|parallel), external_id, metadata, expiry_days (1–365), reminders_enabled, documents[] (up to 20: name, url | base64 | pages[], page_size A4|Letter), recipients[] (key, name, email, role signer|cc, order), fields[] (recipient, document, type signature|initial|date|text|checkbox, page, x_pt, y_pt, width_pt, height_pt, required), send (default true), return_signing_links.

GET/envelopes/{id}/signing-links

Signing links

One URL per recipient still to sign. Redirect the signer to it, or open it in a new tab, instead of waiting for the email. The signing page can't be shown inside a frame on another site.

POST/envelopes/{id}/remind

Remind

Emails everyone still waiting their link again. Each person can be reminded once every 24 hours.

POST/envelopes/{id}/void

Void

{ reason } cancels a sent envelope; its links stop working.

Status & results

Status & results API

Look an envelope up by your own id, read its state, and fetch the signed PDF, audit trail and certificate. Reading needs the sign.envelope.view scope.

GET/envelopes/by-external-id/{external_id}

By your id

The envelope created with that external_id (the latest if several).

GET/envelopes/{id}

Retrieve

Status (draft, sent, in_progress, completed, declined, voided, expired), recipients with signed_at, documents.

GET/envelopes

List

Filter: external_id, filters[status][], q (title/subject), date_from/date_to; paginated {data, meta}.

GET/envelopes/{id}/download

Signed PDF

A short-lived download URL for the signed PDF and its SHA-256 hash, once completed.

GET/envelopes/{id}/certificate

Signing certificate

A short-lived URL for the certificate PDF: recipients, signing times and IP addresses, signature methods, the timeline and document hashes.

GET/envelopes/{id}/audit

Audit trail

Every event (sent, opened, consent, field completed, signed, declined) with IP address and timestamp.

Templates

Templates API

Reuse a prepared document with named roles; fill the roles and send.

GET/templates

List templates

Templates in the workspace with their recipient roles.

GET/templates/{id}

Retrieve template

One template with its roles and fields.

POST/templates/{id}/instantiate

Envelope from template

{ title, recipients: [{ role_name, name, email }] } returns a draft with the template's fields; then POST /envelopes/{id}/send.

Step-by-step (custom flows)

Step-by-step (custom flows) API

The endpoints the app's own wizard uses, for flows that need control over each step.

POST/envelopes

Create draft

{ title, subject, message, signing_mode, metadata }.

POST/envelopes/{id}/documents/upload-init

Upload init

Returns a presigned PUT URL for a PDF; PUT the bytes there, then finalize.

POST/envelopes/{id}/documents/upload-finalize

Upload finalize

{ document_id } checks the upload and marks the document ready.

POST/envelopes/{id}/compose

Compose from HTML

{ title, pages[], page_size } renders a PDF from HTML pages and records signature-block anchors.

POST/envelopes/{id}/recipients

Add recipient

{ name, email, role, order_index }.

POST/envelopes/{id}/fields/bulk

Place fields

{ fields: [{ document_id, recipient_id, field_type, page_number, x_pt, y_pt, width_pt, height_pt }] }, in PDF points with the origin bottom-left.

POST/envelopes/{id}/send

Send

Checks readiness (a ready document, recipients, a field per signer) and sends.

DELETE/envelopes/{id}

Delete

Drafts and closed (voided, expired, declined) envelopes only.

Webhooks

Webhooks API

Register an endpoint once; every subscribed event is POSTed with X-Flaxxa-Event and X-Flaxxa-Signature (HMAC-SHA256 of the body with the endpoint secret, shown once).

POST/../webhooks

Subscribe

POST /api/v1/webhooks { url, subscribed_events[] }, permission workspace.webhooks.manage.

GET/../webhooks

List

GET /api/v1/webhooks: endpoints with failure counts and last delivery.

DELETE/../webhooks/{id}

Unsubscribe

DELETE /api/v1/webhooks/{id}.

Webhook events

Which webhook events can I subscribe to?

Subscribe via POST /webhooks. Verify X-Flaxxa-Signature on every payload.

  • app.sign.envelope.sent

    The envelope was sent; carries external_id, recipient_count and signing_mode.

  • app.sign.recipient.notified

    A recipient was emailed their signing link (the first email or a reminder).

  • app.sign.recipient.opened

    A recipient opened their signing link.

  • app.sign.recipient.signed

    A recipient completed signing.

  • app.sign.recipient.declined

    A recipient declined, with their reason.

  • app.sign.envelope.completed

    Every signer signed: external_id, each recipient's status and signed_at, and download_path for the signed PDF.

  • app.sign.envelope.voided

    The sender voided the envelope.

  • app.sign.certificate.generated

    The signing certificate PDF is ready.

Errors

What errors can the API return?

All errors return a JSON envelope: { error: { code, message, details } }. Retry idempotent calls on 5xx with exponential backoff.

CodeNameWhen it fires
401unauthenticatedAPI key missing, expired or revoked.
402feature_disabledThe workspace's plan doesn't include Sign API access (or the feature asked for).
402quota_reachedThe plan's monthly envelope, template or storage limit is reached; the body names the feature, limit and usage.
403insufficient_scopeKey valid but lacks the scope: sending needs sign.envelope.send, reading needs sign.envelope.view.
404not_foundNo envelope with that id / external_id in this workspace.
422document_source_missingA document had none of base64, url or pages.
422recipient_has_no_required_fieldA signer ended up with nothing to sign: pass fields, or add a signature block.
422no_ready_documentThe URL did not serve a PDF, or the PDF could not be stored.
422invalid_stateThe action does not fit the envelope's state (e.g. void on a draft, remind on a completed one).
429rate_limited120 requests a minute per key by default; Retry-After is set.
Conventions

Which rules apply to every request?

Every Flaxxa app's API follows the same authentication, rate limits, idempotency and webhook signing.

Bearer auth

Every API call uses a Bearer token. Keys belong to a workspace and are scoped per app. Create and revoke them from workspace API settings.

Rate limits

Each key can make 120 requests a minute unless the app sets its own limit. Going over returns 429 with a Retry-After header.

Idempotency

Send an Idempotency-Key header on a POST and a retry with the same key within 24 hours returns the first response instead of running again.

Signed webhooks

Every webhook payload carries an X-Flaxxa-Signature HMAC-SHA256 header made with the secret you get when you subscribe. Verify it before processing.

API FAQ

Flaxxa Sign API: common questions

Frequently asked questions

POST /api/v1/sign/envelopes/send with an API key that has the sign.envelope.send scope. Pass the document as a URL, base64 or HTML pages, the recipients, and optionally the fields. Flaxxa Sign creates the envelope, sends it and can return a signing link per signer.

Get started in minutes

Ship faster with Flaxxa Sign.

Create a workspace, make an API key in its settings, and send your first request.

Free plan · No credit card · Cancel anytime