Skip to content

Quickstart

This walkthrough takes a published template to a signed, sealed PDF. It uses a test key, so nothing is billed and every document is watermarked TEST.

  1. API keys are created in the dashboard, under Settings › API keys. Pick Test for this walkthrough: the key starts with bf_test_. The full key is shown once; store it as an environment variable.

    Terminal window
    export BONA_FIDE_API_KEY=bf_test_…

    Every request sends it as Authorization: Bearer … (or x-api-key: …). See Authentication.

  2. Templates are designed in the dashboard (upload a PDF or Word document and place fields, or write it in Document mode) and then published. List them to get the id:

    Terminal window
    curl https://bonafide-api.republic.global/v1/templates \
    -H "Authorization: Bearer $BONA_FIDE_API_KEY"

    GET /v1/templates/{id}/versions/latest returns the version’s roles, fields and the JSON Schema of its variables. The template page in the dashboard also has a Use in API panel with a ready-made request for that exact version.

  3. POST /v1/envelopes renders the template with your variables, pins the version and starts routing. Give one recipient per role (CC roles are optional). Send an Idempotency-Key so a retry cannot send twice.

    Terminal window
    curl https://bonafide-api.republic.global/v1/envelopes \
    -H "Authorization: Bearer $BONA_FIDE_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: onboarding-8841" \
    -d '{
    "template": "tpl_…",
    "version": "latest",
    "variables": {
    "client": { "name": "Acme Robotics, Inc." },
    "effective_date": "2026-10-13"
    },
    "recipients": [
    { "role": "client", "name": "Jane Doe", "email": "jane@acme.dev" }
    ],
    "delivery": "embedded",
    "externalId": "user_8841",
    "dedupeOn": "external_id"
    }'

    The response is 201 Created with the envelope:

    {
    "id": "env_8Gm2kQ4rT1",
    "organization": "org_…",
    "template": "tpl_…",
    "templateVersion": 3,
    "status": "sent",
    "seal": "not_started",
    "testMode": true,
    "recipients": [
    {
    "id": "rcp_5Jw0pLs9Xe",
    "role": "client",
    "name": "Jane Doe",
    "email": "jane@acme.dev",
    "order": 1,
    "status": "sent",
    "verification": "email_otp",
    "completedAt": null
    }
    ],
    "documentHash": "9f2c…",
    "sealedDocumentHash": null,
    "externalId": "user_8841",
    "metadata": {},
    "createdAt": "2026-10-13T13:58:04.512Z",
    "completedAt": null
    }

    delivery decides how recipients are reached: email (the default) emails each recipient a signing link when it is their turn; embedded and none send no email, and you open signing sessions yourself (next step).

  4. Mint a signing session for the recipient from your backend. Sessions are short-lived (ttlSeconds, 60 to 3600, default 600) and single-use.

    Terminal window
    curl https://bonafide-api.republic.global/v1/envelopes/env_8Gm2kQ4rT1/sessions \
    -H "Authorization: Bearer $BONA_FIDE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
    "recipient": "rcp_5Jw0pLs9Xe",
    "mode": "embedded",
    "allowedOrigins": ["https://app.example.com"]
    }'

    Pass the returned token (bfs_…) to your page and render the web component:

    <script type="module" src="https://js.bonafidesign.com/v1"></script>
    <bf-signing session="bfs_…"></bf-signing>
    <script>
    document.querySelector('bf-signing')
    .addEventListener('completed', (e) => console.log('signed', e.detail.envelope));
    </script>

    React and Vue wrappers, events and the security model: Embedded signing.

    With the default email_otp verification the signer confirms a six-digit code sent to their email before submitting. In test mode, email is only delivered to members of your organization, so use your own address while testing.

  5. Register a webhook endpoint in Settings › Webhooks (or with POST /v1/webhook-endpoints) and keep the whsec_… secret. When the last recipient signs you receive envelope.completed, with every variable and typed value in data.values. The sealed PDF follows with envelope.sealed.

    import express from 'express';
    import { Webhook } from 'standardwebhooks';
    const wh = new Webhook(process.env.BONA_FIDE_WEBHOOK_SECRET!);
    const app = express();
    app.post('/hooks/bona-fide', express.raw({ type: 'application/json' }), async (req, res) => {
    const event = wh.verify(req.body, req.headers as Record<string, string>) as { type: string; data: any };
    if (event.type === 'envelope.completed') await saveValues(event.data.envelope.id, event.data.values);
    if (event.type === 'envelope.sealed') await fetchSealedPdf(event.data.envelope.id);
    res.sendStatus(204);
    });

    Download the sealed PDF (the document with its certificate of completion, timestamped):

    Terminal window
    curl -o signed.pdf "https://bonafide-api.republic.global/v1/envelopes/env_8Gm2kQ4rT1/document?version=sealed" \
    -H "Authorization: Bearer $BONA_FIDE_API_KEY"

    Full event list, retries and manual verification: Webhooks.

Swap the test key for a bf_live_ key. Live envelopes are not watermarked, email goes to any recipient, and completed envelopes count toward your plan (Plans & limits). Everything a key creates takes its mode, its lists only show that mode, and test envelopes only notify test-mode webhook endpoints, so register a live endpoint too.