Authentication
This page explains how the iframe authenticates and how to load it securely in production. Three credentials are involved. Keep them straight and everything else follows.
The three credentials
| Credential | Lives in | Used by | What it is for | Lifetime | Safe in a URL? |
|---|---|---|---|---|---|
| S2S API token | Your backend only | Your backend | Minting keys and fetching reports (/s2s-api/*) | Permanent (issued with your contract) | ❌ Never |
| appKey | The iframe (in memory) | The iframe | Authenticating the iframe's own analysis calls | Default long-lived, or minted with a custom expiry | ⚠️ Reusable while valid |
| embeddedKey | The iframe URL | Your backend mints it, the iframe consumes it | A single-use stand-in that the iframe swaps for an appKey | Seconds, single use | ✅ Yes (recommended) |
At a glance, here is how they relate:
It is the secret that mints every other key. Only the resulting embeddedKey (or an appKey, if you choose Option B) ever belongs in an iframe URL.
Booting the iframe: two options
You authenticate the iframe by putting a key in its URL. There are two ways to do it.
| Option A: embedded key (recommended) | Option B: appKey in URL | |
|---|---|---|
| URL parameter | ?embeddedKey=… | ?company=… |
| What sits in the URL | A single-use key that dies on first use | A reusable appKey |
| Backend call per load | Yes (POST /iframe-embedded-keys) | Optional (only to mint a fresh appKey) |
| If the URL leaks | Worthless, already consumed | Reusable until it expires |
| Setup effort | Slightly more | Minimal |
| Use it for | Production, every time | Embedding inside your own protected app, or quick tests |
Option A (recommended): single-use embedded key
Your backend requests a brand-new embedded key for every iframe load. It is valid for only 60 seconds by default (configurable) and can be used exactly once, so an intercepted URL, from browser history, a Referer header, a screenshot or a log line, is worthless to an attacker.
Turning the embedded key into the real appKey happens inside the iframe, automatically. Your only job is to mint the embedded key on your backend and put it in the URL. See "Under the hood" if you want to know exactly how the swap works.
Option B: app key in the URL
Put an appKey directly in the URL with the company parameter: https://iframe.legit.health/?company=YOUR_APP_KEY. The iframe uses it as-is, with no exchange step. The appKey can come from two places:
| appKey source | Lifetime | What to watch for |
|---|---|---|
| Default appKey (issued with your contract) | Long, but finite: it does expire, the window is simply generous | Reusable by anyone who captures the URL until it expires |
Minted appKey (POST /s2s-api/v3/company-app-keys) | Short, a custom expiry you set | A captured URL stops working once it expires |
This option is simpler to wire up, and it is a perfectly secure choice in the common case.
In most integrations the iframe is embedded inside your own application and internal resources, which already sit behind your own authentication and access controls. The appKey in the URL is therefore only ever exposed within that protected context, which is enough for the majority of integrators. On top of that, you can mint short-lived appKeys on demand, as many as you want, whenever you prefer a tighter expiry. Reach for Option A when the iframe URL could travel outside that protected boundary, for example public links, shared screens, or third-party analytics.
Endpoint: create an embedded key
Mint one single-use embedded key per iframe load.
| Method | POST |
| Full URL | https://{baseUrl}/s2s-api/v3/iframe-embedded-keys |
| Headers | X-API-TOKEN: <your S2S API token> and accept: application/json |
Request body
Both fields are optional. Send an empty body {} to accept the defaults.
| Field | Default | Description |
|---|---|---|
embeddedKeyExpiresInSeconds | ~60 seconds | Window between your backend creating the key and the iframe loading with it |
appKeyExpiresInSeconds | 900 (15 min) | How long the resulting iframe session stays authenticated. Pass a larger value for longer sessions, e.g. 3600 for a one-hour consultation |
curl -X POST "https://{baseUrl}/s2s-api/v3/iframe-embedded-keys" \
-H "X-API-TOKEN: YOUR_S2S_TOKEN" \
-H "accept: application/json" \
-d '{"embeddedKeyExpiresInSeconds": 60, "appKeyExpiresInSeconds": 900}'
Success returns HTTP 201 Created:
{
"embeddedKey": "a-single-use-key-generated-for-this-iframe-load-0123456789abcdef",
"expiresAt": "2026-07-08T12:01:00+00:00"
}
Take the embeddedKey value and place it in the iframe URL as ?embeddedKey=…. The appKey behind it is never part of this response and never appears in any URL.
Endpoint: create an app key
Mint a standalone appKey to use directly in the URL with ?company=… (Option B).
| Method | POST |
| Full URL | https://{baseUrl}/s2s-api/v3/company-app-keys |
| Headers | X-API-TOKEN: <your S2S API token> and accept: application/json |
Request body
The body is optional. Send {} to accept the defaults, or set the fields below.
| Field | Default | Description |
|---|---|---|
| Key value | Auto-generated securely | If you supply your own, it must be at least 64 characters |
| Expiry | 15 minutes | When the appKey stops working |
curl -X POST "https://{baseUrl}/s2s-api/v3/company-app-keys" \
-H "X-API-TOKEN: YOUR_S2S_TOKEN" \
-H "accept: application/json" \
-d '{}'
The response returns the appKey. Place it in the iframe URL as ?company=YOUR_APP_KEY.
Under the hood (optional reading)
You do not need any of this to integrate; it is here so you can reason about the security model end to end.
When the iframe loads with ?embeddedKey=…, it makes one call on your behalf to a public exchange endpoint, POST /iframe-key-exchange, sending the embedded key. Our backend performs an atomic single-use claim:
| Exchange attempt | Result |
|---|---|
| First call with the embedded key | Returns the real appKey and marks the key as consumed |
| Any later call with the same key | 401, the key no longer exists as a credential |
This is why single use is guaranteed and cannot be turned off. The iframe keeps the appKey in memory only (never in the URL, localStorage, or sessionStorage) and uses it as its Authorization: Bearer credential for its own API calls, so a replayed URL can never start a second session.