Discover the type, then write the record
Read the record type first, send each answer in the form its question takes, check before you move a record, and upload a document in three calls. Activating, issuing and delivering stay with a person.
Work records
The rules, in order
01
Where work records are on
Work records are switched on per workspace. Until they are, every write answers 409 NOT_AVAILABLE with feature: workRecords; reads still answer. A workspace may call its records Jobs, Calls, Policies or Matters. The API always says jobs: in the routes, the tools, the events and the fields.
02
Discover before you write
Every record has a type, and the type decides its questions, its stages, its documents and its kinds of change. Read it first, with jobs:read:
| Call | What it answers |
|---|---|
GET /v1/work-config?businessId= | The types a business uses, and the revision of each it pins |
GET /v1/job-types | The types of the workspace |
GET /v1/job-types/{typeId} | One type and its definition |
GET /v1/job-types/{typeId}/revisions/{revision} | The definition a record was created under |
A record pins its type’s revision when it is created, as typeRevision, and keeps it when the type is edited later. Read that revision to know which questions the record asks. typeRevision is set by the server: sending it is 400 FIELD_FORBIDDEN. A type that does not exist is 404 UNKNOWN_TYPE, and an archived one is 409 TYPE_ARCHIVED.
03
Answers are canonical strings
A record carries its answers in fields, keyed by the question’s key. Each value is a string in one form, or null to clear it. Several choices go in fieldLists and addresses in fieldAddresses. A key the type does not ask is refused.
| Question type | Send |
|---|---|
| Short or long text | The text |
| Integer | "42" |
| Money | Integer minor units in the record’s currency: "125000" is 1,250.00 |
| Decimal | Up to 6 decimal places: "12.5" |
| Date | "2026-11-03" |
| Date and time | ISO 8601 with an offset; it is stored in UTC |
| Yes or no | "true" or "false" |
| One choice | The option’s id, not its label |
| Several choices | An array of option ids in fieldLists |
| Email, phone | Normalised; a phone in E.164 |
| Client, vendor, team member | The id |
04
Check before you move a record
GET /v1/jobs/{jobId}/validate-intake?at=activate (or draft, complete, bill) answers what the type still needs at that point, one issue per question: MISSING_ANSWER, INVALID_OPTION, FIELD_FORBIDDEN, TYPE_MISMATCH or MEMBER_UNKNOWN. It changes nothing.
A move that is missing answers is 409 MISSING_ANSWER, with each one in fields. A choice the question does not offer is 400 VALIDATION_FAILED, with that field’s code NOT_ALLOWED.
05
Create, activate, move, close
POST /v1/jobs (jobs:write) creates a draft. Give it a parentJobId to make a sub-job. A draft has no number and counts nothing.
Activation numbers the record, so it is an action request: POST /v1/actions/activateJob (actions:records, tool activate_job). A person approves it, or it runs at once only under a rule the owner set for that kind.
POST /v1/jobs/{jobId}/stage moves an active record. A draft is 409 JOB_NOT_ACTIVE; a completed or cancelled one is 409 JOB_CLOSED. close, reopen and discard are routes of their own. PATCH /v1/jobs/{jobId} needs the expectedUpdatedAt you read; if the record moved on, the answer is 409 TARGET_CHANGED.
POST /v1/jobs/{jobId}/contract-total sets the agreed total. An amount in another currency is 400 CURRENCY_MISMATCH. Once a deposit or progress invoice was raised against it, the total is locked: 409 CONTRACT_TOTAL_LOCKED.
06
Uploading a document
Three calls, with jobs:write:
POST /v1/jobs/{jobId}/documentswithfileName,contentType,size, an optionalcategoryIdand your owncreateRequestId. The answer is the document,pending, and anupload: a signed URL, the headers to send with it, and when it expires.PUTthe bytes to that URL with exactly those headers, within 15 minutes.POST /v1/jobs/{jobId}/documents/{docId}/complete. The server checks that the file is there, its size, and that its first bytes match its type.
A document ends attached or failed (missing, tooLarge, sizeMismatch, typeMismatch or expired); an upload never completed becomes expired. PNG, JPEG, WebP or PDF, up to 25 MB each; a record holds up to 50 documents and 250 MB, and a pending upload counts. Beginning again with the same createRequestId signs a fresh URL for the same document.
A document added through the API is never shown to the client by it. A person decides that in the console.
07
Invoices, bills and changes
POST /v1/jobs/{jobId}/invoices (invoices:write and jobs:read) creates a draft invoice from the record. Nothing is sent: delivery stays an action request. POST /v1/bills/{billId}/allocations spreads a bill across records; allocations that add up to more than the bill are 400 ALLOCATION_EXCEEDS_TOTAL.
Changes such as a change order or an endorsement are drafted with POST /v1/jobs/{jobId}/adjustments, and edited or discarded while they are drafts. Issuing one and voiding one are action requests that always need a person: issueJobAdjustment and voidJobAdjustment, the second with a second factor.
08
Retries
Send an Idempotency-Key on every POST. The same key with the same body within 24 hours replays the first answer, with Idempotent-Replayed: true. The same key with another body is 409 IDEMPOTENCY_CONFLICT; while the first request still runs it is 409 IDEMPOTENCY_IN_FLIGHT with Retry-After. A document upload also carries its own createRequestId.
09
Scopes, and what needs a person
| Scope | What it allows |
|---|---|
jobs:read | Records, their types, documents, changes, activity and messages |
jobs:write | Drafts, answers, stages, notes, documents and draft changes; it includes jobs:read |
workConfig:write | Draft record types, and ask for a proposed setup, which uses AI credits |
The list, GET /v1/jobs, reads with invoices:read. Applying a proposed setup (applyWorkConfig, actions:admin), attaching a file the approver chooses (attachJobDocument), deleting a record (deleteJob) and sending a record’s link to its client (sendJobLink) are action requests that always need a person. A message to the client, POST /v1/jobs/{jobId}/messages, also needs actions:deliver and returns a pending action, not a sent message.
10
Events
Subscribe to job.created, job.updated, job.stageChanged, job.moved, job.adjustmentIssued, job.adjustmentVoided and workConfig.changed. A job in an event is a narrow view: its number, type, label, status, stage, client, currency, totals, and the answers the type marks for the client. Sensitive and internal answers, lists, files, addresses, emails, phones, notes and assignees never leave. Stage labels arrive inside the untrusted-data envelope.
11
Three examples
Towing. Create a Call with its vehicle and pickup answers, ask to activate it, move it through its stages, upload the signed authorization before you complete it, then create the invoice from the Call.
Insurance. Create a Policy with its policy type and premium answers, validate at activate, and ask to activate it. A mid-term endorsement is a change drafted on the Policy and issued by a person.
Legal. Read the Matter type’s revision, create the Matter with its answers, upload the signed engagement letter, and listen for job.stageChanged to follow it.
REST API
One base URL, one envelope, integer money, idempotent writes, and limits that answer with a number instead of a timeout.
Try it in your sandbox
A bmk_test_ key reaches only your sandbox workspace.