# SAIG Sign — API Reference (v1)

AI document generation + e-signature as an ecosystem service, hosted on the SAIG Console.
Base URL: `https://console.skylite.group`. UI: `/sign` · Public signing: `/s/<token>`.
Published: 2026-07 · Conforms to SAIG Integration Guide v2 conventions.

## Authentication

| Caller | How |
|---|---|
| Console UI (humans) | `Authorization: Bearer <SAIG session JWT>` |
| Ecosystem apps | `x-saig-service-token: <token>` + `X-SAIG-App: <appkey>` |
| External recipients | The magic-link token in the URL — no account, single envelope only |

App-created envelopes are visible/manageable only by the same `X-SAIG-App`. Recipient
tokens are stored hashed (SHA-256); a token grants access to exactly one recipient's
view of one envelope and is superseded on every re-issue (remind/embed).

## Endpoints

Pretty REST paths rewrite to the gateway; the `?op=` form is equivalent.

| Method + path | op | Purpose |
|---|---|---|
| `POST /api/sign/generate` | `generate` | AI-draft a document → `{ title, content_md }` (no envelope) |
| `POST /api/sign/envelopes` | `create` | Create envelope `{ title, content_md, recipients[], routing, brainId?, requireCountersign?, webhookUrl?, branding?, expiresInDays? }` → `{ envelopeId }` |
| `POST /api/sign/envelopes/:id/send` | `send` | Mark sent + email due invitations (routing-aware) |
| `GET /api/sign/envelopes` | `list` | Caller's envelopes (UI) |
| `GET /api/sign/envelopes/:id` | `get` | Envelope + recipients + events + document meta |
| `POST /api/sign/envelopes/:id/remind` | `remind` | Re-issue tokens + resend to pending invited recipients |
| `POST /api/sign/envelopes/:id/void` | `void` | Void with reason (audited) |
| `GET /api/sign/envelopes/:id/document` | `document` | Signed PDF + signatures page + completion certificate (also works with `?token=` for recipients) |
| `GET /api/sign/envelopes/:id/embed-url?recipient=email` | `embed` | Short-lived signing URL for iframe/webview embedding (app auth) |
| `GET /api/sign?op=view&token=` | `view` | Recipient's signing payload (public) |
| `POST /api/sign?op=signit&token=` | `signit` | Sign: `{ signedName, consent:true }` — captures name, UTC time, IP, UA, doc SHA-256 |
| `POST /api/sign?op=decline&token=` | `decline` | Decline with reason |
| `GET/POST /api/sign/templates` | `templates` / `template_create` | Reusable templates (UI) |

`recipients[]`: `{ name, email, role: "signer"|"countersigner"|"cc", order? }`.
`brainId` (optional, strongly recommended for multi-tenant apps): scopes the envelope
to the tenant's **business** brain; persisted and returned on `get`. Present from
2026-07-10 — envelopes created earlier have `brain_id = null`.

## Tokens (shape)

Invitation links and `embed-url` return `/s/<token>` — the token is the **last path
segment**, not a query parameter. For programmatic or embedded signing, extract it and
call `?op=view&token=` / `?op=signit&token=` (`signit` is fully supported
programmatically: `{ signedName, consent:true }`). Tokens are single-envelope,
SHA-256-hashed at rest, and superseded on every re-issue (remind / embed-url).
`routing`: `parallel` (all signers at once) or `sequential` (invited in `order`).
Countersigners are invited only after every signer has signed. When all non-cc parties
have signed, the envelope completes: fresh access tokens are issued and every party is
emailed a link to the executed PDF + certificate.

## Merge fields

`{{name}}`, `{{email}}`, `{{date}}` in `content_md` are personalised per recipient at
view/PDF time. The stored document (and its SHA-256) is the un-merged source.

## Webhooks

If `webhookUrl` is set, events POST as
`{ type, envelopeId, data, ts }` with headers `x-saig-event` and
`x-saig-signature: hex(hmac_sha256(body, SIGN_WEBHOOK_SECRET || SAIG_SERVICE_TOKEN))`.
Types: `envelope.sent`, `recipient.viewed`, `recipient.signed`, `recipient.declined`,
`envelope.completed`, `envelope.voided`, `envelope.expired`. Verify the signature before trusting payloads.

## Envelope lifecycle

`draft → sent → completed` · terminal alternatives: `declined`, `voided`, `expired`
(default 30 days). A 15-minute cron now sweeps overdue envelopes to `expired` (firing
`envelope.expired`) and sends reminder nudges to invited-but-unsigned recipients on a
72-hour cadence, max 3 per recipient, each with a fresh link (prior links superseded).

## Legal / evidence model (ESIGN·UETA·eIDAS-style SES)

Explicit consent checkbox before signing · clear intent ("Sign document") · signature
bound to the exact content via SHA-256 · per-signature capture of typed legal name,
UTC timestamp, IP and user-agent · append-only `sign_events` audit trail · certificate
of completion embedded in the final PDF. This implements simple-electronic-signature
practice; it is not legal advice.

## Add signing to your SAIG app in 30 minutes

1. Get `SAIG_SERVICE_TOKEN` provisioned (CI/CD protocol) and pick your `X-SAIG-App` key.
2. `POST /api/sign/envelopes` with your document markdown + recipients, keep `envelopeId`.
3. `POST /api/sign/envelopes/:id/send` — recipients get branded magic-link emails
   (pass `branding: { name, color }` to brand as your app).
4. Receive webhooks; on `envelope.completed`,
   `GET /api/sign/envelopes/:id/document` and store/attach the executed PDF.
5. Optional in-app signing: `embed-url` → open in an iframe/webview.

## Environment (Console project)

`SUPABASE_SERVICE_ROLE_KEY` · `RESEND_API_KEY` · `ANTHROPIC_API_KEY` (AI drafts) ·
`SAIG_SERVICE_TOKEN` · optional `SIGN_WEBHOOK_SECRET`, `SIGN_AI_MODEL`, `SIGN_BASE_URL`.
Schema: run `SETUP-SIGN.sql` once in the Console Supabase.
