# Travel Entry Rules (`s-r/travel-rules`) Actor

Visa and entry rules for a passport and destination, merged across official advisories and cross-check sources. Passport validity check, ETIAS/EES/ESTA flags, transit legs, Schengen 90/180, explicit disagreements and confidence. One merged answer per query.

- **URL**: https://apify.com/s-r/travel-rules.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** Travel, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 lookup completeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Travel Entry Rules: visa requirements API for any passport

One query answers the question every traveller and travel product asks: given
this nationality, this destination and this date, what do the official sources
say about visas, passports and entry? This Actor reads official government
advisories and commercial rule tables and combines them into a single merged
answer, with the disagreements left visible and a confidence rating you can
act on.

### What you get

- **Visa verdict per query.** `visa_required` is the pre-departure question: do
  you need to apply or get an authorisation before you board? Visa-free and
  visa-on-arrival both map to `false`; the raw labels stay on `visa_type_raw`
  per source.
- **Merged stay rule.** Allowed stay as the in-scope sources state it (for
  example `30 days`, `90 days in any 180`), with a recorded disagreement when
  sources quote different lengths.
- **Passport validity check.** The rule the sources state (three months, six
  months at entry, duration of stay, the EU three-month plus ten-year issue
  rule) plus a pass or fail check against the passport expiry and travel date
  you send in.
- **ETIAS, EES and ESTA flags.** Scheme membership per nationality, including
  the case that catches people out: a visa-required nationality needs a
  Schengen short-stay visa, not ETIAS.
- **Transit legs.** Each transit country is its own lookup and folds into an
  itinerary verdict (`ok`, `visa_required_for_some_leg`, `passport_risk`,
  `unknown`) with named blockers.
- **Schengen 90/180 calculator.** Days used in the rolling window, days
  remaining, whether a long absence resets the allowance, and whether a
  planned stay fits.
- **Fees and processing times where a source prints them.** Never invented:
  if no free source quotes a figure, the field stays null.
- **Explicit disagreements and confidence rationale.** Every clash is recorded
  with the field, the values per source, a kind (`value`, `taxonomy`,
  `stay_length`, `scope`) and a short rationale. Confidence is `high`,
  `medium`, `low` or `none`, with the reasons spelled out.
- **Rule-change history.** Each run diffs against the previous run's payload
  hashes and reports which sources changed, because entry rules move without
  notice.

### Why look up travel entry rules this way

Visa and entry rules are scattered. One foreign ministry writes for its own
citizens, another for everyone entering, a commercial table for a third
audience, and they disagree in public. A travel product that hardcodes one
table inherits its blind spots and its update lag. A product that asks a
human to reconcile five sources on every booking does not scale.

This Actor does the reconciliation in the open. A source written for another
passport (a British advisory read for a Dutch traveller, for example) is kept
as a baseline and marked out of scope, so it can never confirm the verdict
for your passenger. First-party sources for the queried nationality get extra
weight. Labels that mean the same practical entry, such as visa-free versus
visa-on-arrival, merge to one pre-departure answer while both raw labels stay
on the record. When sources genuinely disagree on the stay length or the
product label, the answer says so instead of picking one quietly.

That makes the output usable for two audiences at once. A traveller support
desk gets a defensible yes or no with the evidence attached. A compliance or
content team gets the disagreement record and the rule-change diff, which is
what you need when an advisory moves and yesterday's copy is now wrong.

### Input

| Field | Required | Type | Notes |
|---|---|---|---|
| `nationality` | yes | string | Passport nationality, ISO2 (`NL`, `ID`, `US`, `GB`, `DE`) |
| `destination` | yes | string | Destination country, ISO2 |
| `transit` | no | string\[] | Transit countries, ISO2. Each leg is looked up on its own |
| `date` | no | string | Travel date `YYYY-MM-DD`, used for passport check and Schengen window |
| `passport_expiry` | no | string | Passport expiry `YYYY-MM-DD`, checked against the validity rule |
| `sources` | no | string\[] | Optional filter to a subset of source names |
| `include_raw` | no | boolean | Also emit full per-source rows on the dataset item |
| `schengen_stays` | no | string\[] | Past stays as `YYYY-MM-DD:YYYY-MM-DD` (entry:exit) |
| `schengen_planned_days` | no | integer | Planned stay length for the 90/180 fit check |

### Output

One dataset row per query. Annotated shape (values from a real lookup):

```json
{
  "query": {
    "nationality": "NL",
    "destination": "ID",
    "transit": [],
    "date": "2026-11-01",
    "passport_expiry": "2027-06-01"
  },
  "answer": {
    "visa_required": true,
    "visa_type": "e_visa",
    "visa_type_raw": {
      "nederlandwereldwijd": "e-visum",
      "idimigrasi": "e-visa",
      "sherpa": "E_VISA"
    },
    "stay_allowed": "30 days",
    "passport_validity_rule": "6 months at entry",
    "passport_check": {
      "checked": true,
      "ok": true,
      "required_until": "2027-05-01",
      "passport_expiry": "2027-06-01"
    },
    "etias_ees_esta": { "etias": false, "ees": false, "esta": false },
    "vaccines": [],
    "fees": null,
    "processing_time": null
  },
  "confidence": "medium",
  "confidence_rationale": [
    "2 in-scope sources agree on visa_required=True",
    "stay length is contested; treat the merged stay as the in-scope weighted pick"
  ],
  "disagreements": [
    {
      "field": "nationality_scope",
      "kind": "scope",
      "values": { "source": "govuk", "row_scope": "GB", "query_nationality": "NL" },
      "rationale": "written for GB nationals; baseline only"
    }
  ],
  "itinerary": {
    "verdict": "visa_required_for_some_leg",
    "blockers": ["ID: pre-departure visa/authorisation required"]
  },
  "schengen": {
    "rule": "90 days in any 180 days",
    "days_used_in_window": 0,
    "days_remaining": 90
  },
  "source_statuses": [
    { "source_name": "nederlandwereldwijd", "status": "ok", "entity_ok": true, "scope_match": true },
    { "source_name": "idimigrasi", "status": "ok", "entity_ok": true },
    { "source_name": "govuk", "status": "ok", "entity_ok": true, "scope_match": false }
  ],
  "history": {
    "rule_changed": false,
    "changed_source_names": []
  },
  "summary": { "lookups": 1, "ok": 7, "partial": 3, "blocked": 2, "error": 0, "entity_hits": 8 }
}
```

Set `include_raw` to add `sources`, `transit` and `legs` with the full
per-source rows (normalised fields, notes and status per source).

### Use cases

**Travel support desk.** Agents answer "do I need a visa for Indonesia with a
Dutch passport?" twenty times a day. One call returns the verdict, the stay
rule, the passport check and the sources behind it, so the reply is consistent
and defensible. When the passenger's passport expires inside the validity
window, `passport_check.ok` is false and the blocker is named in the itinerary.

**Booking and itinerary engines.** A multi-city trip needs the answer for every
leg, not just the headline destination. Send the transit countries and read
`itinerary.verdict`: one blocked connection is enough to flag the whole trip.
The Schengen 90/180 calculator fits a planned stay against past entries, which
is the check long-stay travellers get wrong.

**Compliance and content teams.** Entry rules change without notice. The
`history` block reports which sources moved since the last run, and the
`disagreements` list is the editorial record of where the official sources
diverge. That is the raw material for a changelog or a "last verified" stamp
on a travel page, without anyone retyping advisory text.

**Market research and risk monitoring.** Roll up `answer.visa_required`,
scheme flags and advisory notes across many pairs to see where a passport is
getting harder or easier to move. Because out-of-scope rows are marked as
such, a cross-country comparison does not silently mix one foreign ministry's
advice into another country's numbers.

### How it compares

| | This Actor | expected\_diet/visa-checker-by-nationality | nerolabs/nz-visa-processing-monitor | nexgenwatch/official-travel-advice-mcp |
|---|---|---|---|---|
| Unit | one merged answer per pair | rows from one table | NZ processing times | one FCDO answer per call |
| Sources | official advisories plus per-citizenship cross-checks | single source | one country's immigration | one foreign ministry |
| Disagreements + confidence | yes, recorded | no | no | no |
| Passport check + itinerary | yes | no | no | no |
| Schengen 90/180 | yes | no | no | no |
| Rule-change diff | yes | no | monitor alerts | no |
| Price per 1k results | **$5.00** | about $1.00 per 1k rows plus a start fee | $10.00 per 1k lookups | $50.00 per 1k calls |

Be honest about the trade-off: the bulk catalogue actors are cheaper per row
and fine if you want one flat table. This Actor is for the case where the
answer has to hold up across sources and be explainable. Pricing is
pay-per-event: $0.005 per `lookup`, one charge per merged answer delivered.
All pricing is pay-per-event, you only pay for results you receive. No
actor-start fee, no per-compute-unit charges.

### Limits and gotchas

- **ISO2 codes only.** `nationality` and `destination` are two-letter codes.
  A name like "Indonesia" is rejected rather than guessed.
- **Coverage is widest for the pairs the first-party sources write for.**
  British, American, Dutch and Indonesian passports have a named first-party
  source; other nationalities lean on the per-citizenship cross-checks and
  usually come back at medium or low confidence.
- **A scoped source never confirms another passport.** Expect `scope`
  disagreements whenever a foreign ministry's advice appears in the row for a
  different nationality. That is intentional.
- **Fees and processing times are sparse.** They appear only when a free
  source prints them. Null means "no source said", not "no fee".
- **`etias`, `ees` and `esta` can be null.** A null flag means no source
  addressed the scheme for that pair.
- **One optional upstream needs its own environment key** (`SHERPA_REQUIREMENTS_API_KEY`).
  Without it that source reports `not_configured` and the rest of the merge
  still runs.
- **Cold runs take 20 to 60 seconds** while the source fan-out completes.
  Transit legs add one smaller fan-out each.

### FAQ

**Can I get visa requirements by nationality and destination as JSON?**
Yes. Send `nationality`, `destination` and optional `date` and
`passport_expiry`. One dataset row comes back with the merged verdict, the
raw labels per source and the confidence rationale.

**Does this check passport validity rules for my travel date?**
Yes. Pass `date` and `passport_expiry` and `answer.passport_check` reports the
required validity date and whether the passport clears it.

**Does it cover Schengen 90/180 day calculations?**
Yes. `schengen` reports days used and remaining in the rolling window. Add
`schengen_stays` and `schengen_planned_days` to fit-check a planned stay
against past entries.

**What about transit and connecting flights?**
List transit countries in `transit`. Each leg is looked up as its own
destination and `itinerary.verdict` answers whether the whole trip is
reachable.

**Do I need an API key for the sources?**
No accounts and no keys are needed to run the Actor beyond your Apify token.
One optional source takes its own environment key and degrades gracefully
without it.

### Related Actors

- [Creator Stats](https://apify.com/s-r/creator-stats) — creator and
  influencer stats across platforms from one handle list
- [Reddit Scraper](https://apify.com/s-r/reddit-scraper) — posts, comments and
  redditor history without a login
- [Backlinks Checker](https://apify.com/s-r/backlinks-checker) — referring
  domains and link counts for a domain

# Actor input Schema

## `nationality` (type: `string`):

Passport nationality as an ISO 3166-1 alpha-2 code (NL, ID, US, GB, DE, ...).

## `destination` (type: `string`):

Destination country as an ISO 3166-1 alpha-2 code.

## `transit` (type: `array`):

Optional transit legs as ISO2 codes. Each leg is looked up as its own destination and folded into the itinerary verdict.

## `date` (type: `string`):

Departure date (YYYY-MM-DD). Used for the passport validity check and the Schengen 90/180 window.

## `passport_expiry` (type: `string`):

Passport expiry date (YYYY-MM-DD). Checked against the validity rule the sources state.

## `sources` (type: `array`):

Optional source filter (usstate, govuk, nederlandwereldwijd, kemlu, indnl, youreurope, idimigrasi, sherpa, traveldoc, passportindex, visahq, etias). Empty runs every source.

## `include_raw` (type: `boolean`):

Also emit full per-source rows (sources, transit, legs) on the dataset item. Off keeps the row compact.

## `schengen_stays` (type: `array`):

Past stays inside the Schengen area as "YYYY-MM-DD:YYYY-MM-DD" (entry:exit). Used for the 90/180 fit check.

## `schengen_planned_days` (type: `integer`):

Optional planned stay length in days to fit-check against the 90/180 allowance.

## Actor input object example

```json
{
  "nationality": "NL",
  "destination": "ID",
  "transit": [
    "AE"
  ],
  "date": "2026-11-01",
  "passport_expiry": "2027-06-01",
  "sources": [
    "govuk",
    "sherpa"
  ],
  "include_raw": false,
  "schengen_stays": [
    "2026-05-01:2026-05-31"
  ],
  "schengen_planned_days": 20
}
```

# Actor output Schema

## `results` (type: `string`):

One row per query with the merged visa/passport answer, confidence, disagreements and per-source statuses.

## `output` (type: `string`):

OUTPUT record with counts, confidence, itinerary verdict and free\_tier\_remaining.

## `summary` (type: `string`):

Counts and headline verdicts for the run.

## `errors` (type: `string`):

Failures with a code and a redacted message.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "nationality": "NL",
    "destination": "ID",
    "transit": [],
    "date": "2026-11-01",
    "passport_expiry": "2027-06-01",
    "sources": [],
    "schengen_stays": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/travel-rules").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "nationality": "NL",
    "destination": "ID",
    "transit": [],
    "date": "2026-11-01",
    "passport_expiry": "2027-06-01",
    "sources": [],
    "schengen_stays": [],
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/travel-rules").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "nationality": "NL",
  "destination": "ID",
  "transit": [],
  "date": "2026-11-01",
  "passport_expiry": "2027-06-01",
  "sources": [],
  "schengen_stays": []
}' |
apify call s-r/travel-rules --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/travel-rules"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/ISHXekIFct6qsoBZL/builds/Z7WoiZwe8PmilpMb6/openapi.json
