DEVELOPER DOCUMENTATION / V1

Build with clarity.

A stateless assessment endpoint with a transparent scoring model.

01 / Quickstart

Create an account in the developer portal, create an API key and choose a paid API subscription. Keep the key on your server. Send a UUID Idempotency-Key header for each assessment.

curl -X POST "$VSA_BASE_URL/api/v1/vsa/assessments" \
  -H "Authorization: Bearer $VSA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 12345678-1234-4123-8123-123456789abc" \
  --data @assessment.json

Base URL: https://checkmyvisa.co.uk. During private preview, API use is restricted to invited accounts, with a valid VSA key and paid subscription.

Test your key without a charge

Sign in, choose an agency account, then open API keys. Create and save a key, paste it into Test your API key, and select Test connection. No subscription is needed for this connection test.

From a terminal, set VSA_API_KEY securely and run:

curl -X POST "https://checkmyvisa.co.uk/api/v1/vsa/key-check"   -H "Authorization: Bearer $VSA_API_KEY"

A 200 response with valid: true confirms the key and linked account can authenticate. assessment_units_used is 0. It does not verify payment, document extraction or scoring. A full assessment uses the endpoint above and requires a paid subscription. Missing or invalid keys return 401; accounts outside the preview return 403; unavailable services return 503. A legacy Unkey credit limit can return 402.

API access with Unkey

Open API keys after signing in. Create a VSA key and copy it immediately: the full key is shown once. Use it only on your server as an Authorization: Bearer header. You do not need your own Unkey account to call VSA.

Unkey verifies whether a key is valid, enabled and within configured limits. VSA additionally checks the issuing API, linked user, private-preview access and paid monthly allowance. Keys for one user share the subscription allowance; creating another key does not reset it.

Starter: £425/month for 200 completed API checks. Business: £1,275/month for 1,000. Requests stop at the allowance; extra usage is not automatically charged. Manage your plan from the workspace.

Retrying and rotating keys

Use a new UUID Idempotency-Key for each new assessment. Keep the same key when retrying the same request after a network interruption. A duplicate returns 409; results are not replayed. Save successful responses. To rotate a key, create a replacement, update your server secret, then ask the administrator to revoke the old key in Unkey.

Administrators: keep UNKEY_ROOT_KEY server-only and scope it to reading, creating and verifying keys in the VSA keyspace. The application links the confirmed email through externalId and checks the Supabase user ID in server-controlled key metadata.

Sources: Unkey quickstart · Unkey identities. Reviewed 16 September 2026.

Student document readiness

Use a Student envelope: {"route":"student","facts":{"student":{...}},"documents":[...]}. The downloadable fictional questionnaire shows facts.student fields. Documents use the pack types including cas, atas_certificate and tb_certificate. is_verified records your review, not issuer authentication.

The response includes kind: student, score (null when unresolved), score_range, status, checks with official sources, required_funds_gbp and the ruleset version. This is a checklist-completion measure, not a probability or UKVI points. A failed check always requires action. Complex funding, dependants and in-country cases require review. Legacy Student payloads are rejected with STUDENT_SCHEMA_REQUIRED; single-document extraction is not a Student assessment.

02 / Request schema

Send all required fields as JSON. Dates use YYYY-MM-DD, amounts are GBP, and verification flags are explicit assertions from your own document checks. Unknown values must be resolved before assessment.

{
  "applicant": {
    "passport_expiry_date": "2030-12-31",
    "nationality": "GBR"
  },
  "trip": {
    "start_date": "2027-04-01",
    "end_date": "2027-04-14",
    "estimated_cost_gbp": 2000,
    "visa_type": "tourist"
  },
  "finances": {
    "available_funds_gbp": 6500,
    "monthly_net_income_gbp": 3000,
    "has_unexplained_large_deposits": false
  },
  "ties": {
    "employment_status": "employed",
    "owns_property": false,
    "has_dependents_in_home_country": false
  },
  "history": {
    "previous_visa_refusals": 0,
    "travel_history_count": 4
  },
  "documents": [
    {
      "type": "passport",
      "is_verified": true
    },
    {
      "type": "bank_statement",
      "is_verified": true
    },
    {
      "type": "employment_letter",
      "is_verified": true
    },
    {
      "type": "accommodation",
      "is_verified": true
    }
  ]
}

Optional extension: trip.purpose_justification is a string documenting the reason for a tourist stay longer than 90 days. Without it, the duration deduction applies.

Supported document types: passport, bank_statement, employment_letter, accommodation and itinerary. Occupation evidence, including student or self-employment evidence, uses the employment_letter slot in this version.

03 / Response

Score and evidence coverage are independent. Identical validated inputs produce identical scores and findings; the timestamp records when the response was generated.

{
  "score": 90,
  "outcome_band": "Strong",
  "evidence_coverage_level": "High",
  "coverage_percentage": 100,
  "deductions_by_category": {
    "eligibility": 0,
    "financial_affordability": 0,
    "document_consistency": 0,
    "reasons_to_return": 10,
    "travel_history": 0,
    "duration_vs_purpose": 0
  },
  "findings": [
    {
      "category": "reasons_to_return",
      "severity": "warning",
      "deduction": 10,
      "message": "No property or dependents declared in the home country.",
      "finding_id": "711b5356-1f1d-482a-af4c-070012dd7cc6",
      "document_id": "application",
      "document_type": "questionnaire",
      "rule_pack_id": "ukvi.visitor.v1",
      "code": "RETURN_TIES_WEAK",
      "tier": "RPF",
      "field_path": "reasons_to_return",
      "evidence": {
        "extracted": "No property or dependents declared in the home country.",
        "expected": "Category cap 20"
      },
      "cross_ref": [],
      "score_delta": -10,
      "queue_human": false,
      "category_key": "reasons_to_return",
      "lifecycle_state": "OPEN",
      "dedupe_key": "ukvi.visitor.v1|application|RETURN_TIES_WEAK|reasons_to_return",
      "adverse": true
    }
  ],
  "timestamp": "2026-09-13T12:00:00.000Z",
  "rule_pack_status": "QA",
  "scanning_live": false
}

04 / Scoring rules

Start at 100 and subtract deductions, capped separately in each category. The final score cannot fall below 0. These are the configured VSA readiness rules; they do not represent destination-specific legal requirements or approval probabilities.

CategoryRulesCap
EligibilityPassport expires before six calendar months after the trip ends: −20. Missing or unverified passport: −20.20
Financial affordabilityFunds below trip cost: −25; otherwise funds below 1.5 × cost: −15. Unexplained large deposits: −10. Net income ≤ 0 for non-students: −10.25
Document consistencyEnd date on or before start date: −20. Flagged bank statement or itinerary discrepancies: −10 per type.20
Reasons to returnUnemployed: −15. No property and no dependents at home: −10.20
Travel historyAny previous refusal: −10. No previous travel: −5.10
Duration vs purposeTourist stay over 90 days without purpose_justification: −5.5

Strong: 85–100. Moderate: 65–84. High Risk: 0–64. Findings remain visible after a category cap is reached; their deduction reports the amount actually applied, which may be 0.

For consistency flags, use issues: ["date_discrepancy"]. Issue strings containing discrepancy, mismatch, inconsisten or conflict trigger the rule. A document type is deducted at most once. Tourist aliases include tourist, tourist visa, standard tourist and e-tourist.

05 / Evidence coverage

Four evidence slots each contribute 25%: passport; bank statement; occupation; and accommodation or itinerary. Only documents with is_verified set to true count. Duplicate documents never increase coverage. Providing both accommodation and itinerary still fills one slot.

LevelCoverageVerified slots
High≥90%4 of 4
Medium75–89%3 of 4
Low50–74%2 of 4
Insufficient<50%0–1 of 4

06 / Optional document extraction

Instead of the direct request, submit a document envelope with exactly one document_url or document_base64. Supply known application facts in assessment. Maximum base64 file size on Netlify: 3 MiB (4 MiB for approved URL imports). Supported types: PDF, JPEG and PNG. Base64 must be raw, without a data URL prefix.

{
  "document_base64": "BASE64_FILE_BYTES",
  "mime_type": "application/pdf",
  "assessment": {
    "trip": {
      "start_date": "2027-04-01",
      "end_date": "2027-04-14",
      "estimated_cost_gbp": 2000,
      "visa_type": "tourist"
    },
    "finances": {
      "available_funds_gbp": 6500,
      "monthly_net_income_gbp": 3000,
      "has_unexplained_large_deposits": false
    },
    "ties": {
      "employment_status": "employed",
      "owns_property": false,
      "has_dependents_in_home_country": false
    },
    "history": {
      "previous_visa_refusals": 0,
      "travel_history_count": 4
    },
    "documents": []
  }
}

URLs must use HTTPS on an administrator-approved hostname and return the file directly. Redirects are rejected. OpenAI through Netlify AI Gateway receives the document and context for extraction; VSA does not store assessment payloads or documents in its database. Extraction requests use store:false; provider processing follows Netlify and OpenAI terms.

Extraction never determines the score. Incomplete or unreadable results are rejected. OCR cannot verify evidence; only verification assertions explicitly supplied in assessment.documents can be retained.

07 / Errors & usage

HTTPMeaning
200Assessment completed
400Missing body or invalid JSON
401Missing, invalid, expired, disabled or wrong-API key
403Account is not invited to the private preview
402Paid subscription required or monthly allowance exceeded
409Request already in progress, completed, or reused with different data
413Request over 4.3 MB or imported document over 4 MiB
415Content-Type must be application/json
422Invalid fields, disallowed document source or incomplete extraction
429Rate limit exceeded
502Document extraction provider unavailable
503Integration not configured or key provider unavailable

Each completed assessment uses one unit from the account’s monthly subscription allowance. Invalid requests and failed extractions do not use a unit. Allowances reset on the paid Stripe billing period, not the calendar month. A UUID Idempotency-Key identifies each request; concurrent duplicates and completed duplicates return 409 without another charge. Results are not stored or replayed: keep successful responses in your own system. Expired reservations are released after two minutes. Overage charging is disabled until a per-check rate and customer opt-in are configured.

Every assessment response uses Cache-Control: private, no-store. Never put API keys in URLs, browser bundles or public logs.