SafarieSIM Partner Catalog API
Read-only, authenticated catalog of SafarieSIM eSIM plans for comparison and affiliate platforms (esims.io, eSIMDB, eSIMRating, …).
Base URL
https://api.safariesim.com/functions/v1/partner-catalog
Version: 2026-09-24 (returned in the X-Api-Version header and in every
response body as api_version). Breaking changes ship as a new version string;
additive fields may appear at any time.
Authentication
Send the partner key on every request:
x-api-key: <partner key>
Authorization: Bearer <partner key> also works. Keys are issued per partner and
stored only as SHA-256 hashes. A missing, unknown, or revoked key returns 401.
Request
GET /partner-catalog
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 |
1-based page number |
per_page |
integer | 100 |
Max 250 |
country |
string | – | ISO-3166 alpha-2. Returns every plan whose coverage includes it (local and regional), e.g. KE |
destination |
string | – | Destination slug filter, e.g. kenya |
currency |
string | USD |
Display currency for price |
format |
string | catalog |
catalog, esims-io, or esimdb |
days |
string | 1,3,7,15,30 |
Durations to price for day-based destinations |
Arbitrary durations (days)
SafarieSIM sells day-based plans from a calendar, so any whole number of days is
purchasable — the default response only showcases 1, 3, 7, 15, 30. Ask for the
durations you need:
?days=10 single duration
?days=1,5,10,21 explicit list
?days=1-30 inclusive range
?days=all every day from 1 to 30
Values outside a destination's configured min_days/max_days are skipped;
invalid values return 400 invalid_days. The durations actually priced are
echoed back as days in the response body. Fixed-catalog destinations ignore
this parameter — their plans have set validity.
Supported currencies are those with an active conversion rate (USD, EUR, GBP,
PLN, AED, SAR, BRL, IDR, KES, MUR, TWD, SGD, NZD, AUD, NGN). An unsupported
value returns 400 unsupported_currency.
Response
{
"api_version": "2026-09-24",
"generated_at": "2026-09-14T16:56:38.024Z",
"currency": "USD",
"pagination": { "page": 1, "per_page": 100, "total": 980, "total_pages": 10 },
"data": [
{
"id": "kenya-7d",
"name": "Kenya eSIM — 7 days",
"destination_slug": "kenya",
"destination_name": "Kenya",
"country_iso2": "KE",
"countries": ["KE"],
"region": "Africa",
"coverage_type": "Nationwide",
"data_amount_mb": null,
"is_unlimited": true,
"validity_days": 7,
"price": 31.25,
"price_usd": 31.25,
"currency": "USD",
"operators": [{ "name": "Safaricom", "network_types": ["5G", "4G", "3G"] }],
"network_types": ["5G", "4G", "3G"],
"plan_type": "unlimited",
"topup_available": false,
"status": "available",
"product_url": "https://safariesim.com/esim-kenya",
"checkout_url": "https://safariesim.com/esim-kenya?days=7&checkout=1",
"last_updated": "2026-04-04T14:02:53.642235+00:00"
}
]
}
Field notes
id— stable. Day-based destinations use<slug>-<days>d; fixed-catalog destinations use the variant UUID.price— retail price for one traveller for the whole plan, incurrency, including the charm rounding customers see.price_usdis the same amount before currency conversion.data_amount_mb—nullwhen the plan is unlimited or the allowance is not published; checkis_unlimitedfirst.countries— every country the plan covers (single-country plans return one entry; regional plans such as Africa Safari return the full list).checkout_url— deep link that opens the product page with the plan preselected. Append your affiliate parameter (?ref=<code>) when linking.
Errors
{ "error": { "code": "unauthorized", "message": "Invalid or revoked partner API key." },
"api_version": "2026-09-24" }
| Status | Code | Meaning |
|---|---|---|
| 400 | unsupported_currency / unsupported_format |
Bad query parameter |
| 401 | unauthorized |
Missing, unknown, or revoked key |
| 405 | method_not_allowed |
Only GET is supported |
| 500 | internal_error |
Temporary failure; retry with backoff |
Freshness and price integrity
Prices are computed at request time from the same resolver the checkout uses
(supabase/functions/_shared/catalogPricing.ts), so a catalog price always
matches the price a customer is charged. Responses carry
Cache-Control: public, max-age=300; polling more often than every 5 minutes
gives no fresher data. Please stay under the issued hourly rate limit
(600 requests/hour by default).
The price source is admin-controlled at /sys/admin/daily-rates → "Partner
catalog pricing": daily-rate pricing only, fixed plan prices only, or automatic.
The active choice is echoed as pricing_source in every response.
Wholesale cost and supplier configuration are never exposed through any public or partner endpoint.
Partner formats
format=esims-io and format=esimdb return the same plans with each partner's
preferred field names. New partners get an adapter in
supabase/functions/partner-catalog/index.ts (adapt()); the normalized
catalog shape stays unchanged.
eSIMDB (Provider API v1.0.2)
Keys set to the eSIMDB format (admin → partner keys → Format) need no query string:
GET /partner-catalog DataPlan[] (raw JSON array, full catalog)
GET /partner-catalog/destination-links DestinationLink[]
- Daily unlimited plans publish
dataCap= daily high-speed allowance,dataCapPer: "day", andreducedSpeed(kbps) after the cap. - With no
daysparameter the eSIMDB format returns every purchasable duration (1–30 days for calendar destinations; fixed packages keep their real validity).days=still filters. - Destination Links only list destinations that have at least one published plan.
- Canary Islands and Kosovo use
ICandXK. - Each
coverages[]entry lists only the networks recorded for that country (one entry per operator, types merged); a country with no recorded networks getsnetworks: []. - Prices are the final public checkout price in USD, with no referral markup.
- Plans whose fair-use allowance is unknown are left out rather than published
as
dataCap: 0. The count is returned in theX-Excluded-Plansheader. Fill in the missing values in thedestination_fair_usesettings to include them.
MCP server (Africa Travel Connectivity)
The same catalog is available to AI assistants and agents as a public Model Context Protocol server:
https://api.safariesim.com/functions/v1/mcp
It is read-only and needs no key: anyone can connect it to Claude, Cursor, VS Code or another MCP client. Full documentation, tool schemas and examples live in the public repository, punivox/africa-travel-connectivity.
Tools
| Tool | What it answers |
|---|---|
search_esim_plans |
Plans that work in every country of a trip, filtered by trip length, data, budget and currency, cheapest first |
compare_country_plans |
Cheapest and cheapest-unlimited plan per country, operators, and single plans covering all countries |
get_country_networks |
Mobile operators and network types per country, and which plans use them |
calculate_safari_data |
Data needed for a trip from its length and activities, matched to plans that fit |
list_destinations |
Covered countries and multi-country plans (Africa first) |
Plans and prices come from the same builder as GET /partner-catalog, so the
MCP returns exactly the prices this API publishes for the same destination,
trip length and currency. Supplier names and wholesale data are never included.
Partner keys and attribution
A partner key is optional. Sent as x-api-key (or Authorization: Bearer), it
adds your ref=<code> to every link the tools return and uses your key's hourly
rate limit; requests count toward the same quota as this API. A key that is
unknown or revoked is rejected with 401 instead of being treated as anonymous.
claude mcp add --transport http safariesim https://api.safariesim.com/functions/v1/mcp \
--header "x-api-key: <partner key>"
Without a key, each IP address gets a flood-protection budget (120 requests per minute).
Protocol notes
- Serves MCP
2026-07-28and, for older clients, the2025-11-25era (initialize handshake) on the same URL. - Stateless: no
Mcp-Session-Id, single JSON responses,GETandDELETEreturn405, andsubscriptions/listenis refused because the tool list never changes. - Catalog reads are cached for up to 60 seconds; checkout always re-prices.
- HTTP-level failures return a JSON-RPC error body with
id: null:
| Status | JSON-RPC code | error.data.code |
Meaning |
|---|---|---|---|
| 401 | -32001 |
unauthorized |
Unknown or revoked partner key |
| 403 | -32003 |
forbidden |
Key's affiliate account is not active |
| 429 | -32029 |
rate_limited |
Limit reached; wait Retry-After seconds |
A bad tool argument (unknown country, unsupported currency, invalid trip
length) comes back as a tool result with isError: true and a message saying
how to fix the call.
Multiple suppliers
Plans come from every supplier (eSIM Access, eSIM Go, Airalo, …). Each
supplier has a small mapper in supabase/functions/_shared/catalog/providers.ts
that turns its private config into public facts: fair use, speed, top-up, and
breakout country. Admin fair-use settings take priority over supplier data.
New comparator formats go in _shared/catalog/formats.ts.
Issuing and revoking keys
Keys live in public.partner_api_keys (admin-only). Store the SHA-256 hash and
a short prefix, never the raw key. Revoke by setting revoked_at.
How this document is published
This file is the single source of truth for the API contract:
- It renders publicly at safariesim.com/api
(alias
/partners/api) — edit the Markdown, deploy, and the page updates. - The machine-readable contract lives at
/partner-catalog.openapi.json(OpenAPI 3.1, importable into Postman/Swagger).
When supabase/functions/partner-catalog/index.ts changes, update both this
file and the OpenAPI spec in the same change, and bump api_version for any
breaking change. Plan building lives in
supabase/functions/_shared/catalog/buildCatalog.ts and is shared with the MCP
server (supabase/functions/mcp/index.ts); a change there affects both, so keep
the "MCP server" section above and the public repository's README in sync too.