Skip to content

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

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.

EndpointWhat it returns
GET /api/public/catalogEvery 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-ratesExchange 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-offerThe campaign offer currently applied to prices, if any.
GET /api/healthzLiveness 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 Content

A 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.

TierCredentialPolicyBudget
Freenonepublic_read_ip300 requests / 60 s per IP address
KeyedAuthorization: Bearer vf_live_…public_read_key3000 requests / 60 s per key

Versioning and deprecation

  • The API surface under https://visaforma.com/api is stable. Every response carries an API-Version header with the date of the current revision (for example API-Version: 2026-08-21). The policy is also spelled out in the info.description of 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 Deprecation header, the RFC 8594 Sunset header (the date after which the old behaviour stops) and, when a replacement exists, a Link: <…>; rel="successor-version" header. The OpenAPI document marks the same operation deprecated: 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=60 for 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 Requests response adds Retry-After with the number of seconds to wait, and the same RateLimit headers with r=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-b1b2

The 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.