{
  "openapi": "3.0.3",
  "info": {
    "title": "Orizn Visa API",
    "version": "1.1.0",
    "x-guidance": "Ask for one passport/destination pair and read `data.requirement` — it is the verdict (visa_free, e_visa, visa_on_arrival, eta, visa_required, no_admission). `visa_free_days` holds the allowed stay when there is one. Country codes are ISO 3166-1 alpha-3 (FRA, JPN). Three ways in, pick one: (1) GET /api/x402/visa pays 0.03 USD per call over x402, no account and no key — best for an autonomous agent; (2) POST /api/v1/visa/register returns a free API key for 50 calls a month, then GET /api/v1/visa with the `x-api-key` header; (3) the keyless MCP server at https://visa.orizn.app/mcp answers 30 pairs a day per IP with no account at all. `source_url` and `last_verified_at` are null on pairs not individually re-sourced yet, which is most of them: branch on the field, never assume. Answers are data, not legal advice.",
    "description": "> Visa requirements, fees, embassies, transit rules and 26 more data points\n> for every passport / destination pair in the world — in 15 languages.\n\n\nThe **Orizn Visa API** returns up to **30 data points** for every one of\n**47,362 passport × destination pairs**, in **15 languages**.\n\n## Authentication\n\nThree modes depending on the endpoint:\n\n1. **API key** — `x-api-key` request header (or `?api_key=` query param).\n   This is what you use for product code.\n2. **Public** — no auth at all. Used by `/stats`, `/score`, `/score/compare`,\n   `/live*`, `/register`, `/affiliate/register|track-click|apply-referral`.\n   The `/visa/check` endpoint is also keyless when the request comes from\n   `visa.orizn.app`, `localhost`, or a Chrome extension origin (for the\n   public landing demo and the browser extension).\n3. **Session cookie** — `orizn_token` cookie. Dashboard-only endpoints\n   (key rotation, Stripe checkout/portal). Not callable through a third-party\n   gateway like RapidAPI.\n\n## Quota\n\nFive endpoints count against your monthly quota: `/visa`, `/visa/check`,\n`/visa/bulk` and `/visa/group` bill one request per call whatever the\nnumber of pairs they resolve; `/visa/decision` bills one request per\ndistinct country resolved, and reports the figure in `meta.pairs_charged`.\nEverything else (stats, score, live, devices, webhooks, team keys, account,\naffiliate) is **free against quota**.\n\nQuotas reset on the 1st of each month, UTC.\n\n## Versioning and deprecation\n\n`/v1/` is stable and stays reachable.\n\n- **Additive changes ship without notice** — new fields, new enum members,\n  new endpoints. Parse defensively and ignore what you do not recognise.\n- **Breaking changes never land in v1.** A removed or renamed field, a\n  narrowed type, a removed endpoint: those ship as `/v2`, and `/v1` keeps\n  answering.\n- **Six months of notice** before anything in v1 stops working, counted\n  from the dated entry in the changelog at\n  <https://visa.orizn.app/visa-api/changelog>.\n- During that window every affected response carries `Deprecation` and\n  `Sunset` headers (RFC 9745 / RFC 8594) and a `Link` header to the\n  migration note, so your monitoring sees it before your users do.\n- No silent removals, no shortened windows.\n\nOne endpoint is currently withdrawn: `/api/v1/visa/changes` returns `503`.\nSee its description below.\n\n## Data quality\n\nResponses carry `source_url` and `last_verified_at` — the official page a\npair was read from and the date it was last read. **Both are `null` on\npairs that have not been re-sourced yet, and that is most of them**: 56 of\n199 passport countries currently have an official source on file. Branch on\nthe field, never on the count.\n\nA separate `requirement_status: \"uncertain\"` field appears on the pairs\nwhose legal regime is unsettled — a country that has left a bloc whose\nfreedom-of-movement treaty was the legal basis for entry. It never replaces\n`requirement`, and the key is absent on every unaffected pair.\n\n## Conventions\n\n- All country codes are ISO 3166-1 alpha-3 (`FRA`, `USA`, `JPN`).\n- All timestamps are ISO 8601 with a UTC suffix.\n- Responses are JSON / UTF-8.\n- CORS open on every public endpoint, OPTIONS preflight implemented.\n- `/visa` and `/visa/check` also support `Accept: text/markdown` for a\n  human-readable rendering.\n\n## SDKs\n\n- **JavaScript / TypeScript** — `npm install orizn`\n- **Python** — `pip install orizn`\n- **MCP server** (Claude, Cursor, Codex) — `npx orizn-visa-mcp`\n",
    "termsOfService": "https://visa.orizn.app/legal/terms",
    "contact": {
      "name": "Orizn API team",
      "email": "api@orizn.app",
      "url": "https://visa.orizn.app"
    },
    "license": {
      "name": "Commercial — see Terms",
      "url": "https://visa.orizn.app/legal/terms"
    },
    "x-logo": {
      "url": "https://visa.orizn.app/favicon.png",
      "altText": "Orizn"
    }
  },
  "servers": [
    {
      "url": "https://visa.orizn.app",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Visa data",
      "description": "The flagship endpoints — visa rules, documents, fees, embassies, transit."
    },
    {
      "name": "Passport scoring",
      "description": "Mobility scores and side-by-side comparisons."
    },
    {
      "name": "Live activity",
      "description": "Real-time SSE stream and recent snapshot for social-proof widgets."
    },
    {
      "name": "Push notifications",
      "description": "Subscribe APNs / FCM device tokens to policy-change alerts."
    },
    {
      "name": "Webhooks",
      "description": "Server-to-server policy-change delivery, HMAC-signed."
    },
    {
      "name": "Team keys",
      "description": "Per-environment subkeys sharing the owner-account quota."
    },
    {
      "name": "Account & billing",
      "description": "Self-serve signup, key rotation and Stripe-hosted billing flow."
    },
    {
      "name": "Affiliate program",
      "description": "15% commission on referred subscriptions — web and iOS."
    }
  ],
  "security": [
    {
      "apiKeyHeader": []
    },
    {
      "apiKeyQuery": []
    }
  ],
  "paths": {
    "/api/v1/visa": {
      "get": {
        "tags": [
          "Visa data"
        ],
        "operationId": "getFullVisa",
        "summary": "Full visa intelligence (30 data points)",
        "description": "The flagship endpoint. Returns the full `VisaData` object — documents,\nprocess, fees, embassies, transit visa, vaccinations, safety advisories,\noverstay penalties, and more — in 15 languages.\n\nPlan gating:\n- `free` returns the core fields plus upgrade stubs; non-English requires Starter or above.\n- `starter` unlocks every extended field except `remote_work_visa` and `reciprocity_history`.\n- `pro+` returns all 32 fields including bidirectional embassy info.\n\nCounts against monthly quota.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/PassportQuery"
          },
          {
            "$ref": "#/components/parameters/DestinationQuery"
          },
          {
            "$ref": "#/components/parameters/LangQuery"
          },
          {
            "in": "header",
            "name": "Accept",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "application/json",
                "text/markdown"
              ],
              "default": "application/json"
            },
            "description": "Set to text/markdown for a human-readable Markdown rendering."
          }
        ],
        "responses": {
          "200": {
            "description": "Visa data successfully returned.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Plan": {
                "$ref": "#/components/headers/XPlan"
              },
              "X-Powered-By": {
                "$ref": "#/components/headers/XPoweredBy"
              },
              "Vary": {
                "$ref": "#/components/headers/Vary"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VisaResponse"
                },
                "examples": {
                  "visaFree": {
                    "$ref": "#/components/examples/VisaResponseExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/visa/check": {
      "get": {
        "tags": [
          "Visa data"
        ],
        "operationId": "quickVisaCheck",
        "summary": "Lightweight yes/no visa check",
        "description": "Minimal payload — just the requirement type, allowed stay, and a one-line\ndescription. An `_upgrade_preview` object reports how much richer data\nthe full `/visa` endpoint would return.\n\nKeyless when called from visa.orizn.app, localhost, or a chrome-extension://\norigin. Otherwise requires an API key.\n\nCounts against monthly quota.\n",
        "security": [
          {
            "apiKeyHeader": []
          },
          {
            "apiKeyQuery": []
          },
          {}
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PassportQuery"
          },
          {
            "$ref": "#/components/parameters/DestinationQuery"
          },
          {
            "in": "header",
            "name": "Accept",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "application/json",
                "text/markdown"
              ],
              "default": "application/json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Quick check result.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Plan": {
                "$ref": "#/components/headers/XPlan"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VisaCheckResponse"
                },
                "example": {
                  "passport": "FRA",
                  "destination": "JPN",
                  "requirement": "visa_free",
                  "visa_free_days": 90,
                  "visa_required": false,
                  "_hint": "Upgrade to get documents, process, embassies, photo specs and 28 more fields.",
                  "_upgrade_preview": {
                    "documents_required": 4,
                    "process_steps": 3,
                    "embassy_info": true,
                    "transit_visa": true,
                    "visa_fees": false,
                    "vaccinations": 0,
                    "safety_advisory": "level 1",
                    "languages": 15,
                    "upgrade_url": "https://visa.orizn.app/visa-api/pricing"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/visa/bulk": {
      "get": {
        "tags": [
          "Visa data"
        ],
        "operationId": "bulkVisa",
        "summary": "All destinations for a single passport (Pro+)",
        "description": "Several destinations for one passport in a single round-trip. The\n`destination` parameter is required: pass a comma-separated list of up\nto 25 ISO3 codes.\n\nEach pair returned counts as one request against your monthly quota.\n\nRequires Pro plan or above.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/PassportQuery"
          },
          {
            "in": "query",
            "name": "destination",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[A-Z]{3}(,[A-Z]{3})*$",
              "example": "JPN,THA,BRA"
            },
            "description": "Comma-separated ISO3 list. Omit to return every destination."
          },
          {
            "$ref": "#/components/parameters/LangQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "Bulk result.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Plan": {
                "$ref": "#/components/headers/XPlan"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/visa/group": {
      "get": {
        "tags": [
          "Visa data"
        ],
        "operationId": "groupVisa",
        "summary": "Group trip — multi-passport intersection (Hobby+)",
        "description": "Pass 2 to 10 passports and get back every destination reachable by\n**all** of them, with the per-passport breakdown and the group's\nworst-case requirement.\n\nA destination qualifies when every passport is `visa_free`, `eta`,\n`visa_on_arrival` or `e_visa` — narrow or widen with `allow`\n(e.g. `allow=visa_free` for strictly visa-free).\n`group_visa_free_days` is the **minimum** allowed stay across the\ngroup, i.e. the binding constraint for a shared trip. Destinations\nare sorted easiest-first.\n\nOne group call counts as **1** request against your monthly quota.\n\nRequires Hobby plan or above.\n",
        "parameters": [
          {
            "in": "query",
            "name": "passports",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Z]{3}(,[A-Z]{3}){1,9}$",
              "example": "USA,FRA,IND"
            },
            "description": "Comma-separated ISO3 list, 2 to 10 distinct codes."
          },
          {
            "in": "query",
            "name": "allow",
            "required": false,
            "schema": {
              "type": "string",
              "example": "visa_free,eta,visa_on_arrival,e_visa",
              "default": "visa_free,eta,visa_on_arrival,e_visa"
            },
            "description": "Comma-separated requirement types that qualify."
          }
        ],
        "responses": {
          "200": {
            "description": "Destinations reachable by every passport in the group.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Plan": {
                "$ref": "#/components/headers/XPlan"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GroupResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/visa/decision": {
      "post": {
        "tags": [
          "Visa data"
        ],
        "operationId": "decideItinerary",
        "summary": "Itinerary decision — answers a trip, not a pair",
        "description": "Post a whole trip — passport, ordered stops with dates, transit stops,\npassport expiry — and get a per-step decision plus the blockers that\nonly a full itinerary reveals:\n\n- days **accumulated** across repeat visits to the same country\n  (two 20-day stops in one country are not two independent stays);\n- passport validity measured against **each arrival date**;\n- a passport that expires before the last exit.\n\nEvery field states its `granularity` (`pair` or `destination`), and\nevery gap comes back as `status: \"unknown\"` **with a reason** — never as\na default value. `verdict` is `no_blocker_found` or `blocked`: it\nreports what the data supports, it is not legal advice.\n\n### Known gaps, by design\n\n- **Transit**: a transit rule exists for 44 of 238 destinations and is\n  recorded *per transited country, not per nationality* — so even where\n  it exists it never decides whether this passport may transit. The\n  other 158 return `status: \"unknown\"`. Confirm with the carrier.\n- **`residence` and `purpose`** are accepted and echoed back but **not\n  modelled**: the dataset has no residence dimension and covers\n  short-stay tourism only. Supplying either adds an entry to `unknowns`.\n- **Length of stay** is computed only where `visa_free_days` holds a\n  number; where the stay depends on the permit issued, `max_stay` is\n  `unknown` rather than a parsed guess.\n\n### Billing\n\nOne request per **distinct country resolved** (stops + transit), like\n`/bulk`. A 3-country trip costs 3. `meta.pairs_charged` reports exactly\nwhat was billed. Maximum 12 itinerary steps and 12 transit countries\nper call.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DecisionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-step decision, blockers and unknowns.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Plan": {
                "$ref": "#/components/headers/XPlan"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DecisionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, non-ISO3 code, non-calendar date (`2026-02-31` is\nrejected), `to` before `from`, or more than 12 steps. The body\ncarries a worked `example` of a valid request.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No visa data for that passport at all — check the ISO3 code."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/visa/changes": {
      "get": {
        "tags": [
          "Visa data"
        ],
        "operationId": "getChanges",
        "summary": "Policy change feed — WITHDRAWN, returns 503",
        "deprecated": true,
        "description": "**This endpoint is not available and is not part of any plan.**\n\nIt returns `503` with `Retry-After: 86400` for every caller.\n\nThe previous feed compared two internal tables rather than official\ngazettes, so it was withdrawn rather than sold as verified change\ndata. It will return only once every event carries a named, verified\nofficial source. No client integration was affected — the endpoint had\nnever been called.\n\nThe parameters below are kept for reference only; none of them are\nhonoured while the feed is withdrawn.\n",
        "parameters": [
          {
            "in": "query",
            "name": "passport",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ISO3"
            },
            "description": "Filter by passport country."
          },
          {
            "in": "query",
            "name": "destination",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ISO3"
            },
            "description": "Filter by destination country."
          },
          {
            "in": "query",
            "name": "since",
            "required": false,
            "schema": {
              "type": "string",
              "example": "2026-01-01"
            },
            "description": "ISO 8601 timestamp or YYYY-MM-DD."
          },
          {
            "in": "query",
            "name": "type",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ChangeType"
            }
          },
          {
            "in": "query",
            "name": "severity",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "minor",
                "major"
              ]
            }
          },
          {
            "in": "query",
            "name": "wishlist",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[A-Z]{3}(,[A-Z]{3})*$",
              "example": "THA,JPN,BRA"
            },
            "description": "Comma-separated ISO3 list."
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "503": {
            "description": "Feed withdrawn pending verified official sources.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string",
                  "example": "86400"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "The policy-change feed is being rebuilt on verified official sources."
                    },
                    "status": {
                      "type": "string",
                      "example": "unavailable"
                    },
                    "contact": {
                      "type": "string",
                      "example": "hello@orizn.app"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/x402/visa": {
      "get": {
        "tags": [
          "Visa data"
        ],
        "operationId": "getVisaX402",
        "x-payment-info": {
          "price": {
            "mode": "fixed",
            "currency": "USD",
            "amount": "0.030000"
          },
          "protocols": [
            {
              "x402": {}
            }
          ]
        },
        "summary": "Full visa record, paid per request over x402 (no account, no API key)",
        "description": "Same record as `GET /api/v1/visa` on a Pro plan, but paid per call over the\nx402 protocol instead of an API key: 0.03 USD in USDC on Base.\n\nCall it once without payment and it answers **402** with a\n`PAYMENT-REQUIRED` header describing the offer; an x402 client signs a\n`TransferWithAuthorization` (EIP-3009) and repeats the call. The payer needs\nUSDC but no ETH — the facilitator submits the transaction and pays the gas.\n\nSettlement happens only after the handler returns a response below 400, so a\n404 on an uncovered pair is never charged.\n\nFree alternatives, if paying per call is not what you want:\n`POST /api/v1/visa/register` for a free key, or the keyless MCP server at\n<https://visa.orizn.app/mcp>.\n",
        "security": [],
        "parameters": [
          {
            "name": "passport",
            "in": "query",
            "required": true,
            "description": "Passport country, ISO 3166-1 alpha-3.",
            "schema": {
              "$ref": "#/components/schemas/ISO3"
            }
          },
          {
            "name": "destination",
            "in": "query",
            "required": true,
            "description": "Destination country, ISO 3166-1 alpha-3.",
            "schema": {
              "$ref": "#/components/schemas/ISO3"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Response language, ISO 639-1. Defaults to en.",
            "schema": {
              "$ref": "#/components/schemas/Language"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Payment settled — the full visa record.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "402": {
            "description": "Payment required. The `PAYMENT-REQUIRED` header carries the base64 offer: amount, asset, network and payTo.",
            "headers": {
              "PAYMENT-REQUIRED": {
                "description": "Base64 x402 v2 payment requirements.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "passport or destination is not an ISO 3166-1 alpha-3 code."
          },
          "404": {
            "description": "No data for this pair. Not charged."
          },
          "503": {
            "description": "Pay-per-request is disabled on this deployment, or the facilitator is unreachable."
          }
        }
      }
    },
    "/api/v1/visa/stats": {
      "get": {
        "tags": [
          "Visa data"
        ],
        "operationId": "getStats",
        "summary": "Coverage statistics (public)",
        "description": "Public, no auth, edge-cached for one hour.",
        "security": [],
        "responses": {
          "200": {
            "description": "Coverage statistics.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=3600"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StatsResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/visa/score": {
      "get": {
        "tags": [
          "Passport scoring"
        ],
        "operationId": "getPassportScore",
        "summary": "Passport mobility score (public)",
        "description": "Composite mobility score and global rank for one passport — factoring\nin visa-free / visa-on-arrival / e-visa access, destination diversity\nand an economic weighting. Public.\n",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/PassportQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "Passport score and breakdown.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScoreResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/visa/score/compare": {
      "get": {
        "tags": [
          "Passport scoring"
        ],
        "operationId": "comparePassportScores",
        "summary": "Compare two passports (public)",
        "description": "Side-by-side comparison of two passports — individual scores, set\ndifference of destinations they unlock, and a normalised combined score\n(max 1000). Powers dual-citizenship calculators.\n",
        "security": [],
        "parameters": [
          {
            "in": "query",
            "name": "passport1",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ISO3"
            }
          },
          {
            "in": "query",
            "name": "passport2",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ISO3"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Comparison result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompareResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/visa/live": {
      "get": {
        "tags": [
          "Live activity"
        ],
        "operationId": "liveStream",
        "summary": "Live activity Server-Sent Events stream (public)",
        "description": "Each successful /visa or /visa/check call worldwide produces an event\nwith {passport, destination, timestamp}. A `: keepalive` comment is\nemitted every 2 s when there's no new traffic. Public.\n",
        "security": [],
        "responses": {
          "200": {
            "description": "Server-Sent Events stream.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "no-store"
                }
              },
              "Connection": {
                "schema": {
                  "type": "string",
                  "example": "keep-alive"
                }
              }
            },
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "example": "data: {\"passport\":\"USA\",\"destination\":\"JPN\",\"timestamp\":\"2026-05-31T09:14:00Z\"}\n\ndata: {\"passport\":\"IND\",\"destination\":\"ARE\",\"timestamp\":\"2026-05-31T09:13:58Z\"}\n\n: keepalive\n"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/visa/live/recent": {
      "get": {
        "tags": [
          "Live activity"
        ],
        "operationId": "liveRecent",
        "summary": "Recent activity snapshot (public)",
        "description": "Same data as /live but as a JSON snapshot. Edge-cached for 5 s.",
        "security": [],
        "responses": {
          "200": {
            "description": "Recent activity snapshot.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=5"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LiveRecentResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/visa/devices": {
      "post": {
        "tags": [
          "Push notifications"
        ],
        "operationId": "registerDevice",
        "summary": "Register a device for push notifications",
        "description": "Subscribe an APNs / FCM device token to receive push notifications when\nvisa policies change. Idempotent on device_token.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceRegisterBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Device registered or updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "device_id": {
                      "type": "integer",
                      "example": 42
                    }
                  },
                  "required": [
                    "device_id"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/visa/devices/{id}": {
      "parameters": [
        {
          "in": "path",
          "name": "id",
          "required": true,
          "schema": {
            "type": "integer",
            "example": 42
          },
          "description": "Device subscription id."
        }
      ],
      "patch": {
        "tags": [
          "Push notifications"
        ],
        "operationId": "updateDevice",
        "summary": "Update a device subscription",
        "description": "At least one field is required.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceUpdateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscription updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "boolean",
                      "example": true
                    },
                    "device_id": {
                      "type": "integer",
                      "example": 42
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Push notifications"
        ],
        "operationId": "deleteDevice",
        "summary": "Unsubscribe a device",
        "responses": {
          "200": {
            "description": "Subscription deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean",
                      "example": true
                    },
                    "device_id": {
                      "type": "integer",
                      "example": 42
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/visa/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhooks",
        "summary": "List your webhooks (Business+)",
        "responses": {
          "200": {
            "description": "List of webhooks.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhooks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "createWebhook",
        "summary": "Create a webhook (Business+)",
        "description": "Register a URL to receive POSTs whenever a policy change matches your\nfilter. Response carries a one-time secret — store it now. Verify with\nHMAC-SHA256 of the raw body keyed by the secret.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookCreateBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhook": {
                      "$ref": "#/components/schemas/WebhookWithSecret"
                    },
                    "message": {
                      "type": "string",
                      "example": "Webhook created. Store the secret — it won't be shown again."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "deleteWebhook",
        "summary": "Delete a webhook (Business+)",
        "parameters": [
          {
            "in": "query",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 17
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean",
                      "example": true
                    },
                    "id": {
                      "type": "integer",
                      "example": 17
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/visa/team-keys": {
      "get": {
        "tags": [
          "Team keys"
        ],
        "operationId": "listTeamKeys",
        "summary": "List team subkeys (Business+)",
        "responses": {
          "200": {
            "description": "List of team keys.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "team_keys": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TeamKey"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Team keys"
        ],
        "operationId": "createTeamKey",
        "summary": "Create a team subkey (Business+)",
        "description": "Mint a new team subkey. Prefix orizn_visa_team_. Shares quota with the owner account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "ci-staging"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Team key created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "team_key": {
                      "$ref": "#/components/schemas/TeamKey"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "delete": {
        "tags": [
          "Team keys"
        ],
        "operationId": "deactivateTeamKey",
        "summary": "Deactivate a team subkey (Business+)",
        "description": "Soft delete — flips active to false.",
        "parameters": [
          {
            "in": "query",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 7
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Team key deactivated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deactivated": {
                      "type": "boolean",
                      "example": true
                    },
                    "team_key": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "example": 7
                        },
                        "name": {
                          "type": "string",
                          "example": "ci-staging"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/visa/register": {
      "post": {
        "tags": [
          "Account & billing"
        ],
        "operationId": "signup",
        "summary": "Sign up (get a free API key)",
        "description": "Self-serve signup. Returns a free-plan API key (50 req/month).\nIdempotent on email — re-registering returns the existing key.\n",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "email"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "Ada Lovelace"
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "example": "ada@example.com"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "API key created or returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "api_key": {
                      "type": "string",
                      "example": "orizn_visa_a06113a2e4f0..."
                    },
                    "plan": {
                      "type": "string",
                      "example": "free"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/api/v1/visa/auth/ensure-key": {
      "get": {
        "tags": [
          "Account & billing"
        ],
        "operationId": "ensureKey",
        "summary": "Get the logged-in user's API key (session-only)",
        "description": "Dashboard helper. Session-cookie only — not callable through RapidAPI.\n",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "User and API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnsureKeyResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/visa/auth/regenerate-key": {
      "post": {
        "tags": [
          "Account & billing"
        ],
        "operationId": "regenerateKey",
        "summary": "Rotate the API key (session-only)",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Key rotated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "api_key": {
                      "type": "string",
                      "example": "orizn_visa_b71224..."
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/visa/stripe/checkout": {
      "post": {
        "tags": [
          "Account & billing"
        ],
        "operationId": "stripeCheckout",
        "summary": "Start a paid-plan checkout (session-only)",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "plan"
                ],
                "properties": {
                  "plan": {
                    "type": "string",
                    "enum": [
                      "hobby",
                      "starter",
                      "pro",
                      "business"
                    ]
                  },
                  "billing": {
                    "type": "string",
                    "enum": [
                      "monthly",
                      "annual"
                    ],
                    "default": "monthly"
                  },
                  "affiliate_id": {
                    "type": "string",
                    "pattern": "^aff_[a-f0-9]{8}$",
                    "example": "aff_4f2a91c8"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stripe Checkout URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "example": "https://checkout.stripe.com/c/pay/cs_test_..."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/visa/stripe/portal": {
      "post": {
        "tags": [
          "Account & billing"
        ],
        "operationId": "stripePortal",
        "summary": "Open the Stripe billing portal (session-only)",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Stripe billing portal URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "example": "https://billing.stripe.com/p/session/..."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/visa/affiliate/register": {
      "post": {
        "tags": [
          "Affiliate program"
        ],
        "operationId": "registerAffiliate",
        "summary": "Become an affiliate (public)",
        "description": "Open a partner account. Returns an affiliate_id (format aff_<8 hex>)\nyou embed in checkout URLs to earn 15% commission. Idempotent on email.\n",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AffiliateRegisterBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing affiliate returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AffiliateRegisterResponse"
                }
              }
            }
          },
          "201": {
            "description": "Affiliate created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AffiliateRegisterResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "409": {
            "description": "Unique-constraint race — safe to retry."
          }
        }
      }
    },
    "/api/v1/visa/affiliate/track-click": {
      "post": {
        "tags": [
          "Affiliate program"
        ],
        "operationId": "trackAffiliateClick",
        "summary": "Record an affiliate click (public)",
        "description": "Increment the click counter. Fire-and-forget.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "affiliate_id"
                ],
                "properties": {
                  "affiliate_id": {
                    "type": "string",
                    "pattern": "^aff_[a-f0-9]{8}$",
                    "example": "aff_4f2a91c8"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Click recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/visa/affiliate/apply-referral": {
      "post": {
        "tags": [
          "Affiliate program"
        ],
        "operationId": "applyReferral",
        "summary": "Attach a referral to a user (public)",
        "description": "Bind an existing user to a referrer after sign-up. Sets referred_by;\nthe referrer earns 15% on the user's future subscriptions.\n",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "referral_code",
                  "email"
                ],
                "properties": {
                  "referral_code": {
                    "type": "string",
                    "pattern": "^aff_[a-f0-9]{8}$",
                    "example": "aff_4f2a91c8"
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "example": "ada@example.com"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Referral applied.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    },
                    "message": {
                      "type": "string"
                    },
                    "referred_by": {
                      "type": "string",
                      "example": "aff_4f2a91c8"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/visa/affiliate/stats": {
      "get": {
        "tags": [
          "Affiliate program"
        ],
        "operationId": "getAffiliateStats",
        "summary": "Affiliate dashboard data",
        "description": "Lifetime + this-month performance for an affiliate account.\n\nAuth: either the orizn_token cookie (dashboard) or affiliate_id +\nemail query params (iOS / API).\n",
        "security": [
          {
            "sessionCookie": []
          },
          {}
        ],
        "parameters": [
          {
            "in": "query",
            "name": "affiliate_id",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^aff_[a-f0-9]{8}$",
              "example": "aff_4f2a91c8"
            },
            "description": "Required when no session cookie is present."
          },
          {
            "in": "query",
            "name": "email",
            "required": false,
            "schema": {
              "type": "string",
              "format": "email"
            },
            "description": "Required when no session cookie is present."
          }
        ],
        "responses": {
          "200": {
            "description": "Affiliate stats.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AffiliateStatsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Pass your API key in the x-api-key request header. Recommended for\nproduction code. Get yours at https://visa.orizn.app.\n"
      },
      "apiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "api_key",
        "description": "Alternative when you cannot set headers — pass the key as ?api_key= query param."
      },
      "sessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "orizn_token",
        "description": "Dashboard session cookie. Used by key rotation, Stripe checkout/portal\nand affiliate stats. Cannot be used through a third-party gateway like\nRapidAPI — these endpoints are dashboard-only.\n"
      }
    },
    "parameters": {
      "PassportQuery": {
        "in": "query",
        "name": "passport",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/ISO3"
        },
        "description": "ISO 3166-1 alpha-3 passport code, e.g. FRA."
      },
      "DestinationQuery": {
        "in": "query",
        "name": "destination",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/ISO3"
        },
        "description": "ISO 3166-1 alpha-3 destination code, e.g. JPN."
      },
      "LangQuery": {
        "in": "query",
        "name": "lang",
        "required": false,
        "schema": {
          "$ref": "#/components/schemas/Language"
        },
        "description": "Response language. Defaults to en. Non-English requires Starter or above."
      }
    },
    "headers": {
      "XRateLimitLimit": {
        "description": "Monthly quota for your plan.",
        "schema": {
          "type": "integer",
          "example": 3000
        }
      },
      "XRateLimitRemaining": {
        "description": "Remaining calls in this month.",
        "schema": {
          "type": "integer",
          "example": 2873
        }
      },
      "XPlan": {
        "description": "Your current plan.",
        "schema": {
          "type": "string",
          "enum": [
            "free",
            "starter",
            "pro",
            "business",
            "enterprise"
          ]
        }
      },
      "XPoweredBy": {
        "description": "Static identifier.",
        "schema": {
          "type": "string",
          "example": "Orizn Visa API v1"
        }
      },
      "Vary": {
        "description": "Set when Markdown negotiation is available.",
        "schema": {
          "type": "string",
          "example": "Accept"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Missing or malformed parameter.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "passport and destination must be 3-letter ISO3 codes (e.g. FRA, JPN, USA)"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing API key, query param or session cookie.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Missing API key. Get one at https://visa.orizn.app"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Invalid / inactive key, or plan below the endpoint's minimum tier.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "This endpoint requires a paid plan (Starter or above).",
              "current_plan": "free",
              "upgrade_url": "https://visa.orizn.app/visa-api/pricing"
            }
          }
        }
      },
      "NotFound": {
        "description": "No data for the requested resource.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "No visa data found for FRA -> XXX"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Monthly quota or burst rate exceeded.",
        "headers": {
          "X-RateLimit-Reset": {
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-06-01T00:00:00Z"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Monthly limit exceeded (50 req/month on free plan). Upgrade at https://visa.orizn.app",
              "plan": "free",
              "limit": 3000,
              "upgrade_url": "https://visa.orizn.app/visa-api/pricing"
            }
          }
        }
      }
    },
    "examples": {
      "VisaResponseExample": {
        "summary": "Visa-free pair (FRA → JPN)",
        "value": {
          "data": {
            "passport": "FRA",
            "destination": "JPN",
            "requirement": "visa_free",
            "visa_free_days": 90,
            "visa_required": false,
            "description": "French citizens can enter Japan visa-free for up to 90 days.",
            "documents_required": [
              "Valid passport (3 months)",
              "Return or onward ticket"
            ],
            "process": [
              "No prior formalities",
              "Immigration form on arrival"
            ],
            "tips": [
              "Carry proof of sufficient funds"
            ],
            "country_info": {
              "currency": "JPY",
              "language": "Japanese",
              "timezone": "UTC+9",
              "capital": "Tokyo"
            },
            "verified": true,
            "transit_visa": {
              "hubs": [
                {
                  "airport": "NRT",
                  "city": "Tokyo",
                  "transit_visa_required": false,
                  "transit_free_hours": 24
                }
              ]
            },
            "passport_validity_months": 3,
            "visa_fee": {
              "single_entry": {
                "amount": 0,
                "currency": "JPY"
              }
            },
            "processing_days": {
              "standard": null,
              "express": null
            },
            "photo_specs": {
              "width_mm": 35,
              "height_mm": 45,
              "background": "white"
            },
            "vaccinations_required": [],
            "insurance_required": {
              "required": false
            },
            "overstay_penalty": {
              "fine_per_day": "Variable + deportation",
              "ban_days": 365,
              "criminal": true
            },
            "entry_by_mode": {
              "air": 90,
              "land": 90,
              "sea": 90
            },
            "safety": {
              "level": 1,
              "advisory": "Exercise normal precautions",
              "source": "diplomatie.gouv.fr"
            },
            "health_requirements": {
              "covid_test": false,
              "quarantine_days": 0
            },
            "embassy": {
              "your_embassy_at_destination": {
                "name": "Ambassade de France au Japon",
                "city": "Tokyo",
                "phone": "+81 3 5798 6000"
              },
              "visa_application_embassy": {
                "name": "Ambassade du Japon en France",
                "city": "Paris"
              }
            }
          },
          "meta": {
            "lang": "en",
            "api_version": "1.1",
            "coverage": "199 x 238",
            "languages": 15,
            "data_points": 32
          }
        }
      }
    },
    "schemas": {
      "ISO3": {
        "type": "string",
        "pattern": "^[A-Z]{3}$",
        "minLength": 3,
        "maxLength": 3,
        "example": "FRA",
        "description": "ISO 3166-1 alpha-3 country code."
      },
      "Language": {
        "type": "string",
        "enum": [
          "en",
          "fr",
          "es",
          "pt",
          "de",
          "it",
          "ja",
          "ko",
          "zh",
          "ru",
          "ar",
          "hi",
          "th",
          "vi",
          "tl"
        ],
        "default": "en",
        "example": "en"
      },
      "VisaRequirement": {
        "type": "string",
        "enum": [
          "visa_free",
          "visa_required",
          "e_visa",
          "visa_on_arrival",
          "eta",
          "no_admission"
        ],
        "example": "visa_free"
      },
      "ChangeType": {
        "type": "string",
        "enum": [
          "requirement_changed",
          "days_changed",
          "new_policy",
          "suspended",
          "restored",
          "fee_changed",
          "process_changed"
        ]
      },
      "Error": {
        "type": "object",
        "additionalProperties": true,
        "description": "Error envelope. Always carries `error` (human-readable message). Some\nendpoints add extra context — `plan`, `limit`, `current_plan`,\n`upgrade_url`, `signup_url`, etc.\n",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message."
          },
          "plan": {
            "type": "string",
            "description": "Your current plan (sent on 403/429 to drive upsell UI)."
          },
          "current_plan": {
            "type": "string"
          },
          "limit": {
            "type": "integer",
            "description": "Monthly quota of your plan (sent on 429)."
          },
          "upgrade_url": {
            "type": "string",
            "format": "uri"
          },
          "signup_url": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "error"
        ]
      },
      "CountryInfo": {
        "type": "object",
        "properties": {
          "currency": {
            "type": "string",
            "example": "JPY"
          },
          "language": {
            "type": "string",
            "example": "Japanese"
          },
          "timezone": {
            "type": "string",
            "example": "UTC+9"
          },
          "capital": {
            "type": "string",
            "example": "Tokyo"
          }
        }
      },
      "MoneyAmount": {
        "type": "object",
        "properties": {
          "amount": {
            "type": "number",
            "example": 50
          },
          "currency": {
            "type": "string",
            "example": "USD"
          }
        }
      },
      "VisaFee": {
        "type": "object",
        "properties": {
          "single_entry": {
            "$ref": "#/components/schemas/MoneyAmount"
          },
          "multiple_entry": {
            "$ref": "#/components/schemas/MoneyAmount"
          }
        }
      },
      "ProcessingDays": {
        "type": "object",
        "properties": {
          "standard": {
            "type": "integer",
            "nullable": true,
            "example": 5
          },
          "express": {
            "type": "integer",
            "nullable": true,
            "example": 2
          },
          "rush": {
            "type": "integer",
            "nullable": true,
            "example": 1
          }
        }
      },
      "PhotoSpecs": {
        "type": "object",
        "properties": {
          "width_mm": {
            "type": "number",
            "example": 35
          },
          "height_mm": {
            "type": "number",
            "example": 45
          },
          "background": {
            "type": "string",
            "example": "white"
          },
          "glasses_allowed": {
            "type": "boolean",
            "example": false
          },
          "head_covering_allowed": {
            "type": "string",
            "example": "religious only"
          }
        }
      },
      "InsuranceRequired": {
        "type": "object",
        "properties": {
          "required": {
            "type": "boolean",
            "example": true
          },
          "min_coverage": {
            "type": "number",
            "example": 30000
          },
          "currency": {
            "type": "string",
            "example": "EUR"
          }
        }
      },
      "HealthRequirements": {
        "type": "object",
        "properties": {
          "covid_test": {
            "type": "boolean",
            "example": false
          },
          "vaccination_proof": {
            "type": "boolean",
            "example": false
          },
          "health_declaration": {
            "type": "boolean",
            "example": false
          },
          "quarantine_days": {
            "type": "integer",
            "example": 0
          },
          "ebola_screening": {
            "type": "boolean",
            "example": false
          }
        }
      },
      "MinorRules": {
        "type": "object",
        "properties": {
          "solo_travel_min_age": {
            "type": "integer",
            "example": 16
          },
          "single_parent_letter_required": {
            "type": "boolean",
            "example": true
          },
          "notarized_consent_required": {
            "type": "boolean",
            "example": true
          }
        }
      },
      "OverstayPenalty": {
        "type": "object",
        "properties": {
          "fine_per_day": {
            "type": "string",
            "example": "10 EUR"
          },
          "ban_days": {
            "type": "integer",
            "example": 365
          },
          "criminal": {
            "type": "boolean",
            "example": false
          },
          "details": {
            "type": "string"
          }
        }
      },
      "EntryByMode": {
        "type": "object",
        "properties": {
          "air": {
            "type": "integer",
            "example": 90
          },
          "land": {
            "type": "integer",
            "example": 30
          },
          "sea": {
            "type": "integer",
            "example": 90
          }
        }
      },
      "RemoteWorkVisa": {
        "type": "object",
        "properties": {
          "available": {
            "type": "boolean",
            "example": true
          },
          "duration_months": {
            "type": "integer",
            "example": 12
          },
          "fee": {
            "$ref": "#/components/schemas/MoneyAmount"
          },
          "requirements": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ExtensionRules": {
        "type": "object",
        "properties": {
          "possible": {
            "type": "boolean",
            "example": true
          },
          "max_days": {
            "type": "integer",
            "example": 90
          },
          "fee": {
            "type": "string",
            "example": "free"
          },
          "where": {
            "type": "string",
            "example": "Regional Immigration Office"
          },
          "notes": {
            "type": "string"
          }
        }
      },
      "ReciprocityChange": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "example": "2024-01-01"
          },
          "from": {
            "type": "string",
            "example": "visa_required"
          },
          "to": {
            "type": "string",
            "example": "visa_free"
          },
          "note": {
            "type": "string"
          }
        }
      },
      "SafetyInfo": {
        "type": "object",
        "properties": {
          "level": {
            "type": "integer",
            "example": 1,
            "minimum": 1,
            "maximum": 4
          },
          "advisory": {
            "type": "string",
            "example": "Exercise normal precautions"
          },
          "source": {
            "type": "string",
            "example": "diplomatie.gouv.fr"
          },
          "updated_at": {
            "type": "string",
            "example": "2026-04-12"
          }
        }
      },
      "TransitHub": {
        "type": "object",
        "properties": {
          "airport": {
            "type": "string",
            "example": "NRT"
          },
          "city": {
            "type": "string",
            "example": "Tokyo"
          },
          "transit_visa_required": {
            "type": "boolean",
            "example": false
          },
          "transit_free_hours": {
            "type": "integer",
            "example": 24
          },
          "conditions": {
            "type": "string"
          }
        }
      },
      "TransitVisa": {
        "type": "object",
        "properties": {
          "hubs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TransitHub"
            }
          }
        }
      },
      "EmbassyInfo": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "address": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "emergency_phone": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "website": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "EmbassyData": {
        "type": "object",
        "properties": {
          "your_embassy_at_destination": {
            "allOf": [
              {
                "$ref": "#/components/schemas/EmbassyInfo"
              }
            ],
            "description": "Your country's embassy at the destination (emergency help)."
          },
          "visa_application_embassy": {
            "allOf": [
              {
                "$ref": "#/components/schemas/EmbassyInfo"
              }
            ],
            "description": "Destination country's embassy in your country (where to apply for visa)."
          }
        }
      },
      "VisaData": {
        "type": "object",
        "required": [
          "passport",
          "destination",
          "requirement",
          "visa_required",
          "description",
          "country_info",
          "verified"
        ],
        "properties": {
          "passport": {
            "$ref": "#/components/schemas/ISO3"
          },
          "destination": {
            "$ref": "#/components/schemas/ISO3"
          },
          "requirement": {
            "$ref": "#/components/schemas/VisaRequirement"
          },
          "visa_free_days": {
            "type": "integer",
            "nullable": true,
            "example": 90
          },
          "visa_required": {
            "type": "boolean",
            "example": false
          },
          "description": {
            "type": "string"
          },
          "documents_required": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "process": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tips": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "country_info": {
            "$ref": "#/components/schemas/CountryInfo"
          },
          "verified": {
            "type": "boolean",
            "example": true
          },
          "source": {
            "type": "string",
            "nullable": true,
            "example": "official",
            "description": "Where the value came from (`official`, `manual`, …)."
          },
          "requirement_status": {
            "type": "string",
            "enum": [
              "uncertain"
            ],
            "example": "uncertain",
            "description": "**Present only on pairs whose legal regime is unsettled — the key is\nabsent otherwise, so test for presence, not for a value.**\n\nBurkina Faso, Mali and Niger left ECOWAS on 2025-01-29. Visa-free\nmovement between them and the remaining member states now rests on\nunilateral, revocable declarations rather than on a treaty.\n\nThis is a separate, additive field: `requirement` is untouched and\nstays `visa_free`, because that is what happens at the border today.\nThe status says the right no longer exists; the requirement says the\npractice continues. A client that ignores this field sees no change.\n"
          },
          "requirement_status_note": {
            "type": "string",
            "nullable": true,
            "description": "Plain-English reason the regime is unsettled. Safe to show to a traveller verbatim."
          },
          "source_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "example": "https://www.mofa.go.jp/j_info/visit/visa/short/novisa.html",
            "description": "Official page this pair was read from. **`null` on pairs not yet\nre-sourced, which is most of them** — 56 of 199 passport countries\ncurrently have an official source on file. A `null` means we cannot\nshow you a document, not that the answer is wrong; if your product\nmakes a claim a user could act on, treat it as unverified.\n"
          },
          "last_verified_at": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2026-05-08",
            "description": "Date `source_url` was last read. `null` whenever `source_url` is `null`."
          },
          "transit_visa": {
            "$ref": "#/components/schemas/TransitVisa"
          },
          "passport_validity_months": {
            "type": "integer",
            "example": 3
          },
          "visa_fee": {
            "$ref": "#/components/schemas/VisaFee"
          },
          "processing_days": {
            "$ref": "#/components/schemas/ProcessingDays"
          },
          "photo_specs": {
            "$ref": "#/components/schemas/PhotoSpecs"
          },
          "vaccinations_required": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "insurance_required": {
            "$ref": "#/components/schemas/InsuranceRequired"
          },
          "dual_nationality_warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "stamp_warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "minor_rules": {
            "$ref": "#/components/schemas/MinorRules"
          },
          "overstay_penalty": {
            "$ref": "#/components/schemas/OverstayPenalty"
          },
          "entry_by_mode": {
            "$ref": "#/components/schemas/EntryByMode"
          },
          "remote_work_visa": {
            "$ref": "#/components/schemas/RemoteWorkVisa"
          },
          "extension_rules": {
            "$ref": "#/components/schemas/ExtensionRules"
          },
          "reciprocity_history": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReciprocityChange"
            }
          },
          "safety": {
            "$ref": "#/components/schemas/SafetyInfo"
          },
          "best_apply_period": {
            "type": "string",
            "nullable": true
          },
          "health_requirements": {
            "$ref": "#/components/schemas/HealthRequirements"
          },
          "embassy": {
            "$ref": "#/components/schemas/EmbassyData"
          }
        }
      },
      "VisaMeta": {
        "type": "object",
        "properties": {
          "lang": {
            "$ref": "#/components/schemas/Language"
          },
          "api_version": {
            "type": "string",
            "example": "1.1"
          },
          "coverage": {
            "type": "string",
            "example": "199 x 238"
          },
          "languages": {
            "type": "integer",
            "example": 15
          },
          "data_points": {
            "type": "integer",
            "example": 32
          }
        }
      },
      "VisaResponse": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/VisaData"
          },
          "meta": {
            "$ref": "#/components/schemas/VisaMeta"
          }
        }
      },
      "VisaCheckResponse": {
        "type": "object",
        "required": [
          "passport",
          "destination",
          "requirement",
          "visa_required"
        ],
        "properties": {
          "passport": {
            "$ref": "#/components/schemas/ISO3"
          },
          "destination": {
            "$ref": "#/components/schemas/ISO3"
          },
          "requirement": {
            "$ref": "#/components/schemas/VisaRequirement"
          },
          "visa_free_days": {
            "type": "integer",
            "nullable": true
          },
          "visa_required": {
            "type": "boolean"
          },
          "_hint": {
            "type": "string"
          },
          "_upgrade_preview": {
            "type": "object",
            "properties": {
              "documents_required": {
                "type": "integer"
              },
              "process_steps": {
                "type": "integer"
              },
              "embassy_info": {
                "type": "boolean"
              },
              "transit_visa": {
                "type": "boolean"
              },
              "visa_fees": {
                "type": "boolean"
              },
              "processing_times": {
                "type": "boolean"
              },
              "vaccinations": {
                "type": "integer"
              },
              "safety_advisory": {
                "type": "string"
              },
              "health_requirements": {
                "type": "boolean"
              },
              "remote_work_visa": {
                "type": "boolean"
              },
              "languages": {
                "type": "integer"
              },
              "upgrade_url": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "BulkDestination": {
        "type": "object",
        "properties": {
          "destination": {
            "$ref": "#/components/schemas/ISO3"
          },
          "requirement": {
            "$ref": "#/components/schemas/VisaRequirement"
          },
          "visa_free_days": {
            "type": "integer",
            "nullable": true
          },
          "description": {
            "type": "string"
          },
          "passport_validity_months": {
            "type": "integer"
          },
          "visa_fee": {
            "$ref": "#/components/schemas/VisaFee"
          },
          "safety": {
            "$ref": "#/components/schemas/SafetyInfo"
          },
          "health_requirements": {
            "$ref": "#/components/schemas/HealthRequirements"
          },
          "vaccinations_required": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "insurance_required": {
            "$ref": "#/components/schemas/InsuranceRequired"
          },
          "entry_by_mode": {
            "$ref": "#/components/schemas/EntryByMode"
          },
          "remote_work_visa": {
            "$ref": "#/components/schemas/RemoteWorkVisa"
          }
        }
      },
      "BulkResponse": {
        "type": "object",
        "required": [
          "passport",
          "lang",
          "total",
          "destinations"
        ],
        "properties": {
          "passport": {
            "$ref": "#/components/schemas/ISO3"
          },
          "lang": {
            "$ref": "#/components/schemas/Language"
          },
          "total": {
            "type": "integer",
            "example": 199
          },
          "destinations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BulkDestination"
            }
          }
        }
      },
      "DecisionRequest": {
        "type": "object",
        "required": [
          "passport",
          "itinerary"
        ],
        "properties": {
          "passport": {
            "$ref": "#/components/schemas/ISO3"
          },
          "itinerary": {
            "type": "array",
            "minItems": 1,
            "maxItems": 12,
            "description": "Ordered stops. Dates are calendar days, arrival and departure both counted.",
            "items": {
              "type": "object",
              "required": [
                "country",
                "from",
                "to"
              ],
              "properties": {
                "country": {
                  "$ref": "#/components/schemas/ISO3"
                },
                "from": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-09-01"
                },
                "to": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-09-20"
                }
              }
            }
          },
          "transit": {
            "type": "array",
            "maxItems": 12,
            "items": {
              "$ref": "#/components/schemas/ISO3"
            },
            "description": "Countries transited without entering. Deduplicated, and billed like a stop."
          },
          "passport_expiry": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2027-03-01",
            "description": "Omit it and every passport-validity check is skipped and reported in `unknowns`."
          },
          "residence": {
            "type": "string",
            "nullable": true,
            "description": "Accepted and echoed back, **not modelled** — adds an entry to `unknowns`."
          },
          "purpose": {
            "type": "string",
            "nullable": true,
            "example": "tourism",
            "description": "Accepted and echoed back. Anything other than `tourism` adds an entry to `unknowns`."
          }
        }
      },
      "DecisionStep": {
        "type": "object",
        "properties": {
          "country": {
            "$ref": "#/components/schemas/ISO3"
          },
          "from": {
            "type": "string",
            "format": "date"
          },
          "to": {
            "type": "string",
            "format": "date"
          },
          "days": {
            "type": "integer",
            "example": 20,
            "description": "Inclusive of arrival and departure day."
          },
          "admission": {
            "type": "string",
            "enum": [
              "allowed",
              "conditional",
              "refused",
              "unknown"
            ],
            "description": "`conditional` covers every regime that needs a formality (`eta`,\n`visa_on_arrival`, `e_visa`, `visa_required`). Requirements that do\nnot map to an outcome return `unknown` rather than a guess.\n"
          },
          "regime": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "known",
                  "unknown"
                ]
              },
              "value": {
                "$ref": "#/components/schemas/VisaRequirement"
              },
              "granularity": {
                "type": "string",
                "enum": [
                  "pair"
                ]
              },
              "source_url": {
                "type": "string",
                "nullable": true
              },
              "last_verified": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "max_stay": {
            "type": "object",
            "description": "`status: unknown` carries a `reason`; `known` carries `days` and `basis`.",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "known",
                  "unknown"
                ]
              },
              "days": {
                "type": "integer",
                "nullable": true
              },
              "granularity": {
                "type": "string",
                "enum": [
                  "pair"
                ]
              },
              "basis": {
                "type": "string",
                "example": "visa_free_days"
              },
              "reason": {
                "type": "string"
              }
            }
          },
          "passport_validity_months": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "known",
                  "unknown"
                ]
              },
              "months": {
                "type": "integer",
                "nullable": true
              },
              "granularity": {
                "type": "string",
                "enum": [
                  "destination"
                ]
              }
            }
          }
        }
      },
      "DecisionResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "passport_country": {
                "$ref": "#/components/schemas/ISO3"
              },
              "residence": {
                "type": "string",
                "nullable": true
              },
              "purpose": {
                "type": "string",
                "nullable": true
              },
              "steps": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/DecisionStep"
                }
              },
              "transit": {
                "type": "array",
                "description": "`status` is `destination_rule` (airport rule, not nationality) or `unknown`.",
                "items": {
                  "type": "object",
                  "properties": {
                    "country": {
                      "$ref": "#/components/schemas/ISO3"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "destination_rule",
                        "unknown"
                      ]
                    },
                    "granularity": {
                      "type": "string",
                      "enum": [
                        "destination"
                      ]
                    },
                    "note": {
                      "type": "string"
                    },
                    "hubs": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              },
              "passport": {
                "type": "object",
                "description": "`status` is `checked`, or `unknown` when `passport_expiry` was omitted.",
                "properties": {
                  "expiry": {
                    "type": "string",
                    "nullable": true
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "checked",
                      "unknown"
                    ]
                  },
                  "reason": {
                    "type": "string"
                  },
                  "latest_arrival": {
                    "type": "string",
                    "nullable": true
                  },
                  "valid_through_itinerary": {
                    "type": "boolean"
                  },
                  "per_destination": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "country": {
                          "$ref": "#/components/schemas/ISO3"
                        },
                        "arrival": {
                          "type": "string",
                          "format": "date"
                        },
                        "required_months": {
                          "type": "integer"
                        },
                        "granularity": {
                          "type": "string",
                          "enum": [
                            "destination"
                          ]
                        },
                        "satisfied": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              },
              "blockers": {
                "type": "array",
                "description": "Empty when nothing was found. Never a reason to conclude the trip is cleared.",
                "items": {
                  "type": "object",
                  "properties": {
                    "type": {
                      "type": "string",
                      "enum": [
                        "no_admission",
                        "admission_refused",
                        "stay_exceeds_allowance",
                        "passport_validity",
                        "passport_expires_before_exit"
                      ]
                    },
                    "country": {
                      "$ref": "#/components/schemas/ISO3"
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              },
              "unknowns": {
                "type": "array",
                "description": "Every gap that affected this answer, per field and per country.",
                "items": {
                  "type": "object",
                  "properties": {
                    "field": {
                      "type": "string",
                      "example": "transit"
                    },
                    "country": {
                      "$ref": "#/components/schemas/ISO3"
                    },
                    "reason": {
                      "type": "string"
                    }
                  }
                }
              },
              "verdict": {
                "type": "string",
                "enum": [
                  "no_blocker_found",
                  "blocked"
                ],
                "description": "`no_blocker_found` means the checks that could run found nothing — not that the trip is cleared."
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "api_version": {
                "type": "string",
                "example": "1.0"
              },
              "pairs_charged": {
                "type": "integer",
                "example": 3
              },
              "max_steps": {
                "type": "integer",
                "example": 12
              },
              "contract": {
                "type": "string"
              }
            }
          }
        }
      },
      "GroupDestination": {
        "type": "object",
        "properties": {
          "destination": {
            "$ref": "#/components/schemas/ISO3"
          },
          "group_requirement": {
            "$ref": "#/components/schemas/VisaRequirement"
          },
          "group_visa_free_days": {
            "type": "integer",
            "nullable": true,
            "example": 120,
            "description": "Minimum allowed stay across the whole group — the binding constraint."
          },
          "by_passport": {
            "type": "object",
            "description": "Per-passport breakdown, keyed by ISO3.",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "requirement": {
                  "$ref": "#/components/schemas/VisaRequirement"
                },
                "visa_free_days": {
                  "type": "integer",
                  "nullable": true
                }
              }
            }
          }
        }
      },
      "GroupResponse": {
        "type": "object",
        "required": [
          "passports",
          "allow",
          "total",
          "destinations"
        ],
        "properties": {
          "passports": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ISO3"
            },
            "example": [
              "USA",
              "FRA",
              "IND"
            ]
          },
          "allow": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VisaRequirement"
            }
          },
          "total": {
            "type": "integer",
            "example": 97
          },
          "excluded": {
            "type": "object",
            "properties": {
              "requirement_not_allowed": {
                "type": "integer",
                "example": 100
              },
              "incomplete_data": {
                "type": "integer",
                "example": 4
              }
            }
          },
          "destinations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GroupDestination"
            }
          }
        }
      },
      "VisaChange": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 14283
          },
          "passport_iso3": {
            "$ref": "#/components/schemas/ISO3"
          },
          "destination_iso3": {
            "$ref": "#/components/schemas/ISO3"
          },
          "change_type": {
            "$ref": "#/components/schemas/ChangeType"
          },
          "severity": {
            "type": "string",
            "enum": [
              "minor",
              "major"
            ]
          },
          "old_requirement": {
            "$ref": "#/components/schemas/VisaRequirement"
          },
          "new_requirement": {
            "$ref": "#/components/schemas/VisaRequirement"
          },
          "old_days": {
            "type": "integer",
            "nullable": true
          },
          "new_days": {
            "type": "integer",
            "nullable": true
          },
          "summary": {
            "type": "string"
          },
          "source_url": {
            "type": "string",
            "format": "uri"
          },
          "source_name": {
            "type": "string"
          },
          "effective_date": {
            "type": "string",
            "example": "2026-06-01"
          },
          "detected_at": {
            "type": "string",
            "format": "date-time"
          },
          "verified": {
            "type": "boolean"
          }
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer",
            "example": 1238
          },
          "limit": {
            "type": "integer",
            "example": 50
          },
          "offset": {
            "type": "integer",
            "example": 0
          },
          "has_more": {
            "type": "boolean",
            "example": true
          }
        }
      },
      "ChangesResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VisaChange"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          },
          "filters": {
            "type": "object"
          },
          "change_types": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChangeType"
            }
          }
        }
      },
      "StatsResponse": {
        "type": "object",
        "properties": {
          "coverage": {
            "type": "object",
            "properties": {
              "visa_details": {
                "type": "integer",
                "example": 47362
              },
              "passports": {
                "type": "integer",
                "example": 199
              },
              "destinations": {
                "type": "integer",
                "example": 238
              },
              "passport_index_pairs": {
                "type": "integer",
                "example": 47362
              },
              "translations": {
                "type": "integer",
                "example": 663068
              },
              "languages": {
                "type": "integer",
                "example": 15
              }
            }
          },
          "supported_languages": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "$ref": "#/components/schemas/Language"
                },
                "name": {
                  "type": "string",
                  "example": "English"
                }
              }
            }
          },
          "requirement_distribution": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "example": {
              "visa_free": 14210,
              "visa_required": 12740,
              "e_visa": 4830,
              "visa_on_arrival": 5102,
              "eta": 2103,
              "no_admission": 600
            }
          },
          "api_version": {
            "type": "string",
            "example": "1.0"
          },
          "docs": {
            "type": "string",
            "example": "https://visa.orizn.app"
          }
        }
      },
      "ScoreResponse": {
        "type": "object",
        "properties": {
          "passport": {
            "$ref": "#/components/schemas/ISO3"
          },
          "score": {
            "type": "number",
            "example": 96.4
          },
          "rank": {
            "type": "integer",
            "example": 3
          },
          "total_accessible": {
            "type": "integer",
            "example": 195
          },
          "breakdown": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "CompareResponse": {
        "type": "object",
        "properties": {
          "passport1": {
            "type": "object",
            "properties": {
              "code": {
                "$ref": "#/components/schemas/ISO3"
              },
              "score": {
                "type": "number"
              },
              "rank": {
                "type": "integer"
              }
            }
          },
          "passport2": {
            "type": "object",
            "properties": {
              "code": {
                "$ref": "#/components/schemas/ISO3"
              },
              "score": {
                "type": "number"
              },
              "rank": {
                "type": "integer"
              }
            }
          },
          "combined": {
            "type": "object",
            "properties": {
              "score": {
                "type": "integer",
                "example": 988
              },
              "total_accessible": {
                "type": "integer",
                "example": 197
              },
              "only_passport1": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ISO3"
                }
              },
              "only_passport2": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ISO3"
                }
              },
              "both": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ISO3"
                }
              },
              "neither": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ISO3"
                }
              }
            }
          },
          "share_text": {
            "type": "string"
          }
        }
      },
      "LiveEntry": {
        "type": "object",
        "properties": {
          "passport": {
            "$ref": "#/components/schemas/ISO3"
          },
          "destination": {
            "$ref": "#/components/schemas/ISO3"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "LiveRecentResponse": {
        "type": "object",
        "properties": {
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LiveEntry"
            }
          },
          "stats": {
            "type": "object",
            "properties": {
              "today": {
                "type": "integer",
                "example": 14328
              },
              "this_week": {
                "type": "integer",
                "example": 92041
              },
              "total": {
                "type": "integer",
                "example": 1248302
              }
            }
          },
          "top_corridors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "pair": {
                  "type": "string",
                  "example": "USA→JPN"
                },
                "count": {
                  "type": "integer",
                  "example": 412
                }
              }
            }
          }
        }
      },
      "DevicePreferences": {
        "type": "object",
        "properties": {
          "instant": {
            "type": "boolean",
            "default": true
          },
          "digest_weekly": {
            "type": "boolean",
            "default": true
          },
          "only_wishlist": {
            "type": "boolean",
            "default": false
          },
          "only_positive": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "DeviceRegisterBody": {
        "type": "object",
        "required": [
          "device_token",
          "passport_iso3"
        ],
        "properties": {
          "device_token": {
            "type": "string",
            "example": "8a3f...e2b1"
          },
          "passport_iso3": {
            "$ref": "#/components/schemas/ISO3"
          },
          "platform": {
            "type": "string",
            "default": "ios",
            "example": "ios"
          },
          "bundle_id": {
            "type": "string",
            "default": "com.orizn-visa",
            "example": "com.orizn-visa"
          },
          "wishlist_iso3": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ISO3"
            }
          },
          "locale": {
            "type": "string",
            "default": "en",
            "example": "en"
          },
          "tz": {
            "type": "string",
            "default": "UTC",
            "example": "Europe/Paris"
          },
          "premium": {
            "type": "boolean",
            "default": false
          },
          "preferences": {
            "$ref": "#/components/schemas/DevicePreferences"
          }
        }
      },
      "DeviceUpdateBody": {
        "type": "object",
        "description": "At least one property required.",
        "minProperties": 1,
        "properties": {
          "passport_iso3": {
            "$ref": "#/components/schemas/ISO3"
          },
          "wishlist_iso3": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ISO3"
            }
          },
          "locale": {
            "type": "string"
          },
          "tz": {
            "type": "string"
          },
          "premium": {
            "type": "boolean"
          },
          "preferences": {
            "$ref": "#/components/schemas/DevicePreferences"
          }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 17
          },
          "url": {
            "type": "string",
            "format": "uri",
            "example": "https://your-app.com/orizn-hook"
          },
          "passport_filter": {
            "type": "array",
            "nullable": true,
            "items": {
              "$ref": "#/components/schemas/ISO3"
            }
          },
          "destination_filter": {
            "type": "array",
            "nullable": true,
            "items": {
              "$ref": "#/components/schemas/ISO3"
            }
          },
          "active": {
            "type": "boolean",
            "example": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_triggered_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "failures": {
            "type": "integer",
            "example": 0
          }
        }
      },
      "WebhookWithSecret": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Webhook"
          },
          {
            "type": "object",
            "properties": {
              "secret": {
                "type": "string",
                "example": "whsec_4f2a91c8...e2b1"
              }
            }
          }
        ]
      },
      "WebhookCreateBody": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "passport_filter": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ISO3"
            }
          },
          "destination_filter": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ISO3"
            }
          }
        }
      },
      "TeamKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 7
          },
          "api_key": {
            "type": "string",
            "example": "orizn_visa_team_a1b2c3..."
          },
          "name": {
            "type": "string",
            "example": "ci-staging"
          },
          "active": {
            "type": "boolean",
            "example": true
          },
          "requests_month": {
            "type": "integer",
            "example": 14823
          },
          "requests_total": {
            "type": "integer",
            "example": 184238
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "EnsureKeyResponse": {
        "type": "object",
        "properties": {
          "user": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer",
                "example": 42
              },
              "orizn_id": {
                "type": "string",
                "example": "01HZ..."
              },
              "email": {
                "type": "string",
                "format": "email",
                "example": "ada@example.com"
              },
              "name": {
                "type": "string",
                "example": "Ada Lovelace"
              },
              "photo": {
                "type": "string",
                "format": "uri"
              },
              "plan": {
                "type": "string",
                "enum": [
                  "free",
                  "starter",
                  "pro",
                  "business",
                  "enterprise"
                ]
              },
              "api_key": {
                "type": "string",
                "example": "orizn_visa_a06113..."
              },
              "requests_month": {
                "type": "integer",
                "example": 14238
              },
              "requests_total": {
                "type": "integer",
                "example": 412938
              },
              "monthly_limit": {
                "type": "integer",
                "example": 250000
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "AffiliateRegisterBody": {
        "type": "object",
        "required": [
          "name",
          "email"
        ],
        "properties": {
          "name": {
            "type": "string",
            "example": "Ada Lovelace"
          },
          "email": {
            "type": "string",
            "format": "email",
            "example": "ada@example.com"
          },
          "website": {
            "type": "string",
            "description": "A valid URL, or the literal string `ios-app` for app-store affiliates.",
            "example": "https://travelblog.example"
          },
          "payment_method": {
            "type": "string",
            "enum": [
              "paypal",
              "iban"
            ]
          },
          "payment_info": {
            "type": "string",
            "example": "ada@paypal.com"
          },
          "source": {
            "type": "string",
            "enum": [
              "web",
              "ios"
            ],
            "default": "web"
          }
        }
      },
      "AffiliateRegisterResponse": {
        "type": "object",
        "properties": {
          "affiliate_id": {
            "type": "string",
            "example": "aff_4f2a91c8"
          },
          "dashboard_url": {
            "type": "string",
            "format": "uri"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "AffiliateStatsResponse": {
        "type": "object",
        "properties": {
          "affiliate_id": {
            "type": "string",
            "example": "aff_4f2a91c8"
          },
          "active": {
            "type": "boolean",
            "example": true
          },
          "source": {
            "type": "string",
            "example": "web"
          },
          "clicks": {
            "type": "integer",
            "example": 1423
          },
          "conversions": {
            "type": "integer",
            "example": 28
          },
          "referred_users": {
            "type": "integer",
            "example": 28
          },
          "revenue_cents": {
            "type": "integer",
            "example": 419800
          },
          "commission_rate": {
            "type": "number",
            "example": 0.15
          },
          "commission_cents": {
            "type": "integer",
            "example": 62970
          },
          "this_month": {
            "type": "object",
            "properties": {
              "transactions": {
                "type": "integer"
              },
              "revenue_cents": {
                "type": "integer"
              },
              "commission_cents": {
                "type": "integer"
              }
            }
          },
          "by_product": {
            "type": "object",
            "additionalProperties": true
          },
          "by_platform": {
            "type": "object",
            "additionalProperties": true
          },
          "recent_transactions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "product": {
                  "type": "string"
                },
                "platform": {
                  "type": "string"
                },
                "amount_cents": {
                  "type": "integer"
                },
                "commission_cents": {
                  "type": "integer"
                },
                "date": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          },
          "payment_method": {
            "type": "string"
          },
          "payment_info_masked": {
            "type": "string"
          },
          "links": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "format": "uri"
            }
          }
        }
      }
    }
  }
}
