# Sheaf docs — every page in one file > https://sheaf.us/docs as markdown. The pages one at a time are listed in https://sheaf.us/llms.txt. # Sheaf docs Source: https://sheaf.us/docs How to use Sheaf: in the app, over the API, and from an agent. Sheaf takes a packet of scanned documents exactly as it arrived and turns it into a filed, checked case. These pages say how to use it. They describe what exists today, not what is planned. ## What Sheaf does, in order 1. **Receives** the packet. Every file lands as an immutable source file, and pre-flight checks flag pages that will read badly. 2. **Splits** it into documents whose page spans tile the packet end to end. 3. **Identifies** each document by what it says about itself. 4. **Files** each document against the case's expected document list, so what is missing is named. 5. **Extracts** field values, each traceable to the page it was read from. 6. **Records** every step and decision in the case's ledger. ## The five words | Word | Meaning | | --- | --- | | Packet | The files as they arrived: merged, out of order, photographed. | | Case | One application: its files, its documents, its decisions. The API calls it an `application`. | | Expected document list | What a complete case must contain. Versioned, and copied into the case when the case starts. | | Workflow | A saved recipe of steps and models. Attached at the start of a run, it lets the run go unattended. | | Ledger | The append-only record of who did what on a case, and under whose authority. | ## Where to go - New here: [create an account](/docs/getting-started/create-an-account), then [run your first packet](/docs/getting-started/first-packet). - Connecting your own software: start at the [API overview](/docs/api/overview). The [quickstart](/docs/api/quickstart) is three commands. - Building or instructing an agent: [using Sheaf from an agent](/docs/agents/using-sheaf-from-an-agent). --- # Create an account Source: https://sheaf.us/docs/getting-started/create-an-account One click makes an organization with you as its first Manager, and a starter kit so the first packet can run straight away. Add your email to keep it. ## Sign up Go to [create an account](/signup). There are two ways in, and both create an **organization** and sign you in as its first **Manager**. - **1-click Demo Account.** Nothing to fill in. The organization and you get a generated three-word name, which you can change under **Settings**, **People**. - **Set it up yourself.** Give an organization name, your email and a password of ten characters or more. ## Look around first: sample cases A new account opens in a **sample case**: a packet that has already been analyzed, in the room where documents are reviewed. Every control is there. Approve, reject, move a document to another requirement, ask the assistant: everything responds as it would on your own case, and nothing you do is saved to the sample or seen by anyone else. Two things a sample does not do — add documents, and run the analyzer. Try either and a note offers **Start a new application**: one is created for you under a name you can change, and opens ready for your documents. **Reset** puts the sample back as it was. The sample cases stay on the **Applications** page, above your own cases. ## Add your email An account started instantly has no email and no password yet. The app asks for the email after a few minutes, and **Add your email** is always in the menu under your initials. With an email, a link arrives to set a password, and the workspace is yours to keep. Until then, two things hold. The workspace is kept for 30 days. And you can upload documents, but analyzing them waits for the email. An organization is a closed space. Its people, cases, lists, schemas, workflows, API keys and webhooks are visible only inside it. To anyone outside, they do not exist. ## What a new organization starts with | Thing | What you get | | --- | --- | | Workflow | `standard-intake`, active and set as the default. | | Expected document lists | `purchase-loan-ca` and `colorado-home-loan`, each with an active version. | | Extraction schemas | None of your own yet. A document reads against its kind's schema, then the official fallback, until you author one on the Extract page. | | Document catalogues | The official catalogues of document types — generic and six industries. Generic is in use until a Manager picks another on the People page. | | Building blocks | Saved sections and document bundles to build lists from. | Everything in the kit is yours to copy, change or ignore. ## Add your colleagues Open **Settings**. Under **People**, a Manager adds a colleague with a name, an email, a role and a temporary password. They sign in with that and change it from the menu under their initials. A Manager can also rename people, change roles, set a new temporary password, and deactivate or reactivate an account. Two guards protect you: nobody can deactivate or demote themself, and the last active Manager cannot be removed. Roles are described in [roles and access](/docs/concepts/roles-and-access). ## Forgot your password Use **Forgot password?** on the sign-in page. The link in the email sets a new password and ends every other session for that account. --- # Run your first packet Source: https://sheaf.us/docs/getting-started/first-packet Start a case, drop the packet in, take the gate, and read what was filed. ## Start a case Open **Applications** and choose **New application**. Give the applicant's name and pick the expected document list the case must satisfy. The case copies that list at this moment and keeps its copy, so later edits to the list never change a case underneath you. ## Drop the packet in Open the case and drop one or more PDFs on it. Each file is stored as it arrived and never altered. Renaming or archiving a file changes its record, not its bytes. As files land, **pre-flight** checks them: page count, scan resolution, blank and duplicate pages, and any file the case already has. Anything that will read badly is flagged before any money is spent on reading. ## Take the gate Nothing is analyzed until someone takes the gate. That is the one decision that spends money, and it is recorded with your name. - With no red flags you confirm the file set. With red flags you either fix the files or explicitly process anyway. The two are different decisions and the ledger keeps them apart. - **Choose a workflow** and the run goes unattended: it previews, parses, identifies, files what it is confident about, and extracts. See [workflows](/docs/concepts/workflows). - **Without a workflow** you go step by step: a cheap preview proposes the documents in the packet, you pick which to read in full, and you file the results yourself. ## Read the result | Lens | What it shows | | --- | --- | | Review | The latest run: each document it found, its pages, and the suggested filing. | | Triage | What still needs a person, most urgent first. | | Documents | Every document the case holds, filed or unmatched, across all runs. | | Register | The expected document list with what is filed against each line, and what is missing. | A filed document is a PDF cut from the packet, stored with a text version that records where every page came from. ## The same thing without the app Everything on this page is one API call plus a poll. See the [API quickstart](/docs/api/quickstart). --- # Expected document lists Source: https://sheaf.us/docs/concepts/expected-document-lists The definition of a complete case: what documents and fields it must contain, who sees them and who may add them. An expected document list says what a complete case contains. It is the thing every document is filed against, and the reason Sheaf can name what is missing instead of only reporting what arrived. Lists are built under **Classify** in the app. ## Families and versions A list has an id (`expectedDocListId`, the slug of its name) and numbered **versions**. | Status | Meaning | | --- | --- | | Draft | Editable. Cannot be used to start a case. | | Active | Frozen. Can start cases. Editing it is refused; make a new version instead. | To change an active list, create a new version from it, edit the draft, and activate it. ## What a list contains - **Sections**, each with an owner and two rules per role: who sees it, and who may add to it. - **Expected documents** inside sections. Each is either a document someone uploads, with accepted file formats, or a group of fields someone fills in. - **Set documents**: an expected document that occurs several times, such as one bank statement per account. The list declares what identifies an instance; the case supplies the actual instances. Saved sections and document bundles are reusable pieces. Inserting one copies it into the list, so later changes to the piece do not reach lists already built. ## How a case uses a list A case **pins a full copy** of the list version it started on. When a newer version is activated, the case can be upgraded to it. An upgrade only adds; it never removes a line a document is already filed against. ## Over the API `GET /expected-doc-lists` lists your lists and versions. A call names a list as `expectedDocListId`, for its active version, or `family/3` for a specific one. The list itself never travels with a request. See [run a workflow](/docs/api/run-a-workflow). --- # Workflows Source: https://sheaf.us/docs/concepts/workflows A saved recipe of steps and models. Attached at the gate, it lets a run go from packet to filed documents with nobody present. A workflow is a named, versioned recipe: which steps a run performs and which model each step uses. Workflows are built under **Workflows** in the app. One of them is the organization's default. ## Steps | Step | What it does | | --- | --- | | Preview | A cheap first pass that proposes where the documents in the packet begin and end. Optional. | | Parse | Reads the pages in full. Required. | | Classify | Composes the read pages into documents, names them, and matches them to the expected document list. | | Judge | An independent second opinion on each document, with a confidence. | | Extract | Pulls field values using the [document schema](/docs/concepts/document-schemas) for each document type. | Steps run in order. There is no branching, on purpose. ## Versions Like lists, a workflow version is a **draft** until activated and **frozen** once active. Only an active version can run. Publishing a new version changes what the workflow does without changing its id, so whoever calls it keeps calling the same thing. ## What attaching a workflow means Taking the gate with a workflow attached is a single consent for the whole chain. When the run lands, the workflow files and approves everything it recommended, and the ledger records those decisions as made by **Workflow**, under the identity of whoever took the gate. What it will not do: override red flags at the gate, reject a document, or delete anything. Those stay with a person. Anything the workflow was unsure of is left unfiled in Triage. ## Over the API `GET /workflows` lists your workflows and their versions. Each one is addressable by id the moment it has an active version: see [run a workflow](/docs/api/run-a-workflow). --- # Document schemas Source: https://sheaf.us/docs/concepts/document-schemas Which fields to pull from each type of document. Saved once per document type, versioned, and used by the extract step. A document schema says which field values to read from one type of document: a pay stub, a bank statement, a W-2. Schemas are built under **Extract** in the app. ## What a schema contains Each field has a name, a type, and optionally a description, synonyms and a required mark. | Type | For | | --- | --- | | text, number, date | Single values. | | select | One of a fixed set of options. | | yesno | A yes or no as printed or ticked. | | object | A group of nested fields. | | list | Repeating rows or values, such as the lines of a statement. | **Synonyms** are the other labels the same field goes by on real paper: "statement date", "date of issue". **Required** fields that a page does not state are recorded as *not stated*. That is kept distinct from unread and from zero. ## Versions, and the default Schemas are versioned like everything else: draft until activated, frozen once active. A document type with no schema of its own is read with the organization's **default** schema, so nothing goes unread for lack of setup. ## Why this lives in Sheaf The schema is saved in your organization and named by the document type it belongs to. It is never sent with a request. What your team learns about reading a document accumulates here, not in integration code. --- # Roles and access Source: https://sheaf.us/docs/concepts/roles-and-access Three roles, three rights, and per-section rules on top. Enforced on every request, for people and API keys alike. ## Roles | Role | In short | | --- | --- | | Originator | Starts cases, uploads, runs and files. | | Underwriter | The same rights; decisions are recorded under this role. | | Manager | The same, plus the organization itself: people, API keys and webhooks. | Every role holds the three platform rights: **view**, **upload** and **edit**. The server checks the right on each request. A capability the screen does not offer you is one the server also refuses. The current table is shown under **Settings** in the app. ## Section rules On top of the platform rights, each section of an [expected document list](/docs/concepts/expected-document-lists) says per role who sees it and who may add to it. An upload into a section your role may not add to is refused with a 403 that names the section. ## Organizations Everything belongs to exactly one organization. A request for something in another organization answers **404**, never 403: from outside, it does not exist. ## API keys A key acts as an **Originator** of its organization. It cannot reach people, keys, webhooks or passwords. See [authentication](/docs/api/authentication). --- # The ledger Source: https://sheaf.us/docs/concepts/the-ledger An append-only record of every step and decision on a case, written in the same transaction as the change it records. Every case has a ledger. A row is written in the same database transaction as the change it records, so there is no change without its row and no row without its change. Rows are never edited or removed. ## What a row says | Field | Meaning | | --- | --- | | `action` | What happened, as `noun.verb`: `file.received`, `gate.confirmed`, `run.ingested`, `document.approved`, `workflow.auto_filed`. | | `actor.name` | Who decided. A person's role, the name of an API key, or `Workflow`. | | `actor.authenticatedAs` | Whose sign-in or key authorized the write. | | `target`, `detail` | What was touched, and the specifics. | `name` and `authenticatedAs` differ exactly when a machine decided. A workflow can file a document but cannot sign in, so the row says `Workflow` decided, under the identity of whoever took the gate. ## Where to read it - In the app: the ledger lens of the case. - Over the API: `GET /applications/{id}/events`, newest first. See [reading results](/docs/api/reading-results). - Pushed to you: [webhooks](/docs/api/webhooks) deliver ledger rows as they are written. ## Retention A case and its ledger are removed together when the case passes the retention window. The ledger is part of what is purged, by design. --- # API overview Source: https://sheaf.us/docs/api/overview One key, one call to run a workflow on a packet, one call to check on the case. What the API is for, and what it is not. The API exposes what the product does: cases, workflows, filing and the ledger. You hand over a packet and name a workflow. Sheaf files every document against the case's expected document list and tells you when the case has landed. ## What stays with Sheaf The expected document list, the document schemas and the workflow live in your organization, versioned, and a call names them by id. Nothing about what to look for travels with the request. Only the file does. What your team teaches the platform stays taught, and your integration stays a few lines. ## The shape of an integration 1. [Authenticate](/docs/api/authentication) with an organization key. 2. [Run a workflow](/docs/api/run-a-workflow) on a packet. One call. 3. [Read the results](/docs/api/reading-results) by polling the case, or let [webhooks](/docs/api/webhooks) tell you. The [quickstart](/docs/api/quickstart) is those three steps as commands. ## Base URL and format `https://sheaf.us/api`. JSON in and out. Files are sent as `multipart/form-data`. ## The contract The exact contract is OpenAPI, served at [/api/openapi.json](/api/openapi.json) and readable without a key. Generate a typed client in your language from it. There are no hand-written SDKs for an API this small. Use of the API is covered by the [terms](/terms.html), which have a section on keys, agents and webhooks. ## What it is not There is no parse or extract call on a loose file with no case around it. If that is all you need, a parsing API is the right purchase, and the [comparison pages](/compare.html) say so. --- # Authentication Source: https://sheaf.us/docs/api/authentication An organization API key, sent as a bearer token. What a key may do, and how it appears in the ledger. ## Make a key A Manager makes a key in the app under **Settings**, in **API keys**, and gives it a name that says what it is for. The secret is shown **once**. Sheaf keeps only a hash of it. Revoke a key in the same place at any time; a revoked key is refused from the next request. ## Try it as a guest No account, no password: one call opens a sandbox of your own with the sample case in it and answers with a key. ``` curl -X POST https://sheaf.us/api/guest ``` ```json { "key": "sheaf_…", "applicationId": "AohWhGa4uo", "expiresAt": "2026-09-27T14:02:11Z", "requestsPerMinute": 10, "pagesIncluded": 50, "docs": "https://sheaf.us/docs/api" } ``` `applicationId` is your copy of the sample case: read it by that id (`GET /applications/{id}`, `/documents`, `/text`). `GET /applications` lists the cases you make yourself. The key acts like any organization key — read the sample, search it, upload a document of your own and run a workflow on it — within three limits: | Limit | Value | | --- | --- | | Requests | 10 a minute | | Lifetime | 24 hours, then the key is refused and the sandbox is deleted with everything in it | | Uploads | 50 pages, across the sandbox | The door has counters of its own: 10 sandboxes an hour from one address, 100 a day in all. A `429` means try again later, or make an account. Research and briefs in a sandbox are also capped by the hour and by the day, as for any workspace without an email. The key is shown once. ## Send it ``` Authorization: Bearer sheaf_… ``` on every call. A secret is `sheaf_` followed by 40 characters. ## What a key may do A key acts for **one organization** with an **Originator's** rights. It can start cases, upload, run workflows and read everything in its organization. | Request | Answer | | --- | --- | | Anything in another organization | 404 | | People, keys, webhooks, passwords, the access table | 403 | | A wrong, malformed or revoked secret | 401, even if a valid session cookie is also present | | More than 500 requests in a minute | 429 with `Retry-After` | ## How it shows up The ledger records what a key does under the key's name, as `LOS integration (API)`. When a workflow files documents on a run the key started, the row says `Workflow` decided, authenticated as the key. The key's row in **Settings** shows when it was last used. ## Keep it out of chat and source Treat the secret like a password: an environment variable or a secret store. If it leaks, revoke it and make another. Nothing else has to change. --- # Quickstart Source: https://sheaf.us/docs/api/quickstart Three commands: run a workflow on a packet, poll the case, read what was filed. You need an [API key](/docs/api/authentication) in `SHEAF_KEY` and a PDF. ## 1. Run a workflow on a packet ```bash curl -X POST "https://sheaf.us/api/workflows/standard-intake/runs" \ -H "Authorization: Bearer $SHEAF_KEY" \ -F "files=@packet.pdf" \ -F "applicant=Jane Doe" \ -F "expectedDocList=purchase-loan-ca" ``` The answer is `201` with the case: ```json { "id": "euw8ydHxTW", "applicantName": "Jane Doe", "run": { "state": "running", "kind": "parse" } } ``` `standard-intake` and `purchase-loan-ca` exist in every new organization. Your own are listed by `GET /workflows` and `GET /expected-doc-lists`. ## 2. Poll until the run lands ```bash curl "https://sheaf.us/api/applications/euw8ydHxTW" \ -H "Authorization: Bearer $SHEAF_KEY" ``` Ask every five to ten seconds. When `run.state` is `report`, the run has landed and the workflow has filed what it could. A run takes minutes. ## 3. Read what was filed ```bash curl "https://sheaf.us/api/applications/euw8ydHxTW/documents" \ -H "Authorization: Bearer $SHEAF_KEY" ``` Each entry is a document with its pages, the line of the list it was filed under, and the decision. The shape is in [reading results](/docs/api/reading-results). ## If the first call answers 409 Pre-flight flagged a file and Sheaf will not override that for you. The case exists and waits for a person. See [run a workflow](/docs/api/run-a-workflow#red-flags). --- # Run a workflow Source: https://sheaf.us/docs/api/run-a-workflow POST /workflows/{workflowId}/runs creates the case, receives the packet and starts the run, in one call. ``` POST /workflows/{workflowId}/runs ``` One call does what a person does in the app: start a case, drop the packet in, take the gate with a workflow attached. The call itself is the consent to spend on the run, and is recorded as such. ## Fields Sent as `multipart/form-data`. | Field | Required | Meaning | | --- | --- | --- | | `files` | yes, for a new case | One or more PDFs. Up to 25 files, 100 MB each. Repeat the field for several files. | | `applicant` | yes, for a new case | The applicant's name. | | `expectedDocList` | yes, for a new case | `expectedDocListId` for its active version, or `expectedDocListId/3` for a specific one. | | `applicationId` | instead of the two above | Run on an existing case. Files are optional then. | | `decidedBy` | no | Who consents to the run. Defaults to the key's name. | ## Versions The workflow's **active** version runs. `?version=2` pins one. A missing version answers 404 and a draft answers 409, both before anything is written. Publishing a new version changes what callers get without changing what they call. ## The answer `201` with the case. `run.state` is `previewing` or `running`, depending on whether the workflow begins with a preview. From here, [read the results](/docs/api/reading-results). ## An existing case Send `applicationId` and the run covers whatever the case holds that no run has read. Add `files` to drop more in first. If there is nothing new to read, the answer is 409 and says so. ## Red flags If pre-flight flags a file, such as a scan too coarse to read or a duplicated page, Sheaf does **not** override that on your behalf. The answer is: ```json { "error": "The file set has 1 red flag(s) — use \"bypassed\" (Process anyway) to override them", "applicationId": "Xjs0t8SWt_", "flags": [ { "fileId": "sf-3kQ9", "page": 1, "code": "low_dpi", "note": "p.1 embedded image ~96 DPI — below 150" } ] } ``` with status `409`. The case **exists** with its files and waits at the gate for a person. Do not send the packet again. This is the one error that carries more than a message. ## Other refusals | Status | When | | --- | --- | | 400 | No files, a missing `applicant` or `expectedDocList`, a file that is not a PDF. | | 404 | Unknown workflow, version, list or case. | | 409 | A run is already in flight on the case, or there is nothing new to read. | | 502 | The analyzer could not be reached. The case is put back at the gate with its files, and `applicationId` is in the answer. | --- # Reading results Source: https://sheaf.us/docs/api/reading-results Poll the case for run.state, then read its documents, their files and the ledger. ## Polling ``` GET /applications/{id} ``` Read `run.state`: | State | Meaning | | --- | --- | | `previewing`, `inventory`, `running` | In flight. A workflow moves through these by itself. | | `report` | Landed. The workflow has filed what it could. | | `gated` with `run.lastRunError` | The run did not finish and the case was put back. The error says why: it failed, someone stopped it, or the analyzer went silent. | Every five to ten seconds is plenty. That is the whole integration; [webhooks](/docs/api/webhooks) are optional. ## Documents ``` GET /applications/{id}/documents ``` One entry, from a real run: ```json { "address": "sf-Fjh0L6mx:p1-2", "title": "Pay Stubs — Last 30 Days", "spans": [ { "fileId": "sf-Fjh0L6mx", "pages": [1, 2] } ], "expectedDocId": "pay-stubs", "settled": true, "document": { "suggestedName": "2026-08-31_pay-stub_example-corp.pdf", "extractKeyData": { "document_date": "2026-08-31", "issuing_party": "Example Corp" } }, "approval": { "outcome": "approved", "decidedBy": "Workflow", "authenticatedAs": "LOS integration (API)", "approvedDocId": "ad-9OiuG41jd5" } } ``` - A document's identity is its **page span**, the `address`. There is no minted document id, and the address stays stable across runs for pages that did not move. - No `expectedDocId` means it matched nothing on the list. On real packets that is common, and not an error. A person can still keep such a document: it is then settled, and its `approval.expectedDocId` is `"unmatched"`. - A range that reads as several documents is listed whole until its split is confirmed — a person approves it, or a workflow follows it. From then on the list shows the pieces in its place, each with its own address. Nothing is cut or re-parsed. - Each page is listed once. A page someone approved into a document belongs to that document only, and a page someone junked is not listed. - `settled: false` means it still waits for a person. ## The filed PDF ``` GET /applications/{id}/approved-docs/{approvedDocId}/file ``` returns the PDF cut from the packet. `?kind=md` returns its text with page provenance. The id is `approval.approvedDocId` above. ## The case list `GET /applications` lists your cases, each with `docsFiled` and `docsTotal`: how many lines of the list are satisfied. ## The ledger ``` GET /applications/{id}/events ``` Newest first. Each row is `{ seq, at, actor, action, target, detail }`. See [the ledger](/docs/concepts/the-ledger). --- # Endpoints Source: https://sheaf.us/docs/api/endpoints The calls an integration uses, in one table. The full contract is the OpenAPI document. All paths are under `https://sheaf.us/api`. | Call | What it does | | --- | --- | | `POST /guest` | A guest key: your own sandbox with the sample case, no account, no body. 10 requests a minute, 24 hours, 50 pages of uploads. | | `POST /workflows/{workflowId}/runs` | [Run a workflow](/docs/api/run-a-workflow) on a packet: creates the case, receives the PDFs, starts the run. | | `GET /workflows` | Your workflows, their versions, which one is the default. | | `GET /expected-doc-lists` | Your expected document lists and their versions. | | `POST /applications` | Create a case without running anything: `expectedDocListId`, `version`, `applicantName`. | | `POST /applications/{id}/files` | Add PDFs to a case, multipart field `files`. `?expectedDocId=` declares what a file is for. | | `GET /applications` | Your cases, with how many lines of each list are satisfied. | | `GET /applications/{id}` | One case: `run.state`, `run.lastRunError`, its files, its pinned list. | | `POST /applications/{id}/run/cancel` | Stop the paid run in flight. The case goes back to where the run started. A preview cannot be stopped. | | `GET /applications/{id}/documents` | Every document the case holds, filed or unmatched. | | `GET /applications/{id}/approved-docs/{approvedDocId}/file` | A filed document's PDF. `?kind=md` for its text. | | `GET /applications/{id}/text` | The whole case as markdown in one response: every document's saved copy in page order, each with front matter naming the document and a marker before each page's text. | | `GET /applications/{id}/pages-pdf` | Any pages of the case as one PDF, in the order given: `?span=:p-`, repeatable. | | `GET /applications/{id}/events` | The ledger for the case, newest first. | | `GET /openapi.json` | The whole contract. Readable without a key. | ## Everything else The app itself runs on this same API, so the [contract](/api/openapi.json) lists more than the table above: building lists, schemas and workflows, review decisions, and so on. A key may call anything an Originator may. The table is what an integration normally needs. Use only operations the contract lists. Endpoint shapes shown in marketing copy elsewhere on this site are illustrations. --- # Webhooks Source: https://sheaf.us/docs/api/webhooks Optional. Signed deliveries of ledger rows to your server, with retries. Off until a Manager adds an endpoint. Polling is the whole integration. Webhooks are for when you would rather be told. ## Set one up A Manager adds an endpoint in the app under **Settings**, in **Webhooks**: a URL, and the events to receive, or none selected for all of them. The signing secret, `whsec_…`, is shown **once**. **Test** sends one signed delivery on the spot and shows what your server answered. Nothing from before the endpoint existed is ever replayed. ## A delivery ```json { "id": "whd-8Hq2mZ1xPq0a", "type": "workflow.auto_filed", "at": "2026-09-17T21:22:48.377Z", "application": "euw8ydHxTW", "data": { "actor": { "kind": "system", "name": "Workflow", "authenticatedAs": "LOS integration (API)" }, "target": { "type": "run", "id": "run-20260917-212242-ef09" }, "detail": { "workflow": "standard-intake v1", "approved": 1, "leftUnassigned": 0, "failed": 0 } } } ``` It is one [ledger](/docs/concepts/the-ledger) row. `type` is the ledger's own action name. ## Events worth subscribing to | Event | Meaning | | --- | --- | | `application.created` | A case was started. | | `file.received` | A file landed on a case. | | `run.ingested` | A run's results landed. | | `workflow.auto_filed` | A workflow finished filing what it recommended. | | `document.approved`, `document.rejected` | A filing decision, by a person or a workflow. | | `run.failed` | A run failed, or the analyzer went silent for 3 minutes, and the case was put back. | | `run.cancelled` | A person stopped the run and the case was put back. | The full list is the `WebhookEvent` enum in the [contract](/api/openapi.json). ## Verify the signature Deliveries follow the Standard Webhooks specification. Three headers arrive with each one: | Header | Value | | --- | --- | | `webhook-id` | The delivery id. The same on every retry, so use it to drop duplicates. | | `webhook-timestamp` | Seconds since the epoch. | | `webhook-signature` | `v1,` followed by the base64 HMAC-SHA256 of `id.timestamp.body`, keyed with your secret. | The key is the base64 part of the secret, after `whsec_`. Any Standard Webhooks library does this for you. Reject a delivery whose signature does not match or whose timestamp is more than a few minutes old. ## Retries Answer `2xx` within 15 seconds. Otherwise the delivery is retried 14 times over about 13 hours, from 30 seconds apart up to 2 hours apart, and then marked failed. Answering `410` turns the endpoint off; add it again to resume. The endpoint's row in **Settings** shows its latest delivery. --- # Errors and limits Source: https://sheaf.us/docs/api/errors-and-limits One error shape, the HTTP status as the code, and the limits a key works within. ## The shape ```json { "error": "what went wrong, in a sentence" } ``` Every error has this shape, and the HTTP status is the code. One error carries more: the red-flag refusal from [run a workflow](/docs/api/run-a-workflow#red-flags) adds `applicationId` and `flags`. ## Statuses | Status | Meaning | | --- | --- | | 400 | The request is malformed: a missing field, a wrong multipart field name, a file that is not a PDF. | | 401 | No key, or a wrong or revoked one. | | 403 | Not something a key, or your role, may do. Also: a run was asked of an organization that has no email yet — a Manager adds one in the app first. | | 404 | Not there, or not in your organization. | | 409 | A conflict with the state of the case: a run in flight, a draft workflow, red flags at the gate, nothing new to read. | | 413 | A file is over the size limit. | | 429 | Too many requests. `Retry-After` says how many seconds to wait. | | 502 | The analyzer could not be reached. The case was put back as it was. | ## Limits | Limit | Value | | --- | --- | | Requests per key | 500 a minute | | Requests per guest key | 10 a minute | | Guest sandboxes | 10 an hour from one address, 100 a day in all | | Research questions in a workspace without an email | 20 an hour, 50 a day | | Briefs in a workspace without an email | 5 an hour, 10 a day | | Pages a workspace without an email holds | 50 | | Files per call | 25 | | File size | 100 MB | | File type when running a workflow | PDF | ## Doing the same thing twice A case is created by every successful call to [run a workflow](/docs/api/run-a-workflow) without an `applicationId`. If a call failed after the case was made, the answer carries `applicationId`: continue with that case instead of sending the packet again. --- # Using Sheaf from an agent Source: https://sheaf.us/docs/agents/using-sheaf-from-an-agent How an agent finds these docs as markdown, what it needs from a person, and the rules it should follow. ## Read the docs as markdown | What | Where | | --- | --- | | The agent skill file | [/skill.md](/skill.md). Short, and says how to act. Start here. | | The index of every page | [/llms.txt](/llms.txt) | | All of these docs in one file | [/llms-full.txt](/llms-full.txt) | | One page | Add `.md` to its address: `/docs/api/quickstart.md`. The overview is `/docs/index.md`. | | The same, by header | Request any docs address with `Accept: text/markdown`. | | The API contract | [/api/openapi.json](/api/openapi.json), readable without a key. | The markdown is the source these pages are built from, so it is never behind them. Pages are one topic each and short on purpose: fetch the one you need rather than all of them. ## What you need from a person An **API key** for their organization, which a Manager makes under Settings. See [authentication](/docs/api/authentication). There is no anonymous demo login, and credentials shown elsewhere on the site are placeholders. Never ask for a person's password, and do not sign up on someone's behalf unless asked to. A key is the credential meant for you. ## The work itself [Run a workflow](/docs/api/run-a-workflow) on the packet, [poll the case](/docs/api/reading-results) until `run.state` is `report`, then read its documents. Documents you send are real work in that organization: stored, filed and recorded in its ledger under the key's name. ## Rules worth following - **A 409 with `flags` is not yours to retry.** Pre-flight flagged a file and the case waits for a person. Tell your user which file and why, and give them the `applicationId`. - **Do not send the same packet twice.** If an answer carries `applicationId`, the case exists. Continue with it. - **Poll gently.** Every five to ten seconds. A run takes minutes. A key may make 120 requests a minute. - **Cite pages, not guesses.** Every document carries its file and page span. Quote those when you report a value. - **Use only what the contract lists.** Endpoint shapes in marketing copy are illustrations.