Source: https://billingmart.com/developers/work-records # 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. [API reference](https://billingmart.com/developers/api) [OpenAPI document](https://api.billingmart.com/v1/openapi.json) 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`: Discovery | 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. Answer forms | 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`: 1. `POST /v1/jobs/{jobId}/documents` with `fileName`, `contentType`, `size`, an optional `categoryId` and your own `createRequestId`. The answer is the document, `pending`, and an `upload`: a signed URL, the headers to send with it, and when it expires. 2. `PUT` the bytes to that URL with exactly those headers, within 15 minutes. 3. `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 Scopes | 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. Next guide ## REST API One base URL, one envelope, integer money, idempotent writes, and limits that answer with a number instead of a timeout. [Continue to REST API →](https://billingmart.com/developers/rest) ## Try it in your sandbox A bmk_test_ key reaches only your sandbox workspace. [Start free](https://app.billingmart.com/sign-up) [All guides](https://billingmart.com/developers) --- This is the Markdown copy of https://billingmart.com/developers/work-records. Live plans, prices and limits are published as JSON at https://billingmart.com/plans.json and https://billingmart.com/agent-info.json.