TOMO
BETA Partner connections are in beta and these docs are under review. Anything marked planned is not built yet — we confirm with you what is live before your service goes live.
● SPECv1.0.0compliance.verify_kyc

compliance.verify_kyc — Full Intent Specification

INTENT NAMESPACE: compliance
INTENT NAME:      verify_kyc
FULL ID:          compliance.verify_kyc
VERSION:          v1.0.0
STATUS:           live
TTBS WEIGHTS:     time 0.30 · taste 0.05 · budget 0.20 · safety 0.45
LAST UPDATED:     2026-05-10

Cross-cutting utility intent. Verifies user identity documents (Aadhaar, PAN, passport, driving license, voter ID) against authoritative sources. Invoked from inside other intents that require KYC (finance.apply_personal_loan, finance.buy_term_insurance, mobility.book_self_drive on first booking, etc.). Safety weight dominates.


1. NATURAL LANGUAGE COVERAGE

Classifies IN

  • "verify my KYC for the loan"
  • "do KYC for car rental"
  • "complete KYC"
  • "Aadhaar verification"
  • "PAN verification"
  • "DigiLocker KYC"
  • "video KYC for insurance"

Classifies OUT — borderline NO

  • "verify RC for buying used car" → compliance.verify_rc_dl
  • "what's my KYC status" (read-only) → utility lookup
  • "register my business with GSTIN" → not a KYC intent

MULTI-INTENT TRIGGERS (most common case)

KYC is almost always runtime-injected by another intent:

  • finance.apply_personal_loan → injected compliance.verify_kyc if user not already KYC'd
  • mobility.book_self_drive → injected if first-time renter
  • marketplace.buy_used_car → injected for buyer + seller
  • finance.buy_term_insurance → injected if claim amount > ₹50L

Standalone invocation is rare but supported (user proactively does KYC before applying for products).


2. INPUT — TOMO → PROVIDER

{
  "intent":          "compliance.verify_kyc",
  "intent_version":  "v1.0.0",
  "request_id":      "req_kyc_8f4k_2026-05-10T08:14:00Z",
  "user_session_id": "anon_user_token_or_uid",

  "kyc_kind":        "video_kyc",
  "kyc_purpose":     "personal_loan",
  "regulator":       "rbi",

  "documents_to_verify": [
    {
      "doc_kind":           "aadhaar",
      "doc_number_masked":  "•••• •••• 1234",
      "verification_method": "digilocker"
    },
    {
      "doc_kind":           "pan",
      "doc_number_masked":  "•••• 1234X",
      "verification_method": "income_tax_api"
    }
  ],

  "user_profile_provided": {
    "full_name_legal":      "REDACTED at TOMO ingest — partner gets via secure channel",
    "date_of_birth":        "REDACTED",
    "gender":               "REDACTED",
    "address_line_1":       "REDACTED",
    "city":                 "REDACTED",
    "state":                "REDACTED",
    "pincode":              "REDACTED",
    "country_code":         "IN",
    "phone_masked":         "+91 98XXX XX234",
    "email_masked":         "k••••@gmail.com"
  },

  "is_runtime_injected":   true,
  "injected_from_intent":  "finance.apply_personal_loan",
  "injected_request_id":   "req_pl_8f4k_...",
  "minimum_kyc_level_required": "full_kyc",

  "context": {
    "user_locale":          "en-IN",
    "user_currency_pref":   "INR",
    "trust_signals": {
      "is_repeat_kyc":            false,
      "prior_kyc_with_partner":   0,
      "user_account_age_days":    312,
      "previously_flagged":       false
    }
  }
}
Field Type Constraint Notes
kyc_kind enum REQUIRED, STRICT see §6
kyc_purpose enum REQUIRED, see §6 drives required document set
regulator enum REQUIRED, see §6 RBI / IRDAI / SEBI / RTO / generic
documents_to_verify array REQUIRED, ≥1 each entry is a doc to verify
documents_to_verify[].doc_kind enum REQUIRED, see §6
documents_to_verify[].doc_number_masked string REQUIRED last-4 only (privacy)
documents_to_verify[].verification_method enum REQUIRED, see §6
user_profile_provided object REQUIRED, all fields REDACTED partner receives full data via secure channel directly
minimum_kyc_level_required enum REQUIRED, STRICT min_kyc | full_kyc | video_kyc | in_person_kyc
is_runtime_injected bool REQUIRED drives different success widget
injected_from_intent string or null REQUIRED parent intent if injected
context.trust_signals.previously_flagged bool REQUIRED prior KYC failure history

Privacy note: TOMO never stores raw KYC data. The user_profile_provided block surfaces redacted previews; the actual document numbers + full identity data flow from user → partner directly via the partner's encrypted upload widget. TOMO's role is orchestration, not data custody.

Anti-fabrication preamble: no paid placement, no commission-based partner ordering, TOMO never holds raw KYC data.


3. PROVIDER TOOLS

Tool 1: initiate_kyc_session

PURPOSE:        create a KYC verification session
INPUT:          §2 request body
OUTPUT:         { session_id, session_token, partner_kyc_url, expires_at, expected_minutes }
SLA:            p95 < 1500ms
IDEMPOTENCY:    REQUIRED on (user_session_id, kyc_purpose) — same purpose for same user returns same session_id within 24h
PRIVACY:        partner returns a URL for user to complete KYC in partner's own surface

Tool 2: get_kyc_status

PURPOSE:        poll session status
INPUT:          { session_id, request_id }
OUTPUT:         KycStatus (§5)
SLA:            p95 < 600ms
RATE LIMIT:     ≤ 1 every 5s

Tool 3: consume_kyc_result

PURPOSE:        receive partner's verified KYC summary (after user completes)
INPUT:          { session_id, request_id }
OUTPUT:         KycVerificationResult (§5) — minimal data
SLA:            p95 < 1000ms
RETURNS:        verification_status booleans + reference IDs.
                NO raw document data. NO scanned image URLs.

Tool 4: cancel_kyc_session

PURPOSE:        user cancels mid-flow
INPUT:          { session_id, reason, request_id }
OUTPUT:         { status, partial_data_purged: bool, refund_to_purpose_intent: bool }
SLA:            p95 < 1500ms

Tool 5: revoke_kyc

PURPOSE:        user requests deletion of completed KYC (DPDP 2023 right-to-erasure)
INPUT:          { session_id, reason, request_id, user_consent_token }
OUTPUT:         { status, deletion_iso, downstream_partners_notified[] }
SLA:            p95 < 5000ms (asynchronous delete OK)

All five REQUIRED. Tool 5 is non-negotiable for DPDP 2023 compliance.


4. RESPONSE SHAPE

initiate_kyc_session output

session_id:                       string, REQUIRED
session_token:                    string, REQUIRED                # opaque, used in subsequent calls
partner_kyc_url:                  URL, REQUIRED                    # user-facing URL for KYC completion
url_expires_at:                   ISO_DATETIME, REQUIRED
expected_completion_minutes:      int, REQUIRED                    # typical user time
session_expires_at:               ISO_DATETIME, REQUIRED          # session lifetime

method:
  primary_method:                 STRICT ENUM, REQUIRED            # see §6
  fallback_methods:               array<enum>, REQUIRED, may be empty
  video_kyc_human_required:       boolean, REQUIRED
  video_kyc_avg_wait_minutes:     int, REQUIRED                    # 0 if not video KYC

documents_required:               array, REQUIRED, ≥1
  - doc_kind:                     STRICT ENUM, REQUIRED
    is_mandatory:                 boolean, REQUIRED
    upload_methods:               array<enum>, REQUIRED, ≥1        # see §6 (digilocker | upload | api)
    sample_format:                STRICT ENUM, REQUIRED            # pdf | jpg | png | scanned

privacy:
  data_minimization_enforced:     boolean, REQUIRED                # MUST be true
  data_retention_days:            int, REQUIRED                    # per regulator / DPDP
  data_retention_purpose:         string, REQUIRED                  # legal basis for retention
  data_sharing_with_third_parties: boolean, REQUIRED                # MUST be false unless regulator-mandated
  user_consent_required:          boolean, REQUIRED                # MUST be true
  user_can_revoke:                boolean, REQUIRED                # MUST be true (DPDP)

trust:
  partner_iso_27001_certified:    boolean, REQUIRED
  partner_soc_2_type_2_audited:   boolean, REQUIRED
  partner_iso_27018_certified:    boolean, REQUIRED                # cloud privacy
  partner_authorized_by:          STRICT ENUM, REQUIRED            # see §6
  partner_license_number:         string, REQUIRED
  partner_license_valid_until:    ISO_DATE, REQUIRED
  digilocker_authorized:          boolean, REQUIRED
  uidai_authorized_user_agency:   boolean, REQUIRED                # for Aadhaar
  uidai_aua_kua_kind:             STRICT ENUM, REQUIRED            # see §6

fees:
  fee_inr:                        INR_INTEGER, REQUIRED            # 0 if free for purpose intent
  fee_charged_to:                 STRICT ENUM, REQUIRED            # user | parent_intent_partner | tomo (NEVER tomo for v1)
  refund_on_failure:              boolean, REQUIRED

_provider:
  name:                           string, REQUIRED
  tomo_partner_id:                string, REQUIRED
  partner_tier:                   STRICT ENUM, REQUIRED
  customer_support_phone:         string, REQUIRED
  customer_support_24x7:          boolean, REQUIRED
  in_app_chat_supported:          boolean, REQUIRED
  human_kyc_specialist_available: boolean, REQUIRED

KycStatus (returned by get_kyc_status)

session_id:                       string, REQUIRED
status:                           STRICT ENUM, REQUIRED            # see §6
status_updated_iso:               ISO_DATETIME, REQUIRED
status_history:                   array, REQUIRED, ≥1
  - status:                       STRICT ENUM, REQUIRED
    iso:                          ISO_DATETIME, REQUIRED
    notes:                        string, REQUIRED                  # "" allowed

progress:
  total_steps:                    int, REQUIRED, ≥1
  completed_steps:                int, REQUIRED, ≥0
  current_step:                   STRICT ENUM, REQUIRED            # see §6
  estimated_remaining_minutes:    int, REQUIRED

documents_received:               array, REQUIRED, may be empty
  - doc_kind:                     STRICT ENUM, REQUIRED
    received_iso:                 ISO_DATETIME, REQUIRED
    verification_status:          STRICT ENUM, REQUIRED            # pending | verified | failed | expired
    verification_iso:             ISO_DATETIME, REQUIRED            # epoch sentinel if pending
    failure_reason:               STRICT ENUM, REQUIRED            # see §6 ("none" if verified or pending)

identity_match:
  name_match:                     STRICT ENUM, REQUIRED            # exact | fuzzy_high | fuzzy_low | mismatch | pending
  dob_match:                      STRICT ENUM, REQUIRED            # match | mismatch | pending
  address_match:                  STRICT ENUM, REQUIRED            # match | partial_match | mismatch | pending

risk:
  risk_score:                     int, REQUIRED, 0-100              # higher = more risky
  risk_signals:                   array<enum>, REQUIRED, may be empty
  watchlist_match:                boolean, REQUIRED                # PEP / sanctions
  pep_status:                     boolean, REQUIRED                # politically exposed person

support_phone:                    string, REQUIRED
support_email:                    string, REQUIRED

KycVerificationResult (returned by consume_kyc_result)

session_id:                       string, REQUIRED
result_id:                        string, REQUIRED                  # opaque KYC reference
verified_at_iso:                  ISO_DATETIME, REQUIRED
verification_kind:                STRICT ENUM, REQUIRED            # min_kyc | full_kyc | video_kyc | in_person_kyc
verification_validity_until_iso:  ISO_DATE, REQUIRED                # KYC expires; user re-verifies after

flags:
  identity_verified:              boolean, REQUIRED
  address_verified:               boolean, REQUIRED
  pan_aadhaar_linked:             boolean, REQUIRED
  watchlist_clear:                boolean, REQUIRED
  pep_status:                     boolean, REQUIRED
  age_at_least_18:                boolean, REQUIRED
  india_resident:                 boolean, REQUIRED
  high_value_eligibility:         boolean, REQUIRED                # for transactions > ₹50L

# NO RAW DOCUMENT DATA RETURNED. NO SCANNED IMAGES.
# Partner's downstream products consume by referencing result_id with user consent.

audit_trail:
  consent_iso:                    ISO_DATETIME, REQUIRED
  consent_text_displayed:         string, REQUIRED                  # exact text user agreed to
  data_collected:                 array<STRICT ENUM>, REQUIRED      # which doc kinds were collected
  data_minimized:                 boolean, REQUIRED                # MUST be true
  retention_days:                 int, REQUIRED
  retention_purpose:              string, REQUIRED

_provider:
  name:                           string, REQUIRED
  tomo_partner_id:                string, REQUIRED

Forbidden fields

paid_placement_score | sponsored_rank | promotion_priority |
ad_bid | hidden_kyc_fee | document_image_url | scanned_aadhaar_url |
raw_document_number_unmasked | partner_will_share_with_advertisers

Critical: no field may contain raw document numbers, unmasked full names or scanned image URLs. Our team checks for these during review; a listing that carries them is rejected.


5. CONTROLLED VOCABULARIES

kyc_kind

min_kyc | full_kyc | video_kyc | in_person_kyc

kyc_purpose

personal_loan | home_loan | credit_card | mutual_fund | demat_account |
trading_account | term_insurance_high_value | health_insurance |
self_drive_rental_first_time | high_value_marketplace_transaction |
employment_verification | tenant_verification | nbfc_credit_check |
generic_kyc

regulator

rbi | irdai | sebi | rto | generic

documents_to_verify[].doc_kind

aadhaar | pan | passport | driving_license | voter_id |
ration_card | bank_statement | utility_bill | property_doc |
employment_letter | itr_form_16 | gstin | shop_establishment_act

documents_to_verify[].verification_method

digilocker | uidai_otp | uidai_offline_xml | income_tax_api |
manual_upload_with_otp_verification | api_provider_verified |
video_kyc_face_match | in_person_visit

method.primary_method

digilocker_pull | aadhaar_otp_authentication | aadhaar_offline_xml |
video_kyc_with_human | video_kyc_automated | document_upload_plus_face_match |
in_person_branch_visit | dsa_field_visit

method.fallback_methods

Same enum as primary_method.

documents_required[].upload_methods

digilocker | direct_upload | api_pull | scan_via_app | physical_upload

documents_required[].sample_format

pdf | jpg | png | scanned_pdf | digilocker_xml | api_json

trust.partner_authorized_by

rbi_pa_psp | rbi_nbfc | irdai_corporate_agent | sebi_registered_intermediary |
mca_registered | uidai_aua_kua | digilocker_authorized_partner

trust.uidai_aua_kua_kind

not_applicable | aua_only | kua_only | aua_kua_both | sub_aua_kua

KycStatus.status

initiated | awaiting_user_action | docs_received | identity_verifying |
identity_verified | risk_assessment | watchlist_check |
verified | failed_doc | failed_identity_match | failed_watchlist |
failed_pep_blocked | timeout | cancelled_by_user | revoked_by_user

KycStatus.progress.current_step

not_started | digilocker_consent | aadhaar_otp_input | document_upload |
face_match | video_kyc_call | review_submitted | risk_assessment |
done

KycStatus.documents_received[].verification_status

pending | verified | failed | expired

KycStatus.documents_received[].failure_reason

none | doc_unreadable | doc_expired | doc_tampered | name_mismatch |
dob_mismatch | photo_mismatch | api_unavailable | uidai_otp_failed |
manual_review_required

KycStatus.identity_match.name_match

exact | fuzzy_high | fuzzy_low | mismatch | pending

KycStatus.identity_match.dob_match

match | mismatch | pending

KycStatus.identity_match.address_match

match | partial_match | mismatch | pending

KycStatus.risk.risk_signals

none | new_pan_within_30_days | aadhaar_recently_updated_address |
multiple_kyc_attempts_24h | mismatched_phone_number |
unusual_geographic_pattern | shared_device_with_other_kyc_user |
recently_blacklisted_in_partner_db | pep_match | sanctions_match

cancel_kyc_session.reason

user_changed_mind | wrong_documents | upload_failure | parent_intent_cancelled |
privacy_concern | technical_failure | other

KycVerificationResult.verification_kind

Same as kyc_kind enum.

audit_trail.data_collected[]

Same as documents_to_verify[].doc_kind enum.

_provider.partner_tier

tier1_path_a | tier1_path_b | tier1_path_c | tier2_path_d

6. TTBS DIMENSIONS

Per-domain weights (locked)

compliance: { time: 0.30, taste: 0.05, budget: 0.20, safety: 0.45 }

Safety weight dominates (highest in any TOMO intent — KYC is identity verification).

TIME

SIGNALS USED:
  - method.expected_completion_minutes (lower = better) weight 0.40
  - method.video_kyc_avg_wait_minutes                   weight 0.20
  - method.primary_method == digilocker_pull (fastest)  weight 0.20
  - partner historical p95 completion time              weight 0.20

USER BAND HANDLING:
  - is_runtime_injected=true → time weight up to 0.45 (user blocked on parent intent)
  - standalone → time weight steady

TASTE

SIGNALS USED:
  - human_kyc_specialist_available                       weight 0.40
  - in_app_chat_supported                                weight 0.30
  - vernacular language support (per user_locale)        weight 0.30

Taste weight is small (0.05) — user just wants KYC done.

BUDGET

SIGNALS USED:
  - fees.fee_inr (lower = better)                        weight 0.50
  - fees.fee_charged_to == user (penalty if true)        weight 0.30
  - refund_on_failure                                    weight 0.20

HARD FILTERS:
  - fees.fee_charged_to == tomo → drop (TOMO doesn't subsidize KYC)

SAFETY (the dominant axis)

SIGNALS USED:
  - trust.partner_iso_27001_certified                    HARD FILTER
  - trust.partner_authorized_by ∈ valid regulator enum   HARD FILTER
  - privacy.data_minimization_enforced=true              HARD FILTER
  - privacy.user_consent_required=true                   HARD FILTER
  - privacy.user_can_revoke=true                         HARD FILTER
  - trust.uidai_authorized_user_agency=true (Aadhaar use) HARD FILTER if Aadhaar in docs
  - trust.partner_iso_27018_certified (cloud privacy)    weight 0.20
  - trust.partner_soc_2_type_2_audited                   weight 0.20
  - privacy.data_sharing_with_third_parties=false        HARD FILTER (must be false)
  - partner_license_valid_until > now+30d                HARD FILTER
  - audit_trail.data_minimized=true                      HARD FILTER
  - method.primary_method ∈ regulator-approved list       HARD FILTER
  - historical breach record (partner)                    weight 0.20 penalty
  - partner_iso_27018_certified                           weight 0.20

User-context HARD FILTERS:
  - kyc_purpose=personal_loan + minimum_kyc_level_required=full_kyc
       → method must support full_kyc → drop video_kyc_only partners
  - kyc_purpose=high_value (>₹50L) → method must include video_kyc OR in_person

Hidden ranking factor

information_completeness_score weight 0.10. historical_kyc_success_rate weight 0.20 — partners with >5% KYC failure rate get penalized.


7. COMPLETION CONTRACT

POST /api/v1/cpc/mcp_provider/{tomo_partner_id}
X-TOMO-Timestamp: <ms>
X-TOMO-Signature: sha256=<hex>

{
  "intent":            "compliance.verify_kyc",
  "intent_version":    "v1.0.0",
  "external_id":       "DIGIO-KYC-XYZ",
  "amount_inr":         0,
  "closed_at":         "2026-05-10T08:42:00+05:30",
  "request_id":        "req_kyc_8f4k_...",
  "status":            "verified",
  "currency":          "INR",
  "session_id":        "DIGIO-KYC-XYZ",
  "verified_at_iso":   "2026-05-10T08:42:00+05:30",
  "verification_kind": "full_kyc",
  "verification_validity_until_iso": "2027-05-10",
  "fees_charged_inr":  0,
  "result_id":         "kyc_result_abc123",
  "documents_verified": ["aadhaar", "pan"],
  "watchlist_clear":   true,
  "high_value_eligible": true,
  "injected_from_intent": "finance.apply_personal_loan",
  "injected_request_id":  "req_pl_8f4k_...",
  "notes":             ""
}

Status enum: verified | failed_doc | failed_identity_match | failed_watchlist | failed_pep_blocked | cancelled_by_user | revoked_by_user

amount_inr is typically 0 because KYC fees are usually absorbed by the parent intent's partner (loan provider, insurance provider, etc.). When amount_inr > 0 (rare standalone KYC fee), TOMO commission applies normally.

Privacy: the completion POST includes ONLY metadata (verification status, validity date, document KINDS verified, eligibility flags). NEVER raw document data.


8. WIDGET

WIDGET TYPE:        kyc_card
SOURCE:             src/widgets/types.ts
TYPE NAME:          KycCardPayload
RENDERED IN:        components/widgets/KycCardWidget.tsx

Two render contexts:

  1. Standalone — full card with KYC kind, expected duration, partner trust badges, "Start KYC" CTA → opens partner_kyc_url in iframe or external.
  2. Runtime-injected — slim banner inside the parent intent's confirmation widget. Shows: "Verify your identity to proceed (estimated 4 min)" with inline "Verify now" button. Completion returns user to parent flow with KYC result attached.

9. CACHING POLICY

Call TTL Rationale
initiate_kyc_session 0s Always fresh
get_kyc_status 0s Live progress
consume_kyc_result 0s Always fresh
cancel_kyc_session 0s
revoke_kyc 0s

10. ERROR CODES

Code HTTP Meaning TOMO behavior
INVALID_REQUEST 400 Malformed surface
INVALID_DOCUMENT_KIND 400 doc_kind not supported surface
UIDAI_DOWNTIME 503 UIDAI Aadhaar API down suggest fallback method
DIGILOCKER_DOWNTIME 503 DigiLocker down suggest fallback method
IDENTITY_MISMATCH 422 name/DOB don't match across docs surface to user, retry
WATCHLIST_BLOCKED 403 PEP/sanctions match surface, terminate KYC
DOC_EXPIRED 422 document past validity surface, request new
SESSION_TIMEOUT 410 user took too long surface, restart
INVALID_AUTH 401 partner credentials bad partner re-auth
RATE_LIMITED 429 partner-side throttle back off
INTERNAL_ERROR 500 partner-side failure drop partner from pool
MANUAL_REVIEW_REQUIRED 202 low-confidence verification escalate to human

11. SANDBOX → PRODUCTION CHECKLIST

[ ] All §2 inputs validated, request_id echoed
[ ] initiate_kyc_session returns valid partner_kyc_url within SLA
[ ] get_kyc_status returns progress updates ≤5s old
[ ] consume_kyc_result returns ONLY metadata (no raw doc data)
[ ] cancel_kyc_session purges partial data per privacy commitments
[ ] revoke_kyc properly purges + notifies downstream consumers (DPDP 2023)
[ ] All §4 required fields populated with REAL data
[ ] No forbidden fields anywhere (no raw doc URLs, no unmasked numbers)
[ ] CPC webhook arrives within 60s of verification completion
[ ] HMAC verification passes
[ ] ISO 27001 certificate uploaded + verified
[ ] ISO 27018 certificate uploaded if cloud-based
[ ] SOC 2 Type 2 audit report uploaded
[ ] RBI / IRDAI / SEBI authorization number valid + verifiable
[ ] UIDAI AUA/KUA authorization for Aadhaar verification (if applicable)
[ ] DigiLocker authorized partner attestation
[ ] Privacy policy URL live + DPDP 2023 compliant
[ ] Data retention period documented + matches regulator requirements
[ ] User consent UX reviewed for clarity
[ ] Failure recovery flow tested (UIDAI downtime, DigiLocker downtime)
[ ] customer_support 24x7 reachable + KYC specialist available
[ ] No commission-based partner ordering (checked by our team)
[ ] Vernacular language support tested (en-IN, hi-IN minimum)
[ ] Field-test with TOMO ops doing real KYC

12. ANTI-FABRICATION RULES

RULE 1 — No paid placement signals
RULE 2 — No raw document data in any TOMO-bound response
  No unmasked Aadhaar / PAN / DL numbers, scanned image URLs or unredacted full names.
  Our team checks for these during review; a listing that carries them is rejected.
RULE 3 — Data minimization mandatory
  privacy.data_minimization_enforced MUST be true. Partners that collect more
  than the kyc_purpose strictly requires = listing rejection.
RULE 4 — User consent must be auditable
  audit_trail.consent_iso + consent_text_displayed must be exact. Partners
  cannot retro-grant consent.
RULE 5 — User right-to-revoke must function
  revoke_kyc must purge data within partner's documented retention SLA AND
  notify downstream consumers. Failure = DPDP 2023 violation + suspension.
RULE 6 — No third-party data sharing without disclosure
  privacy.data_sharing_with_third_parties MUST be false unless regulator-mandated
  AND disclosed in user consent text.
RULE 7 — UIDAI authorization must be valid
  trust.uidai_authorized_user_agency=true requires UIDAI license certificate
  on demand within 24h. Failure = listing rejection.
RULE 8 — No commission-based partner ordering
  Same KYC purpose on TOMO must rank by user-fit (TTBS, safety dominant),
  not by partner commission.
RULE 9 — Customer support honest
  Human KYC specialist available within stated SLA. Field-tested.
RULE 10 — No watchlist data caching beyond regulator window
  PEP / sanctions data refreshed per regulator requirements. Stale data =
  legal liability + breach.
RULE 11 — No forced upsells inside KYC flow
  Partner cannot use KYC session to push other products. KYC is a utility,
  not a sales surface.
RULE 12 — TOMO never holds raw KYC data
  All raw document data flows user → partner directly. TOMO orchestrates
  but does not custody.

VERSION HISTORY

v1.0.0 — 2026-05-10 — Initial spec (Block F.2)