JavaScript / TypeScript SDK
orizn (npm)
The official npm client. Typed responses, and every failure is a typed error carrying the URL that fixes it.
What was executed to write this page
orizn 1.2.0 installed from npm into an empty project, then two scripts were run under Node 25: the quickstart and the shortlist example. Every output block is their real stdout. The error table was produced by a third script that triggered each failure on purpose.
Before you start
- Node.js 18 or later (fetch is used natively).
- 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.
- 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.
Walkthrough
1. Install
npm install orizn
export ORIZN_API_KEY=orizn_visa_...2. Your first call — the complete file
Save this as quickstart.mjs and run node quickstart.mjs. It is the whole file, not a fragment.
import { Orizn } from "orizn";
const orizn = new Orizn(); // reads ORIZN_API_KEY
const quick = await orizn.check("FRA", "JPN");
console.log(quick.requirement); // requirement code
console.log(quick.visa_free_days); // days allowed without a visa
console.log(quick.last_verified); // date the pair was last re-checked
const visa = await orizn.getVisa("FRA", "JPN", "en");
console.log(visa.description);
console.log(visa.documents_required);
console.log(visa.process);visa_free
90
2026-05-08
France citizens can enter Japan without a visa for a stay of up to 90 days. A valid passport is required.
[
'Valid passport (6 months minimum)',
'Return or onward ticket',
'Proof of accommodation',
'Proof of sufficient funds'
]
[
'No prior formalities required',
'Present valid passport upon arrival',
'Entry stamp will be issued at border control'
]3. The four methods
- check(passport, destination) — cheapest call: requirement, visa-free days, last_verified.
- getVisa(passport, destination, lang?) — the full record.
- bulk(passport, destinations[], lang?) — up to 25 destinations at once. Hobby plan and above. Each destination returned costs one request.
- stats() — dataset coverage. No key needed.
4. Normalise before you compare
requirement carries ten distinct values, not four. Beyond the six obvious ones, 33 pairs come back as not_applicable, partial_restrictions, admission_refused or special. Comparing raw strings against "visa_free" silently files an admission_refused pair under "not visa-free", when in fact no visa unlocks it. normalizeRequirement folds these into the canonical set for you.
import { normalizeRequirement } from "orizn";
normalizeRequirement(visa.requirement);
// "visa_free" | "visa_required" | "e_visa" | "visa_on_arrival" | "eta"
// | "no_admission" | "unknown"And for plan-gated fields, isUpgradeNotice tells you whether you are holding data or a placeholder:
import { isUpgradeNotice } from "orizn";
if (isUpgradeNotice(visa.visa_fee)) {
console.log(visa.visa_fee.upgrade); // "Available on Starter plan or above"
} else {
console.log(visa.visa_fee.single_entry); // { amount: 0, currency: "JPY" }
}When it goes wrong
Every failure the integration can hand you, with the message it actually prints.
new Orizn({ apiKey: "" })
OriznAuthError · 401No API key. Every Orizn endpoint except stats() needs one.
1. Get a free key in 10s (50 req/month, no credit card): https://visa.orizn.app/visa-api
2. export ORIZN_API_KEY=orizn_visa_... (or: new Orizn({ apiKey: "orizn_visa_..." }))The SDK also prints a three-line hint to stderr at construction time, before the first call fails.
Key unknown or deactivated
OriznInvalidKeyError · 403API key rejected: Invalid API key.
Check the key on your dashboard: https://visa.orizn.app/visa-api/dashboard
Or create a new free one: https://visa.orizn.app/visa-apiRe-copy from the dashboard.
check("FR", "JP") — alpha-2 codes
OriznBadRequestError · 400passport must be an ISO 3166-1 alpha-3 code (FRA, JPN, USA) — got "FR".Use alpha-3.
check("FRA", "XXX") — valid shape, no record
OriznNotFoundError · 404No visa data for FRA -> XXX. Both must be ISO 3166-1 alpha-3 codes (FRA, JPN, USA).Treat as unknown, never as visa-free.
Monthly quota spent
OriznRateLimitError · 429The message carries the upgrade URL, so you can surface it directly to the user.Catch it separately from OriznAuthError — one is a billing prompt, the other is a bug.
The 401, 403, 400 and 404 rows are verbatim stdout from a script that triggered each case. The 429 row was not reproduced: the key used to write this page is uncapped.
End to end: rank a shortlist of destinations
A traveller names five candidate countries. You want them ordered by how long they can stay without paperwork.
import { Orizn, normalizeRequirement } from "orizn";
const orizn = new Orizn();
const passport = "BRA";
const shortlist = ["JPN", "THA", "VNM", "IDN", "PRT"];
const rows = await Promise.all(
shortlist.map(async (dest) => {
const r = await orizn.check(passport, dest);
return { dest, requirement: normalizeRequirement(r.requirement), days: r.visa_free_days };
})
);
for (const r of rows.sort((a, b) => (b.days ?? 0) - (a.days ?? 0))) {
console.log(`${r.dest} ${r.requirement.padEnd(15)} ${r.days ?? "-"} days`);
}JPN visa_free 90 days
THA visa_free 90 days
PRT visa_free 90 days
VNM e_visa - days
IDN visa_free - daysFive destinations here means five requests. Past a handful, bulk() does the same job in one call on Hobby and above — and each destination returned still counts as one request against the quota, so it saves round-trips, not budget.
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.