curl · any language

REST API

The layer everything else sits on. Four endpoints, one header, no SDK required.

What was executed to write this page

Every request and response on this page was executed with curl against https://visa.orizn.app on the day of writing. The error bodies are verbatim from /api/v1/visa/check; /api/v1/visa returns the same status codes with different wording, so match on the status, not on the message.

Before you start

  • An Orizn API key. Free tier: 50 requests/month, no credit card — get one at visa.orizn.app/visa-api. It is 5 requests until you confirm your email, so click the link in the confirmation mail.
  • curl, or any HTTP client. There is nothing else to install.
  • Country codes are ISO 3166-1 alpha-3 — FRA, JPN, USA. Alpha-2 codes (FR, JP) are rejected with a 400, they are not silently converted.
Get a free API key

Walkthrough

1. Confirm the API is reachable, without a key

/stats is public. If this returns, your network is fine and any later failure is about the key.

Terminal
curl -s "https://visa.orizn.app/api/v1/visa/stats"
Output · Response, trimmed to the coverage block
{
  "coverage": {
    "visa_details": 40027,
    "passports": 199,
    "destinations": 202,
    "passport_index_pairs": 39999,
    "translations": 560378,
    "languages": 15
  },
  "supported_languages": [
    { "code": "fr", "name": "Francais" }, { "code": "en", "name": "English" },
    { "code": "es", "name": "Espanol" },  { "code": "pt", "name": "Portugues" },
    { "code": "de", "name": "Deutsch" },  { "code": "it", "name": "Italiano" },
    { "code": "ja", "name": "Japanese" }, { "code": "ko", "name": "Korean" },
    { "code": "zh", "name": "Chinese" },  { "code": "ru", "name": "Russian" },
    { "code": "ar", "name": "Arabic" },   { "code": "hi", "name": "Hindi" },
    { "code": "th", "name": "Thai" },     { "code": "vi", "name": "Vietnamese" },
    { "code": "tl", "name": "Filipino" }
  ],
  "api_version": "1.0"
}

2. Your first authenticated call

The key goes in an x-api-key header. There is no OAuth dance, no token exchange.

Terminal
export ORIZN_API_KEY=orizn_visa_...

curl -s -H "x-api-key: $ORIZN_API_KEY" \
  "https://visa.orizn.app/api/v1/visa/check?passport=FRA&destination=JPN"
Output · HTTP 200
{
  "passport": "FRA",
  "destination": "JPN",
  "requirement": "visa_free",
  "visa_free_days": 90,
  "visa_required": false,
  "last_verified": "2026-05-08"
}

3. The full record

/check is the cheap yes-or-no. /api/v1/visa is the whole file on that pair.

Terminal
curl -s -H "x-api-key: $ORIZN_API_KEY" \
  "https://visa.orizn.app/api/v1/visa?passport=FRA&destination=JPN"
Output · HTTP 200 — data.*, trimmed
"description": "France citizens can enter Japan without a visa for a stay of up to 90 days.
                A valid passport is required.",
"documents_required": [
  "Valid passport (6 months minimum)",
  "Return or onward ticket",
  "Proof of accommodation",
  "Proof of sufficient funds"
],
"process": [
  "No prior formalities required",
  "Present valid passport upon arrival",
  "Entry stamp will be issued at border control"
],
"visa_types": [
  { "nom": "Visa exemption (tourism)", "cout": "Free", "duree": "90 days",
    "description": "Tourism, family visit, short business" },
  { "nom": "Working Holiday Visa (WHV)", "cout": "Free", "duree": "1 year",
    "description": "For ages 18-30, annual quota of 1,500 places" }
],
"extension": { "possible": true, "cout": "4,000 JPY", "duree": "90 additional days",
               "lieu": "Regional immigration office" }

4. The same record in another language

Add &lang=. All 15 languages are on every plan, free included — it is the written fields that get translated, not the codes.

Terminal
curl -s -H "x-api-key: $ORIZN_API_KEY" \
  "https://visa.orizn.app/api/v1/visa?passport=FRA&destination=JPN&lang=ja"
Output · HTTP 200 — data.description and data.documents_required
Franceの国民は、最大90日間、ビザなしでJapanに入国できます。有効なパスポートが必要です。

["有効なパスポート(残存6ヶ月以上)", "帰国または乗り継ぎの航空券",
 "宿泊証明", "十分な資金の証明"]

And in French, from the same pair — these are written translations, not machine output at request time:

Output · lang=fr
"Les ressortissants français bénéficient d'une exemption de visa de 90 jours pour le
 Japon. Un accord bilatéral de longue date permet les séjours touristiques sans visa."

5. The four endpoints

  • GET /api/v1/visa/check?passport=&destination= — requirement, visa-free days, last_verified. One request. Any key.
  • GET /api/v1/visa?passport=&destination=&lang= — the full record, up to ~30 fields. One request. Any key.
  • GET /api/v1/visa/bulk?passport=&destination=JPN,USA,THA — up to 25 destinations for one passport. Each destination returned counts as one request. Hobby plan and above.
  • GET /api/v1/visa/stats — coverage of the dataset. No key, no quota.

Quota state is on every authenticated response, so you never have to guess where you stand:

Output · Response headers
x-plan: hobby
x-ratelimit-limit: 10000
x-ratelimit-remaining: 9783

When it goes wrong

Every failure the integration can hand you, with the message it actually prints.

No x-api-key header

401
{"error":"API key required","message":"Get your free API key in 10 seconds — no credit card needed.","signup_url":"https://visa.orizn.app/visa-api","free_tier":"50 requests/month"}

Send the header. Note the machine-readable signup_url — an agent can surface it without parsing prose.

Key unknown or deactivated

403
{"error":"Invalid API key","message":"Get your free API key at https://visa.orizn.app/visa-api"}

Check the dashboard. Whitespace around the key is the usual cause.

Alpha-2 codes, or a missing parameter

400
{"error":"Required: ?passport=FRA&destination=JPN (ISO3 codes)"}

Use ISO 3166-1 alpha-3. FR is not FRA.

Valid codes, but no record for that pair

404
{"error":"Pair not found"}

The dataset covers 199 passports into 202 destinations, which is not every conceivable pair. Treat 404 as 'unknown', never as 'no visa needed'.

Monthly quota spent

429
{"error":"Monthly limit exceeded (50 req/month on free plan). Upgrade at https://visa.orizn.app"} with X-RateLimit-Limit, X-RateLimit-Remaining: 0 and X-RateLimit-Reset

Back off until the monthly reset, or upgrade.

Many distinct destinations for one passport in a day

429
{"error":"Anti-abuse limit: N distinct destinations already queried for passport XXX today (cap C on <plan> plan). Upgrade at ..."} with a Retry-After header pointing at the next UTC midnight

This is the anti-scraping guard, not your monthly quota. Legitimate full-passport pulls should use /bulk.

The 401, 403 and 404 bodies were reproduced live against /api/v1/visa/check and pasted verbatim. /api/v1/visa returns the same codes with different messages — branch on the status code. The 400 is unreachable with an invalid key, since the key is checked before the parameters. The two 429 bodies are quoted from the API's source: the key used to write this page is uncapped.

End to end: a booking-flow entry check

The common shape — the traveller has picked a flight, and you need to tell them what they need before they pay.

check.sh
#!/usr/bin/env bash
set -euo pipefail
: "${ORIZN_API_KEY:?set ORIZN_API_KEY}"

PASSPORT=${1:-FRA}
DEST=${2:-JPN}
LANG_CODE=${3:-en}

http=$(curl -s -o /tmp/visa.json -w "%{http_code}" \
  -H "x-api-key: $ORIZN_API_KEY" \
  "https://visa.orizn.app/api/v1/visa?passport=$PASSPORT&destination=$DEST&lang=$LANG_CODE")

case "$http" in
  200) python3 -c "import json;d=json.load(open('/tmp/visa.json'))['data'];\
print(d['requirement'], d.get('visa_free_days'));\
print('\n'.join(d['documents_required']))" ;;
  401|403) echo "Key problem — see /tmp/visa.json"; exit 1 ;;
  404) echo "No record for $PASSPORT -> $DEST. Do NOT assume visa-free."; exit 2 ;;
  429) echo "Rate limited. Retry-After applies."; exit 3 ;;
  *)   echo "Unexpected $http"; exit 4 ;;
esac

The branch that matters is 404. A pair with no record is unknown, and rendering “no visa needed” for unknown is how a traveller gets turned around at the gate. Fail loud.

Source & reference

Other integrations

50 requests a month, no credit card

All 15 languages included on the free plan. Hit a wall with this tutorial? Mail [email protected] — a tutorial that does not work is a bug.