Trumis / Developers
Integrate Trumis
Two integration paths, both live today: a one-line embeddable widget, and a retained-record JSON API. This page is the full contract. The buyer's view — pricing model, security posture, record-keeping — lives at trumis.com.au/organisations.
Embed · API · Request · Response · Stages · Errors · Rate limits · Partner tiers · Receiving complaints · Exports · Webhooks · Versioning · OpenAPI · Changelog
Option 1 — the one-line embed
Paste this where your complaint form lives. Works today, no registration — that's the evaluation (pilot) tier:
<script src="https://trumis.com.au/embed.js"
data-partner="Acme Energy"
data-email="complaints@acme-energy.example"
data-height="640"></script>
data-partner— your organisation's name; the complaint is addressed to you and framed under your complaint-handling policy.data-email— optional; pre-addresses the send button to your complaints inbox. The widget always shows people where their complaint will go — quiet misdirection isn't possible.data-height— iframe height in pixels, default 640.data-lang— optional opening language hint (zh,vi,ar,el).- Microphone: the widget's Talk button records a short audio clip
(transcribed by our provider, then discarded), so the iframe ships with
allow="microphone". If your site sends a restrictivePermissions-Policyheader, includemicrophone=(self "https://trumis.com.au")or the Talk button quietly disappears — everything else works regardless.
Pilot embeds carry a small “Powered by Trumis” mark. Licensed embeds swap the page-supplied attributes for a registered partner id:
<script src="https://trumis.com.au/embed.js"
data-partner-id="acme-energy"
data-height="640"></script>
With data-partner-id, the organisation name, complaints inbox
and branding (including watermark removal) resolve server-side from your
licence configuration via /api/embed-config, and the widget
verifies it is running on one of your registered origins. Copying the id onto
another site fails that check and falls back to the watermarked pilot
behaviour — branding cannot be spoofed or stripped from page HTML. Licensing:
info@trumis.com.au.
The embed is a sandboxed iframe served from trumis.com.au. It adds no scripts to your page context, and conversations stay in the visitor's browser — no conversation content is stored on Trumis servers (security, privacy).
Option 2 — the retained-record API
One endpoint drives the whole conversation. Send the visible transcript each turn plus the facts returned last turn; render the reply. When the person confirms, the response carries the finished complaint. The service holds no state between calls, so there is nothing to provision and nothing to delete.
curl -X POST https://trumis.com.au/api/v1/companion \
-H 'Content-Type: application/json' \
-d '{
"history": [{"role":"user","content":"They cut my power off with no warning."}],
"known_facts": {},
"client_done": false,
"partner": { "name": "Acme Energy" }
}'
JavaScript, each turn feeding the previous response back:
const r = await fetch('https://trumis.com.au/api/v1/companion', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }, // add 'X-Trumis-Key' when licensed
body: JSON.stringify({
history: transcript, // [{role:'user'|'assistant', content}]
known_facts: lastTurn.facts, // echo the previous turn's facts verbatim
client_done: false,
partner: { name: 'Acme Energy' },
}),
});
const turn = await r.json();
transcript.push({ role: 'assistant', content: turn.reply });
Python:
import requests
turn = requests.post(
"https://trumis.com.au/api/v1/companion",
json={"history": transcript, "known_facts": last_facts, "client_done": False,
"partner": {"name": "Acme Energy"}},
timeout=30,
).json()
CORS is open and requests carry no cookies, so the API can be called directly from your own complaint page's front end. Responses are synchronous JSON (no streaming); a turn typically returns in one to three seconds — the deterministic rules run first and the AI layer only phrases conversation.
Request fields
| Field | Type | Notes |
|---|---|---|
history | array, required | {role: "user"|"assistant", content: string} — the whole
visible transcript, oldest first. Capped at 60 turns × 8,000 characters;
longer input is truncated, not rejected. |
known_facts | object | The facts object from the previous response, echoed back
verbatim. Setting a fact to false retracts it — that is how
unticking a confirm claim works. |
client_done | boolean | true when the person indicates they have nothing more to
add (your “I'm done” control). |
partner | object | {name: string} switches to organisation intake: the
complaint is lodged with you under your policy, no third-party routing.
With a licensed X-Trumis-Key header the name is taken from
your registry entry, not the body. See partner
tiers. |
evidence | object | A correspondence transcript from /api/v1/evidence,
after the person has reviewed it:
{source, transcript: [{speaker, name?, time?, text}]}. |
lang | string | Opening-language hint (zh, vi,
ar, el). The person's own words always win. |
Response fields
| Field | Type | Notes |
|---|---|---|
reply | string | The agent's next message, in the person's language. Render it and
append it to history. |
stage | enum | Where the conversation is — see stages. |
method | enum | "rules" or "rules+llm" — whether the AI
layer was active for this turn. Merit decisions are deterministic either
way. |
facts | object | Everything established so far. Echo it back as
known_facts next turn — the encrypted intake archive holds
nothing. |
grounds | object | {sufficient: boolean, passed_arguments: [{id, summary}]}
— the rules engine's merit decision, with the arguments that passed.
Auditable: these ids map to the maintained legal knowledge base. |
urgency | array | [{level: "critical"|"high"|"medium", message, action}] —
route critical flags (e.g. safety) to emergency services
messaging before anything else. |
confirm | object | null | Present at stage confirming: the exact claims the
complaint would make, as tickable lines with the person's quotes —
{intro, claims: [{key, label, quote?, theirs?}], about, subject?,
question, ui: {tick_prompt, unsure_note, confirm_yes, you_said,
their_words}}. Untick = echo that fact back false;
confirm = send the ui.confirm_yes message. The card strings
arrive in the person's language so thin clients stay thin. |
complaint | object | null | The finished complaint once the person confirms:
{document, text, routing, receiver}. text is
the complete complaint ready to send; document is the same
content structured (title, sections, outcomes, evidence list);
routing is the two-step send path with response-day clocks
— the organisation, then that sector’s free external body;
receiver is the recognised organisation or
null. |
complaint.document.sections | array | Includes a context section (“COMPLAINT-HANDLING
CONTEXT”) when the merit gate opened: the complaint-handling
framework for the recognised organisation’s sector, written for
whoever routes the complaint. It states what the framework requires; it
makes no allegation. Absent in partner mode, which has its own policy
framing. |
referral | object | null | {text, url} — where to take it further, present with a
written account whose circumstances also clear the formal bar. Trumis
does not draft complaints for lodgement with an ombudsman; this points
to FairClaim, which does. Show it on your draft screen, not in the chat
flow. |
disclaimer | string | The legal-information disclaimer. Display it with the conversation. |
partner_tier | enum | Partner mode only: "licensed" or "pilot". |
Stages
| Stage | Meaning | Client behaviour |
|---|---|---|
listening | Free talk; the person is telling their story. | Render the reply; keep the composer open. |
probing | One gentle question is being asked. | Same as listening. |
confirming | Partner intake only: the claims card is live. | Render confirm.claims as pre-ticked options. |
account_ready | The written account is done (with
referral when the formal bar is also cleared). |
Show the account; show the referral on the draft screen. |
grounds_ready | Partner intake only: the document lodged with the organisation is built. | Show complaint.text and the routing path. |
safety | The person disclosed thoughts of suicide or self-harm. The reply carries the crisis lines and is the whole turn. | Render the reply and the critical urgency banner; show no
draft, no referral, no next step. |
no_grounds_close | The facts don't support a formal complaint; the close is warm and explains why. | Render the reply; no document exists to show. |
Errors
Every error is JSON with a machine code and a human message, and every
response — success or error — carries an X-Trumis-Request-Id
header. Quote that id when writing to support.
| Status | error | When |
|---|---|---|
| 400 | invalid_json | The body is not valid JSON. |
| 400 | no_screenshots | /evidence without OCR lines. |
| 401 | invalid_partner_key | X-Trumis-Key doesn't match a configured partner. |
| 404 | unknown_partner | /embed-config pid not registered. |
| 405 | method_not_allowed | Wrong HTTP method; Allow header lists the right ones. |
| 422 | nothing_recognised | /evidence OCR lines yielded no correspondence. |
| 429 | rate_limited | Budget spent — honour Retry-After. |
| 502 | companion_failed | Upstream hiccup; safe to retry. Body includes request_id. |
Rate limits
Fair-use limits protect availability. Current budgets, per rolling minute:
| Caller | Budget | Keyed by |
|---|---|---|
| Unauthenticated (consumers, pilots) | 30 requests/min | Client address |
| Licensed partner key | 120 requests/min | The key's partner id |
Every response carries X-RateLimit-Limit,
X-RateLimit-Remaining and X-RateLimit-Reset (seconds).
A conversation turn is one request, so 30/min is generous for a person typing;
server-side integrations that multiplex many users through one address should
use a licensed key. Higher budgets are a licence property — ask
info@trumis.com.au.
Partner tiers and keys
| Pilot (evaluation) | Licensed | |
|---|---|---|
| How | partner.name in the body / embed attributes |
X-Trumis-Key header / data-partner-id embed |
| Organisation name on documents | As supplied, marked pilot | From the registry — server-authoritative |
| “Powered by Trumis” mark | Always shown | Removable |
| Embed origin verification | — | Registry allow-list |
| Rate budget | 30/min per address | 120/min per key (raisable) |
| Cost | Free | Licence |
Keys are secrets for server-side use — never ship one in page JavaScript.
Embeds don't need keys: the public data-partner-id plus the
origin check does the equivalent job in the browser.
Receiving complaints into your systems
Trumis retains an encrypted complete intake record for up to 24 months, while the person still controls whether the complaint is sent. You have two ways to receive:
- Embed: the finished complaint arrives at your complaints inbox as an email from the person's own account — plain text, structured sections, with the correspondence annex. Parse-friendly and, importantly, the timestamps and sender are the complainant's own record too.
- API integration: your page or backend drives the
conversation, so the
complaintobject — document, structured sections, routing, receiver — is already in your hands the moment the person confirms. Post it into your case system directly. - Webhook: if the person used your embed, your backend
never saw the turn. Configure an endpoint and Trumis pushes
complaint.finalisedto it the moment the document exists — see Webhooks. - Pull: fetch any complaint by reference, or export the whole register, at any time — see Exports.
Record-keeping guidance for regulated intake (RG 271 and ombudsman schemes) is on the organisations page.
Exports — getting your data out
Everything that enters Trumis can leave it, machine-readably, the moment
it is finalised. All four surfaces below take a licensed
X-Trumis-Key and return only your own organisation's records —
tenancy comes from the key, never from the request.
| Surface | Returns |
|---|---|
GET /api/v1/complaints/{reference}GET /api/v1/complaints?reference=… |
The complete structured record as JSON: the claims the person
ticked, the facts established, the grounds that passed, urgency
flags, the evidence manifest with content hashes, routing, the
full document, and a record_integrity sha256. |
…&format=pdf |
The formal complaint document as a PDF, reference-stamped, with the evidence manifest annexed. |
GET /api/v1/register?format=csv |
Every matter as a spreadsheet row — reference, date received, channel, language, risk tier, status, response due, days remaining, outcome, escalation flag, legal hold. |
GET /api/v1/export |
A ZIP of all of the above plus your deletion log: the complete offboarding archive. |
curl -H 'X-Trumis-Key: YOUR_KEY' \
https://trumis.com.au/api/v1/complaints/TRM-AB12-CD34 -o complaint.json
curl -H 'X-Trumis-Key: YOUR_KEY' \
'https://trumis.com.au/api/v1/complaints?reference=TRM-AB12-CD34&format=pdf' -o complaint.pdf
curl -H 'X-Trumis-Key: YOUR_KEY' \
'https://trumis.com.au/api/v1/register?format=csv' -o register.csv
curl -H 'X-Trumis-Key: YOUR_KEY' \
https://trumis.com.au/api/v1/export -o trumis-archive.zip
Two honest notes. Evidence screenshots are read on the
person's own device and never reach Trumis, so no image file exists to
export; the evidence manifest carries a sha256 of the extracted text the
person reviewed and approved, which is what proves an export matches what
was submitted. And the PDF renders Latin script only — where a complaint is
written in 中文, العربية, Ελληνικά or Vietnamese with diacritics, the PDF
says so on its first page and sets
X-Trumis-Pdf-Complete: false. The JSON is UTF-8 and always
carries the person's words verbatim.
Webhooks
One event, signed. Send us an HTTPS endpoint and we configure it with a signing secret.
| Event | Fires when |
|---|---|
complaint.finalised |
The complaint document is complete and confirmed by the
complainant. Payload carries event_id,
event_type, schema_version,
occurred_at_utc, partner_id,
complaint_reference, risk_tier,
language, the full complaint JSON (the
same shape as the export) and a signed, expiring
pdf_url. |
register.event |
A lifecycle event the complainant's device confirmed —
sent, response_received,
deadline_passed, escalated,
resolved, withdrawn. Same vocabulary as
/api/v1/register-event. |
Verifying the signature
Every delivery carries X-Trumis-Signature:
sha256=<hex> and X-Trumis-Timestamp. The signed
value is {timestamp}.{raw request body} — sign the bytes you
received, not a re-serialisation of the parsed JSON, or your digest will
never match ours.
import crypto from 'node:crypto';
// express: app.post('/trumis', express.raw({ type: 'application/json' }), …)
function verify(rawBody, headers, secret) {
const timestamp = Number(headers['x-trumis-timestamp']);
const presented = String(headers['x-trumis-signature'] || '').replace(/^sha256=/, '');
// Reject replays before doing any work.
if (!Number.isFinite(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const expected = crypto.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`) // rawBody is a string or Buffer, unparsed
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(presented);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Delivery, retries and duplicates
- We treat any 2xx as delivered. Anything else is retried at roughly 1m, 5m, 30m, 2h, 6h, then abandoned.
- Deduplicate on
event_id. A 2xx we never see — a timeout after your handler committed — is retried, so your handler should be idempotent. - Return quickly. We time out at 8 seconds; queue the work on your side rather than processing inline.
- Every attempt and failure is logged and visible to you at
GET /api/v1/webhooks, including the last status code, the last error, and when the next retry is due.
Test it before you trust it
Fire a real, signed webhook.test event at your endpoint and
get the result back in the same response — status code, latency, and the
exact signature we sent. It carries no complaint data.
curl -X POST -H 'X-Trumis-Key: YOUR_KEY' -H 'Content-Type: application/json' \
-d '{"action":"test"}' https://trumis.com.au/api/v1/webhooks
Not built, on purpose: no Zapier app, no Salesforce/Zendesk/ServiceNow connectors, no per-event subscription management, no webhook for intermediate conversation stages. If you need one, ask — the first partner who needs it is how it gets built.
Versioning and deprecation
/api/v1/…is the canonical path. The unversioned/api/…paths are permanent aliases of v1.- Additive changes (new response fields, new optional request
fields, new enum values on
urgency) ship without a version bump — build clients that ignore unknown fields. - Breaking changes (removing or renaming fields, changing
semantics) ship as
/api/v2/…. v1 then runs for at least 12 months, and licensed partners are notified by email before anything is scheduled for shutdown. - Every observable change is dated in the changelog. The machine-readable contract is /openapi.json.
Evidence endpoint
POST /api/v1/evidence — screenshots in, correspondence out.
Clients run OCR on the device (tesseract.js on the web, iOS Vision,
Android ML Kit) and send only text lines with geometry — images never leave
the person's device. The endpoint reconstructs the conversation
deterministically (who said what, when — deduplicated and stitched across
scrolling captures) and returns it with proposed facts for the person to
review. Limits: 25 screenshots, 500 lines each. Full schema:
/openapi.json.
Health
GET /api/v1/health → {ok, service, time, region} —
liveness for your monitors, no AI call involved. Live state:
trumis.com.au/status.