This is a preview version of the API intended for early testing and integration. Preview versions are not stable and may include breaking changes before general availability.
Intelligence API
Two routes that turn a person's collected products into something a customer can show: rule-based explanations about the person's own insurance and wealth products, and metrics that compare them with people like them.
POST /intelligence/explanations— a deterministic catalogue of explanations: one entry per rule that applies, with the facts behind it and a fixed text in Swedish or English, plus the outcome of every rule the client has enabled.POST /intelligence/metrics— statistical measures from Anessa's metrics service: how concentrated the wealth holdings are, and which covers each car, condo or villa policy holds, each against people like them.
No model-written text is returned by either route, and nothing is stored by a call.
Status
Preview. Both routes run on api.test.anessa.co and api.anessa.co behind the usual API-key headers, with both metrics on in both environments, no per-client switch and no service level. Enum values can grow between releases, so parse statuses leniently.
Access
| Base URL | https://api.test.anessa.co, and https://api.anessa.co in production |
| Authentication | client-id and authorization-token headers of an API client, as on the collection routes |
| Version | Insurely-Version: 2026-04-01 on every call; without it, or with another version, the call is served at the client's default API version, which answers 404 unless that is 2026-04-01 |
| Body | Content-Type: application/json |
| Not authenticated | 401 |
The data a call can see is the client's own: a Hub user the client owns, or collections the client started that are still inside its retention window.
Sources
Both routes take the same sources array and resolve it to one person's portfolio: the newest collection per company wins, older ones are reported as superseded, and hidden accounts are left out for a Hub user. Send one USER, or one or more COLLECTION entries. SUPPLIED is in the enum but refused today.
| Kind | Field | Who can use it | Peer comparison |
|---|---|---|---|
USER | userId, the Hub user's id | any client, for its own Hub users; the wealth line only for clients on the wealth product line | yes, the age comes from the person |
COLLECTION | collectionId, one per entry | any client, for its own collections inside retention | yes, when the collected data carries the person's Swedish personal number |
The response's input block says what the resolver did: complete is true when every source resolved, and sources[] lists each one with a status and, for collections, the collectedAt date. A USER source expands into the collections it resolved to, which appear as their own rows.
| Source status | Meaning |
|---|---|
OK | resolved and used |
NOT_FOUND | no such collection for this client; a request with only such sources answers NO_DATA, not an error |
SUPERSEDED | a newer collection from the same company was used instead |
NOT_WEALTH, NOT_INSURANCE | the collection holds the other product line |
NO_DATA | the collection has nothing to evaluate |
EXPIRED | outside the client's retention window |
A USER together with a COLLECTION, or two USER entries, answer 400. Collections that belong to more than one person, or two collections from the same company collected at the same time, answer 422.
Explanations
POST /intelligence/explanations takes sources and an optional language (sv or en; a region suffix such as sv-SE is accepted; anything else gives en).
The response carries status (READY, or NO_DATA when no source resolved), input, the rulesVersions per product line, computedAt, language, the explanations that apply in rank order, and one evaluated row per enabled id.
Each entry has an id and version, which together are the contract to build on; a rank; a category; the targets it is about (an account or a policy, with its collectionId); evidence shaped per id; and a text with a fixed title and description in the requested language. The ids, what makes each one apply and its evidence fields are listed in the explanation catalogue.
Outcome in evaluated[] | Meaning |
|---|---|
APPLIES | the rule fired; one or more entries carry it |
NOT_APPLICABLE | checked, and it does not apply |
INSUFFICIENT_DATA | the collected data could not answer it |
NOT_CONFIGURED | the id has no producer in this environment |
The difference between NOT_APPLICABLE and INSUFFICIENT_DATA is the point of the row: a customer can tell "checked and clean" from "could not check". On the insurance side INSUFFICIENT_DATA usually means the insurer's data lacks the policy subtype the rule compares, not that the rule is broken. For a USER source of a client outside the wealth product line, the wealth ids are always INSUFFICIENT_DATA.
Metrics
POST /intelligence/metrics takes sources and an optional metrics list of names; omitted means every metric the sources may ask for. An unknown name answers 400 with the supported names. The response carries input and one metrics[] entry per name with source (STATISTICAL: both metrics come from the metrics service), a version, a status and, when the status is OK, the metric's findings. What the rules find about a portfolio is not a metric: it is each explanation's evidence on POST /intelligence/explanations.
| Status | Meaning |
|---|---|
OK | computed; findings present |
NO_DATA | nothing to measure |
UNAVAILABLE | the metrics service failed or timed out; the other metrics still answer |
NOT_CONFIGURED | switched off in this environment, or the client has no market set |
Both metrics are on in test and production. Shares are fractions (0.3107 is 31 %), and every field in findings may be null: null means the metrics service did not report it, and a null list is not an empty one.
wealth.concentration (version 4 today) measures how concentrated the wealth holdings are. It reports what was computed with (version, holdingsCountRequired, benchmarkSnapshotDate, FX availability, the input tallies), the measures at user level (scope, holdings count, hhi from 0 to 1, effectiveHoldingsCount, largestPositionPct, the largest positions, top3Pct) and per product, and, when the person's age is known, a peer block: the cohort (country and age band, for example SE_45_59), its size, the person's percentiles and the peer medians with p10 to p90 ladders. A peer group is published only with at least 200 people in it. Over a USER source it needs a client on the wealth product line: asked for by name it answers 400, left to the default it is left out.
insurance.coverage_held (version 2 today) lists each car, condo and villa policy (policyLevel[], with collectionId and productId) with the covers it holds, and next to each cover the share of comparable Swedish policies holding it. Per policy: the policyGroup its covers are read in (car_full, car_half, car_traffic, car_laid_up, condo, villa_contents, villa_house_only), whether its covers matched the insurer's package catalogue (coverageMatched), and the peer comparison (peerGroup, peerPolicyCount, peerStatus: compared, or why not). Per cover: parameterName, group, held (a null held is unknown, not false), kind (package or add_on) and peerShare. benchmarks is not filled by this route.
Example
Recorded on 4 November 2026 against the test environment with a synthetic user. Ids are shortened.
curl -s -X POST https://api.test.anessa.co/intelligence/explanations \
-H "client-id: $CLIENT_ID" -H "authorization-token: $API_KEY" \
-H "Insurely-Version: 2026-04-01" -H "Content-Type: application/json" \
-d '{"sources":[{"kind":"USER","userId":"'"$HUB_USER_ID"'"}],"language":"en"}'{
"status": "READY",
"input": {
"complete": true,
"sources": [
{"sourceId": "aa596704…", "kind": "USER", "status": "OK"},
{"sourceId": "410202e1…", "kind": "COLLECTION", "status": "OK", "collectedAt": "2026-05-29"},
{"sourceId": "9568cb1b…", "kind": "COLLECTION", "status": "OK", "collectedAt": "2026-10-09"}
]
},
"rulesVersions": {"wealth": 1},
"computedAt": "2026-11-04T13:13:08Z",
"language": "en",
"explanations": [
{
"id": "wealth.loan_above_typical_rate", "version": 1, "source": "RULE", "rank": 1, "category": "LOANS",
"targets": [{"kind": "ACCOUNT", "id": "9cb33099…", "collectionId": "410202e1…"}],
"evidence": {"ratePct": 8.90, "typicalRatePct": 6.0, "potentialAnnualSaving": 4300, "currency": "SEK"},
"text": {
"title": "The loan is priced above the typical rate",
"description": "8.9 % against a typical 6 %, about 4,300 SEK a year in difference."
}
}
],
"evaluated": [
{"id": "insurance.travel_covered_twice", "version": 1, "source": "RULE", "outcome": "INSUFFICIENT_DATA"},
{"id": "insurance.duplicate_cover", "version": 1, "source": "RULE", "outcome": "INSUFFICIENT_DATA"},
{"id": "wealth.high_fee_holding", "version": 1, "source": "RULE", "outcome": "INSUFFICIENT_DATA"},
{"id": "wealth.loan_above_typical_rate", "version": 1, "source": "RULE", "outcome": "APPLIES"}
]
}The same request with "language":"sv" returns the same entries with Swedish texts: Lånet har högre ränta än den typiska. 8,9 % mot typiskt 6 %, cirka 4 300 SEK per år i skillnad. An unknown collection answers 200 with status NO_DATA, the source as NOT_FOUND and every evaluated row INSUFFICIENT_DATA.
Errors and limits
| Status | When |
|---|---|
400 | a body that does not parse, no sources or more than 25, a null source or one without a known kind, a USER without userId or with any other source, a COLLECTION without collectionId, or a SUPPLIED source; on metrics also an unknown metric name, or wealth.concentration asked for by name over a USER source of a client outside the wealth product line |
401 | missing or wrong client-id / authorization-token |
404 | the USER is not one of the client's Hub users; or No endpoint POST … when the call is served at a version other than 2026-04-01 |
422 | collections of more than one person, or two from the same company collected at the same time |
200 with NO_DATA | nothing resolved; not an error |
- Peers are Swedish. Both metrics compare with Swedish peers by age band, and the age comes from a Swedish personal number: the Hub user's, or the one in the collected data.
- Timeouts. Each metric's call to the metrics service is given 3 seconds; after that the metric answers
UNAVAILABLE. APOST /intelligence/metricswithoutmetricscomputes both, one after the other. - No caching, no rate limit. Every call recomputes.
- Versioning. The route is versioned by the
Insurely-Versionheader; each catalogue id and each metric carries its ownversion. New ids and evidence shapes are additive; a changed meaning is a new version with a deprecation window.