India's Public Distribution System (PDS) reaches over 80 crore beneficiaries through state-issued Ration Cards. For government platforms managing welfare schemes, DBT disbursements, Gram Panchayat services, and citizen onboarding, the ability to verify a citizen's identity against their Ration Card is not an optional add-on — it is a core trust layer.
Unlike Aadhaar-based verification which requires a separate OTP to the citizen's mobile, Ration Card verification uses the mobile number already registered with the RC database at the NIC level. This makes it a natural fit for semi-urban and rural citizens who may not have consistent access to DigiLocker or digital identity infrastructure.
Deceptive Simplicity: On the surface, Ration Card integration looks like two API calls — submit an RC number, verify an OTP, get member data. In reality, production implementations must handle family mobile sharing, 140-second OTP windows, Redis session TTLs, Golden Record de-duplication across multiple matching strategies, eSign flows, and address auto-parsing — all of which need to be correct before your first real citizen walks through the door.
At VAF.ai, we approach RC integration as a complete lifecycle: Citizen submits RC number → OTP sent to registered mobile → OTP verified → member list returned with GR enrichment → member selected → data saved to Golden Record (via direct save or eSign) → verified identity persisted for downstream use.
This guide documents that entire lifecycle with the real API shapes, matching strategies, and session constraints your team needs to build it correctly the first time.
Why Ration Card API Integration Matters
The Ration Card is more than a food entitlement document. Across India's welfare stack, it functions as a household identity anchor — linking the head of family and all dependents to a single verified state record. For platforms building citizen services, this makes RC integration uniquely valuable.
Key use cases where RC integration adds direct value:
- Citizen identity verification for welfare scheme enrollment
- Family member onboarding without individual Aadhaar KYC for each person
- DBT beneficiary validation and de-duplication
- Gram Panchayat and VLE-assisted citizen registration
- Auto-fill of applicant profiles from verified NIC data
- Proof of residence and economic classification for eligibility checks
- Integration with Golden Record systems for persistent verified identity
- eSign-enabled document verification for regulatory compliance
The NIC Ration Card API provides structured member data including full name, date of birth, relationship to head of family, mobile number, gender, and address — all verified against the state RC database. This eliminates manual document uploads and significantly reduces onboarding friction for rural populations.
To make this work in production, the integration must handle real-world conditions: shared mobile numbers across families, missing mobile numbers, OTP timing windows, and duplicate record prevention in the Golden Record system.
High-Level RC Verification Architecture
A production-grade Ration Card integration involves five distinct system layers that must be correctly wired together. Tight coupling between any two of these layers creates fragility that will surface in production.
- Client Application The web or mobile UI where the citizen enters their RC number, receives OTP confirmation, and selects a family member to verify.
- IAM Service (Platform Backend) Orchestrates the full RC flow — calls the NIC API, manages Redis session state, sends OTP, enriches member data with Golden Record existence, and exposes the three core endpoints.
- NIC Ration Card API The upstream government API that holds the authoritative RC member database. Credentials are loaded from the platform's encrypted configuration registry — not hard-coded or passed by the client.
- Redis Cache (Session Store) Holds OTP session data for 140 seconds and eSign session data for 300 seconds. Sessions are consumed once and deleted on retrieval — re-initiation is required if TTL expires.
- Golden Record System The persistent verified identity store. RC member data is mapped and upserted via a multi-step matching strategy — by member ID, mobile, name+DOB, or RC number — before creating a new record.
- eSign Gateway For flows requiring legal signature, the platform submits RC member data to an eSign gateway. On successful signing, the callback automatically saves the verified record to the Golden Record.
This separation ensures that NIC credentials are never exposed to the client, OTP handling is server-side, and the Golden Record matching logic is centralised in one place rather than duplicated across services.
Standard vs eSign Verification Path
The platform supports two distinct verification paths depending on the compliance requirement of the service:
| Path | Flow | When to Use |
|---|---|---|
| Standard OTP Path | Fetch Members → Verify OTP → Save to Golden Record | Service enrollment, DBT validation, basic citizen onboarding where OTP consent is sufficient |
| eSign Path | Fetch Members → Verify OTP → Initiate eSign → Callback saves to GR | Services requiring legally signed identity verification — welfare entitlements, land records, regulatory compliance |
Step 1: Initiate Ration Card Lookup (OTP)
The first step is to submit the citizen's Ration Card number. The platform queries the NIC RC database, extracts the registered mobile number, and sends a 6-digit OTP with a 140-second validity window.
This endpoint requires no authentication — it is publicly accessible. NIC credentials are loaded entirely by the platform's configuration registry.
| Detail | Value |
|---|---|
| Method | POST |
| Endpoint | /api/iam/rc/fetch-members |
| Auth Required | No |
| Content-Type | application/json |
import requests
BASE_URL = "https://<platform-host>/api/iam"
def initiate_rc_lookup(rc_number: str) -> dict:
"""
Step 1: Submit RC number to trigger OTP to registered mobile.
No auth header required — credentials handled by the platform.
"""
response = requests.post(
f"{BASE_URL}/rc/fetch-members",
json={"rcno": rc_number},
headers={"Content-Type": "application/json"}
)
data = response.json()
if data.get("code") == 200:
masked_mobile = data["data"]
# Show masked mobile in UI so citizen knows where to expect OTP
# e.g. "OTP sent to ******7890"
return {"success": True, "masked_mobile": masked_mobile}
return {"success": False, "message": data.get("message")}
On success, the response returns a masked mobile number (e.g. ******7890). Display this in the UI so the citizen knows which device will receive the OTP. The OTP is valid for 140 seconds — display a visible countdown timer in your UI to reduce re-initiation requests.
Mobile Number Priority: The OTP is always sent to the SELF (head of family) member's registered mobile. If the SELF member has no mobile number recorded, the platform falls back to the first available mobile across all members. If no mobile number exists across the entire family, a HTTP 400 is returned — handle this explicitly in your UI with a message directing the citizen to update their RC at the nearest PDS office.
Step 2: Verify OTP & Retrieve Member Data
Once the citizen enters the OTP, submit it along with the same RC number to complete verification. On success, the full list of RC family members is returned — enriched with Golden Record existence data so your UI can distinguish new citizens from returning ones.
This is a one-time operation. The Redis cache entry is deleted immediately upon successful retrieval. Calling this endpoint twice with the same OTP will fail — the session is consumed on first use.
| Detail | Value |
|---|---|
| Method | POST |
| Endpoint | /api/iam/rc/verify-otp |
| Auth Required | No |
def verify_otp_and_get_members(otp: str, rc_no: str) -> dict:
"""
Step 2: Verify OTP and retrieve the full RC member list.
Session is consumed (deleted from Redis) on successful retrieval.
"""
response = requests.post(
f"{BASE_URL}/rc/verify-otp",
json={
"otp": otp,
"rc_no": rc_no
},
headers={"Content-Type": "application/json"}
)
data = response.json()
if data.get("code") == 200:
members = data["data"]
# Separate new vs existing members using GR enrichment fields
existing = [m for m in members if m.get("is_exist")]
new_members = [m for m in members if not m.get("is_exist")]
return {
"success": True,
"members": members,
"existing_count": len(existing),
"new_count": len(new_members)
}
return {"success": False, "message": data.get("message")}
Each member object in the response includes an is_exist flag and a gr_record_id. Use these to build a smart member selection UI:
- Members with
is_exist: true— display as "Already Registered" with theirgr_record_idvisible to operators - Members with
is_exist: false— display as "New Member" with a clear action to save to the Golden Record - The
relationshiphoffield (SELF, SPOUSE, SON, DAUGHTER) allows you to render family hierarchy in the UI
Profile Picture Warning: Each member object may contain a profile_picture field with a base64-encoded image string. These can be large. If your UI does not need to display the member photo at this stage, strip or ignore this field before passing the member list to your frontend to avoid slow renders and unnecessary data transfer.
Step 3: Save RC Member Data to Golden Record
After the citizen selects a member, the platform saves the verified RC data into the Golden Record system. This endpoint requires a valid Bearer token — it is the only authenticated step in the standard OTP flow.
The save logic implements a multi-step matching strategy to prevent duplicate records before creating a new Golden Record entry.
| Detail | Value |
|---|---|
| Method | POST |
| Endpoint | /api/iam/rc/save-to-golden |
| Auth Required | Yes — Bearer Token |
Golden Record Matching Strategy
Before creating a new Golden Record, the platform attempts to find an existing record using this ordered sequence — with different rules for SELF (the logged-in citizen verifying themselves) and Family Members (stricter, to prevent incorrect merging):
| Step | Flow | Match Keys | Outcome |
|---|---|---|---|
| 1 | Both | member_unique_id |
Strongest match — always checked first |
| 2a | SELF only | mobile_number |
Reuses existing draft or basic profile |
| 2b | SELF only | citizen_full_name + date_of_birth |
Secondary identity match |
| 2c | SELF only | ration_card_number |
RC number fallback |
| 3a | Family only | full_name + dob + mobile_number |
Strict 3-key match — mobile alone never used for family |
| 3b | Family only | citizen_full_name + date_of_birth |
Name + DOB fallback |
| — | Both | No match found | New Golden Record created via stored procedure |
Why mobile alone is never used for Family members: RC families frequently share a single mobile number (the head of family's). Using mobile as a sole matching key for family members would incorrectly merge all family members into the same Golden Record. The platform enforces a strict 3-key match (name + DOB + mobile) for family members to prevent this.
def save_member_to_golden_record(member: dict, bearer_token: str, is_self: bool) -> dict:
"""
Step 3: Save selected RC member to the Golden Record.
Requires a valid Bearer token. Implements multi-step matching
to prevent duplicate record creation.
"""
from datetime import datetime, timezone
payload = {
"member_unique_id": member["rationcardmemberid"], # Required — primary match key
"mobile_number": member.get("mobilenumber"),
"citizen_full_name": member.get("citizen_full_name"),
"date_of_birth": member.get("date_of_birth"),
"ration_card_number": member.get("ration_card_number"),
"is_self_member": is_self,
"full_present_address": member.get("full_present_address"),
"profile_picture": member.get("profile_picture"), # base64 — platform stores as file ref
"verification_status": "VERIFIED",
"verified_at_utc": datetime.now(timezone.utc).isoformat(),
"verification_mode": "OTP",
"verification_source": "RATION_CARD"
}
response = requests.post(
f"{BASE_URL}/rc/save-to-golden",
json=payload,
headers={
"Authorization": f"Bearer {bearer_token}",
"Content-Type": "application/json"
}
)
result = response.json()
return result # Returns gr_record_id on success
Special Field Handling
Several fields in the payload are handled with automatic processing by the platform — your client does not need to pre-process them:
- full_present_address — Auto-parsed into
sub_division_name,pin_code, andvillage_name. The platform also setscitizen_countryto India automatically. - profile_picture (base64) — Uploaded to file storage under the IAM bucket. Only the file reference key is stored in the Golden Record — not the raw base64 string.
- date_of_birth — Accepts multiple formats (ISO 8601, DD-MM-YYYY, MM/DD/YYYY, datetime strings). The NIC format
MM/DD/YYYY HH:MM:SS AMis handled correctly. - gpward_code — Resolved to GP Ward location fields via an internal lookup before field mapping.
eSign Verification Flow
For services that require legally signed identity verification — welfare entitlements, land record updates, high-value scheme enrollment — the platform supports an eSign path where the citizen digitally signs a document as part of the RC verification process.
In the eSign path, the client does not call Save to Golden Record directly. Instead, the callback from the eSign gateway triggers the save automatically on successful signing.
POST /esign/initiate with the full RC member payload, response URL, and signer name. The platform generates a unique request_id and caches the session for 300 seconds.
POST /esign/callback on completion. The platform enriches the cached RC payload with eSign metadata (mode, status, transaction ID) and saves to the Golden Record automatically.
response_url?status=success. On failure, it redirects to response_url?status=fail&error=eSign failed and the session cache is cleared.
def initiate_esign_for_rc_member(
member: dict,
bearer_token: str,
response_url: str,
signer_name: str,
verifier_name: str = ""
) -> dict:
"""
Initiate eSign flow for RC member verification.
The rc_member_payload here is the COMPLETE Save-to-Golden payload.
It is cached (TTL 300s) and auto-submitted on successful eSign callback.
Do NOT call save-to-golden separately in the eSign path.
"""
from datetime import datetime, timezone
rc_member_payload = {
"member_unique_id": member["rationcardmemberid"],
"citizen_full_name": member.get("citizen_full_name"),
"date_of_birth": member.get("date_of_birth"),
"mobile_number": member.get("mobilenumber"),
"ration_card_number": member.get("ration_card_number"),
"full_present_address": member.get("full_present_address"),
"profile_picture": member.get("profile_picture"),
"verification_status": "VERIFIED",
"verification_mode": "ESIGN",
"verification_source": "RATION_CARD",
"verified_at_utc": datetime.now(timezone.utc).isoformat()
}
response = requests.post(
f"{BASE_URL}/esign/initiate",
json={
"response_url": response_url,
"signer_name": signer_name,
"verifier_name": verifier_name,
"rc_member_payload": rc_member_payload
},
headers={
"Authorization": f"Bearer {bearer_token}",
"Content-Type": "application/json"
}
)
return response.json() # Contains eSign redirect URL or widget payload
Critical: The eSign session TTL is 300 seconds. If the citizen takes longer than 5 minutes to complete signing on the gateway, the session will expire and the callback will fail with "RC member payload missing in eSign cache". Design your UI to clearly communicate the time constraint before redirecting the citizen to the eSign gateway.
Member Data Field Reference
Every object in the member list returned by Step 2 represents one Ration Card family member. Here is the complete field reference:
| Field | Type | Description |
|---|---|---|
rationcardmemberid |
string | Unique RC member ID assigned by NIC — primary matching key for Golden Record |
citizen_full_name |
string | Full name of the member as recorded in the NIC RC database |
date_of_birth |
string | Date of birth in NIC format: MM/DD/YYYY HH:MM:SS AM — the platform normalises this automatically |
mobilenumber |
string | Registered mobile number — often shared across family members |
relationshiphof |
string | Relationship to Head of Family: SELF, SPOUSE, SON, DAUGHTER — use to render family hierarchy |
gender |
string | Gender of the member (MALE / FEMALE) |
ration_card_number |
string | RC number — same for all members of the same family |
full_present_address |
string | Full address as recorded in the NIC RC database — auto-parsed on save |
profile_picture |
string (base64) | Member photo, base64-encoded — may be large; ignore if not needed in UI |
is_exist |
boolean | Whether this member already has a record in the platform's Golden Record system |
gr_record_id |
string | null | Existing Golden Record ID if is_exist is true — null for new members |
verification_details |
object | Prior verification metadata (mode, verified_at, source) if member was previously verified |
Error Reference & Handling
All error responses follow the standard platform envelope with code, message, and data fields. A well-implemented client maps each error scenario to a specific UI message rather than showing a generic "something went wrong".
| HTTP Status | Scenario | Client Action |
|---|---|---|
400 |
No mobile number found across all RC members | Show message: "Your Ration Card does not have a registered mobile number. Please visit your nearest PDS office to update it." |
400 |
OTP expired (140-second TTL elapsed) | Show countdown message. Offer "Resend OTP" which calls Step 1 again. |
400 |
Invalid / wrong OTP entered | Show "Incorrect OTP" message with retry count. After 3 failures, require re-initiation. |
400 |
member_unique_id missing from save payload |
Internal error — log and alert engineering team. |
400 |
No matching Golden Record fields in payload | Payload keys did not match any registered task fields in the field registry — check field key spelling. |
400 |
eSign callback received failure status | Citizen cancelled or failed signing. Redirect to response_url?status=fail and offer retry. |
400 |
RC member payload missing in eSign cache | eSign session expired (300s TTL). Re-initiate from Step 1. |
5xx |
NIC RC API or Golden Record service failure | Retry with exponential backoff. Log full error detail. Display "Service temporarily unavailable" to citizen. |
import time
RC_ERROR_MESSAGES = {
"OTP expired": "Your OTP has expired. Please re-enter your Ration Card number to get a new OTP.",
"OTP invalid": "The OTP you entered is incorrect. Please check and try again.",
"No mobile number": "No mobile number is registered with this Ration Card. Please visit your nearest PDS office.",
"NIC API failure": "The government RC service is temporarily unavailable. Please try again in a few minutes."
}
def call_with_retry(api_fn, max_retries: int = 3, base_delay: float = 1.5):
"""Retry wrapper for 5xx NIC API failures with exponential backoff."""
for attempt in range(max_retries):
try:
result = api_fn()
if result.get("code", 200) < 500:
return result # 2xx and 4xx — don't retry
except Exception as e:
if attempt == max_retries - 1:
raise
delay = base_delay * (2 ** attempt) # 1.5s → 3s → 6s
time.sleep(delay)
return {"code": 503, "message": "NIC API failure after retries"}
Common Integration Challenges
Based on implementation experience with government API integrations, these are the most frequent issues teams encounter during Ration Card integration and how to handle them correctly.
Challenge 1: Shared Family Mobile Number
RC families commonly register a single mobile number — typically the head of family's — for all members. This creates two specific problems:
- The OTP is always sent to the SELF member's mobile. If that member's number is blank, the platform falls back to the first available number. Build your UI to always show the masked mobile so citizens can confirm delivery.
- For family member matching in the Golden Record, mobile alone is never used as the match key. Always use the full 3-key match (name + DOB + mobile) for family records to prevent merging multiple family members into one GR entry.
Challenge 2: OTP Session Expiry (140 Seconds)
The OTP window is tight at 140 seconds. Teams often underestimate how long it takes for an SMS to arrive in low-signal areas and for the citizen to enter the code, especially in VLE-assisted flows. Recommendations:
- Display a visible countdown timer immediately after OTP is sent
- Auto-focus the OTP input field to reduce friction
- Show a clear "Resend OTP" button that becomes active after expiry
- Handle the OTP expired 400 error with a friendly message — never show raw API error text to citizens
Challenge 3: Missing Mobile Number (HTTP 400)
If the RC has no mobile number registered for any member, the NIC API cannot deliver an OTP. The platform returns HTTP 400. Many implementations treat this as a generic error, leaving the citizen with no actionable next step. Always surface a specific message directing them to update their RC at the nearest PDS or CSC centre.
Challenge 4: eSign Session Timeout (300 Seconds)
Citizens occasionally spend time reading the eSign document or get interrupted mid-signing. If the 300-second eSign cache expires before the callback is received, the entire flow must be re-initiated from Step 1. Consider warning the citizen before redirecting: "You have 5 minutes to complete the digital signing. Please do not close this window."
Challenge 5: Profile Picture Payload Size
The profile_picture field returns a base64-encoded image for each member, which can be 50–200KB per member. For a family of 5, the Step 2 response could easily reach 1MB+ before other fields. If you are passing the full member object to a list component in your frontend, strip the profile_picture field at the API service layer and only include it when the operator explicitly requests to view/save a specific member's photo.
Challenge 6: Golden Record Field Registry Mismatch
The Save to Golden Record endpoint silently ignores payload keys that do not match a registered fieldkey in the platform's task field registry. If a field is not being saved, check the field key spelling (the registry uses case-insensitive matching internally, but exact key names are recommended). Always test with a known good payload from the member object rather than constructing field names manually.
Production Readiness Checklist
Before going live with RC integration in a citizen-facing government platform, verify each of these items is in place:
Flow & Session
- Step 1 returns masked mobile and triggers visible OTP countdown in UI
- OTP expiry (140s) handled with specific user-facing message and resend flow
- "No mobile number" 400 error surfaces actionable guidance (visit PDS office)
- Step 2 one-time use confirmed — second call with same OTP handled gracefully
- eSign session TTL (300s) communicated to citizen before redirect
Golden Record
-
member_unique_idalways included in save payload -
is_self_memberflag correctly set for SELF vs family member flows -
is_existandgr_record_idused to show "Already Registered" label in UI - Field keys validated against task field registry before saving
- profile_picture stripped from list view; sent only during save
Security & Credentials
- NIC API credentials stored in encrypted configuration registry — not in code
- Bearer token required and validated on
save-to-goldenand eSign endpoints - No raw NIC credentials exposed in client-facing requests
- Audit log maintained for all save and eSign operations
Error Handling & Resilience
- 5xx NIC API failures retried with exponential backoff
- All error codes mapped to citizen-readable UI messages
- eSign callback failure handled with redirect to
response_url?status=fail - Platform monitoring covers NIC API response times and OTP delivery rates
How VAF.ai Helps
Ration Card integration is not a one-time code task — it is an ongoing operational responsibility. NIC API credential rotations, PDS database updates, OTP gateway changes, and Golden Record schema evolution all require the integration to adapt without breaking citizen-facing services.
VAF.ai API Copilot — RC Integration Lifecycle Management
At VAF.ai, we build and operate RC integrations as a managed layer — so your platform teams stay focused on citizen service delivery, not API maintenance. Our Cognicraft platform and API Copilot handle the full RC integration lifecycle.
This same architecture powers the UNNOTI platform — a government citizen service delivery system we built for Gram Panchayat-level operations — where Ration Card verification is used to onboard VLE-assisted beneficiaries across multiple welfare schemes simultaneously.
Integrate Ration Card Verification into Your Platform
Whether you are building a Gram Panchayat service portal, a welfare scheme management system, or a DBT compliance platform — our team can design, build, and operate a production-ready RC integration lifecycle for you.