Rust SDK

orizn (crates.io)

The official crate. Async on tokio, typed errors, one dependency to add.

What was executed to write this page

orizn 1.0.1 pulled from crates.io into a fresh cargo project, compiled, and run against the live API. The output blocks are the real stdout of that binary. The known issue below was found by running it, then confirmed in the published crate source.

Before you start

  • A Rust toolchain with cargo.
  • 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. Add the dependency

cargo new visa-quickstart && cd visa-quickstart, then Cargo.toml
[dependencies]
orizn = "1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

2. Your first call — the complete main.rs

src/main.rs
use orizn::Orizn;

#[tokio::main]
async fn main() -> Result<(), orizn::Error> {
    // with_key(...), or Orizn::new() which reads ORIZN_API_KEY
    let client = Orizn::with_key(std::env::var("ORIZN_API_KEY").expect("set ORIZN_API_KEY"));

    let visa = client.get_visa("FRA", "JPN", "en").await?;
    println!("requirement    = {}", visa.requirement);
    println!("visa_free_days = {:?}", visa.visa_free_days);
    println!("description    = {}", visa.description);
    println!("documents      = {:#?}", visa.documents_required);
    Ok(())
}
Output · cargo run — real stdout
requirement    = visa_free
visa_free_days = Some(90)
description    = France citizens can enter Japan without a visa for a stay of up to 90 days. A valid passport is required.
documents      = [
    "Valid passport (6 months minimum)",
    "Return or onward ticket",
    "Proof of accommodation",
    "Proof of sufficient funds",
]

3. Known issue in 1.0.1 — use get_visa, not check

The crate exposes a check() method for cheap yes-or-no lookups. In the currently published version it does not attach the API key to the request, and /api/v1/visa/check has required a key since 2026. It therefore fails for everyone, always:

Output · cargo run, with a valid key, calling client.check("FRA", "JPN")
Error: AuthRequired

get_visa() sends the key correctly and returns everything check() would have, plus the rest of the record. Use it until a fixed crate ships. The JavaScript and Python SDKs already carry the equivalent fix.

When it goes wrong

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

No key, or a rejected key

Error::AuthRequired
Error: AuthRequired

Covers both 401 and 403 — the variant does not distinguish them, so check the key exists before blaming the plan.

Valid codes, no record for that pair

Error::NotFound
Error: NotFound { passport: "FRA", destination: "XXX" }

The variant carries both codes, so you can log which pair was missing without threading context through.

Any use of check() in 1.0.1

Error::AuthRequired
Fails even with a valid key — see the section above.

Call get_visa() instead.

All three rows were reproduced by running the binary. Rate limiting was not reproduced: the key used to write this page is uncapped.

End to end: an entry-rules check in a service

The realistic shape in Rust is a fallible lookup behind your own error type, so a missing pair does not read like a network failure.

Sketch
use orizn::{Orizn, Error};

pub enum Entry { Allowed { days: Option<i32> }, NeedsVisa, Refused, Unknown }

pub async fn entry_rules(c: &Orizn, passport: &str, dest: &str) -> Result<Entry, Error> {
    match c.get_visa(passport, dest, "en").await {
        Ok(v) => Ok(match v.requirement.as_str() {
            "visa_free" | "freedom_of_movement" => Entry::Allowed { days: v.visa_free_days },
            // 52 pairs. No visa unlocks these — do not fold them into NeedsVisa.
            "no_admission" | "admission_refused" => Entry::Refused,
            "not_applicable" | "special" => Entry::Unknown,
            _ => Entry::NeedsVisa,
        }),
        // A pair we have no record for is UNKNOWN, never "no visa needed".
        Err(Error::NotFound { .. }) => Ok(Entry::Unknown),
        Err(e) => Err(e),
    }
}

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.