Enrollment API (v3)¶
Mobile app subscriber enrollment flow — 8 steps for chit program applications.
Base URL¶
Authentication¶
All endpoints require a valid JWT bearer token for a SUBSCRIBER role user.
Enrollment Lifecycle¶
An enrollment request is created from a chit program and belongs to the authenticated subscriber. The client keeps using the returned requestId for every later step.
The flow has two phases:
| Phase | Steps | Expected request status | Purpose |
|---|---|---|---|
| Pre-enrollment | Basic info, addresses, income, documents | INCOMPLETE until submit, then REQUESTED | Collect KYC/profile details and submit the application for approval. |
| Payment | Payment initiation | approval/payment flow dependent | Starts payment for the enrollment invoice through the payment pipeline. |
| Post-enrollment | Nominee, PAN/income proof, agreement | ENROLLED | Completes details required after the subscriber has been enrolled. |
Use GET /me to hydrate the enrollment screen and decide which sections already have saved data. Step submit endpoints are idempotent in the product sense: they update the current application slice when the request is still editable, and validation prevents edits after the application reaches a disallowed status.
Uploaded Assets¶
Some enrollment steps do not upload files directly. The mobile app should upload files through the existing file upload/document signing flows first, then call these endpoints to attach or copy the already stored assets.
| Step | Required pre-uploaded data |
|---|---|
| Documents | Aadhaar front, Aadhaar back, and photograph on subscriber profile |
| PAN income | PAN card and income proof on subscriber profile |
| Agreement | Active signature asset owned by the subscriber |
The signature asset APIs are available in v3 at:
Common Response Envelope¶
Every success response uses this shape:
{
"status": "success", // "success" | "error" | "pending"
"code": 200,
"message": "...",
"data": {/* endpoint-specific payload */}
}
Enums¶
Gender¶
MALE | FEMALE | OTHER
RelationType¶
FATHER | MOTHER | HUSBAND | WIFE
IncomeSource¶
PROFESSIONAL | AGRICULTURE | EMPLOYMENT | BUSINESS | OTHER
EmploymentType¶
FULL_TIME | PART_TIME | CONTRACT | SELF_EMPLOYED
EnrollmentStep¶
BASIC_INFO | ADDRESSES | INCOME | DOCUMENTS | SUBMITTED | NOMINEE | PAN_INCOME | AGREEMENT | COMPLETED
Endpoints¶
GET /me¶
Get the current subscriber's enrollment data across all steps.
Response 200
{
"status": "success",
"code": 200,
"message": "Data fetched successfully",
"data": {
"BASIC_INFO": {
"name": "Jane Doe" | null,
"mobileNumber": "+919999999999" | null,
"mobileNumberVerified": true | null,
"countryCode": "IN" | null,
"email": "jane@example.com" | null,
"emailVerified": true | null,
"dob": "1990-01-01" | null,
"gender": "FEMALE" | null,
"aadhaarNumber": "****-****-6789" | null,
"pan": "ABCD****F" | null,
"relationName": "John" | null,
"relationType": "FATHER" | null
},
"ADDRESSES": {
"presentAddress": { /* Address object */ } | null,
"permanentAddress": { /* Address object */ } | null
},
"INCOME": {
"monthlyIncome": "50000" | null,
"incomeSource": "EMPLOYMENT" | null,
"employmentDetails": {
"employer": "ABC Corp" | null,
"employmentType": "FULL_TIME" | null,
"jobTitle": "Engineer" | null,
"officeAddress": { /* Address object */ } | null
} | null
},
"DOCUMENTS": {
"aadhaarCardFrontUrl": "https://..." | null,
"aadhaarCardBackUrl": "https://..." | null,
"photographUrl": "https://..." | null
},
"NOMINEE": {
"nominee": {
"name": "John Doe" | null,
"dob": "1995-06-15" | null,
"relation": "Brother" | null,
"mobileNumber": "+919999999998" | null,
"countryCode": "IN" | null,
"address": { /* Address object */ } | null
}
},
"PAN_INCOME": {
"panCardUrl": "https://..." | null,
"incomeProofUrl": "https://..." | null
},
"AGREEMENT": {
"signed": true | false
}
}
}
Address object¶
{
"line1": "123 Main St" | null,
"line2": "Apt 4B" | null,
"pincode": "671310" | null,
"postOffice": "Kasaragod" | null,
"city": "Kasaragod" | null,
"district": "Kasaragod" | null,
"state": "Kerala" | null,
"cityId": 12 | null,
"postOfficeId": 12 | null
}
POST /programs/:programId/steps/basic-info¶
Step 1 — Submit basic personal information and start a new application or resume an incomplete one.
Params
| Field | Type | Required | Description |
|---|---|---|---|
programId | cuid2 string | yes | ID of the chit program to apply for |
Body (all fields optional — missing values use existing subscriber data)
{
"name": "Jane Doe", // string, 1–100 chars, Unicode letters/spaces/hyphens/apostrophes/periods
"dob": "1990-01-01", // string (ISO date), age ≥ 18, must be in the past
"gender": "FEMALE", // "MALE" | "FEMALE" | "OTHER"
"aadhaarNumber": "523456789012", // string, 12 digits, must not start with 0 or 1
"pan": "ABCDE1234F", // string, uppercase, pattern: 5 letters + 4 digits + 1 letter
"relationType": "FATHER", // "FATHER" | "MOTHER" | "HUSBAND" | "WIFE"
"relationName": "John Doe" // string, 1–100 chars
}
Response 200
{
"status": "success",
"code": 200,
"message": "Basic info saved successfully",
"data": {
"requestId": "cuid2-string",
"step": "BASIC_INFO"
}
}
Errors
| Code | Message |
|---|---|
400 | This program doesn't exist or may have been removed. |
400 | You've already been invited. Please wait for the program to start. |
400 | You've already applied for this program. |
400 | Email has to be verified. Please verify your email and try again. |
400 | Mobile Number has to be verified. Please verify your mobile number and try again. |
400 | Missing required fields. Please fill in all required fields. |
POST /:requestId/steps/addresses¶
Step 2 — Submit present and permanent addresses.
Params
| Field | Type | Required | Description |
|---|---|---|---|
requestId | cuid2 string | yes | Enrollment request ID |
Body (all fields optional)
{
"isPresentAddressSameAsPermanentAddress": false, // boolean (when true, permanentAddress is copied from presentAddress on the server)
"presentAddress": {/* Address */},
"permanentAddress": {/* Address */}
}
Address (all fields optional, at least line1 required in practice)
{
"line1": "123 Main St", // string, 1–200 chars
"line2": "Apt 4B", // string, 1–200 chars
"pincode": "671310", // string, 6 digits
"postOffice": "Kasaragod", // string, 1–100 chars
"city": "Kasaragod", // string, 1–100 chars
"district": "Kasaragod", // string, 1–100 chars
"state": "Kerala", // string, 1–100 chars
"cityId": 12, // number, positive integer
"postOfficeId": 12 // number, positive integer
}
Response 200
{
"status": "success",
"code": 200,
"message": "Addresses saved successfully",
"data": {
"requestId": "cuid2-string",
"step": "ADDRESSES"
}
}
Errors
| Code | Message |
|---|---|
404 | Request not found. |
400 | You've already applied for this program. |
400 | Please provide a valid address. (when line1 is missing and addresses are not null) |
POST /:requestId/steps/income¶
Step 3 — Submit income details and employment information.
Params
| Field | Type | Required | Description |
|---|---|---|---|
requestId | cuid2 string | yes | Enrollment request ID |
Body (all fields optional)
{
"monthlyIncome": "50000", // string | number — converted to Prisma.Decimal
"incomeSource": "EMPLOYMENT", // "PROFESSIONAL" | "AGRICULTURE" | "EMPLOYMENT" | "BUSINESS" | "OTHER"
"employmentDetails": {
"employer": "ABC Corp", // string, 1–100 chars
"employmentType": "FULL_TIME", // "FULL_TIME" | "PART_TIME" | "CONTRACT" | "SELF_EMPLOYED"
"jobTitle": "Engineer", // string, 1–100 chars
"officeAddress": {/* Address */}
}
}
Response 200
{
"status": "success",
"code": 200,
"message": "Income details saved successfully",
"data": {
"requestId": "cuid2-string",
"step": "INCOME"
}
}
Errors
| Code | Message |
|---|---|
404 | Request not found. |
400 | You've already applied for this program. |
400 | Missing required employment details. (when incomeSource is EMPLOYMENT but employmentDetails is missing/incomplete) |
POST /:requestId/steps/documents¶
Step 4 — Accept terms and submit the application (moves from INCOMPLETE → REQUESTED).
Params
| Field | Type | Required | Description |
|---|---|---|---|
requestId | cuid2 string | yes | Enrollment request ID |
Body
{
"acceptedTermsAndConditions": true // must be exactly `true`; validation rejects any other value
}
Server-side injected fields: ipAddress (from req.ip), userAgent (from user-agent header).
Response 200
{
"status": "success",
"code": 200,
"message": "Application submitted successfully",
"data": {
"requestId": "cuid2-string",
"step": "SUBMITTED"
}
}
Errors
| Code | Message |
|---|---|
404 | Request not found. |
400 | You've already applied for this program. |
400 | Almost there! Please accept the terms and conditions to continue. |
POST /:requestId/payments¶
Step 5 — Initiate payment for an enrollment invoice.
Params
| Field | Type | Required | Description |
|---|---|---|---|
requestId | cuid2 string | yes | Enrollment request ID |
Body
{
"invoiceId": "cuid2-string", // required — invoice ID for the enrollment
"idempotencyKey": "unique-key-1", // required — string, 1–255 chars, prevents duplicate payments
"grossAmount": "5000.00", // required — string, at least 1 char
"currency": "INR", // required — only "INR" is accepted
"deviceId": "device-abc-123" // optional — string, 1–255 chars; falls back to idempotencyKey
}
Server-side injected fields: ipAddress (from req.ip), userAgent (from user-agent header).
Response 200
{
"status": "success",
"code": 200,
"message": "Payment initiated successfully",
"data": {
"transactionId": "txn-cuid2",
"paymentToken": "pg-token",
"accessKey": "pg-access-key",
"checkoutMode": "SDK" | "REDIRECT" | "IFRAME"
}
}
Errors
| Code | Message |
|---|---|
404 | We couldn't find this application. Please check and try again. |
POST /:requestId/cancel¶
Cancel an enrollment request. Allows cancelling from INCOMPLETE, REQUESTED, ADMIN_APPROVED, or COMPANY_APPROVED status.
Params
| Field | Type | Required | Description |
|---|---|---|---|
requestId | cuid2 string | yes | Enrollment request ID |
Body
Response 200
{
"status": "success",
"code": 200,
"message": "Enrollment cancelled successfully",
"data": {
"requestId": "cuid2-string"
}
}
Errors
| Code | Message |
|---|---|
404 | Application request not found. |
400 | Cannot cancel application in "ENROLLED" status. |
POST /:requestId/post-enrollment/nominee¶
Step 6 — Submit nominee details (post-enrollment, after payment is complete and status is ENROLLED).
Params
| Field | Type | Required | Description |
|---|---|---|---|
requestId | cuid2 string | yes | Enrollment request ID |
Body
{
"nominee": {
"name": "John Doe", // string, 1–100 chars
"relation": "Brother", // string
"mobileNumber": "+919999999999", // string — preprocessed: trimmed, leading zeros removed, prepended with "+" (or kept if already present), must be 11–16 total chars
"countryCode": "IN", // string, ISO 3166-1 alpha-2 (default: "IN")
"dob": "1995-06-15", // string (ISO date)
"address": {/* Address */}
}
}
Response 200
{
"status": "success",
"code": 200,
"message": "Nominee details saved successfully",
"data": {
"requestId": "cuid2-string",
"step": "NOMINEE"
}
}
Errors
| Code | Message |
|---|---|
404 | This application doesn't exist or may have been removed. |
400 | Enrollment is not yet complete. Complete payment first. |
400 | Nominee details are required. |
400 | We couldn't save the nominee details right now. Please try again. |
POST /:requestId/post-enrollment/pan-income¶
Step 7 — Submit PAN card and income proof documents to the enrolled subscriber record (post-enrollment, status ENROLLED).
Documents must already be uploaded to the subscriber's profile via the file upload flow. This step copies the URLs from the subscriber record to the enrolled subscriber record.
Params
| Field | Type | Required | Description |
|---|---|---|---|
requestId | cuid2 string | yes | Enrollment request ID |
Body: none.
Response 200
{
"status": "success",
"code": 200,
"message": "Documents submitted successfully",
"data": {
"requestId": "cuid2-string",
"step": "PAN_INCOME"
}
}
Errors
| Code | Message |
|---|---|
404 | This application doesn't exist or may have been removed. |
400 | Enrollment is not yet complete. Complete payment first. |
404 | Subscriber not found. |
400 | Please upload all the required documents. (plus message, path, missing details) |
POST /:requestId/post-enrollment/agreement¶
Step 8 — Sign the chit agreement (post-enrollment, status ENROLLED). Requires a pre-uploaded signature asset and acceptance of terms. Generates a blank monthly-chitty-agreement-en PDF; enrollment details will be filled in a later version.
Params
| Field | Type | Required | Description |
|---|---|---|---|
requestId | cuid2 string | yes | Enrollment request ID |
Body
{
"signatureAssetId": "cuid2-string", // required — signature asset ID from the document signing module
"acceptedTermsAndConditions": true // must be exactly `true`
}
Response 200
{
"status": "success",
"code": 200,
"message": "Agreement signed successfully",
"data": {
"requestId": "cuid2-string",
"step": "AGREEMENT",
"templateKey": "monthly-chitty-agreement-en",
"fileName": "request-id-monthly-chitty-agreement-en.pdf",
"fileKey": "generated-agreements/monthly-chitty-agreement-en/...",
"fileUrl": "https://example.com/generated-agreement.pdf",
"sizeBytes": 12345,
"message": "Agreement PDF generated successfully."
}
}
Errors
| Code | Message |
|---|---|
400 | Signature is required to sign the agreement. |
400 | Almost there! Please accept the terms and conditions to continue. |
404 | This application doesn't exist or may have been removed. |
400 | Enrollment is not yet complete. Complete payment first. |
400 | Signature asset not found or not active. Please upload a new signature. |
Signature Asset APIs¶
These APIs let the mobile app upload and reuse subscriber signature images for agreement signing. They use the same authentication envelope as the enrollment APIs and require a SUBSCRIBER bearer token.
POST /me/documents/signatures/generate¶
Generate a signed upload URL for a subscriber signature image.
Body
{
"fileName": "signature.png", // required
"contentType": "image/png" // required — "image/png" | "image/jpeg"
}
Response 200
{
"status": "success",
"code": 200,
"message": "Signature upload URL generated successfully.",
"data": {
"signatureAssetId": "cuid2-string",
"signedUrl": "https://s3-signed-upload-url"
}
}
Client flow: upload the image bytes to signedUrl using the returned content type, then call the confirm endpoint with signatureAssetId.
POST /me/documents/signatures/confirm¶
Confirm that the signature image was uploaded successfully. Confirmation marks the signature asset as ready and deactivates other signature assets owned by the subscriber.
Body
Response 200
{
"status": "success",
"code": 200,
"message": "Signature upload confirmed successfully.",
"data": {
"signatureAssetId": "cuid2-string",
"fileUrl": "https://..."
}
}
GET /me/documents/signatures¶
List existing signature assets for the authenticated subscriber. Use this to show previously uploaded signatures and pick an active/ready signatureAssetId for agreement signing.
Query
| Field | Type | Required | Description |
|---|---|---|---|
pageNumber | number | no | Page number, defaults normally. |
pageSize | number | no | Page size, defaults normally. |
Response 200
{
"status": "success",
"code": 200,
"message": "Signature assets fetched successfully.",
"data": {
"count": 1,
"hasPreviousPage": false,
"hasNextPage": false,
"pageNumber": 1,
"pageSize": 10,
"totalPages": 1,
"signatures": [
{
"id": "cuid2-string",
"fileName": "signature.png",
"contentType": "image/png",
"fileUrl": "https://...",
"status": "READY",
"isActive": true,
"uploadedAt": "2026-03-29T10:00:00.000Z",
"createdAt": "2026-03-29T09:59:00.000Z",
"updatedAt": "2026-03-29T10:00:00.000Z"
}
]
}
}
Use a ready, active signature asset ID as signatureAssetId in POST /:requestId/post-enrollment/agreement.
Flow Summary¶
Pre-enrollment (INCOMPLETE):
1. BASIC_INFO ──→ 2. ADDRESSES ──→ 3. INCOME ──→ 4. DOCUMENTS + Terms
│
▼
REQUESTED (submitted for approval)
(admin/company approval process)
│
▼
ENROLLED (payment complete)
Post-enrollment:
5. Payment (initiate)
6. NOMINEE ──→ 7. PAN_INCOME ──→ 8. AGREEMENT ──→ COMPLETED
- Steps 1–4 transition from
INCOMPLETE→REQUESTED(step 4 is the submit action). - Steps 6–8 require status
ENROLLED. - Step 5 (payment) is called during the admin approval window; it delegates to the existing payments pipeline.
- Cancel is available at any pre-
ENROLLEDstatus (INCOMPLETE,REQUESTED,ADMIN_APPROVED,COMPANY_APPROVED). - The agreement step currently generates and stores a blank
monthly-chitty-agreement-enPDF. The response includes storage metadata (fileKey,fileUrl,sizeBytes) instead of the older document-templatesubmissionId. - Successful step responses return the saved
requestIdand logicalstep; detailed saved values should be refreshed fromGET /mewhen the client needs the latest screen state.
Zod Validation Reference¶
| Schema | Field | Zod Type | Notes |
|---|---|---|---|
CuidSchema | IDs | z.cuid2() | cuid2 format |
userNameSchema() | names | string → trim → NFC normalize → 1–100 chars → Unicode regex → optional titleCase | letters, spaces, hyphens, apostrophes, periods only |
AadhaarNoSchema | aadhaar | string → 12 digits, not starting with 0/1 | |
PanSchema | pan | string → uppercase → /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/ | |
dateOfBirthSchema({ minAge: 18 }) | dob | string/Date → past date → age ≥ 18 → age ≤ 120 | |
MobileNumberSchema | mobile | string → normalize → starts with + → digits only → 11–16 chars | |
CountryCodeSchema | countryCode | z.enum([...ISO 3166-1 alpha-2]) | 249 country codes |
PinCodeSchema | pincode | string → 6 digits, not starting with 0 | |
IntegerIdSchema | cityId/etc | z.number().int().positive() | |
DecimalSchema | monthlyIncome | string/number/Prisma.Decimal → Prisma.Decimal | preprocesses input |