API reference, v3
Everything the interface does is an API call, including every refusal. This reference documents the shapes a compliance officer is most likely to be asked about.
Stable since 4.0.0. Current build 4.12.0. Breaking changes ship behind a new major path, never behind a flag.
Base URL and versioning
All requests go to a tenant host. The version is in the path and never in a header, because a header is easy to forget and a path is visible in every log line you will later be asked about.
https://{tenant}.underwrite.example/v3A major version is supported for eighteen months after its successor ships. Additive changes appear inside a major version and are listed on the changelog.
Authentication
A tenant scoped bearer key, sent on every request. Keys carry a seat identity so the audit trail can name an actor for an API write exactly as it does for an interface write.
curl https://northgate.underwrite.example/v3/cases/CR-2026-04187 \
-H "Authorization: Bearer uw_live_..." \
-H "Underwrite-Seat: seat/di.okafor"A key without a seat header is rejected rather than attributed to the tenant, becauseactor: tenant is not an answer to who did this.
Idempotency
Every write accepts Idempotency-Key. Replaying a key returns the original response, including the original refusal. A retry after a network timeout therefore cannot start a second statutory clock on the same dispute.
Errors and refusals
A refusal is not an error in your integration. It is the compliance engine doing its job, and it carries the rule, the control and the object it refused.
| Code | Rule | Meaning |
|---|---|---|
| 409 fee_before_performance | CROA 1679b(b) | A billing call referenced a service line that has not reached a performed state. |
| 409 disclosure_missing | CROA 1679c | No delivery receipt exists for the statutory disclosure on this consumer. |
| 409 cancellation_window_open | CROA 1679e | An outbound action was requested during the three business day window. It has been queued, not rejected. |
| 422 contract_incomplete | CROA 1679d | A required contract term is absent. The response body names each missing term. |
| 409 despatch_evidence_missing | FCRA 611(a)(1) | A dispute was sent without proof of despatch attached. |
| 403 clock_not_writable | FCRA 611(a)(1) | A due date was written directly. Record the extending event instead. |
Pagination
Cursor based, stable under insertion. Audit listings are ordered by entry id rather than by timestamp, so two entries written in the same millisecond never swap places between pages.
GET /v3/audit?case=CR-2026-04187&limit=50&after=AUD-2026-0918442Endpoints
- POST
/v3/consumersCreate a tokenised consumer record.
Identity fields are stored encrypted and returned as a token, never in plain text.
- POST
/v3/disclosuresServe the statutory disclosure and record its delivery receipt.
Returns the document hash the consumer received. Required before a contract will create.
- POST
/v3/contractsGenerate a written contract with the cancellation form in duplicate.
422 if any required term under CROA 1679d is absent, naming the term.
- GET
/v3/contracts/{id}/cancellation-windowThe three business day window, its calendar and its expiry.
- POST
/v3/casesOpen a dispute case against one or more reported items.
- GET
/v3/cases/{id}The full case record: parties, clock, state, documents, audit.
- POST
/v3/cases/{id}/disputesDespatch a dispute and start the statutory clock.
409 unless proof of despatch is attached first.
- PATCH
/v3/cases/{id}/clockRecord an event that extends the window.
The due date is not writable. Only the extending event is, and it must name its rule.
- POST
/v3/cases/{id}/mov-requestsRequest a description of the procedure used to determine accuracy.
Opens a separate fifteen day clock under FCRA 611(a)(7).
- POST
/v3/cases/{id}/statementsFile a consumer statement of dispute.
Enforces the one hundred word limit at the API boundary.
- POST
/v3/invoicesIssue an invoice against performed service lines.
409 with the unperformed line named if any line is not yet fully performed.
- GET
/v3/auditAppend only audit entries, filterable by case, seat, rule and window.
- GET
/v3/exports/{id}A signed, hash chained export of a case or a date range.
- POST
/v3/webhooksSubscribe to clock, state and control events.
Signed with a per tenant secret. Replay window is five minutes.
Webhooks
Signed with a per tenant secret using HMAC SHA256 over the raw body and the timestamp. Anything outside a five minute window is rejected. Verify before you parse.
Underwrite-Timestamp: 1787059320
Underwrite-Signature: v1="9f1c0a4e..."
signature = HMAC_SHA256(secret, timestamp + "." + raw_body)Events: clock.started, clock.extended,clock.expired, control.blocked,document.delivered, case.state_changed,export.generated.
Rate limits
600 requests per minute per tenant, 60 per minute for export generation. Exceeding a limit returns 429 with Retry-After. Limits are per tenant rather than per key, so rotating a key does not buy headroom.
Sandbox
Every tenant gets a sandbox tenant with the same controls and no production data. Clocks in the sandbox can be advanced with POST /v3/sandbox/advance, which is the only place in the platform where time is writable, and it is unavailable in production by construction rather than by permission.