T
TOMO
Developer Docs
BETA These docs are under partner review. Some features described are roadmap items, not yet shipped. Verify against your sandbox before relying on any contract.
● DRAFTv1.0.0travel.book_visa_assistance

travel.book_visa_assistance — Full Intent Specification

INTENT NAMESPACE: travel
INTENT NAME:      book_visa_assistance
FULL ID:          travel.book_visa_assistance
VERSION:          v1.0.0
STATUS:           draft
TTBS WEIGHTS:     time 0.30 · taste 0.15 · budget 0.20 · safety 0.35
LAST UPDATED:     2026-05-14

User engages a visa-application assistance service — document checklist, form completion, biometric appointment, courier-to-embassy, status tracking. Distinct from direct embassy / consulate booking (which is the underlying government action) — TOMO's partner orchestrates the assistance, not the visa issuance. Distinct from travel.book_package (which may include visa as a sub-component). Partner exemplars: VFS Global, BLS International, TLScontact, OneVasco, Atlys, Stamp This Passport, Thomas Cook Visa, MakeMyTrip Visa.


1. NATURAL LANGUAGE COVERAGE

Classifies IN

  • "visa for Thailand"
  • "Schengen visa from Hyderabad"
  • "USA tourist visa appointment"
  • "UK visa application help"
  • "Dubai visa on arrival or e-visa"
  • "Japan visa documents"
  • "Singapore visa for next month"
  • "visa renewal Canada"
  • "fast track visa Australia"
  • "visa for kids travel UK"

Classifies OUT — borderline NO

  • "buy flight to Bangkok" → travel.book_flight
  • "hotel Bali" → travel.book_hotel
  • "passport renewal" → out of scope v1 (route to government Passport Seva Kendra)
  • "OCI card" — out of scope v1
  • "PR / work visa lawyer" — out of scope v1

MULTI-INTENT TRIGGERS

  • "Schengen visa + travel insurance" → travel.book_visa_assistance + finance.buy_travel_insurance
  • "Visa + flight + hotel" → travel.book_visa_assistance + travel.book_flight + travel.book_hotel
  • "Visa + forex" → travel.book_visa_assistance + travel.book_forex

2. INPUT — TOMO → PROVIDER

{
  "intent":          "travel.book_visa_assistance",
  "intent_version":  "v1.0.0",
  "request_id":      "req_vis_2h4k_2026-05-14T10:00:00+05:30",
  "user_session_id": "anon_user_token_or_uid",

  "visa_request": {
    "destination_country_code": "TH",
    "visa_kind":              "tourist",
    "visa_sub_kind":          "single_entry",
    "travel_start_date":      "2026-08-10",
    "travel_end_date":        "2026-08-20",
    "duration_of_stay_days":  10,
    "applicant_count":        2,
    "applicants_profile_summary": [
      {
        "applicant_kind": "adult",
        "passport_country_code": "IN",
        "passport_validity_months_remaining": 14,
        "prior_travel_history_summary": "ASEAN_visited_within_3yr",
        "salaried_or_business": "salaried",
        "estimated_income_band_inr": "10_to_25_lakh",
        "previous_visa_refusals": false,
        "minor_under_18": false
      },
      {
        "applicant_kind": "adult",
        "passport_country_code": "IN",
        "passport_validity_months_remaining": 14,
        "prior_travel_history_summary": "ASEAN_visited_within_3yr",
        "salaried_or_business": "salaried",
        "estimated_income_band_inr": "10_to_25_lakh",
        "previous_visa_refusals": false,
        "minor_under_18": false
      }
    ],
    "application_mode_preferred": "e_visa",
    "biometric_required":         false,
    "submission_city":            "Hyderabad",
    "premium_service_preferred":  false,
    "express_processing_required": false,
    "budget_band":                "good",
    "budget_max_inr_total":       8000
  },

  "context": {
    "user_locale":          "en-IN",
    "user_currency_pref":   "INR",
    "trust_signals": {
      "is_repeat_customer":          false,
      "user_account_age_days":       312
    }
  }
}
Field Type Constraint Notes
intent string REQUIRED, equals "travel.book_visa_assistance"
visa_request.destination_country_code string REQUIRED, ISO_3166_2 Validated against partner coverage
visa_request.visa_kind enum REQUIRED, STRICT §6 tourist / business / transit / medical / student / employment / dependant / journalist
visa_request.visa_sub_kind enum REQUIRED, STRICT §6 single_entry / multiple_entry / on_arrival / e_visa / sticker / long_stay
visa_request.travel_start_date ISO_DATE REQUIRED Drives processing-timeline filter
visa_request.duration_of_stay_days int REQUIRED, 1-365
visa_request.applicant_count int REQUIRED, 1-20
visa_request.applicants_profile_summary array REQUIRED, length = applicant_count Per-applicant summary
visa_request.applicants_profile_summary[].passport_country_code string REQUIRED, ISO_3166_2 Passport-issuing country
visa_request.applicants_profile_summary[].passport_validity_months_remaining int REQUIRED, ≥0 Most countries require ≥6 mo
visa_request.applicants_profile_summary[].previous_visa_refusals bool REQUIRED Triggers extra-care advisory
visa_request.application_mode_preferred enum REQUIRED, STRICT §6 e_visa / sticker_at_consulate / sticker_at_vfs / on_arrival
visa_request.biometric_required bool REQUIRED Drives in-person VFS visit
visa_request.express_processing_required bool REQUIRED Premium fast-track

Anti-fabrication preamble: no paid placement. TOMO never represents that a visa will be approved. Partner provides assistance only — issuance is the embassy / consulate's sovereign decision. Partner cannot guarantee outcome. Partners offering "guaranteed visa" = ingest reject + suspension.


3. PROVIDER TOOLS

Tool 1: search_visa_services

PURPOSE:        return assistance offerings for country + visa kind + applicants
INPUT:          §2 request body
OUTPUT:         { services: VisaServiceOption[], result_token, expires_at }
SLA:            p50 < 800ms, p95 < 2500ms
RATE LIMIT:     ≤ 1/sec per (user_session_id, partner)
RESULT SET:     up to 10 services

Tool 2: get_visa_service_detail

PURPOSE:        full document checklist + sample-rejection reasons + timeline
INPUT:          { service_id, request_id }
OUTPUT:         VisaServiceDetail (§5)
SLA:            p95 < 1500ms

Tool 3: hold_appointment_slot

PURPOSE:        soft-lock VFS / BLS / consulate biometric slot for 30 min
INPUT:          { service_id, slot_id, applicants, request_id }
OUTPUT:         { hold_id, hold_expires_at, locked_price_inr }
SLA:            p95 < 1500ms

Tool 4: confirm_visa_engagement

PURPOSE:        confirm engagement + document upload portal + payment lock
INPUT:          { hold_id, payment_method_id, request_id }
OUTPUT:         ConfirmedVisaEngagement (§5)
SLA:            p95 < 3500ms
IDEMPOTENCY:    request_id

Tool 5: submit_documents

PURPOSE:        encrypted document upload (passport scan, ITRs, bank statements, photos)
INPUT:          { engagement_id, document_kind, file_payload, request_id }
OUTPUT:         { document_id, accepted, rejection_reason_code }
SLA:            p95 < 5000ms
RATE LIMIT:     50 uploads / engagement

Tool 6: check_application_status

PURPOSE:        poll visa application status
INPUT:          { engagement_id, request_id }
OUTPUT:         ApplicationStatus (§5)
SLA:            p95 < 1500ms

Tool 7: cancel_engagement

PURPOSE:        cancel + tiered refund per stage policy
INPUT:          { engagement_id, reason_code, request_id }
OUTPUT:         CancellationResult (§5)
SLA:            p95 < 2500ms

4. RESPONSE SHAPE

VisaServiceOption

VisaServiceOption:
  service_id: { type: string, constraint: REQUIRED, opaque }
  provider:
    provider_id: { type: string, constraint: REQUIRED }
    name: { type: string, constraint: REQUIRED, semantics: "e.g. 'VFS Global', 'BLS International'" }
    authorized_for_country: { type: boolean, constraint: REQUIRED, semantics: "TRUE = appointed by destination embassy/consulate for IN applicants" }
    embassy_authorization_proof_url: { type: string, constraint: REQUIRED, HTTPS URL }
    years_in_market: { type: int, constraint: REQUIRED, ≥0 }
    iata_or_dot_registration: { type: string, constraint: REQUIRED, semantics: "Empty when not applicable; visa-only agencies may not need IATA" }

  destination_country_code: { type: string, constraint: REQUIRED, ISO_3166_2 }
  visa_kind: { type: enum, constraint: REQUIRED, STRICT §6 }
  visa_sub_kind: { type: enum, constraint: REQUIRED, STRICT §6 }
  application_mode: { type: enum, constraint: REQUIRED, STRICT §6 }

  processing_timeline:
    typical_business_days_min: { type: int, constraint: REQUIRED, ≥0 }
    typical_business_days_max: { type: int, constraint: REQUIRED, ≥0 }
    express_business_days_min: { type: int, constraint: REQUIRED, ≥0 }
    express_business_days_max: { type: int, constraint: REQUIRED, ≥0 }
    last_safe_submission_date: { type: string, constraint: REQUIRED, ISO_DATE }

  pricing:
    government_visa_fee_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0, semantics: "Pass-through to embassy/consulate" }
    service_fee_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0, semantics: "VFS / BLS service fee" }
    courier_fee_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    biometric_fee_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    express_premium_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    gst_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    total_per_applicant_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
    total_for_party_inr: { type: int, constraint: REQUIRED, INR_INTEGER }

  submission_centers:
    type: array<object>
    constraint: REQUIRED, ≥1
    shape:
      city: { type: string, constraint: REQUIRED }
      address: { type: string, constraint: REQUIRED }
      next_available_slot: { type: string, constraint: REQUIRED, ISO_DATETIME }

  documents_checklist: { type: array<enum>, constraint: REQUIRED, STRICT §6, ≥1 }
  applicant_eligibility_notes: { type: array<string>, constraint: REQUIRED, may be empty }

  approval_rate_pct:
    type: float
    constraint: REQUIRED, 0-100
    semantics: "Embassy's published or platform-tracked approval rate for IN applicants in past 12 months"

  rating:
    average_score: { type: float, constraint: REQUIRED, 0.0-5.0 }
    review_count: { type: int, constraint: REQUIRED, ≥0 }

  partner_reference:
    source: { type: string, constraint: REQUIRED }
    deeplink: { type: string, constraint: REQUIRED, HTTPS URL }

VisaServiceDetail

VisaServiceDetail:
  service_id: { type: string, constraint: REQUIRED }
  documents_full_list_with_explanations:
    type: array<object>
    constraint: REQUIRED, ≥1
    shape:
      document_kind: { type: enum, constraint: REQUIRED, STRICT §6 }
      required: { type: boolean, constraint: REQUIRED }
      format_spec: { type: string, constraint: REQUIRED, semantics: "PDF/PNG/JPG, dimensions, color etc." }
      explanation: { type: string, constraint: REQUIRED }
      sample_url: { type: string, constraint: REQUIRED, HTTPS URL, semantics: "Empty when no sample" }
  common_rejection_reasons: { type: array<string>, constraint: REQUIRED, ≥3 }
  passport_validity_required_months: { type: int, constraint: REQUIRED }
  photo_specifications: { type: string, constraint: REQUIRED }
  financial_proof_floor_inr_per_day: { type: int, constraint: REQUIRED, ≥0 }
  travel_insurance_required: { type: boolean, constraint: REQUIRED }
  travel_insurance_minimum_cover_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0, semantics: "0 when not required" }

ConfirmedVisaEngagement

ConfirmedVisaEngagement:
  engagement_id: { type: string, constraint: REQUIRED, immutable }
  service_id: { type: string, constraint: REQUIRED }
  applicants: { type: array<object>, constraint: REQUIRED, ≥1, shape: { name, passport_number_masked } }
  appointment:
    scheduled_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
    center_address: { type: string, constraint: REQUIRED }
    biometric_required: { type: boolean, constraint: REQUIRED }
  documents_upload_portal_url: { type: string, constraint: REQUIRED, HTTPS URL }
  documents_due_by: { type: string, constraint: REQUIRED, ISO_DATETIME }
  total_paid_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
  invoice_url: { type: string, constraint: REQUIRED, HTTPS URL }
  expected_decision_window:
    earliest: { type: string, constraint: REQUIRED, ISO_DATE }
    latest: { type: string, constraint: REQUIRED, ISO_DATE }
  passport_return_courier_url: { type: string, constraint: REQUIRED, HTTPS URL, semantics: "Empty until passport in transit" }
  emergency_support:
    helpline_phone: { type: string, constraint: REQUIRED, E.164 }
    helpline_email: { type: string, constraint: REQUIRED }

ApplicationStatus

ApplicationStatus:
  engagement_id: { type: string, constraint: REQUIRED }
  status:
    type: enum
    constraint: REQUIRED, STRICT §6
    values: [docs_pending, docs_in_review, appointment_scheduled, biometric_done, submitted_to_embassy, in_processing, additional_docs_requested, decision_made, passport_in_transit, completed_approved, completed_rejected, withdrawn]
  reason_code: { type: enum, constraint: REQUIRED, STRICT §6, may be 'none' }
  additional_docs_required: { type: array<string>, constraint: REQUIRED, may be empty }
  last_updated_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
  estimated_resolution_date: { type: string, constraint: REQUIRED, ISO_DATE }

CancellationResult

CancellationResult:
  engagement_id: { type: string, constraint: REQUIRED }
  cancelled_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
  stage_at_cancellation: { type: enum, constraint: REQUIRED, STRICT §6 }
  refund_amount_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  refund_method: { type: enum, constraint: REQUIRED, STRICT §6, values: [original_payment, bank_transfer] }
  refund_eta_days: { type: int, constraint: REQUIRED, 0-30 }
  non_refundable_government_fee_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0, semantics: "Government visa fee is non-refundable once submitted" }

FORBIDDEN FIELDS

  • paid_placement_score, ad_bid, sponsored_rank, featured_visa_service
  • editor_pick, tomo_recommended, top_choice
  • approval_guarantee_text, guaranteed_visa_in_X_days_text
  • embassy_authorization_proof_fake
  • inflated_approval_rate_pct
  • commission_padded_service_fee
  • confidential_embassy_communication_disclosed (any embassy direct comms are confidential)
  • applicant_real_names in reviews

5. CONTROLLED VOCABULARIES

visa_kind:
  values: [tourist, business, transit, medical, student, employment, dependant, journalist, official, diplomatic]

visa_sub_kind:
  values:
    single_entry, multiple_entry, on_arrival, e_visa, sticker, long_stay,
    short_stay_schengen_C, schengen_long_D, transit_airport_A, transit_C

application_mode_preferred / application_mode:
  values: [e_visa, sticker_at_consulate, sticker_at_vfs, sticker_at_bls, on_arrival, courier_only]

applicants_profile_summary[].passport_country_code:
  values: ISO_3166_2 list (full)

applicants_profile_summary[].salaried_or_business:
  values: [salaried, self_employed, business, student, dependant, retired, unemployed_disclosed]

applicants_profile_summary[].estimated_income_band_inr:
  values: under_5_lakh, 5_to_10_lakh, 10_to_25_lakh, 25_to_50_lakh, 50_to_1cr, above_1cr

applicants_profile_summary[].prior_travel_history_summary:
  values:
    none, ASEAN_visited_within_3yr, Schengen_visited_within_3yr,
    US_UK_visited_within_3yr, Australia_NZ_visited_within_3yr,
    Visited_5_plus_countries_within_5yr, no_recent_international

documents_checklist / documents_full_list_with_explanations.document_kind:
  values:
    passport_first_last_page, passport_all_used_pages, photograph_specs_compliant,
    cover_letter, application_form_filled, bank_statement_6_month,
    itr_3_years, salary_slip_3_month, employment_letter, business_proof,
    student_id, school_admission_letter, hotel_booking_proof,
    flight_booking_proof, travel_insurance_proof, sponsor_letter_NOC,
    sponsor_financial_proof, marriage_certificate, birth_certificate_minor,
    parental_consent_minor, gst_registration_business, partnership_deed,
    pan_card, aadhaar, no_objection_certificate

ApplicationStatus.reason_code:
  values:
    none, docs_incomplete, photo_non_compliant, financial_proof_insufficient,
    travel_history_concern, prior_refusal_concern, sponsor_doc_concern,
    interview_required, additional_verification, embassy_internal,
    rejected_security, rejected_misrepresentation, rejected_insufficient_ties,
    rejected_other

CancellationResult.stage_at_cancellation:
  values:
    pre_documents, documents_partial, documents_complete,
    appointment_scheduled, submitted_to_embassy, decision_made_pre_return

CancellationResult.refund_method:
  values: [original_payment, bank_transfer]

6. TTBS DIMENSIONS

TIME (weight = 0.30):
  signals_used:
    - processing_timeline.typical_business_days_max vs days-until-travel
    - last_safe_submission_date proximity
    - submission_centers[].next_available_slot
    - express_business_days_min when express_processing_required=true
  weighting:
    timeline_fit: 0.45
    last_safe_buffer: 0.25
    next_slot: 0.20
    express_speed: 0.10
  user_band_handling:
    fast: prefer express_premium-priced services
    balanced: standard
    flexible: choose slower cheaper provider when timeline allows

TASTE (weight = 0.15):
  signals_used:
    - submission_city matches user_location
    - application_mode_preferred match
    - DNA repeat provider
  weighting:
    city_match: 0.50
    mode_match: 0.30
    dna: 0.20

BUDGET (weight = 0.20):
  signals_used:
    - pricing.total_per_applicant_inr
    - pricing.total_for_party_inr
    - express_premium_inr (when applicable)
  weighting:
    per_applicant: 0.45
    total: 0.40
    express_premium: 0.15

SAFETY (weight = 0.35):
  signals_used:
    - provider.authorized_for_country (HARD filter when destination's embassy
      has appointed an exclusive partner — partner must be that one or
      ingest reject)
    - provider.embassy_authorization_proof_url verifiable
    - approval_rate_pct (signal of expert handling; not embassy outcome)
    - rating.average_score ≥4.0 floor
    - documents_full_list_with_explanations completeness
    - financial_proof_floor_inr_per_day disclosed clearly
    - travel_insurance_required + minimum_cover stated
  weighting:
    authorized: 0.40
    embassy_proof: 0.15
    approval_rate: 0.10
    rating_floor: 0.15
    docs_completeness: 0.10
    proof_disclosures: 0.10
  user_band_handling:
    fast: relax rating to 3.8 (when authorized=true)
    balanced: floor 4.0
    great: only embassy-authorized provider regardless of price

Locked weights: time 0.30 / taste 0.15 / budget 0.20 / safety 0.35. Time is critical (travel date is a hard deadline). Safety enforces embassy-authorization hard filter where applicable (e.g., VFS holds exclusive UK/Schengen IN-applicant rights).


7. COMPLETION CONTRACT

POST /api/v1/cpc/mcp_provider/<your_partner_id>
Body:
{
  "intent":           "travel.book_visa_assistance",
  "external_id":      "<engagement_id>",
  "request_id":       "<request_id>",
  "amount_inr":       400,    // NET partner commission only
  "gst_inr":          72,
  "tips_inr":         0,
  "pass_through_inr": 7200,   // government visa fee + service fee + courier + biometric
  "closed_at":        "2026-07-25T17:00:00+05:30",
  "status":           "completed",
  "destination_country_code": "TH",
  "visa_kind":        "tourist",
  "visa_sub_kind":    "e_visa",
  "applicant_count":  2,
  "approval_outcome": "approved"     // approved / rejected / withdrawn
}

User pays full ticket. Partner's commission (~₹400) is amount_inr. Government fee + VFS / BLS service fees + courier + biometric ride in pass_through_inr. TOMO charges 10% × ₹400. HMAC-SHA256.

Rejected visa applications still bill the service fee — government fee non-refundable. Status completed regardless of approval outcome; outcome is tracked separately.


8. WIDGET

VisaServiceListingWidget.

Field mapping:
  - provider.name + "Embassy-authorized" badge when authorized_for_country=true
  - destination_country_code + visa_kind chip
  - application_mode chip
  - processing_timeline.typical_business_days_max → "5-7 working days"
  - last_safe_submission_date → "Submit by X to make travel"
  - pricing.total_for_party_inr → big price
  - approval_rate_pct → "92% approval (IN, past 12mo)" pill
  - rating.average_score + review_count → star pill
  - submission_centers.next_available_slot → "Earliest slot: Tue 10 AM"

9. CACHING POLICY

Call TTL Rationale
search_visa_services 5min Slot volatility moderate
get_visa_service_detail 24h Checklist stable
hold_appointment_slot NO CACHE Stateful lock
confirm_visa_engagement NO CACHE Idempotent
submit_documents NO CACHE Content-addressed
check_application_status 30min Status changes slowly
cancel_engagement NO CACHE
Embassy authorization verification 30d Updates rare

10. ERROR CODES

Code HTTP Meaning Retry
INVALID_REQUEST 400 Malformed No
RATE_LIMITED 429 Throttle 1, 2s
INTERNAL_ERROR 500 Partner failure 2, exp
SIGNATURE_INVALID 401 HMAC fail No
NO_SERVICE_MATCH 200 (empty) No service for country+kind n/a
SLOT_TAKEN 409 Hold race-lost UI re-queries
HOLD_EXPIRED 410 Hold elapsed UI re-holds
NOT_AUTHORIZED_PROVIDER 422 Provider not embassy-appointed for IN No
PASSPORT_VALIDITY_INSUFFICIENT 422 <6mo validity at return date No
TIMELINE_INFEASIBLE 422 Even express won't make travel date No
DOCUMENT_FORMAT_REJECTED 422 Photo / scan non-compliant No (UI re-uploads)
PAYMENT_FAILED 402 Gateway declined No
EMBASSY_REJECTED 200 (advisory) Final embassy decision n/a
ADDITIONAL_DOCS_REQUESTED 200 (advisory) Embassy follow-up n/a
CANCELLED_PAST_GOV_SUBMIT 410 Government fee non-refundable No
BIOMETRIC_NO_SHOW 422 Applicant missed biometric No (re-book)

11. SANDBOX → PRODUCTION CHECKLIST

[ ] All seven tools implemented
[ ] At least 30 destination countries supported
[ ] Embassy-authorized provider mapping verified per country
[ ] embassy_authorization_proof_url verifiable (signed document)
[ ] approval_rate_pct from past 12 months IN-applicant data (not global)
[ ] Document format specs match current embassy requirements
[ ] Common rejection reasons curated from real embassy feedback
[ ] last_safe_submission_date computation correctly accounts for embassy
    + courier + buffer
[ ] HMAC signing verified on test CPC webhook
[ ] amount_inr in CPC is COMMISSION (NET); pass_through_inr = gov fee + VFS + courier + biometric
[ ] All controlled vocabularies respected
[ ] No forbidden fields anywhere
[ ] SLA p95 met
[ ] Document upload endpoint encrypts at rest; auto-deletes 90 days post-decision
[ ] No client real names in reviews
[ ] Privacy policy + grievance officer contact uploaded
[ ] Customer support: 24×7 visa helpline + emergency escalation
[ ] Rejection-handling SOP reviewed (no false-hope language)

12. ANTI-FABRICATION RULES

RULE 1: provider.authorized_for_country must be verifiable for the
        destination country. Many embassies (UK, Schengen-area) have
        exclusive partners (e.g., VFS for UK from IN); other providers
        cannot serve those routes. Falsely claiming authorization =
        customer-harm + suspension.

RULE 2: No paid_placement, ad_bid, sponsored_rank, featured_visa_service.

RULE 3: NO approval guarantees. "Guaranteed visa in 7 days" / "100%
        approval" / "money-back guarantee" forbidden. Visa issuance is
        sovereign embassy decision; partner cannot guarantee.
        TOMO ingest scans for these phrases.

RULE 4: approval_rate_pct must be from past 12 months of IN applicants
        through this provider for this visa_kind. Partner inflating with
        global / cross-product / older data = ingest reject.

RULE 5: rating.average_score from completed engagements only;
        review_count from completed engagements only.

RULE 6: No client real names in reviews — applicant identifiability is
        sensitive PII for refused-visa applicants.

RULE 7: Embassy-direct communications (interview transcripts, internal
        reason codes from embassy) must not be disclosed. Partner
        publishing = privacy breach + ingest reject.

RULE 8: amount_inr is partner COMMISSION only. Government fee + VFS / BLS
        service fee + courier + biometric are pass_through_inr. Inflated
        commission base = audit.

RULE 9: Document upload endpoint must encrypt at rest; auto-delete 90
        days post-decision. Visa documents (bank statements, ITRs) are
        sensitive PII; perpetual retention forbidden.

RULE 10: Rejection-handling must use plain factual language ("Embassy
         did not approve"); partner cannot blame applicant or speculate
         on reasons unless embassy explicitly disclosed them.

RULE 11: Information-completeness score (hidden rank factor weight 0.10)
         rewards full §4 shape.

RULE 12: last_safe_submission_date must reflect realistic worst-case
         processing + courier + travel-buffer. Aggressive estimates that
         cause missed travel = customer-harm.

RULE 13: Government visa fee correctly identified as non-refundable
         once submitted; partner cannot hide this from refund table.

RULE 14: photo_specifications must match current embassy spec sheet;
         outdated specs = guaranteed rejection = customer-harm.

RULE 15: For minors, parental_consent_minor + birth_certificate_minor
         must be in checklist; skipping = embassy rejection guaranteed.

VERSION HISTORY

v1.0.0 — 2026-05-14 — Initial spec. Embassy-authorized provider mapping
                       per country (VFS / BLS / TLScontact / direct).
                       No approval guarantees (regulatory).
                       Approval-rate from past 12 months IN-applicants only.
                       Net-commission base. Document encryption + 90-day
                       auto-delete. Government fee non-refundable
                       once submitted.