VisaForma developer documentation
VisaForma exposes the catalog behind visaforma.com through a public, read-only REST API, publishes its OpenAPI specification, and serves every public page as Markdown for AI agents. This page is the entry point for all of it.
Quick links
- Base URL:
https://visaforma.com/api - OpenAPI 3.1 specification: https://visaforma.com/api/openapi.json
- Agent overview (llms.txt): https://visaforma.com/llms.txt
- Sitemap of every public page: https://visaforma.com/sitemap.xml
- Support for integrations: account@visaforma.com
Public endpoints (no authentication)
The endpoints below need no API key, cookie or token. They are read-only, return JSON and are safe to cache.
| Endpoint | What it returns |
|---|---|
GET /api/public/catalog | Every market (passport country) with its locales, currency and the list of visa directions, each with slug, destination, visa kind, localized title and price. |
GET /api/public/fx-rates | Exchange rates used to present prices in USD, EUR, TRY and INR. |
GET /api/public/markets/{market}/landings/{locale} | The localized content of a market landing page (market = tr or in). |
GET /api/public/markets/{market}/directions/{slug}/seo/{locale} | The localized SEO content of one direction page: requirements, documents, facts, FAQ. |
GET /api/promotions/current-offer | The campaign offer currently applied to prices, if any. |
GET /api/healthz | Liveness probe. |
Example:
curl -s https://visaforma.com/api/public/catalog | jq '.markets[] | {code, directions: (.directions | length)}'Every operation — public and private — is described in the OpenAPI document with an operationId, a summary, a description and typed request/response schemas. Private operations (applications, documents, billing, account) require a signed-in customer session or a staff role and are documented for completeness; the self-serve API key below applies to the public read endpoints only.
API keys (self-serve)
The public endpoints work without any key (the free tier: 300 requests per minute per IP address). If you need more — a crawler, an agent making many calls, a backend that fans out requests from one address — issue yourself a key. No sign-up, no confirmation e-mail, no sales call: one request returns a working key.
Issue a key. POST /api/developers/api-keys with your contact e-mail and an optional label:
curl -s -X POST https://visaforma.com/api/developers/api-keys \
-H "Content-Type: application/json" \
-d '{"email": "agent@example.com", "label": "my-crawler"}'{
"key": "vf_live_AbCdEfGhIjKlMnOpQrStUvWxYz012345",
"key_prefix": "vf_live_AbCdEfGh",
"created_at": "2026-08-22T10:15:00Z",
"quota": {"requests_per_minute": 3000}
}The key is shown once — store it. We keep only its hash. Limits on issuance: 3 keys per hour per IP address (429 with Retry-After) and 5 active keys per e-mail address (409; revoke one first).
Use it. Send the key as a bearer token on any public endpoint; the quota is then charged to the key (policy public_read_key, 3000 requests per minute) instead of to your IP:
curl -s https://visaforma.com/api/public/catalog \
-H "Authorization: Bearer vf_live_AbCdEfGhIjKlMnOpQrStUvWxYz012345"An unknown or revoked key is refused with 401 {"detail": "invalid api key"} — the request is never silently downgraded to the anonymous budget, so a broken key shows up immediately rather than as mysterious 429s later. In the OpenAPI document the scheme is ApiKeyBearer, listed as optional (security: [{}, {"ApiKeyBearer": []}]) on every public read operation.
Inspect and revoke. The key is its own credential for the two management calls:
curl -s https://visaforma.com/api/developers/api-keys/current \
-H "Authorization: Bearer vf_live_…"
# → {"key_prefix": "vf_live_AbCdEfGh", "label": "my-crawler", "created_at": "…",
# "last_used_at": "…", "quota": {"requests_per_minute": 3000}}
curl -s -X DELETE https://visaforma.com/api/developers/api-keys/current \
-H "Authorization: Bearer vf_live_…"
# → 204 No ContentA revoked key stops being accepted within 60 seconds at most (immediately on the node that handled the revocation) and no longer counts toward the 5-key ceiling. last_used_at is coarse — updated at most once a minute.
| Tier | Credential | Policy | Budget |
|---|---|---|---|
| Free | none | public_read_ip | 300 requests / 60 s per IP address |
| Keyed | Authorization: Bearer vf_live_… | public_read_key | 3000 requests / 60 s per key |
Versioning and deprecation
- The API surface under
https://visaforma.com/apiis stable. Every response carries anAPI-Versionheader with the date of the current revision (for exampleAPI-Version: 2026-08-21). The policy is also spelled out in theinfo.descriptionof the OpenAPI document. - Additive changes (new fields, new endpoints) are not breaking and do not change the version date.
- A breaking change is announced at least 90 days in advance on the affected operation with the RFC 9745
Deprecationheader, the RFC 8594Sunsetheader (the date after which the old behaviour stops) and, when a replacement exists, aLink: <…>; rel="successor-version"header. The OpenAPI document marks the same operationdeprecated: true.
Rate limits
Public endpoints are rate limited per client address: 300 requests per 60-second window (policy public_read_ip). With a self-serve key (see above) the same endpoints are limited per key instead: 3000 requests per 60-second window (policy public_read_key); the IP budget is not touched by keyed requests. Every response from the fenced endpoints includes the IETF rate-limit headers so a client can self-throttle:
RateLimit-Policy: "public_read_ip";q=300;w=60— the quota and its window in seconds ("public_read_key";q=3000;w=60for a keyed request).RateLimit: "public_read_ip";r=287;t=41— remaining requests in the window and seconds until it resets.- A
429 Too Many Requestsresponse addsRetry-Afterwith the number of seconds to wait, and the sameRateLimitheaders withr=0. - Issuing keys has its own budget:
developer_key_issue_ip, 3 per 3600 s per IP address.
Authenticated operations have their own budgets and announce them the same way when a budget is spent. Cache GET /api/public/catalog on your side — it changes a few times a day at most.
Markdown for agents
Every public HTML page on visaforma.com has a Markdown twin served from the same URL through content negotiation:
curl -H "Accept: text/markdown" https://visaforma.com/
curl -H "Accept: text/markdown" https://visaforma.com/en/turkey/us-b1b2The response is Content-Type: text/markdown; charset=utf-8 with Vary: Accept; it contains the page content without navigation, scripts or layout. Unknown paths return a 404 Markdown body that points back to the sitemap and llms.txt, and a request that accepts neither HTML nor Markdown receives 406 Not Acceptable.
Errors
Errors are JSON objects with a detail field. 404 means the market, direction or locale does not exist, 422 means the request failed validation (FastAPI style detail array), 429 means the rate limit was hit, 401 with {"detail": "invalid api key"} means the bearer key you sent is unknown or revoked. Every response carries an x-request-id header; quote it when you write to support.
Terms
Use of the API is subject to the Terms of Service and the Privacy Policy. The catalog is informational: visa requirements and fees change, and the official government source is always authoritative.