Integrate

There is no SDK to install and nothing to license. The engine is an HTTP endpoint, the rating matrix is published at another one, and the audit ledger is a single table. Everything below runs against the live validation build right now.

Quickstart Keys and the audit trail Three patterns Rendering the matrix Failure cases Limits Run it yourself What you bring

Quickstart

Price a configuration in one request

No key, no account, no session. Post a vertical and the components the customer switched on. You get a real price. Recording that quote in the audit ledger is a separate decision, and it is the one that needs a key — see Keys and the audit trail.

curl -X POST https://api.riskrouter.eu/api/v1/quote \
  -H 'Content-Type: application/json' \
  -d '{
        "active_vertical": "urban_rental",
        "selected_components": { "deposit_liquidity_swap": true, "smart_home_iot": true }
      }'

You get back priced line items, a total, the normalised component state the engine used, and a separate report of whether an audit write was attempted and whether it landed.

From a Node backend

const res = await fetch('https://api.riskrouter.eu/api/v1/quote', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    active_vertical: 'mobility',
    selected_components: { commuter_roadside: true }
  })
});

const quote = await res.json();
if (!res.ok || quote.ok !== true) throw new Error(quote.error?.code ?? `HTTP ${res.status}`);

// Show this. Never show a number your own code calculated.
const total = quote.quote.totals.total_monthly_premium;   // "3.00"

// A 200 does not mean a record exists. Check the write separately.
// Without an API key this is false by design, not by failure.
const recorded = quote.persistence.persisted === true;

From Python

import requests

r = requests.post(
    "https://api.riskrouter.eu/api/v1/quote",
    json={"active_vertical": "micro_travel",
          "selected_components": {"winter_sports_gear": True}},
    timeout=10,
)
r.raise_for_status()
quote = r.json()

total    = quote["quote"]["totals"]["total_monthly_premium"]   # "6.00"
recorded = quote["persistence"]["persisted"] is True

Keys and the audit trail

Pricing is open. Recording is attributed.

The ledger is tamper-evident: every entry seals the one before it. That is what makes it worth anything to an auditor, and it is also why the write path cannot be open. An entry written by nobody in particular is sealed in permanently, and removing it later would break every digest after it. So the engine treats three cases differently, and the third one is the point.

You sendYou getLedger
No keyA real price, immediatelyNothing written. attribution.mode is ANONYMOUS
A valid keyA real priceRecorded against your distributor, inside the hash chain
An invalid, revoked or suspended keyHTTP 401, no priceNothing written

A bad key is refused outright and never quietly downgraded to an anonymous quote. The alternative looks friendlier and is far worse: a distributor whose key was mistyped or revoked would keep getting 200s for months, believing they were building an audit trail, and would find out during the audit that nothing had been written. Loud now beats silent then.

curl -X POST https://api.riskrouter.eu/api/v1/quote \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $RISKROUTER_API_KEY" \
  -d '{ "active_vertical": "mobility", "selected_components": { "commuter_roadside": true } }'
// Two different questions. Ask both.
const priced   = quote.ok === true;                        // is this number real
const recorded = quote.attribution.recorded === true;      // is it in the ledger

We store the SHA-256 of your key, never the key. That means we cannot show it to you again, cannot email it to you, and cannot recover it if you lose it — we can only revoke it and issue another. It also means a breach of our database does not hand anyone the ability to write to your audit trail.

Keys are per environment. A sandbox key and a production key are separate rows and separate digests, so a test quote can never land in a production ledger by accident.

Getting your evidence back out

The same key exports it. This is the endpoint to wire into a monthly job on day one, not the day you need it.

curl -s -H "Authorization: Bearer $RISKROUTER_API_KEY" \
  https://api.riskrouter.eu/api/v1/ledger/export > "ledger-$(date +%F).json"

You get your entries in full, the digest skeleton of the whole chain they sit inside, and the signed attestation for the head. Two standard-library Node scripts check all three, offline, with no key of ours and no call to us:

node tools/verify-attestation.mjs ledger-2026-09-20.json   # we signed this head
node tools/verify-ledger.mjs      ledger-2026-09-20.json   # your entries produce it

Keep them. An export you downloaded last year still verifies after we have stopped answering the phone, which is the only honest answer to what happens to my audit trail if you go under. It is also why there is no SDK and nothing to license: a product that is hard to leave is a product that has to be, and we would rather not need that.

Three patterns

Pick the one that matches your risk appetite

1. Call the API from your backend

Your server calls ours, your interface renders the result. You keep full control of the customer relationship and we never see your traffic pattern or your customer. This is what we would choose. Cross-origin calls from a browser are restricted to our own console, so this pattern is server-to-server by design.

2. Link out to the hosted console

Point a customer at the demo console to configure cover and hand the quote reference back to your flow. The fastest thing to stand up, and the least control: the page is ours, so its appearance and copy change when ours do.

3. Run the whole thing yourself

Three files and one table. Deploy the engine to your own Cloudflare account, point it at your own database, and we are no longer in the path at all. Covered in Run it yourself below.

Rendering

Ask the engine what it charges

Do not hardcode the price list. GET /api/v1/verticals publishes every vertical, its mandatory base layer and its optional modules, with the prices the engine will actually apply. Build your interface from that response and a price change never desynchronises your checkout from the quote.

const { verticals } = await (await fetch(BASE + '/api/v1/verticals')).json();

for (const v of verticals) {
  renderTab(v.key, v.label, v.description);
  renderLocked(v.base);              // mandatory, always on
  v.addons.forEach(renderToggle);    // optional modules
}

If you must cache it, cache it with an expiry and re-check on load. Our own console keeps an embedded copy for the first paint and hydrates from this endpoint immediately, and a test fails the build if the two ever disagree.

Failure cases

Handle these five before you ship

Each one has burned a real integration somewhere. They are cheap to handle now and expensive to discover in an audit.

What happensWhat to do
A 200 comes back but no audit record was written
You sent no key, or the ledger was briefly unreachable.
Read persistence.persisted. Show the price, but never tell a customer their quote is on file unless it is literally true. persistence.attempted tells the two causes apart.
The customer toggles quickly
Replies arrive out of order and a stale price lands last.
Tag each request with an incrementing number and ignore any reply older than the newest one you have rendered. Abort the previous request.
You send a component we do not price
Your build is ahead of ours, or behind it.
Check ignored_components on every response. It is never empty by accident, so treat anything in it as a deployment mismatch and alert on it.
A 401 comes back where a 200 used to
Your key was revoked, or your distributor was suspended.
Treat it as an outage, not as a validation error, and page someone. Do not strip the key and retry: that turns a loud failure into a silent one, and every quote after it disappears from your audit trail.
The engine does not answer
Network fault or timeout.
Set a client timeout of a few seconds. Do not fall back to a locally computed price: showing a number we did not produce is how a customer gets quoted something you cannot honour.

Limits

What the engine refuses

LimitValueOn breach
Request body16 KB413 PAYLOAD_TOO_LARGE
Ledger write timeout5 sQuote still returns, marked not persisted
Ledger writes, all callers120 / minQuote still returns, marked not persisted
Browser originsconsole onlyBlocked by the browser, not by us

The write ceiling is deliberately shared rather than per-caller. The ledger refuses deletion, so a flood would be permanent damage, and a cap that one caller cannot talk its way around is worth more than a generous one that can be.

No lock-in

Run it yourself

The platform is three files: a SQL schema, a Cloudflare Worker and a single-page console. There is no proprietary runtime and nothing phones home. Standing up your own copy takes about ten minutes.

# 1. Create the audit ledger in your own Postgres or Supabase project
psql -f schema.sql

# 2. Point the engine at it and deploy to your own Cloudflare account
wrangler secret put SUPABASE_SERVICE_ROLE_KEY
wrangler deploy

# 3. Publish the console
npm run deploy:console

The rating matrix lives in one frozen object at the top of the Worker. Change a price there, run the tests, deploy. The test suite recomputes every premium from the matrix and fails if the console or the documentation disagrees with it.

We would rather you could leave than have you locked in. An integration you can walk away from is one you can commit to.

Before a pilot

What you bring

RiskRouter is infrastructure. It does not make anyone an insurance distributor, and it cannot stand in for the things a regulator expects you to hold.

We supply the routing, the pricing determinism and the audit trail. The regulatory position explains where the boundary sits and why it is drawn there.