SMS_Analysis_V1 — SMS Financial Intelligence API

SMS_Analysis_V1 v1 live

Download as Markdownthe full page as a single .md file — for offline reading or as context for an AI assistant

Authentication

Every request must carry your API key in the X-API-Key header. Your key is issued to you separately by ops and is never shown in this document.

X-API-Key: <your-key>

Base URL

All endpoints are served through the gateway:

https://api.pehchaantech.com

Endpoints

POST /sms/v1/ingest
URLhttps://api.pehchaantech.com/sms/v1/ingest
Timeout10000 ms
Rate limit60 req/min · 100000 req/day
POST /sms/v1/report
URLhttps://api.pehchaantech.com/sms/v1/report
Timeout5000 ms
Rate limit60 req/min · 100000 req/day
POST /sms/v1/summary
URLhttps://api.pehchaantech.com/sms/v1/summary
Timeout5000 ms
Rate limit60 req/min · 100000 req/day

Overview

Turn a consented SMS inbox into a structured financial-identity profile: income, obligations, credit-seeking behaviour, cash-flow health and 25 more families of derived signal.

Analysis is ASYNCHRONOUS and takes three calls. POST /sms/v1/ingest with the raw messages and you immediately receive an sms_id — submission never waits on analysis, so uploading 50,000 messages is as fast as uploading 50. Then redeem that id at POST /sms/v1/summary (the decision-grade headline, ~2 KB) or POST /sms/v1/report (all 29 families with their underlying evidence, plus cross-family correlations and our own watchlist/registry data joined in). Poll the endpoint you actually want: while analysis is running it returns 202 with status "processing" and a Retry-After header — there is no separate status endpoint.

Two things to internalise before you integrate. First, null means NOT DETECTED, not zero — a null monthly income means we could not establish a salary, so gate your rules on the coverage map rather than on null checks. Second, the sms_id is a CREDENTIAL: anyone holding it can redeem the analysis, so keep it server-side and never put it in a browser URL or a client-side log. It expires after 7 days.

Request example

{
  "messages": [
    {
      "body": "Rs.85,000 credited to a/c XX1234 by SALARY ACME PVT LTD",
      "sender": "VK-HDFCBK",
      "sentAtMs": 1751360400000
    },
    {
      "body": "Rs 15000 debited via NACH EMI to PNB HOUSING FINANCE",
      "sender": "AX-HDFCBK",
      "sentAt": "2025-07-05T06:00:00Z"
    }
  ]
}

Response example

{
  "sms_id": "sms_9dQ2mF7xK1pR4vT8zL0bN3sW",
  "status": "processing",
  "success": true,
  "expires_at": "2026-07-28T11:04:12Z",
  "description": "Inbox accepted and queued for analysis",
  "messageCount": 4820
}

Fields

FieldDescription
messagesRequired on /ingest. 1–50,000 items, 10 MB max. This is the ENTIRE request body — you do not send a reference number or idempotency key; the sms_id we return identifies the whole lifecycle.
messages[].bodyRequired. The raw SMS text, 1–2000 characters.
messages[].senderOptional sender header, e.g. VK-HDFCBK. Used for bank and app attribution — supplying it materially improves detection.
messages[].sentAtMs / sentAtOne is required. sentAtMs is epoch milliseconds and is preferred (no parsing); sentAt accepts any ISO-8601 timestamp. Rows with an empty body OR no parseable timestamp are silently dropped before analysis — you will see this as meta.analysed being lower than meta.messageCount. If those differ a lot, check your timestamp format.
sms_idRequired on /summary and /report. Returned by /ingest in the body and the X-Sms-Id header. Treat it as a bearer credential; it is sent in the request BODY so it never lands in access logs or Referer headers. Valid for 7 days.
result.coverageSummary only. Per-family populated | empty | degraded. READ THIS FIRST: null means "not detected", not zero, and coverage is how you tell "we found no salary signal" apart from "the salary is zero".
result (report)All 29 families verbatim, including the evidence arrays behind every summary count. bureauChecks[].checks[].evidence carries up to 140 characters of the original SMS for each detected credit enquiry — the only place raw message text is returned.
correlationsReport only. Cross-family analysis no single detector produces: affordability (total fixed commitment incl. subscriptions, utilities and rent), employerAgreement, creditHunger, stressComposite, lifestyleVsIncome, geoConsistency, wealthCorroboration, confidenceMap. Each carries {value, inputs, confidence}.
referenceDataReport only. Our reference data joined to theirs: darkPatterns (predatory subscriptions they are actually paying for, with the reason each is flagged and the parent entities that appear on NACH lines), lenderExposure, appTaxonomy, and datasetVersions for reconciliation.

Errors

CodeMeaning
processing (HTTP 202)On /ingest: accepted and queued. On /summary or /report: analysis is still running — retry after Retry-After (1 s). Typical end-to-end is under a second, so the first or second poll usually succeeds.
completed (HTTP 200)Analysis ready — consume result.
no_data (HTTP 200)Processed, but nothing in the inbox was analysable. Check your timestamp format and meta.analysed. Do not retry the read.
failed (HTTP 200)Analysis failed permanently. Deliberately a 200, not a 5xx: the read itself worked, so retrying it will never help — submit the inbox again.
not_found (HTTP 404)Unknown sms_id. Check the value you sent.
expired (HTTP 410)The sms_id existed but passed its 7-day retention window. Distinct from 404 on purpose. Submit the inbox again.
422Validation failed — empty messages array, over 50,000 messages, a body longer than 2000 chars, or a malformed sms_id.
503 (ingest)The ingest queue is at capacity. Retry after Retry-After (2 s) — this is backpressure, not breakage.
401 / 403Missing or invalid API key, or the key is not granted this API. Note a uat key on a prod route returns 404, not 403.
429Rate limited — respect the Retry-After header.
502 / 504Storage or gateway problem — retry with exponential backoff.

Changelog