Lead Enrichment
kycOne call that turns a lead into a profile. Send a mobile number — and a PAN if you have one — and we run the relevant lookups across the catalogue and return a single merged result: employment, GST, MSME, directorships and company. THE SECOND HOP IS THE POINT. Identifiers discovered in the first pass are followed automatically: a DIN found from the PAN fetches director details, a CIN fetches company master data. You never round-trip to chase them yourself. PRICING IS NOT FIXED — READ THIS BEFORE BUDGETING. You pay the normal price of each sub-lookup that SUCCEEDED, and nothing for the ones that failed or were skipped. There is no separate orchestration fee. The price shown on this page is the basic-tier FLOOR; a full-tier call on a rich lead costs considerably more. Every response itemises each lookup and what it cost in `lookups[]`, and `totalSalePaise` is exactly what you were billed — so every invoice line traces back to a lookup. WHAT COUNTS AS BILLABLE: a lookup that returns a definitive "no record" IS billed — the source did the work, and `verified: false` is still information. A lookup that FAILED (upstream error) or was SKIPPED (you did not supply the input, or that source is unavailable) costs nothing. TIMING: basic returns in 1-3s. Full includes the MSME lookup, which is currently slow (24-27s), so set your client timeout to at least 45 seconds for `tier: "full"`.
Authentication
Pass your key in the X-API-Key header. Use a test_ key against the sandbox and a live_ key in production. Send an optional Idempotency-Key header to safely retry — the same key returns the same response for 24h.
Request
Endpoint: POST https://apisathi.in/gw/v1/lead-enrich-v1/
The trailing slash is required. /v1/lead-enrich-v1/ works; /v1/lead-enrich-v1 returns 404 Not Found. This applies to every product.
| Field | Type | Required | Constraints |
|---|---|---|---|
| mobile | string | required | Borrower mobile number. Formatting is stripped; the last 10 digits are used. |
| pan | string | optional | Optional but high value. Unlocks PAN verification, GSTIN-by-PAN, directorships and company lookups. Without it only the mobile-based lookups run. |
| tier | string | optional | basic = employment + GST by mobile. standard = adds PAN verification, GSTIN by PAN, directorships and director detail. full = adds MSME/Udyam, company by PAN, company and GST detail. Defaults to basic. Tiers are cumulative. |
Code snippets
curl -X POST https://apisathi.in/gw/v1/lead-enrich-v1/ \
-H "X-API-Key: $API_SATHI_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"mobile":"9876543210","pan":"ABCDE1234F","tier":"standard"}'Response
| Field | Type | Required | Constraints |
|---|---|---|---|
| tier | string | optional | The tier that was actually run. |
| profile | object | optional | Merged enrichment. Keys appear only when that lookup succeeded: employment, gst_by_mobile, pan, gstins, directorships, msme, company_by_pan, and the second-hop director_detail, company_detail, gst_detail. |
| lookups | array | optional | One entry per attempted lookup: { product, status, salePaise, costPaise, reason }. status is ok | no_data | failed | skipped. |
| totalSalePaise | number | optional | What you were billed — the sum of successful lookups only. |
Sample response
{
"tier": "standard",
"profile": {
"pan": {
"verified": true,
"name": "RAHUL VERMA"
},
"employment": {
"uan": "XXXXXXXX1234",
"employer": "ACME PRIVATE LIMITED"
},
"directorships": {
"din": "09999999"
},
"director_detail": {
"name": "RAHUL VERMA",
"companies": 2
}
},
"lookups": [
{
"product": "uan-basic-v3",
"status": "ok",
"salePaise": 450,
"costPaise": 150
},
{
"product": "contact-to-gst-v1",
"status": "no_data",
"salePaise": 210,
"costPaise": 70
},
{
"product": "pan-verification-v1",
"status": "ok",
"salePaise": 240,
"costPaise": 80
},
{
"product": "gstin-by-pan-v1",
"status": "failed",
"salePaise": 0,
"costPaise": 0,
"reason": "VENDOR_ERROR"
},
{
"product": "pan-to-din-v1",
"status": "ok",
"salePaise": 135,
"costPaise": 45
},
{
"product": "din-basic-v1",
"status": "ok",
"salePaise": 150,
"costPaise": 50
}
],
"totalSalePaise": 1185
}Reading a 400
A schema rejection returns INVALID_REQUEST with a details array naming the exact field. Read it before guessing — it gives you the JSON path and what was wrong with it.
{
"error": {
"code": "INVALID_REQUEST",
"message": "Request body failed schema validation",
"details": [
{ "path": "/callback_url",
"message": "unexpected property 'callback_url' — this endpoint does not accept unknown fields" }
]
}
}Most endpoints reject unknown top-level fields outright, so an extra key you added for your own bookkeeping will fail the call. Enum errors list the accepted values, and a missing required field is named with its full path.
Error codes
| Code | HTTP | When |
|---|---|---|
| INVALID_INPUT | 422 | The request was rejected as invalid — either it failed OUR schema validation (malformed input; not charged), or the upstream source rejected the value you sent. NOTE: an identifier that is well-formed but simply has NO RECORD is no longer an error — it returns 200 with `verified: false` and is charged (see the result-code table below). FIX the input before retrying; retrying the same value will fail again. |
| INVALID_API_KEY | 401 | Missing, malformed, or revoked X-API-Key. |
| OUT_OF_SCOPE | 403 | API key is not scoped for this product. |
| INSUFFICIENT_BALANCE | 402 | Wallet balance is below the per-call sale price. Recharge and retry. |
| RATE_LIMITED | 429 | Per-key RPS or RPM limit exceeded. Back off and retry after the Retry-After header. |
| PRODUCT_DEPRECATED | 410 | This API has been retired and is no longer available. Stop calling it — it will not return. Check the catalog for the current equivalent. |
| ROUTER_NO_VENDOR | 503 | No healthy vendor is currently available for this product. Transient — safe to retry after a short backoff. Not charged. |
| VENDOR_AUTH_FAILED | 502 | Upstream vendor rejected our credentials (our config issue). Not charged. |
| VENDOR_ERROR | 502 | A genuine transient upstream error (the source was briefly unavailable). Safe to RETRY after a short backoff. Not charged. NOTE: this is NOT for bad input — invalid values return 422 INVALID_INPUT, not 502. |
| TIMEOUT | 504 | Upstream vendor did not respond within the SLA window. Safe to retry after a short backoff. Not charged. |
OpenAPI 3.1
Generated from this product's request/response JSON Schemas.
{
"openapi": "3.1.0",
"info": {
"title": "API Sathi — Lead Enrichment",
"version": "1.0.0",
"description": "One call that turns a lead into a profile. Send a mobile number — and a PAN if you have one — and we run the relevant lookups across the catalogue and return a single merged result: employment, GST, MSME, directorships and company.\n\nTHE SECOND HOP IS THE POINT. Identifiers discovered in the first pass are followed automatically: a DIN found from the PAN fetches director details, a CIN fetches company master data. You never round-trip to chase them yourself.\n\nPRICING IS NOT FIXED — READ THIS BEFORE BUDGETING. You pay the normal price of each sub-lookup that SUCCEEDED, and nothing for the ones that failed or were skipped. There is no separate orchestration fee. The price shown on this page is the basic-tier FLOOR; a full-tier call on a rich lead costs considerably more. Every response itemises each lookup and what it cost in `lookups[]`, and `totalSalePaise` is exactly what you were billed — so every invoice line traces back to a lookup.\n\nWHAT COUNTS AS BILLABLE: a lookup that returns a definitive \"no record\" IS billed — the source did the work, and `verified: false` is still information. A lookup that FAILED (upstream error) or was SKIPPED (you did not supply the input, or that source is unavailable) costs nothing.\n\nTIMING: basic returns in 1-3s. Full includes the MSME lookup, which is currently slow (24-27s), so set your client timeout to at least 45 seconds for `tier: \"full\"`."
},
"servers": [
{
"url": "https://apisathi.in/gw/v1"
}
],
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key",
"description": "Your live or test key, e.g. `live_xxxxxxxxxxxx`."
}
}
},
"paths": {
"/lead-enrich-v1": {
"post": {
"operationId": "leadEnrichV1",
"tags": [
"kyc"
],
"summary": "Lead Enrichment",
"description": "One call that turns a lead into a profile. Send a mobile number — and a PAN if you have one — and we run the relevant lookups across the catalogue and return a single merged result: employment, GST, MSME, directorships and company.\n\nTHE SECOND HOP IS THE POINT. Identifiers discovered in the first pass are followed automatically: a DIN found from the PAN fetches director details, a CIN fetches company master data. You never round-trip to chase them yourself.\n\nPRICING IS NOT FIXED — READ THIS BEFORE BUDGETING. You pay the normal price of each sub-lookup that SUCCEEDED, and nothing for the ones that failed or were skipped. There is no separate orchestration fee. The price shown on this page is the basic-tier FLOOR; a full-tier call on a rich lead costs considerably more. Every response itemises each lookup and what it cost in `lookups[]`, and `totalSalePaise` is exactly what you were billed — so every invoice line traces back to a lookup.\n\nWHAT COUNTS AS BILLABLE: a lookup that returns a definitive \"no record\" IS billed — the source did the work, and `verified: false` is still information. A lookup that FAILED (upstream error) or was SKIPPED (you did not supply the input, or that source is unavailable) costs nothing.\n\nTIMING: basic returns in 1-3s. Full includes the MSME lookup, which is currently slow (24-27s), so set your client timeout to at least 45 seconds for `tier: \"full\"`.",
"security": [
{
"ApiKeyAuth": []
}
],
"parameters": [
{
"name": "Idempotency-Key",
"in": "header",
"required": false,
"schema": {
"type": "string"
},
"description": "Optional. Same key returns the same response for 24h."
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"mobile"
],
"properties": {
"mobile": {
"type": "string",
"description": "Borrower mobile number. Formatting is stripped; the last 10 digits are used."
},
"pan": {
"type": "string",
"description": "Optional but high value. Unlocks PAN verification, GSTIN-by-PAN, directorships and company lookups. Without it only the mobile-based lookups run."
},
"tier": {
"type": "string",
"enum": [
"basic",
"standard",
"full"
],
"description": "basic = employment + GST by mobile. standard = adds PAN verification, GSTIN by PAN, directorships and director detail. full = adds MSME/Udyam, company by PAN, company and GST detail. Defaults to basic. Tiers are cumulative."
}
},
"additionalProperties": false
},
"example": {
"mobile": "9876543210",
"pan": "ABCDE1234F",
"tier": "standard"
}
}
}
},
"responses": {
"200": {
"description": "Successful, normalized response.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"tier": {
"type": "string",
"description": "The tier that was actually run."
},
"profile": {
"type": "object",
"description": "Merged enrichment. Keys appear only when that lookup succeeded: employment, gst_by_mobile, pan, gstins, directorships, msme, company_by_pan, and the second-hop director_detail, company_detail, gst_detail."
},
"lookups": {
"type": "array",
"description": "One entry per attempted lookup: { product, status, salePaise, costPaise, reason }. status is ok | no_data | failed | skipped."
},
"totalSalePaise": {
"type": "number",
"description": "What you were billed — the sum of successful lookups only."
}
},
"additionalProperties": true
},
"example": {
"tier": "standard",
"profile": {
"pan": {
"verified": true,
"name": "RAHUL VERMA"
},
"employment": {
"uan": "XXXXXXXX1234",
"employer": "ACME PRIVATE LIMITED"
},
"directorships": {
"din": "09999999"
},
"director_detail": {
"name": "RAHUL VERMA",
"companies": 2
}
},
"lookups": [
{
"product": "uan-basic-v3",
"status": "ok",
"salePaise": 450,
"costPaise": 150
},
{
"product": "contact-to-gst-v1",
"status": "no_data",
"salePaise": 210,
"costPaise": 70
},
{
"product": "pan-verification-v1",
"status": "ok",
"salePaise": 240,
"costPaise": 80
},
{
"product": "gstin-by-pan-v1",
"status": "failed",
"salePaise": 0,
"costPaise": 0,
"reason": "VENDOR_ERROR"
},
{
"product": "pan-to-din-v1",
"status": "ok",
"salePaise": 135,
"costPaise": 45
},
{
"product": "din-basic-v1",
"status": "ok",
"salePaise": 150,
"costPaise": 50
}
],
"totalSalePaise": 1185
}
}
}
},
"401": {
"description": "Missing, malformed, or revoked X-API-Key.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"call_id": {
"type": "string"
}
}
}
}
}
}
}
},
"402": {
"description": "Wallet balance is below the per-call sale price. Recharge and retry.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"call_id": {
"type": "string"
}
}
}
}
}
}
}
},
"403": {
"description": "API key is not scoped for this product.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"call_id": {
"type": "string"
}
}
}
}
}
}
}
},
"410": {
"description": "This API has been retired and is no longer available. Stop calling it — it will not return. Check the catalog for the current equivalent.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"call_id": {
"type": "string"
}
}
}
}
}
}
}
},
"422": {
"description": "The request was rejected as invalid — either it failed OUR schema validation (malformed input; not charged), or the upstream source rejected the value you sent. NOTE: an identifier that is well-formed but simply has NO RECORD is no longer an error — it returns 200 with `verified: false` and is charged (see the result-code table below). FIX the input before retrying; retrying the same value will fail again.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"call_id": {
"type": "string"
}
}
}
}
}
}
}
},
"429": {
"description": "Per-key RPS or RPM limit exceeded. Back off and retry after the Retry-After header.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"call_id": {
"type": "string"
}
}
}
}
}
}
}
},
"502": {
"description": "Upstream vendor rejected our credentials (our config issue). Not charged.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"call_id": {
"type": "string"
}
}
}
}
}
}
}
},
"503": {
"description": "No healthy vendor is currently available for this product. Transient — safe to retry after a short backoff. Not charged.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"call_id": {
"type": "string"
}
}
}
}
}
}
}
},
"504": {
"description": "Upstream vendor did not respond within the SLA window. Safe to retry after a short backoff. Not charged.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"call_id": {
"type": "string"
}
}
}
}
}
}
}
}
}
}
}
}
}