Overview
What the API does
The Coach analyses a traveller's situation against the travel-insurance cover carried by their bank card, for the chosen destinations. It returns, as data: the card's coverage gaps (the guarantees in default), a ranked list of arguments (each with a strength and a category), the medical recommendation, a social-proof figure, and an attributed quote link.
Before you start
What you need
| Item | Value |
|---|---|
| Base URL | https://atlas.hellosafe.com |
| Endpoint | POST /api/v1/coach/bilan |
| Key ID | Provided by HelloSafe (the x-atlas-key-id header) |
| Signing secret | Provided by HelloSafe, separately. Keep it server-side only. |
Unlike the pricing API, whose sandbox key you mint yourself from the dashboard, Coach keys are still issued by hand: ask for one from the contact page and your key_id and signing_secret arrive over a secure channel. This is a server to server API: call it from your backend, never from a browser, and never expose the secret client side.
Authentication
Signing a request
Every request carries three headers. The signature is an HMAC-SHA256 over the raw request body, so you sign exactly the bytes you send.
| Header | Value |
|---|---|
x-atlas-key-id | Your key id. |
x-atlas-timestamp | Current time in unix seconds. Rejected if more than 5 minutes off (replay guard). |
x-atlas-signature | v2= followed by the hex HMAC-SHA256 of the message below, keyed with your signing secret. |
v2 `${ts}.${METHOD}.${pathname}.${rawBody}` (recommended)
v1 `${ts}.${rawBody}` (legacy, still accepted) v2 binds the signature to the endpoint it was minted for. v1 stays accepted, because this API shipped with it, but a v1 signature captured on one route replays on any other route of the same API: prefer v2 on new integrations. HMAC-SHA256 exists in every language, and the two snippets below run as they are once you drop in your credentials.
Quickstart
Make your first call
Node.js
import crypto from "node:crypto";
const KEY_ID = "<YOUR_KEY_ID>";
const SIGNING_SECRET = "<YOUR_SIGNING_SECRET>";
const ENDPOINT = "https://atlas.hellosafe.com/api/v1/coach/bilan";
const payload = {
residence: "FR", // traveller residence (ISO alpha-2)
destinations: ["TH", "VN"], // where they travel
card: { network: "visa", tier: "premium" },
trip: { friends: true, longTrip: true, riskyActivity: true },
market: "fr",
};
const body = JSON.stringify(payload);
const ts = Math.floor(Date.now() / 1000).toString();
const signature =
"v2=" + crypto.createHmac("sha256", SIGNING_SECRET)
.update(`${ts}.POST./api/v1/coach/bilan.${body}`).digest("hex");
const res = await fetch(ENDPOINT, {
method: "POST",
headers: {
"content-type": "application/json",
"x-atlas-key-id": KEY_ID,
"x-atlas-timestamp": ts,
"x-atlas-signature": signature,
},
body,
});
console.log(res.status, await res.json()); cURL (bash + openssl)
KEY_ID="<YOUR_KEY_ID>"
SECRET="<YOUR_SIGNING_SECRET>"
BODY='{"residence":"FR","destinations":["TH","VN"],"card":{"network":"visa","tier":"premium"},"market":"fr"}'
TS=$(date +%s)
SIG="v2=$(printf '%s.POST./api/v1/coach/bilan.%s' "$TS" "$BODY" \
| openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1)"
curl -s https://atlas.hellosafe.com/api/v1/coach/bilan \
-H "content-type: application/json" \
-H "x-atlas-key-id: $KEY_ID" \
-H "x-atlas-timestamp: $TS" \
-H "x-atlas-signature: $SIG" \
-d "$BODY" Reference
Request body
| Field | Req | Description |
|---|---|---|
residence | yes | Traveller residence, ISO 3166-1 alpha-2. Drives the health rules and the default market. |
destinations | yes | Array of ISO alpha-2 codes. At least 1, up to 50. |
card | no | One card mode (see below). Omit for a no-card analysis. |
trip | no | friends, longTrip, riskyActivity booleans. All default to false. |
market | no | fr · us · ca · sg · my · universal. Derived from residence when omitted. |
ref | no | Your affiliate ref. Sets the attribution on the returned quote link. |
Card modes
Provide exactly one shape under card. Identifying a specific card needs one of two things: a BIN, or the bank name + tier. Both match the real catalogue card and fall back to a generic profile on a miss.
| Mode | Shape | Resolves to |
|---|---|---|
| BIN | { "bin": "497010" } | First 6-8 digits of the card. Matched to the real catalogue card, else a generic profile. |
| Bank + tier | { "bank": "BNP Paribas", "network": "visa", "tier": "premium" } | The card read off its face, no BIN. Same matcher as BIN, scoped to the cardholder's residence. tier in entry · mid · premium · elite. |
| Catalogue card | { "catalogueId": 1234 } | The exact card contract. Deterministic. |
| Generic | { "network": "visa", "tier": "premium" } | Generic profile, no specific card. tier in entry · mid · premium · elite. |
| No card | { "none": true } | Every guarantee reads "not available". |
Response
A 200 returns { ok, card, bilan, destinations, offer, meta }. Trimmed example (a BIN matched to a real card):
{
"ok": true,
"card": {
"origin": "exact", // exact | generic | baseline | none
"currency": "EUR",
"detected": {
"bank": "BOURSORAMA BANQUE", "brand": "VISA", "level": "SIGNATURE",
"matched": { "cardId": 6, "cardName": "Visa Premier", "bank": "BoursoBank" }
},
"guarantees": { // the card's GAPS only (adequately-covered ones omitted)
"hospitalFeesAbroad": { "state": "value", "covered": true,
"value": 155000, "recommendedValue": 500000, "badge": null }
}
},
"bilan": {
"shouldSell": true,
"argumentCount": 9, // count of the sell arguments actually returned
"social": { "pct": 73, "subjectKey": "coach.social.profile" },
"focusCategories": ["sante", "voyage"],
"arguments": [
{
"id": "med_ceiling",
"category": "sante", // sante | voyage | biens | responsabilite | global
"strength": "decisive", // decisive | strong | useful
"figure": "155 000 €", // pre-formatted
"messageKeys": { "hook": "coach.med_ceiling.hook",
"fact": "coach.med_ceiling.fact",
"pitch": "coach.med_ceiling.pitch" },
"vars": { "ceil": "155 000 €", "target": "500 000 €" }
}
]
},
"offer": { "quoteUrl": "https://hellosafe.com/fr/travel-insurance/app?ref=..." },
"meta": { "engineVersion": "1.0.0", "market": "fr", "warnings": [] }
} Rendering the arguments. Each argument's copy is a set of messageKeys plus vars. Interpolate the vars into your own wording for each key, or ask us for the HelloSafe key dictionary. figure and quote are ready-formatted strings.
offer.quoteUrl carries your attribution when your key is set up with it: sales through that link are credited to you automatically.
Edge cases
Warnings and errors
Non-fatal issues never fail the call: the Bilan still returns, and a code is added to meta.warnings.
Warning Meaning BIN_LOOKUP_FAILEDBIN could not be looked up; baseline card used. CARD_PROFILE_UNMATCHEDNo card matched; baseline card used. CARD_NOT_FOUNDcatalogueId did not resolve; baseline card used. FORMALITIES_UNAVAILABLEDestination facts unavailable; the Bilan runs without them.
Errors
Shape: { "error": "CODE" }.
HTTP Code Cause 400 BAD_RESIDENCEresidence is not a 2-letter code. 400 NO_DESTINATIONSNo valid destination supplied. 401 UNAUTHORIZEDMissing / unknown / revoked key. 401 STALE_TIMESTAMPTimestamp outside the 5-minute window. 401 INVALID_SIGNATURESignature does not verify. 403 SCOPE_FORBIDDENKey lacks the coach scope.
Good to know
Versioning & limits
The path is versioned (/api/v1/). Structured fields are stable; the set of argument ids and message keys can grow as the engine is tuned, so treat unknown ids and keys as forward-compatible additions. The endpoint is rate-limited per key. For a high-volume integration, tell us your expected throughput and we will raise the ceiling.