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.
Get a free API key

Walkthrough

1. Install

Terminal
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.

quickstart.mjs
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);
Output · node quickstart.mjs — real stdout
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.

Always do this
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 · 401
No 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 · 403
API 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-api

Re-copy from the dashboard.

check("FR", "JP") — alpha-2 codes

OriznBadRequestError · 400
passport 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 · 404
No 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 · 429
The 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.

shortlist.mjs
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`);
}
Output · node shortlist.mjs — real stdout
JPN  visa_free       90 days
THA  visa_free       90 days
PRT  visa_free       90 days
VNM  e_visa          - days
IDN  visa_free       - days

Five 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.