API Documentation
Production-ready visa intelligence — 47,362 passport/destination pairs, 30 data points each, 15 languages. 28 REST endpoints across 8 product surfaces.
Quickstart
Every Orizn account ships with a personal API key. Pass it via the x-api-key header (or?api_key= query param) and you're live.
Working in a specific stack? The integration tutorials take you from an empty folder to a running call — MCP, REST, JavaScript, Python, Rust, LangChain, React and Next.js. Every code block on those pages was executed before publishing.
# Free key: evaluation only. Shipping to users? -> commercial license required. curl "https://visa.orizn.app/api/v1/visa?passport=FRA&destination=JPN&lang=en" \ -H "x-api-key: YOUR_API_KEY"
GET /api/v1/visa/check?passport=FRA&destination=JPNNo key needed — run this demo call live from your browser. You get a real 200 response from the API, same-origin.
Base URL: https://visa.orizn.app · JSON in/out · UTF-8 · CORS open on all public endpoints.
1. Authentication
There are three ways to authenticate, depending on the endpoint:
x-api-key header
Default for product code. Also accepted as ?api_key= query param.
no credentials
Stats, score, live, register, affiliate flows. /visa/check is also keyless when called from visa.orizn.app, localhost, or a Chrome extension.
orizn_token cookie
Dashboard endpoints (key rotation, Stripe checkout/portal, analytics). Set automatically when you log in on the dashboard.
# 1) API key — recommended curl "https://visa.orizn.app/api/v1/visa?passport=FRA&destination=JPN" \ -H "x-api-key: YOUR_API_KEY" # 2) API key via query string (only when headers can't be set) curl "https://visa.orizn.app/api/v1/visa?passport=FRA&destination=JPN&api_key=YOUR_API_KEY" # 3) Session cookie (dashboard endpoints) curl "https://visa.orizn.app/api/v1/visa/auth/ensure-key" \ --cookie "orizn_token=YOUR_SESSION"
Never expose your key in client-side code shipped to untrusted users — proxy through your backend. Keys can be rotated at any time from the dashboard.
Conventions
2. Endpoints
All endpoints live under https://visa.orizn.app. The badges under each path tell you the auth mode and minimum plan. Public endpoints have a Run ▶ button you can fire right here.
The endpoints you'll actually call from product code.
/api/v1/visaapi key · any planFull visa intelligence
The flagship endpoint. Returns 30 data points: documents, process, fees, embassies, transit, vaccinations, safety advisories, overstay penalties, and more — in 15 languages. Plan gating: free returns the core fields plus upgrade stubs; starter unlocks every extended field except remote-work-visa and reciprocity history; pro+ returns everything including bidirectional embassy info.
| Parameter | Type | Required | Description |
|---|---|---|---|
| passport | string | Yes | ISO 3166-1 alpha-3 (e.g. FRA). |
| destination | string | Yes | ISO 3166-1 alpha-3 (e.g. JPN). |
| lang | string | No | One of 15 supported codes (see Languages). Free plan is English-only — other languages require Starter+. |
curl "https://visa.orizn.app/api/v1/visa?passport=FRA&destination=JPN" \ -H "x-api-key: YOUR_API_KEY"
{
"data": {
"passport": "FRA",
"destination": "JPN",
"requirement": "visa_free",
"visa_free_days": 90,
"visa_required": false,
"description": "French citizens can enter Japan visa-free for up to 90 days.",
"documents_required": ["Valid passport (3 months)", "Return or onward ticket"],
"process": ["No prior formalities", "Immigration form on arrival"],
"tips": ["Carry proof of sufficient funds"],
"country_info": { "currency": "JPY", "language": "Japanese", "timezone": "UTC+9", "capital": "Tokyo" },
"verified": true,
"transit_visa": { "hubs": [{ "airport": "NRT", "city": "Tokyo", "transit_visa_required": false, "transit_free_hours": 24 }] },
"passport_validity_months": 3,
"visa_fee": { "single_entry": { "amount": 0, "currency": "JPY" } },
"processing_days": { "standard": null, "express": null },
"photo_specs": { "width_mm": 35, "height_mm": 45, "background": "white" },
"vaccinations_required": [],
"insurance_required": { "required": false },
"overstay_penalty": { "fine_per_day": "Variable + deportation", "ban_days": 365, "criminal": true },
"entry_by_mode": { "air": 90, "land": 90, "sea": 90 },
"safety": { "level": 1, "advisory": "Exercise normal precautions", "source": "diplomatie.gouv.fr" },
"health_requirements": { "covid_test": false, "quarantine_days": 0 },
"embassy": {
"your_embassy_at_destination": { "name": "Ambassade de France au Japon", "city": "Tokyo", "phone": "+81 3 5798 6000" },
"visa_application_embassy": { "name": "Ambassade du Japon en France", "city": "Paris" }
}
},
"meta": { "lang": "en", "api_version": "1.1", "coverage": "199 passports x 238 destinations", "languages": 15, "data_points": 30 }
}/api/v1/visa/checkapi key · any planQuick visa check
Lightweight yes/no — returns just the requirement type and allowed stay. The embedded _upgrade_preview field counts what the full /visa endpoint would have returned, so you can drive upsell UI without a second call.
| Parameter | Type | Required | Description |
|---|---|---|---|
| passport | string | Yes | ISO 3166-1 alpha-3. |
| destination | string | Yes | ISO 3166-1 alpha-3. |
curl "https://visa.orizn.app/api/v1/visa/check?passport=FRA&destination=JPN" \ -H "x-api-key: YOUR_API_KEY"
{
"passport": "FRA",
"destination": "JPN",
"requirement": "visa_free",
"visa_free_days": 90,
"visa_required": false,
"_hint": "Upgrade to get documents, process, embassies, photo specs and 28 more fields.",
"_upgrade_preview": {
"documents_required": 4,
"process_steps": 3,
"embassy_info": true,
"transit_visa": true,
"visa_fees": false,
"vaccinations": 0,
"safety_advisory": "level 1",
"languages": 15,
"upgrade_url": "https://visa.orizn.app/visa-api/pricing"
}
}/api/v1/visa/bulkapi key · hobby+Bulk destinations for a passport
One passport against up to 25 destinations in a single round-trip. Pass a comma-separated destinations list (required, max 25 per call). Each returned pair counts as one request against your monthly quota. Returns a curated subset of the extended fields (fees, safety, health, vaccinations, insurance, entry-by-mode, remote-work).
| Parameter | Type | Required | Description |
|---|---|---|---|
| passport | string | Yes | ISO 3166-1 alpha-3. |
| destination | string | No | Comma-separated ISO3 list, e.g. JPN,THA,BRA. Omit to return every destination. |
| lang | string | No | Default en. One of the 15 supported codes. |
curl "https://visa.orizn.app/api/v1/visa/bulk?passport=FRA" \ -H "x-api-key: YOUR_API_KEY"
{
"passport": "FRA",
"lang": "en",
"total": 199,
"destinations": [
{
"destination": "JPN",
"requirement": "visa_free",
"visa_free_days": 90,
"description": "Visa-free for up to 90 days.",
"passport_validity_months": 3,
"visa_fee": { "single_entry": { "amount": 0, "currency": "JPY" } },
"safety": { "level": 1 },
"health_requirements": { "covid_test": false },
"vaccinations_required": [],
"insurance_required": { "required": false },
"entry_by_mode": { "air": 90, "land": 90, "sea": 90 },
"remote_work_visa": { "available": false }
}
]
}/api/v1/visa/groupapi key · hobby+Group trip — multi-passport intersection
Built for group travel: pass 2-10 passports and get back every destination accessible by ALL of them, with the per-passport breakdown and the group's worst-case requirement. By default a destination qualifies when every passport is visa_free, eta, visa_on_arrival or e_visa — narrow or widen with the allow param (e.g. allow=visa_free for strictly visa-free). group_visa_free_days is the minimum allowed stay across the group, i.e. the binding constraint for a shared trip. Destinations are sorted easiest-first. Each passport×destination pair served counts as one request.
| Parameter | Type | Required | Description |
|---|---|---|---|
| passports | string | Yes | Comma-separated ISO3 list, 2 to 10 distinct codes. Example: USA,FRA,IND. |
| allow | string | No | Comma-separated requirement types that qualify. Default: visa_free,eta,visa_on_arrival,e_visa. |
curl "https://visa.orizn.app/api/v1/visa/group?passports=USA,FRA,IND" \ -H "x-api-key: YOUR_API_KEY"
{
"passports": ["USA", "FRA", "IND"],
"allow": ["visa_free", "eta", "visa_on_arrival", "e_visa"],
"total": 97,
"excluded": { "requirement_not_allowed": 100, "incomplete_data": 4 },
"destinations": [
{
"destination": "FJI",
"group_requirement": "visa_free",
"group_visa_free_days": 120,
"by_passport": {
"USA": { "requirement": "visa_free", "visa_free_days": 120 },
"FRA": { "requirement": "visa_free", "visa_free_days": 120 },
"IND": { "requirement": "visa_free", "visa_free_days": 120 }
}
}
]
}/api/v1/visa/decisionapi key · any planItinerary decision — answers a trip, not a pair
Post a whole trip — passport, ordered stops with dates, transit stops, passport expiry — and get a per-step decision plus the blockers only a full itinerary reveals: days accumulated across repeat visits to the same country, passport validity measured against each arrival date, and an expiry that falls before the last exit. Every field states its granularity (pair or destination) and every gap comes back as status: "unknown" with a reason, never as a default value. verdict is no_blocker_found or blocked — it reports what the data supports, it is not legal advice.
{
"passport": "FRA",
"residence": "VNM",
"passport_expiry": "2027-03-01",
"itinerary": [
{ "country": "THA", "from": "2026-09-01", "to": "2026-09-20" },
{ "country": "VNM", "from": "2026-09-20", "to": "2026-10-10" },
{ "country": "THA", "from": "2026-10-10", "to": "2026-10-25" }
],
"transit": ["SGP"],
"purpose": "tourism"
}curl -X POST "https://visa.orizn.app/api/v1/visa/decision" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "passport": "FRA", "residence": "VNM", "passport_expiry": "2027-03-01", "itinerary": [ { "country": "THA", "from": "2026-09-01", "to": "2026-09-20" }, { "country": "VNM", "from": "2026-09-20", "to": "2026-10-10" }, { "country": "THA", "from": "2026-10-10", "to": "2026-10-25" } ], "transit": ["SGP"], "purpose": "tourism" }'
{
"data": {
"passport_country": "FRA",
"steps": [
{
"country": "THA", "from": "2026-09-01", "to": "2026-09-20", "days": 20,
"admission": "allowed",
"regime": { "status": "known", "value": "visa_free", "granularity": "pair",
"source_url": null, "last_verified": null },
"max_stay": { "status": "known", "days": 30, "granularity": "pair", "basis": "visa_free_days" },
"passport_validity_months": { "status": "known", "months": 6, "granularity": "destination" }
}
],
"transit": [
{ "country": "SGP", "status": "destination_rule", "granularity": "destination",
"note": "Applies to the airport, not to this nationality. Confirm with the carrier before booking." }
],
"passport": { "expiry": "2027-03-01", "status": "checked", "valid_through_itinerary": true,
"per_destination": [{ "country": "THA", "arrival": "2026-09-01",
"required_months": 6, "satisfied": true }] },
"blockers": [
{ "type": "stay_exceeds_allowance", "country": "THA",
"detail": "Itinerary spends 36 day(s) in THA, above the 30-day allowance recorded for this pair." }
],
"unknowns": [
{ "field": "residence",
"reason": "Residence is not a dimension of the dataset. Decisions assume a passport holder applying as a national; residence permits may grant additional rights not modelled here." }
],
"verdict": "blocked"
},
"meta": { "api_version": "1.0", "pairs_charged": 3, "max_steps": 12 }
}/api/v1/visa/statspublic — no authfree to quotaCoverage statistics
Public, no auth, edge-cached for one hour. Use it on marketing pages to display live coverage numbers and a breakdown of how many pairs fall into each requirement bucket.
curl "https://visa.orizn.app/api/v1/visa/stats"
{
"coverage": {
"visa_details": 47362,
"passports": 199,
"destinations": 238,
"passport_index_pairs": 47362,
"translations": 663068,
"languages": 15
},
"supported_languages": [
{ "code": "en", "name": "English" },
{ "code": "fr", "name": "Français" }
],
"requirement_distribution": {
"visa_free": 14210,
"visa_required": 12740,
"e_visa": 4830,
"visa_on_arrival": 5102,
"eta": 2103,
"no_admission": 600
},
"api_version": "1.0",
"docs": "https://visa.orizn.app"
}Public mobility scores — single passport or side-by-side comparison.
/api/v1/visa/scorepublic — no authfree to quotaPassport mobility score
Composite mobility score and global rank for one passport. The score factors in visa-free / visa-on-arrival / e-visa access, destination diversity, and an economic weighting. Public — no key required.
| Parameter | Type | Required | Description |
|---|---|---|---|
| passport | string | Yes | ISO 3166-1 alpha-3. |
curl "https://visa.orizn.app/api/v1/visa/score?passport=FRA"
{
"passport": "FRA",
"score": 96.4,
"rank": 3,
"breakdown": {
"visa_free": { "count": 158, "weight": 0.55 },
"visa_on_arrival": { "count": 17, "weight": 0.15 },
"e_visa": { "count": 20, "weight": 0.10 },
"diversity": { "continents": 6, "weight": 0.10 },
"economic": { "score": 88, "weight": 0.10 }
},
"total_accessible": 195
}/api/v1/visa/score/comparepublic — no authfree to quotaCompare two passports
Side-by-side comparison of two passports: their individual scores, the set difference of destinations they unlock, and a normalised combined-passport score (the max destinations any single passport can reach is 238 → 1000 points). Powers dual-citizenship calculators and second-passport landing pages.
| Parameter | Type | Required | Description |
|---|---|---|---|
| passport1 | string | Yes | First passport, ISO 3166-1 alpha-3. |
| passport2 | string | Yes | Second passport, ISO 3166-1 alpha-3. Must differ from passport1. |
curl "https://visa.orizn.app/api/v1/visa/score/compare?passport1=FRA&passport2=MAR"
{
"passport1": { "code": "FRA", "score": 96.4, "rank": 3 },
"passport2": { "code": "MAR", "score": 41.2, "rank": 75 },
"combined": {
"score": 988,
"total_accessible": 197,
"only_passport1": ["USA", "CAN", "GBR"],
"only_passport2": ["DZA", "TUN"],
"both": ["JPN", "THA", "BRA"],
"neither": ["PRK"]
},
"share_text": "FRA + MAR unlock 197/238 destinations — share your dual-passport score on https://visa.orizn.app"
}Real-time SSE stream + recent snapshot for social-proof widgets.
/api/v1/visa/livepublic — no authfree to quotaLive activity stream (SSE)
Server-Sent Events. Each successful /visa or /visa/check call worldwide produces an event with the passport + destination + timestamp. A `: keepalive` comment is sent every 2 s when no new traffic. Perfect for social-proof tickers on landing pages.
curl "https://visa.orizn.app/api/v1/visa/live"
// Content-Type: text/event-stream
data: {"passport":"USA","destination":"JPN","timestamp":"2026-05-31T09: 14: 00Z"}
data: {"passport":"IND","destination":"ARE","timestamp":"2026-05-31T09: 13: 58Z"}
: keepalive/api/v1/visa/live/recentpublic — no authfree to quotaRecent activity snapshot
Same data as /live but as a single JSON snapshot — last 20 events, plus aggregate counters and the top-5 most popular corridors today. Edge-cached for 5 seconds.
curl "https://visa.orizn.app/api/v1/visa/live/recent"
{
"entries": [
{ "passport": "USA", "destination": "JPN", "timestamp": "2026-05-31T09: 14: 00Z" },
{ "passport": "IND", "destination": "ARE", "timestamp": "2026-05-31T09: 13: 58Z" }
],
"stats": { "today": 14328, "this_week": 92041, "total": 1248302 },
"top_corridors": [
{ "pair": "USA→JPN", "count": 412 },
{ "pair": "IND→ARE", "count": 388 },
{ "pair": "DEU→THA", "count": 301 }
]
}Subscribe devices (iOS / Android) to policy-change alerts.
/api/v1/visa/devicesapi key · any planfree to quotaRegister a device for push
Subscribe an APNs / FCM device token to receive push notifications when visa policies change. Each device tracks one passport plus an optional wishlist of destinations and a set of preferences (instant alerts, weekly digest, wishlist-only, positive-changes-only).
{
"device_token": "8a3f...e2b1",
"passport_iso3": "FRA",
"platform": "ios",
"bundle_id": "com.orizn-visa",
"wishlist_iso3": ["THA", "JPN", "BRA"],
"locale": "en",
"tz": "Europe/Paris",
"premium": false,
"preferences": {
"instant": true,
"digest_weekly": true,
"only_wishlist": false,
"only_positive": false
}
}curl -X POST "https://visa.orizn.app/api/v1/visa/devices" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "device_token": "8a3f...e2b1", "passport_iso3": "FRA", "platform": "ios", "bundle_id": "com.orizn-visa", "wishlist_iso3": ["THA", "JPN", "BRA"], "locale": "en", "tz": "Europe/Paris", "premium": false, "preferences": { "instant": true, "digest_weekly": true, "only_wishlist": false, "only_positive": false } }'
{ "device_id": 42 }/api/v1/visa/devices/{id}api key · any planfree to quotaUpdate a device subscription
Update any subset of the subscription fields — passport, wishlist, locale, timezone, premium flag, preferences. At least one field is required.
{
"wishlist_iso3": ["THA", "JPN", "BRA", "PRT"],
"preferences": { "only_wishlist": true }
}curl -X PATCH "https://visa.orizn.app/api/v1/visa/devices/42" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "wishlist_iso3": ["THA", "JPN", "BRA", "PRT"], "preferences": { "only_wishlist": true } }'
{ "updated": true, "device_id": 42 }/api/v1/visa/devices/{id}api key · any planfree to quotaUnsubscribe a device
Permanently delete the device subscription. Use this when the user revokes notifications or uninstalls.
curl -X DELETE "https://visa.orizn.app/api/v1/visa/devices/42" \ -H "x-api-key: YOUR_API_KEY"
{ "deleted": true, "device_id": 42 }Server-to-server policy-change delivery. HMAC-signed payloads.
/api/v1/visa/webhooksapi key · business+free to quotaList your webhooks
Returns every webhook subscription owned by your account, including its filters, last trigger time, and failure counter.
curl "https://visa.orizn.app/api/v1/visa/webhooks" \ -H "x-api-key: YOUR_API_KEY"
{
"webhooks": [
{
"id": 17,
"url": "https://your-app.com/orizn-hook",
"passport_filter": ["FRA"],
"destination_filter": null,
"active": true,
"created_at": "2026-05-29T09: 14: 00Z",
"last_triggered_at": "2026-05-31T08: 02: 11Z",
"failures": 0
}
]
}/api/v1/visa/webhooksapi key · business+free to quotaCreate a webhook
Register a URL to receive POSTs whenever a policy change matches your filter. The response contains a one-time secret — store it now, we won't show it again. Sign verifications use HMAC-SHA256 of the raw body keyed by the secret.
{
"url": "https://your-app.com/orizn-hook",
"passport_filter": ["FRA"],
"destination_filter": ["THA", "JPN"]
}curl -X POST "https://visa.orizn.app/api/v1/visa/webhooks" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.com/orizn-hook", "passport_filter": ["FRA"], "destination_filter": ["THA", "JPN"] }'
{
"webhook": {
"id": 17,
"url": "https://your-app.com/orizn-hook",
"passport_filter": ["FRA"],
"destination_filter": ["THA", "JPN"],
"active": true,
"secret": "whsec_4f2a91c8...e2b1",
"created_at": "2026-05-31T09: 14: 00Z"
},
"message": "Webhook created. Store the secret — it won't be shown again."
}/api/v1/visa/webhooks?id={id}api key · business+free to quotaDelete a webhook
Permanently delete a webhook subscription you own.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Yes | Webhook id, from list/create. |
curl -X DELETE "https://visa.orizn.app/api/v1/visa/webhooks?id=42?id=42" \ -H "x-api-key: YOUR_API_KEY"
{ "deleted": true, "id": 17 }Per-environment subkeys sharing the owner account quota.
/api/v1/visa/team-keysapi key · business+free to quotaList team keys
Return every team subkey under your account. Each team key inherits its owner's plan and shares the same monthly quota — useful for isolating environments or attributing usage.
curl "https://visa.orizn.app/api/v1/visa/team-keys" \ -H "x-api-key: YOUR_API_KEY"
{
"team_keys": [
{
"id": 7,
"api_key": "orizn_visa_team_a1b2c3...",
"name": "ci-staging",
"active": true,
"requests_month": 14823,
"requests_total": 184238,
"created_at": "2026-05-12T11: 04: 00Z"
}
]
}/api/v1/visa/team-keysapi key · business+free to quotaCreate a team key
Mint a new team subkey. The key is prefixed orizn_visa_team_ and immediately usable in the x-api-key header. Shared quota with the owner account.
{ "name": "ci-staging" }curl -X POST "https://visa.orizn.app/api/v1/visa/team-keys" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "ci-staging" }'
{
"team_key": {
"id": 7,
"api_key": "orizn_visa_team_a1b2c3...",
"name": "ci-staging",
"active": true,
"requests_month": 0,
"requests_total": 0,
"created_at": "2026-05-31T09: 14: 00Z"
},
"message": "Team key created. It shares the monthly quota of the owner account."
}/api/v1/visa/team-keys?id={id}api key · business+free to quotaDeactivate a team key
Soft delete — flips active to false. Existing requests with the key start returning 403 immediately.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Yes | Team key id. |
curl -X DELETE "https://visa.orizn.app/api/v1/visa/team-keys?id=42?id=42" \ -H "x-api-key: YOUR_API_KEY"
{ "deactivated": true, "team_key": { "id": 7, "name": "ci-staging" } }Self-serve signup, key rotation, and Stripe-hosted billing flow.
/api/v1/visa/registerpublic — no authfree to quotaSign up (get a free API key)
Self-serve signup. Returns a free-plan API key (50 req/month). Idempotent on email — re-registering returns the existing key instead of creating a duplicate.
{ "name": "Ada Lovelace", "email": "[email protected]" }curl -X POST "https://visa.orizn.app/api/v1/visa/register" \ -H "Content-Type: application/json" \ -d '{ "name": "Ada Lovelace", "email": "[email protected]" }'
{
"api_key": "orizn_visa_a06113a2e4f0...",
"plan": "free",
"message": "API key created successfully."
}/api/v1/visa/auth/ensure-keysession cookiefree to quotaGet the logged-in user's API key
Dashboard helper: validates the orizn_token cookie against api.orizn.app/auth/me, then returns the matching visa_api_users row — creating one on the free plan if the Orizn account doesn't have one yet.
curl "https://visa.orizn.app/api/v1/visa/auth/ensure-key" \ --cookie "orizn_token=YOUR_SESSION"
{
"user": {
"id": 42,
"orizn_id": "01HZ...",
"email": "[email protected]",
"name": "Ada Lovelace",
"photo": "https://...",
"plan": "pro",
"api_key": "orizn_visa_a06113...",
"requests_month": 14238,
"requests_total": 412938,
"monthly_limit": 250000,
"created_at": "2026-04-12T11: 04: 00Z"
}
}/api/v1/visa/auth/regenerate-keysession cookiefree to quotaRotate the API key
Generate a new key and invalidate the old one. Use this when a key may have leaked. Affects only the owner row, not team subkeys.
curl -X POST "https://visa.orizn.app/api/v1/visa/auth/regenerate-key" \ --cookie "orizn_token=YOUR_SESSION" \ -H "Content-Type: application/json" \ -d '{}'
{
"api_key": "orizn_visa_b71224...",
"message": "API key rotated. Update your clients."
}/api/v1/visa/stripe/checkoutsession cookiefree to quotaStart a paid-plan checkout
Returns a Stripe Checkout session URL. Redirect the user to it; on success Stripe pings our webhook which upgrades their plan. Annual billing with a valid affiliate_id grants a 30-day trial.
{
"plan": "pro",
"billing": "monthly",
"affiliate_id": "aff_4f2a91c8"
}curl -X POST "https://visa.orizn.app/api/v1/visa/stripe/checkout" \ --cookie "orizn_token=YOUR_SESSION" \ -H "Content-Type: application/json" \ -d '{ "plan": "pro", "billing": "monthly", "affiliate_id": "aff_4f2a91c8" }'
{ "url": "https://checkout.stripe.com/c/pay/cs_test_..." }/api/v1/visa/stripe/portalsession cookiefree to quotaOpen the Stripe billing portal
Returns a one-time URL into Stripe's customer portal — invoices, payment method, plan changes, cancellation.
curl -X POST "https://visa.orizn.app/api/v1/visa/stripe/portal" \ --cookie "orizn_token=YOUR_SESSION" \ -H "Content-Type: application/json" \ -d '{}'
{ "url": "https://billing.stripe.com/p/session/..." }Earn 15% commission on referred subscriptions — web + iOS.
/api/v1/visa/affiliate/registerpublic — no authfree to quotaBecome an affiliate
Open a partner account. We mint an affiliate_id (format aff_<8 hex>) you embed in checkout URLs to earn 15% commission on every subscription you refer. Idempotent on email.
{
"name": "Ada Lovelace",
"email": "[email protected]",
"website": "https://travelblog.example",
"payment_method": "paypal",
"payment_info": "[email protected]",
"source": "web"
}curl -X POST "https://visa.orizn.app/api/v1/visa/affiliate/register" \ -H "Content-Type: application/json" \ -d '{ "name": "Ada Lovelace", "email": "[email protected]", "website": "https://travelblog.example", "payment_method": "paypal", "payment_info": "[email protected]", "source": "web" }'
{
"affiliate_id": "aff_4f2a91c8",
"dashboard_url": "https://visa.orizn.app/affiliate?aff=aff_4f2a91c8&email=ada%40example.com",
"message": "Affiliate account ready. Share your link to earn 15% commission."
}/api/v1/visa/affiliate/track-clickpublic — no authfree to quotaRecord an affiliate click
Increment the click counter for an affiliate_id. Fire-and-forget from your landing pages and ad creatives.
{ "affiliate_id": "aff_4f2a91c8" }curl -X POST "https://visa.orizn.app/api/v1/visa/affiliate/track-click" \ -H "Content-Type: application/json" \ -d '{ "affiliate_id": "aff_4f2a91c8" }'
{ "ok": true }/api/v1/visa/affiliate/apply-referralapi key · any planfree to quotaAttach a referral to a user
Bind YOUR account to a referrer after sign-up — typical use is the iOS app collecting a referral code post-registration. Requires your API key: the referred account is the one owning the key. Sets referred_by; the referrer earns 15% on your future subscriptions.
{ "referral_code": "aff_4f2a91c8" }curl -X POST "https://visa.orizn.app/api/v1/visa/affiliate/apply-referral" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "referral_code": "aff_4f2a91c8" }'
{
"ok": true,
"message": "Referral applied successfully. The referrer will earn 15% commission on your future subscriptions.",
"referred_by": "aff_4f2a91c8"
}/api/v1/visa/affiliate/statspublic — no authfree to quotaAffiliate dashboard data
Lifetime + this-month performance for an affiliate account: clicks, conversions, referred users, revenue, commission breakdowns by product and platform, and the 20 most recent transactions. Authenticated via either the orizn_token cookie (Orizn account login) or affiliate_id + email query params.
| Parameter | Type | Required | Description |
|---|---|---|---|
| affiliate_id | string | No | Required when no session cookie. Format aff_<8 hex>. |
| string | No | Required when no session cookie. Must match the affiliate's email. |
curl "https://visa.orizn.app/api/v1/visa/affiliate/stats"
{
"affiliate_id": "aff_4f2a91c8",
"active": true,
"source": "web",
"clicks": 1423,
"conversions": 28,
"referred_users": 28,
"revenue_cents": 419800,
"commission_rate": 0.15,
"commission_cents": 62970,
"this_month": { "transactions": 7, "revenue_cents": 138600, "commission_cents": 20790 },
"by_product": {
"orizn.visa.premium.annual": { "transactions": 18, "commission_cents": 47100 },
"orizn.visa.premium.monthly": { "transactions": 10, "commission_cents": 15870 }
},
"by_platform": { "ios": 22, "web": 6 },
"recent_transactions": [
{ "product": "orizn.visa.premium.annual", "platform": "ios", "amount_cents": 19800, "commission_cents": 2970, "date": "2026-05-30T18: 14: 00Z" }
],
"payment_method": "paypal",
"payment_info_masked": "a***@paypal.com",
"links": {
"visa_app": "https://visa.orizn.app?aff=aff_4f2a91c8",
"guide": "https://visa.orizn.app/guide?aff=aff_4f2a91c8",
"extension": "https://visa.orizn.app/extension?aff=aff_4f2a91c8",
"api": "https://visa.orizn.app/api?aff=aff_4f2a91c8"
}
}30 Data Points
Every GET /api/v1/visa response carries up to 32 fields. Fields not relevant to a given pair are null, an empty array, or omitted — you can rely on the shape, not on presence.
| field | Type | Description |
|---|---|---|
| passport | string | ISO 3166-1 alpha-3 (e.g. FRA) |
| destination | string | ISO 3166-1 alpha-3 (e.g. JPN) |
| requirement | enum | visa_free | visa_required | e_visa | visa_on_arrival | eta | no_admission |
| visa_free_days | int | null | Number of days allowed without a visa (null if visa required) |
| visa_required | bool | True if any visa formality is needed |
| description | string | Localized human-readable summary |
| documents_required | string[] | Documents to bring/submit |
| process | string[] | Step-by-step application process |
| tips | string[] | Travel tips |
| country_info | object | Currency, language, timezone, capital |
| verified | bool | True only when an official source confirms this exact pair |
| source | string | null | Where the value came from (e.g. official, manual) |
| source_url | string | null | Official page this pair was read from — null until the pair has been re-sourced |
| last_verified_at | string | null | ISO 8601 date source_url was last read — null whenever source_url is null |
| requirement_status | string | Only on legally unsettled pairs. Currently the single value uncertain — key absent otherwise |
| requirement_status_note | string | null | Plain-English reason the regime is unsettled, safe to show to a traveler |
| field | Type | what it tells you |
|---|---|---|
| transit_visa | object | Transit visa rules and free transit hours at top hubs |
| passport_validity_months | int | Minimum passport validity required at entry |
| visa_fee | object | Single-entry and multiple-entry visa cost, with currency |
| processing_days | object | Standard / express / rush processing times |
| photo_specs | object | Photo dimensions (mm), background, glasses & head-covering rules |
| vaccinations_required | string[] | Mandatory vaccines (e.g. yellow_fever) |
| insurance_required | object | Minimum travel insurance coverage required |
| dual_nationality_warnings | string[] | Warnings for dual-nationals (e.g. military service) |
| stamp_warnings | string[] | Passport stamps that may block entry |
| minor_rules | object | Rules for travelers under 18 |
| overstay_penalty | object | Fine per day, ban duration, criminal liability |
| entry_by_mode | object | Different stay limits for air / land / sea arrivals |
| remote_work_visa | object | Digital nomad visa availability, duration, fee |
| extension_rules | object | Whether the stay can be extended, max days, fee, where |
| reciprocity_history | object[] | Historical policy changes between the two countries |
| safety | object | Travel advisory level (1–4) with source and last update |
| best_apply_period | string | Recommended application window |
| health_requirements | object | COVID test, vaccination proof, quarantine, screenings |
| embassy.your_embassy_at_destination | object | Your country's embassy at the destination — emergencies |
| embassy.visa_application_embassy | object | Destination's embassy in your country — where to apply |
Data quality
Every /visa response carries source_url and last_verified_at: the official page a pair was read from, and the date it was last read. Cite them in your own UI — that is what they are for.
Both fields are null on pairs that have not been re-sourced yet, and that is most of them. 56 of the 199 passport countries currently have an official source on file; the rest are answered from the consolidated dataset without a citation. A null here means we cannot show you a document, not that the answer is wrong — but if your product makes a claim a user could act on, treat a null as unverified and say so.
Coverage grows; the numbers above are a snapshot, not a ceiling. Do not branch on the count — branch on the field.
Most visa datasets answer every pair with a definite requirement, because their schema has no way to say anything else. Some pairs do not have a definite answer. When a country leaves a bloc whose freedom-of-movement treaty was the legal basis for entry, what happens at the border and what the law guarantees stop being the same thing. Burkina Faso, Mali and Niger left ECOWAS on 2025-01-29: visa-free movement between them and the remaining member states now rests on unilateral, revocable declarations rather than on a treaty.
On those pairs the response carries requirement_status: "uncertain" and a requirement_status_note you can show to a traveler verbatim. requirement itself is untouched — it stays visa_free, because that is what happens at the border today. The status says the right no longer exists; the requirement says the practice continues. Both are true, and a comparison test found no other source that reports the difference.
Because it is a separate, additive field, a client that ignores it sees no change at all — nothing breaks, you simply keep the old blind spot. The key is absent on every pair that is not affected, so test for presence, not for a value. Currently uncertain is the only value.
Three gaps, stated here because an endpoint you are meant to defend a decision with has to name its blind spots. They also come back inside every response, in the unknowns array, per field and per country — you never have to remember this page at runtime.
- Transit is a dead end on most of the map. A transit rule exists for 44 of 238 destinations and is recorded per transited country, not per nationality — so even where it exists it never says whether this passport may transit. Every other destination returns status: "unknown". Confirm transit with the carrier, always.
- residence and purpose are accepted, echoed back, and not modelled. The dataset has no residence dimension and covers short-stay tourism only. Send a residence different from the passport, or a purpose other than tourism, and you get an entry in unknowns saying exactly that — a residence permit may grant rights this endpoint cannot see.
- Length of stay is only computed where a numeric allowance exists. visa_free_days is the one figure the dataset holds; where the permitted stay depends on the permit issued, max_stay is unknown rather than a parsed guess, and no accumulation blocker can be raised for that country.
verdict: "no_blocker_found" is worded the way it is on purpose. It means the checks that could run found nothing — not that the trip is cleared.
Plan matrix
| feature | Free | hobby $9 | starter $49 | pro $199 | business $699 |
|---|---|---|---|---|---|
| Monthly requests | 50 | 10,000 | 30,000 | 250,000 | 1,000,000 |
| Burst rate (req/s) | 10 | 25 | 50 | 100 | 200 |
| /visa, /visa/check | ✓ (en only) | ✓ (15 langs) | ✓ (15 langs) | ✓ | ✓ |
| /visa — extended fields | stubs | stubs | all except remote-work, reciprocity | all 32 | all 32 |
| /visa/bulk (all destinations) | — | ✓ | ✓ | ✓ | ✓ |
| /visa/group (multi-passport) | — | ✓ | ✓ | ✓ | ✓ |
| /visa/decision (itinerary) | ✓ | ✓ | ✓ | ✓ | ✓ |
| Device push subscriptions | ✓ | ✓ | ✓ | ✓ | ✓ |
| Webhooks (server-to-server) | — | — | — | — | ✓ |
| Team subkeys | — | — | — | — | ✓ |
| Score, compare, stats, live | ✓ (public) | ✓ | ✓ | ✓ | ✓ |
Enterprise plans add unlimited requests, custom burst rates, on-prem deployment, dedicated IPs, and a 99.95% SLA. Talk to us.
5. Rate Limits
Quotas reset on the 1st of each calendar month (UTC). Only the five counted endpoints — /visa, /visa/check, /visa/bulk, /visa/group and /visa/decision — increment your monthly counter. Score, stats, live, devices, webhooks and team-keys management are free against your quota.
| Plan | Price | Monthly Limit | Rate | highlights |
|---|---|---|---|---|
| Free | $0 | 50 | 10 req/s | /check + /visa (en only) + public endpoints |
| Starter | $49/mo | 30,000 | 50 req/s | + all 15 languages, extended fields |
| Pro | $199/mo | 250,000 | 100 req/s | + /bulk, remote-work-visa, reciprocity history |
| Business | $699/mo | 1,000,000 | 200 req/s | + webhooks, team subkeys, SLA |
| Enterprise | Custom | Unlimited | Custom | + on-prem, dedicated IP, 99.95% SLA |
Over your quota you receive HTTP 429 with X-RateLimit-Reset indicating when the next window opens.
Response headers
| header | value | where |
|---|---|---|
| X-RateLimit-Limit | Monthly quota for your plan | /visa, /visa/check, /visa/bulk |
| X-RateLimit-Remaining | Remaining calls in this month | /visa, /visa/check, /visa/bulk |
| X-RateLimit-Reset | ISO timestamp of next reset (sent only on 429) | /visa, /visa/check, /visa/bulk |
| X-Plan | Your current plan (free, starter, pro, business, enterprise) | /visa, /visa/check, /visa/bulk |
| X-Powered-By | orizn Visa API v1 | All authenticated endpoints |
| X-Orizn-Upgrade | URL to the upgrade page (only on free) | /visa, /visa/check |
| Vary | Accept (set when Markdown negotiation is available) | /visa, /visa/check |
| Cache-Control | public, max-age=3600 (stats) · max-age=5 (live/recent) · no-store (live) | Public endpoints |
6. Error Codes
Errors return a JSON body with a stable shape. Use the HTTP status for routing, the body for diagnostics, and the per-endpoint error pills above when you want to know exactly which 4xx code comes from which condition.
| Code | Meaning | typical cause |
|---|---|---|
| 400 | Bad Request | Missing or malformed parameter — non-ISO3 code, unsupported lang, invalid body |
| 401 | Unauthorized | Missing x-api-key header / api_key query / orizn_token cookie |
| 403 | Forbidden | Invalid or inactive key, or your plan does not include this endpoint |
| 404 | Not Found | No data for this pair / resource not owned by you |
| 409 | Conflict | Unique-constraint race (affiliate signup) — safe to retry |
| 429 | Too Many Requests | Monthly quota or burst rate exceeded |
| 500 | Internal Error | Transient backend issue — retry with exponential backoff |
{
"error": "Monthly limit exceeded (50 req/month on free plan). Upgrade at https://visa.orizn.app",
"plan": "free",
"limit": 50,
"upgrade_url": "https://visa.orizn.app/visa-api/pricing"
}7. Supported Languages
Pass lang as a query parameter (or in the JSON body for POSTs). Defaults to en. Non-English requires the Starter plan or above.
Official SDKs
Hand-written wrappers — fully typed, with retries, rate-limit backoff, and a structured error type. Same 30 data points everywhere.
npm install orizn # JavaScript / TypeScript pip install orizn # Python cargo add orizn # Rust npx orizn-visa-mcp # MCP server (Claude, Cursor, Codex) pip install langchain-orizn # LangChain Python npm install @orizn/langchain # LangChain JS
8. Changelog
30 data points + full endpoint reference
- 21 new optional fields on /visa: transit, fees, photo specs, vaccinations, insurance, embassies, safety, overstay penalties, reciprocity history, remote-work visa, extension rules.
- New MCP tool
check_transit_visa; enriched descriptions so agents pick the right tool. - Docs page now covers all 28 public endpoints — visa data, scoring, live, devices, webhooks, team keys, account, affiliate.
- SDK v1.1:
[email protected](npm),orizn==1.1.0(PyPI),[email protected]. - Backward-compatible — old clients keep working, new fields are additive.
Launch
- 47,362 passport/destination pairs covered
- 15 supported languages (en, fr, es, pt, de, it, ja, ko, zh, ru, ar, hi, th, vi, tl)
- Endpoints: check, visa, bulk, changes, stats, register
- Plans: Free (50/mo), Starter ($49), Pro ($199), Business ($699), Enterprise
- Dashboard with usage analytics, billing and interactive documentation