HelloSafe Atlas · Travel Insurance API

Coach API

Send a traveller profile and a bank card reference. Get back a structured bilan: the card's coverage gaps and a ranked set of sell arguments you render in your own product.

POSThttps://atlas.hellosafe.com/api/v1/coach/bilan

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.

Sell-only, structured output. The response is scoped to the insurance pitch: it returns the card's shortcomings (not what it covers well) and destination context only through the insurance lens. Copy comes as message keys plus interpolation variables, not finished sentences. You render the UI on your side (or map the keys to your own wording). Numbers and money figures come pre-formatted.

Before you start

What you need

ItemValue
Base URLhttps://atlas.hellosafe.com
EndpointPOST /api/v1/coach/bilan
Key IDProvided by HelloSafe (the x-atlas-key-id header)
Signing secretProvided 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.

HeaderValue
x-atlas-key-idYour key id.
x-atlas-timestampCurrent time in unix seconds. Rejected if more than 5 minutes off (replay guard).
x-atlas-signaturev2= followed by the hex HMAC-SHA256 of the message below, keyed with your signing secret.
The signed messagehex HMAC-SHA256, keyed with your 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

node ≥ 18 (built-in fetch + crypto)bilan.mjs
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)

bashrun in a terminal
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

FieldReqDescription
residenceyesTraveller residence, ISO 3166-1 alpha-2. Drives the health rules and the default market.
destinationsyesArray of ISO alpha-2 codes. At least 1, up to 50.
cardnoOne card mode (see below). Omit for a no-card analysis.
tripnofriends, longTrip, riskyActivity booleans. All default to false.
marketnofr · us · ca · sg · my · universal. Derived from residence when omitted.
refnoYour 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.

ModeShapeResolves 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):

200 OKapplication/json
{
  "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.

WarningMeaning
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" }.

HTTPCodeCause
400BAD_RESIDENCEresidence is not a 2-letter code.
400NO_DESTINATIONSNo valid destination supplied.
401UNAUTHORIZEDMissing / unknown / revoked key.
401STALE_TIMESTAMPTimestamp outside the 5-minute window.
401INVALID_SIGNATURESignature does not verify.
403SCOPE_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.