{"openapi":"3.1.0","info":{"title":"Aevamo Rules API","version":"2026-10-09","summary":"Cross-border entry and stay rules, with the passage they come from.","description":"Versioned, source-cited cross-border rules.\n\nEvery answer carries four things nobody else in this category returns together:\nthe **quoted passage** from the governing instrument, that document's **URL**,\n**`checkedAt`** (the date *we* verified the rule against that document, never the\npage's own claim), and **`dataVersion`** (the canonical store commit the answer\nderives from, so any answer is reproducible and disputable).\n\nThree contract rules worth reading before you integrate:\n\n- **Unknown is never a 404.** Absent coverage answers HTTP 200, `status: \"unknown\"`,\n  and a `coverage` block saying why. A malformed question is a 400.\n- **Rules are dated.** Pass the traveller's real `date`. A rule that changes next\n  month gives a different answer for a trip next month, and the API will give it.\n- **An ETA is never told as \"get a visa\".** `requirement.kind` distinguishes `eta`,\n  `evisa`, `visa_consular` and `permit`, because they are different journeys.\n\n**No key required to start.** Every question-shaped endpoint answers keyless callers\non the demo tier (see `securitySchemes`), returning the full sourced answer including\nevidence passages. A key raises the limit and unlocks `GET /v1/snapshot`.\n\nLive discovery document: `GET /v1`. MCP endpoint: `POST /mcp`, keyless.\nFull contract: `Docs/technical/rules-api.md`.\n","contact":{"name":"Aevamo support","email":"support@aevamo.com"},"license":{"name":"Proprietary","url":"https://aevamo.com/terms"}},"servers":[{"url":"https://api.aevamo.com","description":"Production"}],"security":[{},{"apiKey":[]}],"tags":[{"name":"entry","description":"Can this passport enter, and on what terms?"},{"name":"stay","description":"How much of an allowance a visit history has used."},{"name":"trips","description":"A whole route at once."},{"name":"rules","description":"The held rules themselves."},{"name":"sources","description":"The documents the rules rest on."},{"name":"meta","description":"Discovery, health, the machine-readable contract."}],"paths":{"/v1":{"get":{"tags":["meta"],"operationId":"discover","security":[],"summary":"What this API is, what it costs you, and every endpoint on it","description":"Keyless. The document a developer or an agent lands on with no account: endpoints, both tiers and their limits, the MCP URL, the OpenAPI URLs, and the versioning and deprecation policy.\n","responses":{"200":{"description":"The discovery document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Discovery"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/health":{"get":{"tags":["meta"],"operationId":"health","security":[],"summary":"Liveness and the loaded projection version","description":"503 when the projection is unloaded or the database is unreachable. A deploy serving no data is unhealthy, not merely empty.\n","responses":{"200":{"description":"Healthy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"The projection is unloaded, or the database is unreachable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}},"/openapi.yaml":{"get":{"tags":["meta"],"operationId":"getOpenApiYaml","security":[],"summary":"This document, served by the running service","responses":{"200":{"description":"The OpenAPI description, YAML.","content":{"application/yaml":{"schema":{"type":"string"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/openapi.json":{"get":{"tags":["meta"],"operationId":"getOpenApiJson","security":[],"summary":"This document as JSON, for codegen that wants it","responses":{"200":{"description":"The OpenAPI description, JSON.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/mcp":{"post":{"tags":["meta"],"operationId":"mcp","security":[],"summary":"Model Context Protocol endpoint (keyless, stateless Streamable HTTP)","description":"JSON-RPC 2.0 over a single POST. Implements `initialize`, `tools/list`, `tools/call` and `ping`. Six tools, each returning the sourced answer with its passage and check date. Point any Streamable HTTP MCP client at this URL with no credentials. Copyable HTTP examples: https://aevamo.com/developers/.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcRequest"}}}},"responses":{"200":{"description":"A JSON-RPC response (or an array of them, for a batch).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}},"202":{"description":"The batch was notifications only; nothing to answer."},"400":{"description":"Malformed JSON-RPC.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}},"/v1/entry/check":{"get":{"tags":["entry"],"operationId":"entryCheckGet","summary":"Can this passport enter, and for how long?","description":"Query-string form of the POST question. Keyless callers receive evidence by default.","parameters":[{"name":"passport","in":"query","required":true,"schema":{"type":"string"}},{"name":"destination","in":"query","required":true,"schema":{"type":"string"}},{"name":"date","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"The sourced answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntryAnswer"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["entry"],"operationId":"entryCheck","summary":"Can this traveller enter, and for how long?","description":"The flagship call. Returns the headline rule for this passport and destination on this date, with the requirement, the stay allowance, the conditions attached, the source it comes from and the scope of the answer. When no official source settles the row (`establishment` is NOT_ESTABLISHED or CONTESTED, issue 398), `unconfirmed` carries a best answer, why it isn't confirmed, and ready-to-show copy; `status` never reads as a confirmed allowed/not_allowed in that case.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntryRequest"},"examples":{"shorthand":{"summary":"The whole question","value":{"passport":"GBR","destination":"TH"}},"dated":{"summary":"A trip after a rule change","value":{"passport":"GBR","destination":"TH","date":"2026-09-15"}},"withEvidence":{"summary":"Ask for the passages explicitly (the demo tier returns them anyway)","value":{"passport":{"country":"GBR","expires":"2030-01-31"},"destination":"THA","options":{"evidence":true}}}}}}},"responses":{"200":{"description":"The answer. `status: \"unknown\"` with a `coverage` block is a valid 200, not an error: it says we hold nothing for this pairing and why.\n","headers":{"Aevamo-Version":{"$ref":"#/components/headers/AevamoVersion"},"Aevamo-Data-Version":{"$ref":"#/components/headers/AevamoDataVersion"},"Aevamo-Tier":{"$ref":"#/components/headers/AevamoTier"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntryAnswer"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServiceBusy"}}}},"/v1/entry/requirements":{"get":{"tags":["entry"],"operationId":"entryRequirementsGet","summary":"Every applicable entry rule.","description":"Query-string form of the POST question. Keyless callers receive evidence by default.","parameters":[{"name":"passport","in":"query","required":true,"schema":{"type":"string"}},{"name":"destination","in":"query","required":true,"schema":{"type":"string"}},{"name":"date","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"The sourced answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequirementsAnswer"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["entry"],"operationId":"entryRequirements","summary":"Every rule that applies, not only the headline one","description":"A different question from `entry.check` and it deserves its own verb: what must be satisfied, across ALL applicable rows. Use it when the headline answer is `conditional` and you need the conditions themselves, or when a traveller may qualify under more than one route.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntryRequest"}}}},"responses":{"200":{"description":"Every applicable rule, each with its own conditions and citations.","headers":{"Aevamo-Version":{"$ref":"#/components/headers/AevamoVersion"},"Aevamo-Tier":{"$ref":"#/components/headers/AevamoTier"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequirementsAnswer"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/stay/check":{"post":{"tags":["stay"],"operationId":"stayCheck","summary":"Evaluate a visit history against the held stay rule","description":"Stateless: you supply the visits, the rule comes from the projection, and the arithmetic is the same evaluator the Aevamo app runs offline, so the two cannot disagree. Nothing is stored.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayRequest"},"examples":{"schengenish":{"summary":"Two past visits and one open","value":{"passport":"GBR","destination":"ESP","trips":[{"entry":"2026-01-10","exit":"2026-02-01"},{"entry":"2026-05-03","exit":"2026-05-20"},{"entry":"2026-08-28"}]}}}}}},"responses":{"200":{"description":"The rule, the evaluation, and the scope of both.","headers":{"Aevamo-Version":{"$ref":"#/components/headers/AevamoVersion"},"Aevamo-Tier":{"$ref":"#/components/headers/AevamoTier"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayAnswer"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/stay/remaining":{"post":{"tags":["stay"],"operationId":"stayRemaining","summary":"Days left under the held rule","description":"The same computation as `stay.check`, narrowed to the number most callers want.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayRequest"}}}},"responses":{"200":{"description":"Remaining and used days, and the window they are counted in.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemainingAnswer"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/trips/check":{"post":{"tags":["trips"],"operationId":"tripsCheck","summary":"A whole route, one leg per destination, worst-of status","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripRequest"},"examples":{"route":{"value":{"passport":"GBR","route":["FRA","TH","VN"]}}}}}},"responses":{"200":{"description":"Every leg, plus the ones that need something arranged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripAnswer"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/rules/{id}":{"get":{"tags":["rules"],"operationId":"rulesGet","summary":"One rule verbatim, with citations and the regime's referee record","description":"The end of the provenance chain on the rules side: every citation, the passage it quotes, why that passage settles the question, and how the regime has fared when its answers were contested.\n","parameters":[{"name":"id","in":"path","required":true,"description":"A rule id as returned in `ruleId`, e.g. `THA:thai-visa-free-30d`.","schema":{"type":"string"}}],"responses":{"200":{"description":"The rule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuleDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/rules/search":{"post":{"tags":["rules"],"operationId":"rulesSearch","summary":"Filter the held rules, with the coverage story attached","description":"The lower-level escape hatch. The `coverage` block exists so that absence reads as absence: an empty result means we hold nothing matching, never \"no requirement\".\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuleSearchRequest"},"examples":{"byRegime":{"value":{"regime":"THA"}},"byPassportAndMode":{"value":{"passport":"GBR","entryMode":"VISA_FREE"}}}}}},"responses":{"200":{"description":"Up to 200 rules (`truncated` says whether the cap bit), and the coverage story.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuleSearchAnswer"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/sources/{id}":{"get":{"tags":["sources"],"operationId":"sourcesGet","summary":"One source document, authority, fetch record, and every passage cited from it","parameters":[{"name":"id","in":"path","required":true,"description":"A source id as returned in `sources[].id`.","schema":{"type":"string"}}],"responses":{"200":{"description":"The document record.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourceDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/snapshot":{"get":{"tags":["rules"],"operationId":"snapshot","security":[{"apiKey":[]}],"summary":"The whole held ruleset, compact, for offline use","description":"**Key required.** This is the only endpoint the keyless demo tier does not answer: it is a bulk export rather than a demonstration of an answer. ETagged by `dataVersion`, so `If-None-Match` revalidates for free.\n","parameters":[{"name":"If-None-Match","in":"header","required":false,"description":"A previously returned ETag; a match answers 304 with no body.","schema":{"type":"string"}}],"responses":{"200":{"description":"The full snapshot.","headers":{"ETag":{"description":"The `dataVersion`, quoted.","schema":{"type":"string"}},"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Snapshot"}}}},"304":{"description":"Your copy is current."},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"`Authorization: Bearer <key>`. **Optional.** Omit it entirely and you are on the\nkeyless demo tier: every question-shaped endpoint answers, with the full sourced\nanswer including evidence passages, up to a per-IP daily allowance (`GET /v1`\nstates the current number). A key raises the limit and unlocks `GET /v1/snapshot`.\nA malformed or unknown key is a 401, it is not silently downgraded to the demo\ntier, because a caller who thinks they are authenticated should be told they are not.\n"}},"headers":{"AevamoVersion":{"description":"The dated revision of the contract this response was produced under.","schema":{"type":"string","examples":["2026-09-04"]}},"AevamoDataVersion":{"description":"The canonical store commit this answer derives from.","schema":{"type":"string"}},"AevamoTier":{"description":"Which door the caller came through.","schema":{"type":"string","enum":["demo","key"]}},"RateLimitRemaining":{"description":"Requests left in the current window (demo tier).","schema":{"type":"integer"}},"RetryAfter":{"description":"Seconds until the limit lifts.","schema":{"type":"integer"}}},"responses":{"BadRequest":{"description":"The question was malformed. `error.param` names the field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"A key was supplied and is unknown, or the endpoint needs one and none was given. The body carries a `help` block: the demo tier's terms, the MCP URL, the discovery document and where a key comes from.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"No such rule, source, or route. Note that an unanswerable travel *question* is a 200 with `status: \"unknown\"`, never a 404.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PayloadTooLarge":{"description":"The request body exceeded the cap (64 KB by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"A burst limit or the demo tier's daily allowance. `Retry-After` and `error.help.resetsAt` say when it lifts.\n","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ServerError":{"description":"Something broke on our side. `x-request-id` identifies the request in our logs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ServiceBusy":{"description":"The database pool saturated; shed rather than hang. Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"CountryCode":{"type":"string","description":"ISO 3166 alpha-2 or alpha-3, case-insensitive. `D` is accepted as an alias for `DEU` (the MRZ code). ISO3 always comes back out.\n","examples":["GB","GBR","TH","THA"]},"IsoDate":{"type":"string","format":"date","examples":["2026-09-15"]},"Decision":{"type":"string","description":"Never a bare boolean.\n- `allowed`, nothing to arrange, no condition gates this passport.\n- `conditional`, entry is available but something must be arranged or satisfied.\n  A required visa is `conditional`, not `not_allowed`: it is obtainable.\n- `not_allowed`, suspensions and bans only.\n- `unknown`, we hold nothing for this pairing. See `coverage`.\n","enum":["allowed","not_allowed","conditional","unknown"]},"SourceAvailability":{"type":["object","null"],"description":"The regime's own declaration about whether its governing law is online at all, null until it has said. Replaced the promotion-derived Confidence tier on 6 Sep 2026: a rating of the research record, not a fact about the rule, is not what a caller needs. `state` is INSTRUMENT_HELD, NO_OFFICIAL_INSTRUMENT_ONLINE, or INSTRUMENT_NOT_FOUND_YET; `why` and `checkedAt` may be null.\n","properties":{"state":{"type":"string","enum":["INSTRUMENT_HELD","NO_OFFICIAL_INSTRUMENT_ONLINE","INSTRUMENT_NOT_FOUND_YET"]},"why":{"type":["string","null"]},"checkedAt":{"type":["string","null"],"format":"date"}},"additionalProperties":false},"Establishment":{"type":"string","enum":["ESTABLISHED","NOT_ESTABLISHED","CONTESTED"],"description":"Whether an official source settles this row's answer (issue 398). ESTABLISHED for every ordinary row, including rows withheld for an older, different reason (a lead, an unresolved coverage gap): this contract concerns only rows no official source settles. `status` at the top level of the containing answer is never a confirmed allowed/not_allowed when establishment is not ESTABLISHED.\n"},"Unconfirmed":{"type":["object","null"],"description":"Present (non-null) only when the row's establishment is NOT_ESTABLISHED or CONTESTED. Carries the best answer we can give, why it isn't confirmed, and ready-to-show copy so every interface repeats the caveat instead of dropping it.\n","properties":{"bestAnswer":{"type":["object","null"],"description":"null when the store could not settle even a best answer (see noBestAnswerBecause).\n","properties":{"status":{"type":"string","enum":["allowed","not_allowed","conditional","unknown"],"description":"The ordinary Decision vocabulary, derived from bestAnswer.advanceAuthorisation and .entryMode exactly as an ordinary row's status is. Never presented as confirmed: read it beside `unconfirmed`, never in its place.\n"},"requirementKind":{"type":["string","null"],"enum":["none","free_movement","eta","evisa","visa_consular","visa_unspecified","permit","entry_suspended",null],"description":"AMENDMENT 2 (20 Sep 2026): null, never \"unrecorded\", when an authorisation is needed but the store's best answer does not say which kind (no `authorisationKind` on the source row, or one this API does not recognise). An honest absence rather than a guess; `phrasing.headline` carries a fixed generic sentence (\"Get a visa or travel authorisation before you travel.\") instead of the ordinary-row \"no kind recorded\" wording, which would read as noise ahead of anything useful inside an unconfirmed block.\n"},"basis":{"type":"string","enum":["PREVIOUS_ANSWER","THIRD_PARTY_REPORT","OFFICIAL_INDIRECT","RESIDUAL_RULE"]},"basisPhrase":{"type":"string","description":"Plain words for what the basis means, ready to show, never the code above."},"why":{"type":"string","description":"AMENDMENT 1 (20 Sep 2026): our working notes, written for the harness, not for a traveller. Kept as data for anyone who wants the detail (a collapsed \"the detail\" section, an evidence view); do NOT show as the main explanation. Show `unconfirmed.phrasing.caveat` instead, which is built from `unconfirmed.inPlainWords` and never from this field.\n"},"source":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":["string","null"]}},"additionalProperties":false}},"additionalProperties":false},"noBestAnswerBecause":{"type":["string","null"],"description":"Set only when bestAnswer is null; why we cannot give one. AMENDMENT 1: the same working-notes caveat as `why` applies. Do not show as the main explanation; use `phrasing.caveat`.\n"},"reason":{"type":"string","description":"AMENDMENT 1: working notes, written for the harness, not for a traveller. Kept as data; never the main explanation. Use `phrasing.caveat` instead.\n"},"inPlainWords":{"type":["string","null"],"description":"AMENDMENT 1: one sentence written FOR A TRAVELLER, at most 40 words, no jargon. Null until the store has one. This is data, not display copy: `phrasing.caveat` already incorporates it (or the fixed fallback sentence when null), so a consumer needs this field only to know whether the store's own words were used.\n"},"readings":{"type":"array","description":"Non-empty only on a CONTESTED row; each is one credible reading, with who says it.","items":{"type":"object","properties":{"status":{"type":"string","enum":["allowed","not_allowed","conditional","unknown"]},"saysWho":{"type":"string"},"source":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":["string","null"]}},"additionalProperties":false}},"additionalProperties":false}},"searched":{"type":"array","items":{"type":"string"}},"phrasing":{"type":"object","description":"Ready-to-show copy (copy register: Kerr, 20 Sep 2026; AMENDMENT 1 and 2: 20 Sep 2026). headline states the best answer first: when there is one, it is phrased through the same wording an ordinary row gets (TOLD by authorisationKind, or by entryMode when no advance authorisation is needed), falling back to one of two fixed sentences (\"Get a visa or travel authorisation before you travel.\" / \"You can travel without arranging a visa first.\") when the store's best answer does not say which kind or which entry mode; NEVER the ordinary-row \"no kind recorded\" phrase, which reads as noise ahead of anything useful here. caveat says plainly that it is not confirmed, in the same breath as ONE plain sentence of why (built from `inPlainWords`, or a fixed fallback naming the destination when the store has none yet; NEVER built from `reason`, `bestAnswer.why` or `noBestAnswerBecause`, which are our own working notes and broke the copy register when shown as the main explanation); stance is the trust-building sentence, said once per answer; feedback names the product's \"This looks wrong\" mechanism.\n","properties":{"headline":{"type":"string"},"caveat":{"type":"string"},"stance":{"type":"string"},"feedback":{"type":"string"}},"additionalProperties":false}},"additionalProperties":false},"Coverage":{"type":"object","description":"Why we have no answer. Present only when `status` is `unknown`.","required":["held","reason"],"properties":{"held":{"type":"boolean","const":false},"reason":{"type":"string","enum":["regime_not_promoted","territory_differs_uncovered","no_row_for_passport"],"description":"- `regime_not_promoted`, the destination has not passed the research bar yet.\n- `territory_differs_uncovered`, the territory has its own rules, which we do\n  not hold, and it must NEVER inherit its parent's (French Polynesia is not\n  Schengen).\n- `no_row_for_passport`, the regime is held but no rule covers this passport.\n"},"note":{"type":"string","description":"The specific trap, where one is recorded."}},"additionalProperties":false},"RequirementKind":{"type":"string","description":"What must be arranged. An ETA is never collapsed into \"get a visa\": sending someone to a consular appointment for what is an online form is a wrong answer.\n","enum":["none","free_movement","eta","evisa","visa_consular","visa_unspecified","permit","entry_suspended","unrecorded"]},"PortOfEntry":{"type":"object","description":"Present only when the document restricts how these passports may arrive.","properties":{"modes":{"type":"array","items":{"type":"string","enum":["AIR","LAND","SEA"]}},"ports":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"Requirement":{"type":"object","required":["kind","message"],"properties":{"kind":{"$ref":"#/components/schemas/RequirementKind"},"message":{"type":"string","description":"The sentence to show a traveller. No reassurances about speed or ease."},"authorisationValidityDays":{"type":"integer","description":"How long the authorisation itself is valid. This is NOT how long you may stay.\n"},"schemeName":{"type":"string","description":"The destination's own name for what THIS row's passports must obtain (NZeTA, K-ETA, ESTA). Per rule, never per country. Absent when no held document names one.\n"},"portOfEntry":{"$ref":"#/components/schemas/PortOfEntry"}},"additionalProperties":false},"Stay":{"type":"object","description":"How long, and under what counting. Every \"does not apply\" is a named kind rather than a null, because a null reads as \"we hold nothing\".\n","required":["kind","phrasing"],"properties":{"kind":{"type":"string","enum":["DAYS","MONTHS","YEARS","NO_LIMIT","SET_BY_VISA","NOT_STATED"],"description":"- `SET_BY_VISA`, the grant fixes it per traveller; no class-level figure exists.\n- `NOT_STATED`, the cited rule grants entry and states no maximum.\n"},"maxDays":{"type":"integer"},"maxMonths":{"type":"integer"},"maxYears":{"type":"integer","minimum":1,"description":"Calendar years. The entry anniversary is exclusive; 29 February clamps to 28 February in non-leap years."},"ruleType":{"$ref":"#/components/schemas/RuleType"},"windowDays":{"type":"integer","description":"The rolling window, when `ruleType` is `ROLLING_WINDOW`."},"phrasing":{"type":"string","description":"The sentence to show a traveller.","examples":["You may stay up to 30 days."]}},"additionalProperties":false},"RuleType":{"type":"string","description":"- `RESETS_ON_EXIT`, the allowance restarts each visit.\n- `ROLLING_WINDOW`, counted across `windowDays` looking back.\n- `ANCHORED_YEAR`, counted per calendar or anchored year.\n","enum":["RESETS_ON_EXIT","ROLLING_WINDOW","ANCHORED_YEAR"]},"EntryMode":{"type":"string","description":"How you enter. `WITH_AUTHORISATION` is set on every advance-authorisation row; null appears only on a withheld row.\n","enum":["VISA_FREE","VISA_ON_ARRIVAL","FREE_MOVEMENT","ENTRY_SUSPENDED","WITH_AUTHORISATION"]},"Condition":{"type":"object","required":["text"],"properties":{"kind":{"type":["string","null"],"description":"The condition vocabulary, e.g. `PORT_OF_ENTRY`, `PASSPORT_VALIDITY`."},"text":{"type":"string"}},"additionalProperties":false},"SourceRef":{"type":"object","required":["id"],"properties":{"id":{"type":"string"},"url":{"type":"string","format":"uri"}},"additionalProperties":false},"Evidence":{"type":"object","description":"The quoted passage from the instrument. The product, not a footnote.","required":["sourceId","passage"],"properties":{"sourceId":{"type":"string"},"field":{"type":["string","null"],"description":"Which field this passage settles; null = the rule's primary source."},"passage":{"type":"string"}},"additionalProperties":false},"Scope":{"type":"string","description":"What this answer covers and what it does not, in one sentence-set, derived from held fields only: the pairing and date asked, whether the matched rule was written for this passport or is the destination's catch-all, the rule's dated validity, and the question classes the store does not answer. Show it next to the passage.\n","examples":["Entry rules for a GBR passport arriving in THA on 2026-09-15. Matched THA's catch-all rule, not one written for GBR passports. That rule is in force from 2026-09-15. Covers entry and length of stay only: not transit, work, study or tax residence."]},"EntryRequest":{"type":"object","required":["passport","destination"],"properties":{"passport":{"description":"A country code, or an object if you also know the expiry.","oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"object","required":["country"],"properties":{"country":{"$ref":"#/components/schemas/CountryCode"},"expires":{"$ref":"#/components/schemas/IsoDate"}},"additionalProperties":false}]},"destination":{"$ref":"#/components/schemas/CountryCode"},"date":{"allOf":[{"$ref":"#/components/schemas/IsoDate"}],"description":"Date of arrival. Defaults to today. Rules are dated, send the real one."},"options":{"$ref":"#/components/schemas/Options"}},"additionalProperties":false},"Options":{"type":"object","description":"API behaviour, never part of the travel question. On the keyless demo tier `evidence` is on regardless.\n","properties":{"evidence":{"type":"boolean","default":false,"description":"Include the quoted passages."}},"additionalProperties":false},"EntryAnswer":{"type":"object","required":["status","passport","destination","regime","requirement","stay","conditions","ruleId","alternatives","sources","scope","checkedAt","dataVersion"],"properties":{"status":{"$ref":"#/components/schemas/Decision"},"passport":{"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"description":"The canonical ISO3 for the passport you asked about."},"destination":{"type":["string","null"],"description":"The canonical ISO3 for the destination, null if unrecognised."},"regime":{"type":["string","null"],"description":"The ruleset that governs the destination. Often the destination's own code; `SCHENGEN` for its members; null when nothing is held.\n"},"requirement":{"oneOf":[{"$ref":"#/components/schemas/Requirement"},{"type":"null"}]},"stay":{"oneOf":[{"$ref":"#/components/schemas/Stay"},{"type":"null"}]},"conditions":{"type":"array","items":{"$ref":"#/components/schemas/Condition"}},"ruleId":{"type":["string","null"],"description":"Pass to `GET /v1/rules/{id}` for the rule verbatim."},"match":{"type":"string","enum":["specific","residual"],"description":"How coverage was established, and it is a statement about the DOCUMENT rather than about this traveller. `specific`, the cited instrument names this passport in its own list. `residual`, the instrument does not enumerate issuers, and this passport was established as covered by sweeping the exemption lists we hold. Both are real matches; `residual` is the one where a missing exemption elsewhere could in principle send someone to the fallback, so it is surfaced rather than hidden.\n"},"scope":{"$ref":"#/components/schemas/Scope"},"alternatives":{"type":"array","items":{"type":"string"},"description":"Other rule ids that also matched, ranked below the headline one."},"sources":{"type":"array","items":{"$ref":"#/components/schemas/SourceRef"}},"evidence":{"type":"array","items":{"$ref":"#/components/schemas/Evidence"},"description":"Present when `options.evidence` was set, and always on the demo tier."},"checkedAt":{"type":["string","null"],"format":"date","description":"The date WE last verified this regime against its governing instrument. Never the source page's own claim about itself.\n"},"dataVersion":{"type":"string"},"sourceAvailability":{"$ref":"#/components/schemas/SourceAvailability"},"establishment":{"$ref":"#/components/schemas/Establishment"},"unconfirmed":{"$ref":"#/components/schemas/Unconfirmed"},"coverage":{"$ref":"#/components/schemas/Coverage"}},"additionalProperties":false},"RequirementsAnswer":{"type":"object","required":["status","passport","destination","regime","requirements","scope","checkedAt","dataVersion"],"properties":{"status":{"$ref":"#/components/schemas/Decision"},"passport":{"type":"string"},"destination":{"type":["string","null"]},"regime":{"type":["string","null"]},"requirements":{"type":"array","items":{"type":"object","required":["ruleId","requirement","stay","conditions","inForce","sources"],"properties":{"ruleId":{"type":"string"},"requirement":{"$ref":"#/components/schemas/Requirement"},"stay":{"oneOf":[{"$ref":"#/components/schemas/Stay"},{"type":"null"}]},"conditions":{"type":"array","items":{"$ref":"#/components/schemas/Condition"}},"inForce":{"type":"boolean"},"establishment":{"$ref":"#/components/schemas/Establishment"},"unconfirmed":{"$ref":"#/components/schemas/Unconfirmed"},"sources":{"type":"array","items":{"$ref":"#/components/schemas/SourceRef"}},"evidence":{"type":"array","items":{"$ref":"#/components/schemas/Evidence"}}},"additionalProperties":false}},"scope":{"$ref":"#/components/schemas/Scope"},"checkedAt":{"type":["string","null"],"format":"date"},"dataVersion":{"type":"string"},"sourceAvailability":{"$ref":"#/components/schemas/SourceAvailability"},"establishment":{"$ref":"#/components/schemas/Establishment"},"unconfirmed":{"$ref":"#/components/schemas/Unconfirmed"},"coverage":{"$ref":"#/components/schemas/Coverage"}},"additionalProperties":false},"Visit":{"type":"object","required":["entry"],"properties":{"entry":{"$ref":"#/components/schemas/IsoDate"},"exit":{"oneOf":[{"$ref":"#/components/schemas/IsoDate"},{"type":"null"}],"description":"Omit or null if the visit is still open."}},"additionalProperties":false},"StayRequest":{"type":"object","required":["passport","destination"],"properties":{"passport":{"oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"object","required":["country"],"properties":{"country":{"$ref":"#/components/schemas/CountryCode"},"expires":{"$ref":"#/components/schemas/IsoDate"}},"additionalProperties":false}]},"destination":{"$ref":"#/components/schemas/CountryCode"},"trips":{"type":"array","maxItems":365,"items":{"$ref":"#/components/schemas/Visit"}},"date":{"allOf":[{"$ref":"#/components/schemas/IsoDate"}],"description":"Evaluate as at this date. Defaults to today."}},"additionalProperties":false},"StayRule":{"type":"object","required":["ruleId","maxDays","maxMonths","ruleType","windowDays","phrasing"],"properties":{"ruleId":{"type":"string"},"maxDays":{"type":["integer","null"]},"maxMonths":{"type":["integer","null"]},"maxYears":{"type":["integer","null"],"minimum":1},"ruleType":{"oneOf":[{"$ref":"#/components/schemas/RuleType"},{"type":"null"}]},"windowDays":{"type":["integer","null"]},"phrasing":{"type":["string","null"]}},"additionalProperties":false},"StayEvaluation":{"type":"object","required":["usedDays","remainingDays","evaluatorStatus","windowStart","windowEnd","active"],"properties":{"usedDays":{"type":"integer"},"remainingDays":{"type":"integer"},"evaluatorStatus":{"type":"string","enum":["OK","WARNING","CRITICAL","EXCEEDED"],"description":"WARNING at 7 days left, CRITICAL at 3, EXCEEDED at 0 or below."},"windowStart":{"$ref":"#/components/schemas/IsoDate"},"windowEnd":{"$ref":"#/components/schemas/IsoDate"},"active":{"type":"boolean","description":"Whether the traveller is inside the destination as at the evaluation date."}},"additionalProperties":false},"StayAnswer":{"type":"object","required":["status","passport","destination","regime","rule","evaluation","scope","asOf","checkedAt","dataVersion"],"properties":{"status":{"$ref":"#/components/schemas/Decision"},"passport":{"type":"string"},"destination":{"type":["string","null"]},"regime":{"type":["string","null"]},"rule":{"oneOf":[{"$ref":"#/components/schemas/StayRule"},{"type":"null"}]},"evaluation":{"oneOf":[{"$ref":"#/components/schemas/StayEvaluation"},{"type":"null"}],"description":"Null when the held rule states no day figure to evaluate against."},"scope":{"$ref":"#/components/schemas/Scope"},"asOf":{"$ref":"#/components/schemas/IsoDate"},"checkedAt":{"type":["string","null"],"format":"date"},"dataVersion":{"type":"string"},"sourceAvailability":{"$ref":"#/components/schemas/SourceAvailability"},"establishment":{"$ref":"#/components/schemas/Establishment"},"unconfirmed":{"$ref":"#/components/schemas/Unconfirmed"},"coverage":{"$ref":"#/components/schemas/Coverage"}},"additionalProperties":false},"RemainingAnswer":{"type":"object","required":["status","remaining","used","unit","window","rule","scope","asOf","checkedAt","dataVersion"],"properties":{"status":{"$ref":"#/components/schemas/Decision"},"remaining":{"type":["integer","null"]},"used":{"type":["integer","null"]},"unit":{"type":"string","const":"days"},"window":{"oneOf":[{"type":"object","required":["start","end"],"properties":{"start":{"$ref":"#/components/schemas/IsoDate"},"end":{"$ref":"#/components/schemas/IsoDate"}},"additionalProperties":false},{"type":"null"}]},"rule":{"oneOf":[{"$ref":"#/components/schemas/StayRule"},{"type":"null"}]},"scope":{"$ref":"#/components/schemas/Scope"},"asOf":{"$ref":"#/components/schemas/IsoDate"},"checkedAt":{"type":["string","null"],"format":"date"},"dataVersion":{"type":"string"},"establishment":{"$ref":"#/components/schemas/Establishment"},"unconfirmed":{"$ref":"#/components/schemas/Unconfirmed"},"coverage":{"$ref":"#/components/schemas/Coverage"}},"additionalProperties":false},"TripRequest":{"type":"object","required":["passport","route"],"properties":{"passport":{"oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"object","required":["country"],"properties":{"country":{"$ref":"#/components/schemas/CountryCode"},"expires":{"$ref":"#/components/schemas/IsoDate"}},"additionalProperties":false}]},"route":{"type":"array","minItems":1,"maxItems":50,"items":{"$ref":"#/components/schemas/CountryCode"},"description":"Destinations in travel order."}},"additionalProperties":false},"TripAnswer":{"type":"object","required":["status","passport","legs","issues","dataVersion"],"properties":{"status":{"allOf":[{"$ref":"#/components/schemas/Decision"}],"description":"The worst status across all legs."},"passport":{"type":"string"},"legs":{"type":"array","items":{"type":"object","required":["destination","status","requirement","stay","regime","ruleId","scope"],"properties":{"destination":{"type":"string"},"status":{"$ref":"#/components/schemas/Decision"},"requirement":{"oneOf":[{"$ref":"#/components/schemas/Requirement"},{"type":"null"}]},"stay":{"oneOf":[{"$ref":"#/components/schemas/Stay"},{"type":"null"}]},"regime":{"type":["string","null"]},"ruleId":{"type":["string","null"]},"scope":{"$ref":"#/components/schemas/Scope"},"establishment":{"$ref":"#/components/schemas/Establishment"},"unconfirmed":{"$ref":"#/components/schemas/Unconfirmed"},"coverage":{"$ref":"#/components/schemas/Coverage"}},"additionalProperties":false}},"issues":{"type":"array","description":"Only the legs that are not plain `allowed`, for a summary line.","items":{"type":"object","required":["destination","status","message"],"properties":{"destination":{"type":"string"},"status":{"$ref":"#/components/schemas/Decision"},"message":{"type":"string"}},"additionalProperties":false}},"dataVersion":{"type":"string"}},"additionalProperties":false},"PublicRule":{"type":"object","description":"An entitlement row as the API exposes it.","required":["id","regime","issuers","entryMode","advanceAuthorisation","authorisationKind","authorisationType","maxDays","maxMonths","limitKind","ruleType","windowDays","authorisationValidityDays","schemeName","portOfEntry","inForce","establishment"],"properties":{"id":{"type":"string","examples":["THA:thai-visa-free-30d"]},"regime":{"type":"string"},"issuers":{"type":"array","items":{"type":"string"},"description":"The passport issuers this rule is written for, ISO3."},"entryMode":{"oneOf":[{"$ref":"#/components/schemas/EntryMode"},{"type":"null"}]},"advanceAuthorisation":{"type":["boolean","null"]},"authorisationKind":{"type":["string","null"],"description":"ETA, EVISA, VISA_CONSULAR…"},"authorisationType":{"type":["string","null"],"description":"The coarser VISA | ETA | NONE | PERMIT. PERMIT outranks `authorisationKind`: permit-only places issue no visas at all.\n"},"maxDays":{"type":["integer","null"]},"maxMonths":{"type":["integer","null"]},"maxYears":{"type":["integer","null"],"minimum":1},"limitKind":{"type":["string","null"],"description":"DAYS | MONTHS | YEARS | NO_LIMIT | SET_BY_VISA | NOT_STATED_BY_SOURCE | UNKNOWN."},"ruleType":{"oneOf":[{"$ref":"#/components/schemas/RuleType"},{"type":"null"}]},"windowDays":{"type":["integer","null"]},"authorisationValidityDays":{"type":["integer","null"]},"schemeName":{"type":["string","null"]},"portOfEntry":{"oneOf":[{"$ref":"#/components/schemas/PortOfEntry"},{"type":"null"}]},"inForce":{"type":"boolean"},"establishment":{"$ref":"#/components/schemas/Establishment"},"unconfirmed":{"$ref":"#/components/schemas/Unconfirmed"}},"additionalProperties":false},"Citation":{"type":"object","required":["field","sourceId","url","passage","whySettles"],"properties":{"field":{"type":["string","null"],"description":"Which field this citation settles; null = the rule's primary source."},"sourceId":{"type":"string"},"url":{"type":["string","null"]},"passage":{"type":["string","null"]},"whySettles":{"type":["string","null"],"description":"Why this passage answers the question, in the researcher's words."}},"additionalProperties":false},"RegimeTrust":{"type":"object","description":"The referee record. `unassistedCompared` counts issuers whose answers were cross-checked without help; a won dispute is a strength, but it marks a fought row.\n","properties":{"unassistedCompared":{"type":["integer","null"]},"unassistedContested":{"type":["integer","null"]},"disputesWon":{"type":["integer","null"]},"disputesLost":{"type":["integer","null"]}},"additionalProperties":false},"RuleDetail":{"type":"object","required":["rule","conditions","citations","regime","dataVersion"],"properties":{"rule":{"$ref":"#/components/schemas/PublicRule"},"conditions":{"type":"array","items":{"$ref":"#/components/schemas/Condition"}},"citations":{"type":"array","items":{"$ref":"#/components/schemas/Citation"}},"regime":{"oneOf":[{"type":"object","required":["code","provenance","checkedAt","trust"],"properties":{"code":{"type":"string"},"provenance":{"type":"string","enum":["promoted","legacy"]},"checkedAt":{"type":["string","null"],"format":"date"},"sourceAvailability":{"$ref":"#/components/schemas/SourceAvailability"},"trust":{"$ref":"#/components/schemas/RegimeTrust"}},"additionalProperties":false},{"type":"null"}]},"dataVersion":{"type":"string"}},"additionalProperties":false},"RuleSearchRequest":{"type":"object","description":"All filters optional; combined with AND.","properties":{"destination":{"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"description":"Resolved to its regime first, so territories behave correctly."},"regime":{"type":"string","description":"A regime code directly, bypassing resolution."},"passport":{"$ref":"#/components/schemas/CountryCode"},"entryMode":{"$ref":"#/components/schemas/EntryMode"},"ruleType":{"$ref":"#/components/schemas/RuleType"},"options":{"$ref":"#/components/schemas/Options"}},"additionalProperties":false},"RuleSearchAnswer":{"type":"object","required":["results","coverage","dataVersion"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/PublicRule"}},"truncated":{"type":"boolean","description":"True when the 200-row cap bit. Absent on the branch where the destination could not be resolved at all.\n"},"coverage":{"description":"Two shapes. When the destination resolved, the store's own coverage (`regimesHeld`, and that regime's provenance and check date). When it did not, the same `Coverage` block the answer endpoints return, saying why.\n","oneOf":[{"type":"object","required":["regimesHeld"],"properties":{"regimesHeld":{"type":"integer"},"regimes":{"type":"array","items":{"type":"object","required":["code","provenance","checkedAt"],"properties":{"code":{"type":"string"},"provenance":{"type":"string","enum":["promoted","legacy"]},"checkedAt":{"type":["string","null"],"format":"date"}},"additionalProperties":false}}},"additionalProperties":false},{"$ref":"#/components/schemas/Coverage"}]},"dataVersion":{"type":"string"}},"additionalProperties":false},"SourceDetail":{"type":"object","required":["id","url","sha256","fetchedAt","transport","authority","citedBy","passages","dataVersion"],"properties":{"id":{"type":"string"},"url":{"type":["string","null"]},"sha256":{"type":["string","null"],"description":"The hash of the bytes we read. Document bytes stay in the canonical git store; this is the pointer that makes the answer reproducible.\n"},"fetchedAt":{"type":["string","null"],"format":"date-time"},"transport":{"type":["string","null"],"description":"How the document was retrieved."},"authority":{"oneOf":[{"type":"object","properties":{"publisherQuote":{"type":["string","null"],"description":"The publisher's own statement of its authority, quoted."},"claim":{"type":["string","null"]}},"additionalProperties":false},{"type":"null"}]},"citedBy":{"type":"array","items":{"type":"string"}},"passages":{"type":"array","items":{"type":"object","required":["ruleId","field","quote","whySettles"],"properties":{"ruleId":{"type":"string"},"field":{"type":["string","null"]},"quote":{"type":["string","null"]},"whySettles":{"type":["string","null"]}},"additionalProperties":false}},"dataVersion":{"type":"string"}},"additionalProperties":false},"ScopeRef":{"type":"object","required":["id","version"],"properties":{"id":{"type":"string"},"version":{"type":"string"}}},"LimitScope":{"allOf":[{"$ref":"#/components/schemas/ScopeRef"},{"type":"object","required":["members"],"properties":{"members":{"type":"array","items":{"type":"object","required":["countryCode","validFrom","validTo"],"properties":{"countryCode":{"type":"string","description":"ISO3 territory code"},"validFrom":{"type":["string","null"],"format":"date"},"validTo":{"type":["string","null"],"format":"date"}}}}}}]},"LimitProperties":{"type":"object","required":["scope","ruleType","maxDays","maxMonths","windowDays","anchorMonth","anchorDay","dayCountingMethod","gapPolicy","warningThresholds"],"properties":{"scope":{"$ref":"#/components/schemas/ScopeRef"},"ruleType":{"type":"string","enum":["ROLLING_WINDOW","ANCHORED_YEAR","RESETS_ON_EXIT"]},"maxDays":{"type":"integer"},"maxMonths":{"type":["integer","null"]},"maxYears":{"type":["integer","null"],"minimum":1},"windowDays":{"type":["integer","null"]},"anchorMonth":{"type":["integer","null"]},"anchorDay":{"type":["integer","null"]},"dayCountingMethod":{"type":"string","enum":["ANY_PART","MIDNIGHT","FULL_24H"]},"gapPolicy":{"type":"string","enum":["RECORDED_ONLY","CAUTIOUS_ADJACENT_V1"]},"warningThresholds":{"type":"object","required":["warningDays","criticalDays"],"properties":{"warningDays":{"type":"integer"},"criticalDays":{"type":"integer"}}}}},"SnapshotRule":{"type":"object","required":["id","regime","issuers","entryMode","advanceAuthorisation","authorisationKind","maxDays","maxMonths","ruleType","windowDays","inForce","establishment"],"properties":{"id":{"type":"string"},"regime":{"type":"string"},"issuers":{"type":"array","items":{"type":"string"}},"entryMode":{"oneOf":[{"$ref":"#/components/schemas/EntryMode"},{"type":"null"}]},"advanceAuthorisation":{"type":["boolean","null"]},"authorisationKind":{"type":["string","null"]},"maxDays":{"type":["integer","null"]},"maxMonths":{"type":["integer","null"]},"maxYears":{"type":["integer","null"],"minimum":1},"ruleType":{"oneOf":[{"$ref":"#/components/schemas/RuleType"},{"type":"null"}]},"windowDays":{"type":["integer","null"]},"inForce":{"type":"boolean"},"headline":{"type":"boolean"},"validFrom":{"type":["string","null"],"format":"date"},"validTo":{"type":["string","null"],"format":"date"},"limitKind":{"type":["string","null"]},"requirement":{"oneOf":[{"$ref":"#/components/schemas/Requirement"},{"type":"null"}]},"stay":{"oneOf":[{"$ref":"#/components/schemas/Stay"},{"type":"null"}]},"establishment":{"$ref":"#/components/schemas/Establishment"},"unconfirmed":{"$ref":"#/components/schemas/Unconfirmed"},"sourceUrl":{"type":["string","null"]},"properties":{"oneOf":[{"$ref":"#/components/schemas/LimitProperties"},{"type":"null"}]}},"additionalProperties":false},"Snapshot":{"type":"object","required":["dataVersion","generatedAt","regimes","territories","rules"],"properties":{"snapshotVersion":{"type":"integer","description":"Bumped when the served shape changes for the same dataVersion. Version 4 adds limitData and per-rule properties; counting remains unchanged.\n"},"dataVersion":{"type":"string"},"generatedAt":{"type":"string","format":"date-time"},"regimes":{"type":"object","description":"Keyed by regime code.","additionalProperties":{"type":"object","required":["members","provenance","checkedAt"],"properties":{"members":{"type":"array","items":{"type":"string"}},"provenance":{"type":"string","enum":["promoted","legacy"]},"checkedAt":{"type":["string","null"],"format":"date"}},"additionalProperties":false}},"territories":{"type":"object","description":"Keyed by ISO2. `differs: true` never inherits the parent's rules.","additionalProperties":{"type":"object","required":["parent","differs"],"properties":{"parent":{"type":"string"},"differs":{"type":"boolean"}},"additionalProperties":false}},"limitData":{"type":"object","required":["version","scopes","countingCompatibility"],"properties":{"version":{"type":"integer","const":1},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/LimitScope"}},"countingCompatibility":{"type":"object","required":["scopes","limits"],"properties":{"scopes":{"type":"array","items":{"$ref":"#/components/schemas/LimitScope"}},"limits":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/LimitProperties"}}}}}},"rules":{"type":"array","items":{"$ref":"#/components/schemas/SnapshotRule"}}},"additionalProperties":false},"Health":{"type":"object","required":["ok","dbOk"],"properties":{"ok":{"type":"boolean"},"dbOk":{"type":"boolean"},"dataVersion":{"type":"string"}},"additionalProperties":false},"DemoTerms":{"type":"object","properties":{"enabled":{"type":"boolean"},"dailyQuota":{"type":"integer"},"endpoints":{"type":"array","items":{"type":"string"}},"note":{"type":"string"}},"additionalProperties":true},"Help":{"type":"object","description":"Attached to every 401 and 429. The point is that a rejection is not a dead end: it carries the demo tier's terms, the MCP URL, the discovery document, where a key comes from, and when a limit lifts.\n","properties":{"discover":{"type":"string","format":"uri"},"openapi":{"type":"string","format":"uri"},"mcp":{"type":"string","format":"uri"},"getKey":{"type":"string","format":"uri"},"demo":{"$ref":"#/components/schemas/DemoTerms"},"retryAfterSeconds":{"type":"integer"},"resetsAt":{"type":"string","format":"date-time"}},"additionalProperties":true},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["type","code","message"],"properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","permission_error","not_found_error","rate_limit_error","server_error"],"description":"A small stable taxonomy; the HTTP status follows it."},"code":{"type":"string","description":"The specific cause, e.g. `passport_invalid`, `api_key_invalid`, `demo_quota_exhausted`, `rule_not_found`, `body_too_large`, `database_busy`.\n"},"message":{"type":"string"},"param":{"type":"string","description":"The offending field, on validation errors."},"docsUrl":{"type":"string","format":"uri"},"help":{"$ref":"#/components/schemas/Help"}},"additionalProperties":false}},"additionalProperties":false},"Discovery":{"type":"object","description":"The keyless `GET /v1` document. Additive; treat unknown fields as forward compatibility.","required":["name","version","endpoints","tiers","versioning"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"version":{"type":"string"},"dataVersion":{"type":["string","null"]},"docs":{"type":"string"},"openapi":{"type":"object","properties":{"yaml":{"type":"string","format":"uri"},"json":{"type":"string","format":"uri"}}},"mcp":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"transport":{"type":"string"},"auth":{"type":"string"}}},"auth":{"type":"object","additionalProperties":true},"tiers":{"type":"object","additionalProperties":true},"endpoints":{"type":"array","items":{"type":"object","properties":{"method":{"type":"string"},"path":{"type":"string"},"sdk":{"type":"string"},"summary":{"type":"string"}},"additionalProperties":false}},"versioning":{"type":"object","additionalProperties":true},"examples":{"type":"string","format":"uri"},"support":{"type":"object","additionalProperties":true}},"additionalProperties":true},"JsonRpcRequest":{"type":"object","required":["jsonrpc","method"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"type":["string","integer","null"]},"method":{"type":"string","examples":["initialize","tools/list","tools/call","ping"]},"params":{"type":"object","additionalProperties":true}},"additionalProperties":false},"JsonRpcResponse":{"type":"object","required":["jsonrpc"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"type":["string","integer","null"]},"result":{"type":"object","additionalProperties":true},"error":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}},"additionalProperties":true}},"additionalProperties":false}}}}