Server-to-server (secret keys)
This page covers tier-3 server-to-server integration with a secret key (sk_op_...) — what it’s for, its honest current status, the planned request shape, and how to store and rotate the secret. It’s for operator backends that want to call Expedition Insure from a trusted server instead of (or alongside) the browser embed.
Before reading further, know the headline: the server API is not callable today. Secret keys are minted, stored, and resolvable, but no HTTP route consumes one yet. Treat this page as forward guidance, not a working contract. For what does work today, see the integration guide (browser embed, publishable keys), the events reference, and the webhooks guide.
When you’d use a secret key
Section titled “When you’d use a secret key”The browser embed authenticates every request with a publishable key (pk_op_...). A publishable key is public by design — like a Stripe pk_ — because the security boundary is your per-operator origin allowlist plus a per-key rate limit, not key secrecy. It ships in your page’s HTML and runs in the browser.
A secret key (sk_op_...) is the opposite: it is meant to live only on your server and never reach a browser. You’d reach for one when you want to call Expedition Insure from a trusted backend — for example to create a quote server-side, fetch options without exposing the call to the client, or run a higher-trust tier-3 flow where origin-based browser trust doesn’t apply.
Not available yet. The use cases above describe the intended role of
sk_op_. As of today there is no endpoint that accepts a secret key. See the next section.
Honest status: not consumable yet
Section titled “Honest status: not consumable yet”This is the part to read carefully before you plan any work around secret keys.
What exists end-to-end today:
- Minting — a secret key can be issued for your operator. It is returned once at mint time and never retrievable again.
- Storage — Expedition Insure stores only a SHA-256 hash of the secret (plus a short non-secret prefix for identification), never the raw secret.
- Resolution — an internal resolver can look up an operator from a presented secret by hashing it and matching the stored hash.
What does not exist today:
- No HTTP route consumes a secret key. The resolver has zero callers. There is no tier-3 server-to-server endpoint.
- The two embed routes that exist —
POST /api/embed/quoteandGET /api/embed/options— authenticate only with a publishable key (pk_op_...) via the embed origin/rate-limit gate. Presenting ansk_op_...to them does nothing useful.
Net: the secret key is plumbed end-to-end (mint → store-as-hash → resolver), but the server API it is meant to gate is not callable. Right now an sk_op_... gates nothing reachable over HTTP. Build against the publishable-key embed flow today; revisit this page when the server API ships.
[needs-product-work]A tier-3 server-to-server endpoint that consumessk_op_...is not implemented. The request shape below is planned, not live — do not integrate against it yet.
Planned request shape [needs-product-work]
Section titled “Planned request shape [needs-product-work]”When the server API lands, expect it to mirror the existing embed contract closely, with the secret key replacing the browser’s origin-based trust. Nothing below is callable today; it is a forecast of the shape so you can plan storage and rotation now.
The publishable key is presented today via the X-EI-Publishable-Key header. A server endpoint would analogously carry the secret key in a request header, sent only from your backend over HTTPS — never embedded in client code, never in a query string.
POST /api/.../quote HTTP/1.1Host: <to be published>Content-Type: application/jsonAuthorization: Bearer sk_op_...The body and response would track the existing embed quote contract. For reference, today’s publishable-key quote route accepts these fields:
| Field | Type | Required | Notes |
|---|---|---|---|
destination | string | yes | Trip destination. |
tripCost | number | yes | Total trip cost (whole dollars). |
travelers | number | yes | Traveler count. |
residence | string | yes | Residence country code. |
email | string | yes | Lead email, e.g. traveler@example.com. |
startDate | string | no | ISO date. |
endDate | string | no | ISO date. |
durationDays | number | no | Derived from startDate/endDate when omitted. |
travelerAges | number[] | no | Ages at departure. |
travelerDobs | string[] | no | Resolved to age at departure server-side. |
currency | string | no | Defaults to the quote’s currency. |
name | string | no | Defaults to "Embedded quote". |
A successful quote response today returns:
{ "quoteId": "<quoteId>", "quoteNumber": "EXP-XXXX", "instantQuoteEligible": true }Money fields. Throughout the platform,
premiumCentsis the integer-minor-units field — integrate againstpremiumCentsand divide by 100 for display. Whole-dollar values liketripCostare plain dollars.
Payment sequencing. If a server flow ever surfaces a
payment.succeededsignal, treat it as a UX-only hint that can be missed. The authoritative post-sale signal is thepolicy.issuedwebhook — reconcile against that, never against a payment-success event.
Secret storage and rotation guidance
Section titled “Secret storage and rotation guidance”Even though the server API isn’t live, the storage and rotation discipline below is what you’ll need the moment it is — and it’s good hygiene for any secret credential.
Storage
Section titled “Storage”- Capture the secret at mint time. A secret key is shown once and is never retrievable again. If you lose it, you must rotate (issue a new one).
- Keep it server-side only. Never ship
sk_op_...to a browser, a mobile client, a public repo, or client-side bundles. The publishable key (pk_op_...) is the credential for browser contexts; the secret key is for your backend. - Store it in a secrets manager, not in source control or plain config files. Use environment variables or a dedicated secrets store (for example your platform’s secret manager). Inject it at runtime.
- Send it only over HTTPS, only in a request header from your server. Never put it in a URL, query string, or log line.
- Identify keys by their prefix. Expedition Insure stores a short non-secret prefix (
sk_op_plus a few characters) so you can recognize which key is in use without exposing the full secret. Use that prefix in your own dashboards and logs.
Rotation
Section titled “Rotation”Rotate a secret key when you suspect exposure, when staff with access leave, or on a regular schedule. Plan your backend so rotation is a config change, not a code change:
- Read the secret from one place (a single env var or secret-store entry) so rotating means updating one value.
- Issue a new secret for your operator, then update the stored value and redeploy/restart so your backend picks it up.
- Revoke the old key once the new one is confirmed working. Revocation is idempotent.
- Never email, paste, or commit a secret during rotation. Move it only through your secrets manager.
Because the server API is not live yet, you cannot exercise an end-to-end rotation against a working endpoint today. Set up the single-source-of-truth storage now so you’re ready when it ships.
Related pages
Section titled “Related pages”- Integration guide — the browser embed with publishable keys (works today).
- Events reference — the
quote.*andpayment.*events the embed emits. - Webhooks — the signed
policy.issuedwebhook, the authoritative post-sale signal.