API and automation
What this page is — every way to drive Orbit Papers without the screens: the public REST API, outbound webhooks, event-driven document generation, and the assistant tools.
What it is for — so other systems can create, move and react to Papers documents without anyone opening the screens.
The problem it solves — the documents most worth automating — one per order, one per won deal — otherwise cost a person every time, and your other systems learn about a signature days late.
Route: /api/v1/ext/papers/… · Credentials: an API key from Developers → API keys
(Manage Api Keys) carrying the scopes below
1. What it is
| Mechanism | Direction | Use it for |
|---|---|---|
| REST API | Your system → Papers | Create and submit documents, approve, share, run AI, read reports |
| Webhooks | Papers → your system | React when a document is finalized, signed, revoked… |
| Generation triggers | Orbit event → Papers | A won deal or submitted form creates a document with no code |
| Assistant / MCP tools | A person or agent → Papers | The same operations in conversation |
| Embedded views | Papers UI inside another site | When a person must act, not a system |
Every route obeys the same rules as the screens. A type requiring approval cannot be finalized through the API, and a key cannot see documents outside its organisation and project.
2. Why you would use it
- The repetitive documents write themselves. An offer letter per accepted candidate or a contract per won deal needs no one to open Papers.
- Your systems learn the moment something is signed.
paper.fully_executedcan start billing within seconds, instead of when someone remembers to tell finance. - Least privilege by design. Narrow scopes mean an integration that files documents cannot approve them.
- Safe retries. POSTs accept an
Idempotency-Key, so a network retry cannot create two contracts.
3. Step by step — REST
- Developers → API keys: create a key, choose the scopes, and bind it to a project if the integration should see only one.
- Send
Authorization: Bearerwith the key on every call. - On POSTs, send an
Idempotency-Keyheader with a unique value per logical operation. - Read the
RateLimit-*response headers and back off on HTTP 429.
The organisation, and the project for a project-bound key, always come from the key, never from the
request. An org-wide key may pass project_id when creating; a project-bound key gets
project_scope_mismatch if it names another project.
4. Field reference
Scopes
| Scope | Allows |
|---|---|
papers:document:read | Types, documents, comments, pending decisions, zones (read) |
papers:document:write | Create and submit documents, share links, generate from an event, replace zones |
papers:workflow:act | Approve, reject or return on behalf of a named approver |
papers:obligation:read | Obligations, per document or across the key's scope |
papers:ai:run | Summarize and risk analysis — credits billed to the organisation |
papers:report:read | Project-shared saved reports and their data |
Routes (prefix /api/v1/ext/papers)
| Method and path | Scope | Body fields |
|---|---|---|
GET /types | document:read | — |
POST /documents | document:write | type_code, title, data_values, source_ref, project_id |
GET /documents/{id} | document:read | — |
GET /documents/{id}/comments | document:read | — |
GET /documents/{id}/obligations, GET /obligations | obligation:read | — |
POST /documents/{id}/submit | document:write | comment |
GET /approvals/pending | document:read | — routed and unrouted decisions |
PATCH /documents/{id}/workflow/status | workflow:act | action, comments, approver_email, lifecycle_action |
POST /documents/{id}/share-link | document:write | expires_days, passcode, allow_download |
POST /generate | document:write | event_type, payload |
POST /ai/summarize, POST /ai/risks | ai:run | document_id or raw_text, project_id, perspective, custom_focus, force |
GET / PUT /types/{id}/zones | document:read / write | Zone list — see zonal extraction |
GET /reports, /reports/{id}, /reports/{id}/data | report:read | — shared reports only |
Errors
Errors carry a type and a code:
| HTTP | Code | Meaning |
|---|---|---|
| 400 | missing_fields, invalid_body, validation_failed | Bad input — the message says which field |
| 400 | invalid_transition / invalid_action | Not allowed from the document's state |
| 400 | missing_approver_email | Workflow actions must name the approver |
| 403 | project_scope_mismatch | The key cannot act in that project |
| 403 | not_an_approver / approver_not_found | The named person is not an approver on the current step, or does not exist |
| 403 | key_missing_actor | The key has no user to act as |
| 409 | invalid_lifecycle | The type's lifecycle cannot run this move |
| 429 | — | Rate limit: 60/min per key by default, 600/min per organisation (set by the platform operator) |
| 502 | ai_execution_failed, approval_action_failed | A downstream step failed — safe to retry with the same idempotency key |
Webhook events (Orbit Papers)
paper.created · paper.state_changed · paper.lifecycle_override · paper.finalized · paper.sent
(signature request sent) · paper.signed · paper.fully_executed · paper.declined · paper.expired ·
paper.revoked · paper.superseded · paper.comment_added · paper.obligation_created ·
paper.obligation_completed · ai.risk.critical_detected (from the regulatory drift sweep)
Webhook delivery
| Aspect | Behaviour |
|---|---|
| Where configured | Developers → Webhooks: endpoint URL plus selected events |
| Headers | Orbit-Signature: t=<timestamp>,v1=<hmac>, Orbit-Event-Id, Orbit-Event-Type, User-Agent: Orbit-Webhooks/1.0 |
| Signature | HMAC-SHA256 with the endpoint secret over the string <timestamp>.<raw body> |
| Success | Any 2xx within 10 seconds |
| Retries | Up to 10 attempts, exponential backoff from 30 s, capped at 6 h |
| Disabled endpoint | Pending deliveries are marked failed |
Use Orbit-Event-Id to de-duplicate, because a retried delivery can arrive after your first successful
handling timed out.
5. Generation triggers
A trigger binds an event type to a document type. It has a title template, a source map from
event payload to fields, and optional auto-submit and auto-finalize. When the event fires, every
active trigger for it creates a document as a system actor. Each run leaves a papers.generation audit
row, marked completed or failed.
| Event | Typical document |
|---|---|
deal.won | The customer agreement |
form.submitted | An application or intake document — also runs intake form bridges |
| Any event another module enqueues | Anything with a matching trigger |
POST /generate from your system | Your own events |
A trigger on a type without a published version fails with "type has no published version". Configure triggers in generation triggers (Configure triggers/webhooks/auto-generation rules.).
6. Worked example
Gatiro wants a customer agreement drafted whenever a deal is won in Pulse, and its billing system told when the agreement is signed.
- Trigger.
- Event
deal.won→ type Customer agreement. - Title template: Agreement — deal name.
- Source map: deal value →
contract_value, account →customer_name. - Auto-submit on.
- Event
- Webhook. In Developers → Webhooks, an endpoint
https://billing.gatiro.example/hooks/orbitsubscribes topaper.fully_executed. - Verification in the billing service:
const [t, v1] = req.get('Orbit-Signature').split(',').map(p => p.split('=')[1]);
const expected = crypto.createHmac('sha256', SECRET).update(`${t}.${rawBody}`).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) return res.sendStatus(401);
if (await seen(req.get('Orbit-Event-Id'))) return res.sendStatus(200);
- A deal is won at 10:02. By 10:02:05 Agreement — Harbor Health exists and has been submitted for approval.
- Approval via API. An internal tool approves on behalf of the CFO:
PATCH /api/v1/ext/papers/documents/9b1c…/workflow/status
Idempotency-Key: approve-9b1c-cfo
{"action": "APPROVE", "approver_email": "[email protected]", "comments": "Within pricing policy"}
- Nine days later the last signature lands. Billing receives
paper.fully_executed, verifies the signature, and opens the first invoice.
7. The admin contract
| What must be configured | Otherwise |
|---|---|
| An API key with exactly the scopes needed | 403 on out-of-scope calls |
| The key bound to a user, for workflow actions | key_missing_actor |
| Approvers who are real approvers on the step | not_an_approver |
| Published types for triggers and creation | type has no published version |
| A webhook secret stored by your service | Signatures cannot be verified |
AI credits, for papers:ai:run | ai_execution_failed |
8. Downstream
- API-created documents behave exactly like UI-created ones: numbering, approvals, AI auto-runs, obligations.
- Webhook deliveries and API calls appear in the Developers logs and analytics.
- The assistant exposes create, submit, workflow act, share link, generate from event, summarize, risks, list types, list pending approvals, list and replace zones, and read obligations and comments — the same rules again.
9. Don't confuse this with…
| Embedded views | A UI in someone else's page; the API has none |
| Capture inbox sources | Push files in to be classified; the API creates structured documents |
| Inbound webhooks from payment gateways | Unrelated to Papers events |
10. Troubleshooting
| Symptom | Cause |
|---|---|
403 insufficient scope | The key lacks the route's scope (§4) |
| A document exists in Orbit but the API returns 404 | Outside the key's project; the API never reveals existence |
| Two documents from one retry | No Idempotency-Key on the POST |
| Webhooks never arrive | Endpoint disabled, not subscribed to that event, or non-2xx responses exhausting 10 attempts |
| Signature check fails | Signed over a re-serialised body instead of the raw bytes |
| Trigger never fired | No active trigger for that event and organisation, or the type is unpublished |
| Finalize refused through the API | The type requires approval, just as in the UI |