Integration · API
The whole integration reduces to one idea: you send claim events, we return an obligation position and any reconciliation findings, each carrying the source pages it came from. The event vocabulary below is not aspirational - it is extracted from the rules the engine actually ships.
Read this before you plan against anything below.
| Component | Status | Where to verify |
|---|---|---|
| Obligation engine (Clockwork) | Built - 142 rules, 6 markets | engine/clockwork.js, engine/rules/* |
| Event vocabulary | Built - 84 events | Extracted from the shipped rules; listed below |
| Integrity detectors (Signal) | Built - 5 detectors | engine/integrity.js, examination demo |
| Coverage assessment | Built - 7 checks | engine/policy.js |
| Compliance reports (Ledger) | Built | engine/report.js |
| Entitlement engine | Built - 6 schemes | engine/benefits.js, entitlement demo |
| Decision sufficiency | Built - 5 decisions | engine/sufficiency.js, decision demo |
| Composite assessment | Built - 8 interactions | engine/composite.js |
| Document understanding (Casefile) | Specified | Demo runs against structured fixtures, not live OCR |
| REST API and webhooks | Specified | This page |
There is no batch file, no nightly extract and no replication of your claims database. Three flows carry everything:
A claim was lodged. A decision was notified. A complaint was received. One small JSON object per event, from whatever system already knows.
Either upload bytes or hand us a pre-signed URL from your existing content store. Documents stay classified, extracted and anchored; the originals remain yours.
Pull them when you want them, or receive a webhook when something changes. Every answer is a pure function of the events and documents you sent, so it can be recomputed and replayed.
Server-to-server only. There is no browser-callable key, because a key that can read claim documents has no business in a browser.
| Method | Use | Notes |
|---|---|---|
| OAuth 2.0 client credentials | Default | Short-lived bearer token, scoped per environment. Rotate the client secret without downtime. |
| mTLS | On request | Where your security team requires certificate-pinned transport in addition to bearer auth. |
| Private connectivity | On request | AWS PrivateLink into ap-southeast-2 where traffic must not traverse the public internet. |
Scopes are separated so an ingestion service cannot read documents back out.
# Exchange client credentials for a token curl -X POST https://api.intactly.ai/oauth/token \ -d grant_type=client_credentials \ -d client_id=$INTACTLY_CLIENT_ID \ -d client_secret=$INTACTLY_CLIENT_SECRET \ -d scope="events:write positions:read"
Base URL https://api.intactly.ai/v1. Australian tenants resolve to
ap-southeast-2 and data does not leave it - see data residency.
| Method | Path | Purpose | Scope |
|---|---|---|---|
POST | /claims/{claimId}/events | Append one or more claim events | events:write |
POST | /claims/{claimId}/documents | Attach a document by upload or URL | documents:write |
GET | /claims/{claimId}/position | Obligation position as at a date | positions:read |
GET | /claims/{claimId}/findings | Reconciliation findings with anchors | findings:read |
GET | /claims/{claimId}/coverage | Coverage assessment against the product | positions:read |
GET | /claims/{claimId}/report | Compliance report, JSON or text | reports:read |
GET | /claims/{claimId}/entitlement | Weekly benefit schedule with the arithmetic | positions:read |
POST | /claims/{claimId}/entitlement/reconcile | Entitlement against payment history | positions:read |
GET | /claims/{claimId}/sufficiency | Whether the file supports a named decision | positions:read |
GET | /claims/{claimId}/composite | Every engine, plus the interactions between them | positions:read |
GET | /portfolio/position | Ranked work queue across claims | positions:read |
GET | /portfolio/report | Portfolio compliance report | reports:read |
GET | /rules | Active rule set and its fingerprint | rules:read |
POST | /rules/draft | Draft a rule from a prompt for human review | rules:admin |
GET | /documents/{documentId} | Document classification and extracted facts | documents:write |
84 events, extracted from the obligation rules that ship today. An event either starts an obligation clock, discharges one, or both - and the marker beside each name says which. Anything not in this list is accepted and stored, but no rule currently reads it.
An event is four fields. at is the date the thing happened, not the date you
got around to telling us - obligations are computed from the former and a backdated event
correctly recomputes the position.
POST /v1/claims/WC-NSW-2026-0912/events Authorization: Bearer <token> Idempotency-Key: "pas-evt-88401273" { "events": [ { "type": "claim.lodged", "at": "2026-08-05", "source": "guidewire.claimcenter", "sourceRef": "CC-4471902" }, { "type": "liability.provisional.accepted", "at": "2026-08-11", "source": "guidewire.claimcenter" } ], "claim": { "productClass": "workers-compensation", "jurisdiction": "NSW" } }
claim is optional after the first call and only carries the attributes that
determine which rules apply - product class, jurisdiction, scheme. It is not a copy of
your claim record and we do not want one.
Australian obligation clocks are a mixture of calendar days and business days, and business days differ by state. A five business day clock starting Friday 30 October 2026 falls due 6 November in NSW and 9 November in Victoria, because Victoria loses the Tuesday to the Melbourne Cup. The engine models eight jurisdictions including that case; you supply the jurisdiction and the date, and it handles the rest.
asOf is required and never defaults to today. A report generated for AFCA
about a decision made in March must be computed as at March, and an API that quietly
assumes "now" makes that mistake easy to make and impossible to notice.
GET /v1/claims/WC-NSW-2026-0912/position?asOf=2026-08-17
{
"claimId": "WC-NSW-2026-0912",
"asOf": "2026-08-17",
"summary": {
"breached": 0,
"atRisk": 1,
"open": 6,
"discharged": 2,
"horizonExposure": 3,
"unverifiedRulesApplied": 9
},
"next": {
"ruleId": "wc-nsw-provisional-liability",
"obligation": "Commence provisional weekly payments",
"status": "at-risk",
"dueAt": "2026-08-18",
"daysRemaining": 1,
"triggeredBy": "injury.notified",
"triggeredAt": "2026-08-04",
"citation": "Workers Compensation Act 1987 (NSW) s 267",
"verified": false
},
"obligations": [ /* every applicable rule, ranked */ ]
}
| Field | What it means |
|---|---|
horizonExposure | Obligations under instruments that have not commenced yet - the 2026 Code redraft, APP 1 automated-decision transparency from 10 December 2026. Counted separately and never reported as a live breach, because being non-compliant with a rule that does not yet bind you is not a breach. It is a plan. |
verified | Every rule ships false. It flips to true only when a named person has checked that rule against the primary instrument and recorded it. We publish the count rather than hide it - see below. |
A finding is a reconciliation, not an allegation. It names what does not line up, shows
both source pages, decomposes into factors, and states the innocent explanations
alongside the conflict. The response below is the actual output of
examine() against two documents - not an illustration of one.
GET /v1/claims/MOT-2026-04417/findings
{
"id": "MOT-2026-04417:temporal:D-4417-01:D-4417-02",
"claimId": "MOT-2026-04417",
"kind": "temporal",
"summary": "Incident date differs by 3 days between two documents",
"detail": "Motor claim form records 2026-05-24; Police event report records 2026-05-21.",
"anchors": [
{ "documentId": "D-4417-01", "documentType": "Motor claim form",
"page": 1, "quote": "24 May 2026", "region": null },
{ "documentId": "D-4417-02", "documentType": "Police event report",
"page": 1, "quote": "21 May 2026", "region": null }
],
"confidence": "probable",
"factors": [
{ "name": "Date discrepancy", "value": "3 days" },
{ "name": "Sources", "value": "Motor claim form vs Police event report" },
{ "name": "Benign explanations",
"value": "Late reporting, gradual-onset injury, or transcription error at intake" }
],
"recommendation": "review",
"decidedBy": null
}
recommendation is always "review". It is not a configurable
field and there is no code path that sets it to anything else.
| Finding kind | What it reconciles |
|---|---|
temporal | Dates that cannot all be true |
capacity | Certified restriction against recorded activity |
quantum | Amounts that do not reconcile |
identity | Party, vehicle or property identifiers that disagree |
causation | Mechanism of injury or damage inconsistent across sources |
coverage | Policy period, exclusion or endorsement conflict |
duplication | The same loss presented more than once |
provenance | Document integrity: metadata, alteration, template mismatch |
The obligation engine answers when. Coverage answers whether. Two later engines answer the questions that sit either side of a decision.
/entitlement returns the weekly benefit schedule for four schemes - NSW,
Victorian and Queensland workers compensation, and NSW CTP - week by week, with the
provision applied, the formula as written, the inputs substituted into it, and the
indexed cap in force on that date. History is calculated at the rates that were in
force then, not now.
GET /v1/claims/WC-NSW-2025-2214/entitlement?asOf=2026-08-17
{
"scheme": "NSW workers compensation",
"basis": { "label": "PIAWE", "value": 1800 },
"periods": [
{ "fromWeek": 1, "toWeek": 13, "provision": "s 36", "amount": 1710,
"formula": "min(95% x PIAWE, cap)" },
{ "fromWeek": 14, "toWeek": 17, "provision": "s 37", "amount": 1440,
"formula": "min(80% x PIAWE, cap)" },
{ "fromWeek": 18, "toWeek": 71, "provision": "s 37", "amount": 1110,
"formula": "min(95% x PIAWE, cap) - current weekly earnings" }
],
"upcoming": [ { "label": "78 weeks", "at": "2026-10-05",
"actionBy": "2026-07-07", "citation": "s 44A" } ],
"limitations": [ /* stated in the payload, not in documentation */ ]
}
Three refusals rather than guesses: an unmodelled scheme, a missing earnings basis, and a date outside the rate table each throw. Indexed amounts are effective-dated and the engine will not apply a stale cap, because a stale cap produces a confident, wrong, auditable-looking number.
/sufficiency assesses whether the file supports a decision - not whether the
decision is right. Five decisions are modelled with 22 requirements between them, each
carrying a citation and a reason. It checks currency against the decision being made,
source appropriateness, procedural sequence, and whether contrary material on the file
has been engaged with.
| Decision | What it turns on |
|---|---|
| Decline liability | Evidenced ground, written reasons naming the term, s 54 considered, contrary evidence engaged, review rights |
| Cease or reduce weekly payments | Current certificate, a recorded work capacity decision, notice elapsed, suitable employment, impairment where a limit is in play |
| Deny a treatment request | Clinical opinion from an appropriate source addressing reasonable necessity |
| Cash settle | Itemised scope, written explanation, catastrophe review right |
| Classify a threshold injury | Qualified assessment, psychological injury considered separately, dispute right notified |
The position is one of three words - supported,
supported-with-gaps, not-supported - never a percentage. A
score gets rounded up by whoever reads it; a list of unmet mandatory requirements does not.
/composite runs every engine with inputs and evaluates eight declared
interactions between them. The facts that matter most sit between engines: cessation
contemplated on a claim being underpaid, an integrity finding relied on beyond the
confidence it carries, an adverse decision on a claim already out of time, a
vulnerability signal with an adverse outcome imminent.
An interaction whose inputs were not supplied is returned as not evaluated, never as clear. There is no composite score, deliberately.
Polling a claim position on a timer is the wrong shape - most claims do not change most days, and the ones that do need attention within hours. Subscribe instead.
| Webhook | Fires when | Typical destination |
|---|---|---|
obligation.at_risk | An obligation enters its escalation window | Handler work queue |
obligation.breached | An obligation passes its due date undischarged | Team leader queue, breach register |
finding.raised | A reconciliation finding is produced | Technical claims / review queue |
document.processed | A document is classified and extracted | Content store metadata update |
report.generated | A compliance report is produced | Governance evidence store |
rule.changed | The active rule set changes fingerprint | Change management, model risk register |
The escalation window scales with the obligation: two days' warning on a seven day payment clock, thirty on a twelve month one. A single fixed window makes long obligations invisible until they are already lost.
X-Intactly-Signature: t=1786982400,v1=5257a869e7... X-Intactly-Delivery: 018f2c41-... # HMAC-SHA256 over "{t}.{raw_body}" with your signing secret. # Reject deliveries where |now - t| > 300 seconds.
Retries run on exponential backoff for 24 hours. Non-2xx and timeouts both retry; a
duplicate delivery is expected behaviour, which is why every payload carries a stable
X-Intactly-Delivery id for you to de-duplicate against.
Claims systems retry. Middleware replays. An integration that double-counts an event under those conditions produces a wrong obligation position, which is worse than no position at all.
POST accepts an Idempotency-Key header. Repeating a
key returns the original response and changes nothing.(claimId, type, at, sourceRef), so an
accidental duplicate without a key is still absorbed.Every report carries the fingerprint of the rule set that produced it. Given the same events and the same fingerprint, regenerating the report produces the same output - which is what "show us how you reached that decision" actually requires two years later at AFCA.
GET /v1/claims/WC-NSW-2026-0912/report?asOf=2026-03-11&ruleSet=fnv1a-3c8e21b4
# Same events + same rule set fingerprint = byte-identical report.
Errors are typed and name the field. Two of them are unusual enough to explain, because they are invariants failing loudly rather than bugs.
| Status | Code | Meaning |
|---|---|---|
400 | invalid_event_type | Event accepted into storage but no rule reads it. Advisory, not fatal. |
400 | as_of_required | A position or report was requested without an explicit date. There is no default. |
409 | idempotency_conflict | The key was reused with a different body. |
422 | unanchored_fact | An extracted fact arrived without a document, page and quote. Discarded rather than stored. |
422 | vulnerability_leak | A vulnerability signal reached the integrity examination. The request fails; it is not filtered and quietly continued. See below. |
429 | rate_limited | Retry after the interval in Retry-After. |
503 | drafting_unavailable | Rule drafting is not configured on this tenant. |
| Surface | Default limit | Notes |
|---|---|---|
| Event ingestion | 500 req/min, 100 events per request | Raised on request; backfill runs on a separate quota |
| Document submission | 60 req/min, 100 MB per document | Pre-signed URL submission does not count against transfer |
| Position and findings reads | 1,000 req/min | Computed, not cached - a read is always current as at the date you asked |
| Report generation | 60 req/min | Portfolio reports are asynchronous above 5,000 claims |