API reference
Create files from your case-management, CRM or practice system. The client agreement is pre-filled, signed for the firm (with the firm signature on file, if the agreement needs it) and sent. A webhook tells your system when work can start.
Authentication
Admins create keys in Settings → API. Send Authorization: Bearer ag_live_…. Keys are shown once and stored only as a hash; revoke them any time.
GET /api/v1/templates
Published agreements, with the per-file values each one needs.
{ "templates": [{ "id": "…", "name": "Client Services Agreement", "version": 2,
"values": [{ "key": "fee", "label": "Fee" }], "has_firm_fields": true }] }POST /api/v1/files
curl -X POST https://first-ink.vercel.app/api/v1/files \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"template_id": "…",
"client_name": "Jane Testperson",
"client_email": "jane@example.test",
"reference": "2026-0142", // optional, generated if omitted
"title": "Estate planning", // optional
"values": { "fee": "$2,500" }, // per-file values, if the agreement has any
"send_email": true // default true
}'
201 { "id": "…", "reference": "2026-0142", "status": "awaiting_signature",
"signing_url": "https://first-ink.vercel.app/sign/…", "email_sent": true, "email_note": null }422 with error when input is invalid (e.g. a missing value, or the agreement needs a firm signature and none is on file).
GET /api/v1/files/:id
status is the gate: awaiting_signature, ready_to_start, declined or voided. agreement.state shows progress (sent, viewed, consented, completed…).
GET /api/v1/files/:id/document
The signed PDF with its audit certificate. 409 until the agreement is completed.
Webhooks
Add an endpoint in Settings → Webhooks. Events: agreement.completed, agreement.declined, test.ping.
POST <your url>
X-FirstInk-Signature: t=1762718400,v1=<hex>
{ "type": "agreement.completed", "created_at": "…",
"data": { "file_id": "…", "file_reference": "2026-0142", "agreement_id": "…",
"client": { "name": "Jane Testperson", "email": "jane@example.test" },
"completed_at": "…", "sealed_sha256": "…",
"document_url": "https://first-ink.vercel.app/api/v1/files/…/document" } }
// Verify: v1 == HMAC_SHA256(secret, t + "." + rawBody), and reject old t values.Each event is attempted once with a 10-second timeout; poll GET /api/v1/files/:id as a fallback.