openapi: 3.1.0 info: title: CAS Parser - Track Portfolios from CDSL, NSDL, CAMS, KFintech description: API for parsing and analyzing CAS (Consolidated Account Statement) PDF files from NSDL, CDSL, and CAMS/KFintech, with a unified response format version: 4.0.0 contact: name: Sameer Kumar email: sameer@casparser.in servers: - url: https://api.casparser.in description: Production server - url: https://portfolio-parser.api.casparser.in description: Legacy production server (still supported) - url: http://localhost:5000 description: Local development server tags: - name: CAS Parser description: Endpoints for parsing CAS PDF files from different sources. - name: CAS Generator description: Endpoints for generating new CAS documents via email mailback (KFintech). - name: CAS Fetch description: | Endpoints for fetching CAS documents with instant download. Currently supports CDSL via OTP authentication. - name: Email Import description: | Endpoints for importing CAS files directly from user email inboxes. **Supported Providers:** Gmail (more coming soon) **How it works:** 1. Call `POST /v4/inbox/connect` to get an OAuth URL 2. Redirect user to the OAuth URL for consent 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) **Security:** - Read-only access (we cannot send emails) - Tokens are encrypted with server-side secret - User can revoke access anytime via `/v4/inbox/disconnect` - name: Inbound Email description: | Create dedicated inbound email addresses for investors to forward their CAS statements. **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth or file upload. **How it works:** 1. Call `POST /v4/inbound-email` to create a unique inbound email address 2. Display this email to your user: "Forward your CAS statement to ie_xxx@import.casparser.in" 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your webhook 4. Your webhook receives email metadata + attachment download URLs **Sender Validation:** - Only emails from verified CAS authorities are processed: - CDSL: `eCAS@cdslstatement.com` - NSDL: `NSDL-CAS@nsdl.co.in` - CAMS: `donotreply@camsonline.com` - KFintech: `samfS@kfintech.com` - Emails failing SPF/DKIM/DMARC are rejected - Forwarded emails must contain the original sender in headers **Billing:** 0.2 credits per successfully processed valid email - name: Contract Note Parser description: Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, Groww, Upstox, ICICI etc. - name: Authorization description: | Endpoints for checking API quota and credits usage. These endpoints help you monitor your API usage and remaining quota. - name: Portfolio Connect description: | Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate short-lived `at_` prefixed tokens that can be safely passed to frontend applications. Access tokens can be used in place of API keys on all v4 endpoints. - name: Agent Auth description: | Endpoints for coding agents to obtain API keys via a browser-based approval flow. **How it works:** 1. Agent generates a random token locally (e.g. `openssl rand -hex 32`). No API call needed. 2. Agent asks the user to open `https://app.casparser.in/agent-auth?token=&client_name=` 3. User signs in via the browser and clicks Approve. 4. Agent polls `GET /v1/agent-auth/token/{token}` every 5 seconds until the key is delivered. **Security:** - Token must be 16-128 characters (recommended: 64 hex chars / 256 bits) - Approved keys are delivered once (one-shot) and then deleted - Tokens expire after 10 minutes if not approved - name: KYC description: | Endpoints for verifying KYC (Know Your Customer) status of Indian investors. **Data source:** CVL KRA public inquiry portal (cvlkra.com), which aggregates records from all five SEBI-registered KRAs: CVL, NDML, CAMS, Karvy, and KFin. **Status normalization:** Raw CVL portal strings are normalized into clean enums. See the `kyc_status` property on `KycPanStatusResponse` for the full list. **Credits:** 0.5 per successful lookup. Failed lookups are not billed. paths: /v4/smart/parse: post: tags: - CAS Parser summary: Smart Parse CAS PDF description: | This endpoint parses CAS (Consolidated Account Statement) PDF files from NSDL, CDSL, or CAMS/KFintech and returns data in a unified format. It auto-detects the CAS type and transforms the data into a consistent structure regardless of the source. operationId: smartParse security: - ApiKeyAuth: [] requestBody: $ref: '#/components/requestBodies/CASParseRequest' responses: '200': description: Successful operation content: application/json: schema: $ref: '#/components/schemas/UnifiedResponse' '400': description: Bad request (Invalid PDF file, missing parameters, or invalid password) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '500': $ref: '#/components/responses/InternalError' /v4/nsdl/parse: post: tags: - CAS Parser summary: Parse NSDL CAS PDF description: | This endpoint specifically parses NSDL CAS (Consolidated Account Statement) PDF files and returns data in a unified format. Use this endpoint when you know the PDF is from NSDL. operationId: nsdlParse security: - ApiKeyAuth: [] requestBody: $ref: '#/components/requestBodies/CASParseRequest' responses: '200': description: Successful operation content: application/json: schema: $ref: '#/components/schemas/UnifiedResponse' '400': description: Bad request (Invalid PDF file, missing parameters, invalid password, or incorrect CAS type) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '500': $ref: '#/components/responses/InternalError' /v4/cdsl/parse: post: tags: - CAS Parser summary: Parse CDSL CAS PDF description: | This endpoint specifically parses CDSL CAS (Consolidated Account Statement) PDF files and returns data in a unified format. Use this endpoint when you know the PDF is from CDSL. operationId: cdslParse security: - ApiKeyAuth: [] requestBody: $ref: '#/components/requestBodies/CASParseRequest' responses: '200': description: Successful operation content: application/json: schema: $ref: '#/components/schemas/UnifiedResponse' '400': description: Bad request (Invalid PDF file, missing parameters, invalid password, or incorrect CAS type) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '500': $ref: '#/components/responses/InternalError' /v4/cams_kfintech/parse: post: tags: - CAS Parser summary: Parse CAMS/KFintech CAS PDF description: | This endpoint specifically parses CAMS/KFintech CAS (Consolidated Account Statement) PDF files and returns data in a unified format. Use this endpoint when you know the PDF is from CAMS or KFintech. operationId: camsKfintechParse security: - ApiKeyAuth: [] requestBody: $ref: '#/components/requestBodies/CASParseRequest' responses: '200': description: Successful operation content: application/json: schema: $ref: '#/components/schemas/UnifiedResponse' '400': description: Bad request (Invalid PDF file, missing parameters, invalid password, or incorrect CAS type) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '500': $ref: '#/components/responses/InternalError' /v4/contract_note/parse: post: tags: - Contract Note Parser summary: Parse Contract Note PDF description: | This endpoint parses Contract Note PDF files from various brokers including Zerodha, Groww, Upstox, ICICI Securities, and others. **What is a Contract Note?** A contract note is a legal document that provides details of all trades executed by an investor. It includes: - Trade details with timestamps, quantities, and prices - Brokerage and charges breakdown - Settlement information - Regulatory compliance details **Supported Brokers:** - Zerodha Broking Limited - Groww Invest Tech Private Limited - Upstox (RKSV Securities) - ICICI Securities Limited - Auto-detection for unknown brokers **Key Features:** - **Auto-detection**: Automatically identifies broker type from PDF content - **Comprehensive parsing**: Extracts equity transactions, derivatives transactions, detailed trades, and charges - **Flexible input**: Accepts both file upload and URL-based PDF input - **Password protection**: Supports password-protected PDFs The API returns structured data including contract note information, client details, transaction summaries, and detailed trade-by-trade breakdowns. operationId: parseContractNote security: - ApiKeyAuth: [] requestBody: $ref: '#/components/requestBodies/ContractNoteParseRequest' responses: '200': description: Successfully parsed contract note content: application/json: schema: type: object properties: status: type: string example: "success" msg: type: string example: "success" data: $ref: '#/components/schemas/ContractNoteResponse' '400': $ref: '#/components/responses/ParseBadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '500': $ref: '#/components/responses/InternalError' /v4/generate: post: tags: - CAS Generator summary: CAS Generator (Email Mailback) description: | Generate a mutual fund CAS statement via email mailback. The CAS PDF will be sent to the investor's email. KFintech and CAMS have a mutual partnership for data sharing and consolidation, so this endpoint retrieves mutual fund data from **both RTAs**, covering all mutual funds in India. This is an async operation - the investor receives the CAS via email within a few minutes. By default, a **Detailed CAS** is generated, which includes the complete transaction history. The parsing layer supports all statement types (Detailed, Summary, etc.), but generation always requests the Detailed variant for maximum data coverage. operationId: generateCas security: - ApiKeyAuth: [] requestBody: content: application/json: schema: type: object required: - email - from_date - to_date - password properties: email: type: string description: Email address to receive the CAS document example: "user@example.com" pan_no: type: string description: PAN number (optional) example: "ABCDE1234F" from_date: type: string description: Start date (YYYY-MM-DD) example: "2023-01-01" to_date: type: string description: End date (YYYY-MM-DD) example: "2023-12-31" password: type: string description: Password for the PDF example: "Abcdefghi12$" responses: '200': description: Request submitted successfully content: application/json: schema: type: object properties: status: type: string example: "success" msg: type: string example: "CAS request submitted. Check email shortly." '400': $ref: '#/components/responses/ParseBadRequest' '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalError' /v4/inbox/connect: post: tags: - Email Import summary: Connect Email Provider (Initiate OAuth) description: | Initiate OAuth flow to connect user's email inbox. Returns an `oauth_url` that you should redirect the user to. After authorization, they are redirected back to your `redirect_uri` with the following query parameters: **On success:** - `inbox_token` - Encrypted token to store client-side - `email` - Email address of the connected account - `state` - Your original state parameter (for CSRF verification) **On error:** - `error` - Error code (e.g., `access_denied`, `token_exchange_failed`) - `state` - Your original state parameter **Store the `inbox_token` client-side** and use it for all subsequent inbox API calls. operationId: inboxConnect security: - ApiKeyAuth: [] requestBody: required: true content: application/json: schema: type: object required: - redirect_uri properties: redirect_uri: type: string format: uri description: Your callback URL to receive the inbox_token (must be http or https) example: "https://yourapp.com/oauth-callback" state: type: string description: State parameter for CSRF protection (returned in redirect) example: "abc123" responses: '200': description: OAuth URL generated successfully content: application/json: schema: type: object properties: status: type: string example: "success" oauth_url: type: string format: uri description: Redirect user to this URL to start OAuth flow example: "https://accounts.google.com/o/oauth2/v2/auth?client_id=..." expires_in: type: integer description: Seconds until the OAuth URL expires (typically 10 minutes) example: 600 '400': description: Invalid or missing redirect_uri '401': $ref: '#/components/responses/Unauthorized' /v4/inbox/status: post: tags: - Email Import summary: Check Email Connection Status description: | Verify if an `inbox_token` is still valid and check connection status. Use this to check if the user needs to re-authenticate (e.g., if they revoked access in their email provider settings). operationId: inboxStatus security: - ApiKeyAuth: [] parameters: - name: x-inbox-token in: header required: true schema: type: string description: The encrypted inbox token responses: '200': description: Token status retrieved content: application/json: schema: type: object properties: status: type: string example: "success" connected: type: boolean description: Whether the token is valid and usable example: true provider: type: string example: "gmail" email: type: string format: email description: Email address of the connected account example: "user@gmail.com" '401': description: Token is invalid, expired, or revoked. User must reconnect. content: application/json: schema: type: object properties: status: type: string example: "error" msg: type: string example: "Email access revoked. Please reconnect." requires_reconnect: type: boolean example: true /v4/inbox/cas: post: tags: - Email Import summary: List CAS Files from Email Inbox description: | Search the user's email inbox for CAS files from known senders (CAMS, KFintech, CDSL, NSDL). Files are uploaded to temporary cloud storage. **URLs expire in 24 hours.** Optionally filter by CAS provider and date range. **Billing:** 0.2 credits per request (charged regardless of success or number of files found). operationId: inboxCasList security: - ApiKeyAuth: [] parameters: - name: x-inbox-token in: header required: true schema: type: string description: The encrypted inbox token requestBody: required: false content: application/json: schema: type: object properties: start_date: type: string format: date description: Start date in ISO format (YYYY-MM-DD). Defaults to 30 days ago. example: "2025-12-01" end_date: type: string format: date description: End date in ISO format (YYYY-MM-DD). Defaults to today. example: "2025-12-31" cas_types: type: array items: type: string enum: [cdsl, nsdl, cams, kfintech] description: | Filter by CAS provider(s): - `cdsl` → eCAS@cdslstatement.com - `nsdl` → NSDL-CAS@nsdl.co.in - `cams` → donotreply@camsonline.com - `kfintech` → samfS@kfintech.com example: ["cdsl", "nsdl"] responses: '200': description: CAS files found in inbox content: application/json: schema: type: object properties: status: type: string example: "success" files: type: array items: $ref: '#/components/schemas/EmailCASFile' count: type: integer description: Number of CAS files found example: 5 '401': description: Token invalid or revoked content: application/json: schema: type: object properties: status: type: string example: "error" msg: type: string example: "Email access revoked. Please reconnect." requires_reconnect: type: boolean example: true '500': $ref: '#/components/responses/InternalError' /v4/inbox/disconnect: post: tags: - Email Import summary: Disconnect Email Provider description: | Revoke email access and invalidate the token. This calls the provider's token revocation API (e.g., Google's revoke endpoint) to ensure the user's consent is properly removed. After calling this, the `inbox_token` becomes unusable. operationId: inboxDisconnect security: - ApiKeyAuth: [] parameters: - name: x-inbox-token in: header required: true schema: type: string description: The encrypted inbox token to revoke responses: '200': description: Successfully disconnected content: application/json: schema: type: object properties: status: type: string example: "success" msg: type: string example: "Email disconnected successfully" '401': $ref: '#/components/responses/Unauthorized' # ============================================ # Inbound Email Endpoints # ============================================ /v4/inbound-email: post: tags: - Inbound Email summary: Create Inbound Email description: | Create a dedicated inbound email address for collecting CAS statements via email forwarding. When an investor forwards a CAS email to this address, we verify the sender and make the file available to you. `callback_url` is **optional**: - **Set it** — we POST each parsed email to your webhook as it arrives. - **Omit it** — retrieve files via `GET /v4/inbound-email/{id}/files` without building a webhook consumer. operationId: createInboundEmail security: - ApiKeyAuth: [] requestBody: required: true content: application/json: schema: type: object properties: alias: type: string minLength: 3 maxLength: 32 pattern: '^[a-z0-9][a-z0-9-]*[a-z0-9]$' description: | Optional custom email prefix (e.g. `john-portfolio@import.casparser.in`). 3-32 chars, alphanumeric + hyphens, must start/end with a letter or number. If omitted, a random ID is generated. example: "john-portfolio" reference: type: string maxLength: 256 description: | Your internal identifier (e.g., user_id, account_id). Returned in webhook payload for correlation. example: "user_12345" callback_url: type: [string, "null"] format: uri description: | Optional webhook URL where we POST parsed emails. Must be HTTPS in production (HTTP allowed for localhost). If omitted, retrieve files via `GET /v4/inbound-email/{id}/files`. example: "https://api.yourapp.com/webhooks/cas-email" allowed_sources: type: array items: type: string enum: [cdsl, nsdl, cams, kfintech] description: | Filter emails by CAS provider. If omitted, accepts all providers. - `cdsl` → eCAS@cdslstatement.com - `nsdl` → NSDL-CAS@nsdl.co.in - `cams` → donotreply@camsonline.com - `kfintech` → samfS@kfintech.com example: ["cdsl", "nsdl"] metadata: type: object additionalProperties: type: string maxProperties: 10 description: | Optional key-value pairs (max 10) to include in webhook payload. Useful for passing context like plan_type, campaign_id, etc. example: plan: "premium" source: "onboarding" responses: '201': description: Inbound email created successfully content: application/json: schema: $ref: '#/components/schemas/InboundEmail' '400': description: Invalid request (malformed URL, invalid sources, etc.) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': $ref: '#/components/responses/Unauthorized' get: tags: - Inbound Email summary: List Inbound Emails description: | List all inbound emails associated with your API key. Returns active and paused inbound emails (deleted ones are excluded). operationId: listInboundEmails security: - ApiKeyAuth: [] parameters: - name: status in: query schema: type: string enum: [active, paused, all] default: all description: Filter by status - name: limit in: query schema: type: integer minimum: 1 maximum: 100 default: 50 description: Maximum number of inbound emails to return - name: offset in: query schema: type: integer minimum: 0 default: 0 description: Pagination offset responses: '200': description: List of inbound emails content: application/json: schema: type: object properties: status: type: string example: "success" inbound_emails: type: array items: $ref: '#/components/schemas/InboundEmail' total: type: integer description: Total number of inbound emails (for pagination) example: 15 limit: type: integer example: 50 offset: type: integer example: 0 '401': $ref: '#/components/responses/Unauthorized' /v4/inbound-email/{inbound_email_id}: get: tags: - Inbound Email summary: Get Inbound Email Details description: | Retrieve details of a specific inbound email including statistics. operationId: getInboundEmail security: - ApiKeyAuth: [] parameters: - name: inbound_email_id in: path required: true schema: type: string description: Inbound Email ID responses: '200': description: Inbound email details content: application/json: schema: $ref: '#/components/schemas/InboundEmail' '401': $ref: '#/components/responses/Unauthorized' '404': description: Inbound email not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' delete: tags: - Inbound Email summary: Delete Inbound Email description: | Permanently delete an inbound email address. It will stop accepting emails. **Note:** Deletion is immediate and cannot be undone. Any emails received after deletion will be rejected. operationId: deleteInboundEmail security: - ApiKeyAuth: [] parameters: - name: inbound_email_id in: path required: true schema: type: string description: Inbound Email ID to delete responses: '200': description: Inbound email deleted content: application/json: schema: type: object properties: status: type: string example: "success" msg: type: string example: "Inbound email deleted successfully" '401': $ref: '#/components/responses/Unauthorized' '404': description: Inbound email not found /v4/inbound-email/{inbound_email_id}/files: get: tags: - Inbound Email summary: List Files Received description: | Retrieve CAS files forwarded to this inbound email. Available for every inbound email, regardless of whether a `callback_url` was set — use it to avoid building a webhook consumer, to poll alongside a webhook, or to replay missed webhook deliveries. Pass the `cursor` from the previous response as `since` to receive only new files. operationId: listInboundEmailFiles security: - ApiKeyAuth: [] parameters: - name: inbound_email_id in: path required: true schema: type: string description: Inbound Email ID (e.g., ie_a1b2c3d4e5f6) example: "ie_a1b2c3d4e5f6" - name: since in: query required: false schema: type: string format: date-time description: | ISO 8601 timestamp. Only files with `received_at` strictly greater than this value are returned. Use the `cursor` value from the previous response. - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 50 default: 20 description: Maximum files to return. responses: '200': description: Files received at this inbound email since the cursor content: application/json: schema: type: object properties: status: type: string example: "success" files: type: array description: Files received (sorted oldest-first) items: $ref: '#/components/schemas/ReceivedEmailCASFile' count: type: integer example: 1 cursor: type: [string, "null"] format: date-time description: Pass as `since` on the next poll. `null` if no files and no prior cursor. example: "2025-02-21T10:45:12.000123+00:00" '400': description: Malformed query parameters content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': $ref: '#/components/responses/Unauthorized' '404': description: Inbound email not found or expired content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # ============================================ # KYC Endpoints # ============================================ /v1/kyc/pan/status: post: tags: - KYC summary: Check KYC PAN Status description: | Query KYC registration status for a PAN number across all five SEBI-registered KRAs (CVL, NDML, CAMS, Karvy, KFin) via the CVL KRA portal. Raw portal status strings are normalized into clean enums (see `kyc_status`). **`kyc_compliant`** is `true` only when `kyc_status` is `validated` or `registered`. Use this for quick onboarding gate decisions. **`remarks`** on a KRA object is only actionable when `status` is `on_hold` — it contains the reason (e.g. `"APPLICANT PHOTO MISMATCH"`). For `validated` KRAs, remarks like `"FATCA NOT APPLICABLE"` can be safely ignored. **Note:** This endpoint may take up to 60 seconds. Set your client timeout accordingly. **Credits:** 0.25 per successful lookup. Failed lookups are not billed. operationId: checkKycPanStatus security: - ApiKeyAuth: [] - AccessTokenAuth: [] requestBody: required: true content: application/json: schema: type: object required: - pan_no properties: pan_no: type: string description: PAN number to look up (10 characters, uppercase) example: "ABCDE1234F" pattern: '^[A-Z]{5}[0-9]{4}[A-Z]{1}$' responses: '200': description: KYC status retrieved successfully content: application/json: schema: $ref: '#/components/schemas/KycPanStatusResponse' examples: validated: summary: Fully validated KYC (CVL, DigiLocker) value: status: "success" pan: "ABCDE1234F" kyc_compliant: true kyc_status: "validated" kyc_mode: "digilocker" active_kra: "cvl" registered_on: "2011-03-18" last_updated_on: "2024-09-19" kras: cvl: status: "validated" registered_on: "2011-03-18" last_updated_on: "2024-09-19" kyc_mode: "digilocker" remarks: null raw_status: null ndml: status: "not_available" registered_on: null last_updated_on: null kyc_mode: null remarks: null raw_status: null cams: status: "not_checked" registered_on: null last_updated_on: null kyc_mode: null remarks: null raw_status: null karvy: status: "not_checked" registered_on: null last_updated_on: null kyc_mode: null remarks: null raw_status: null kfin: status: "not_checked" registered_on: null last_updated_on: null kyc_mode: null remarks: null raw_status: null on_hold: summary: KYC on hold — action required value: status: "success" pan: "XYZPQ9876R" kyc_compliant: false kyc_status: "on_hold" kyc_mode: "normal" active_kra: "cvl" registered_on: "2026-04-24" last_updated_on: "2026-04-25" kras: cvl: status: "on_hold" registered_on: "2026-04-24" last_updated_on: "2026-04-25" kyc_mode: "normal" remarks: "APPLICANT PHOTO MISMATCH IN THE APPLICATION,FATCA NOT APPLICABLE" raw_status: "ON HOLD - New KYC" ndml: status: "not_available" registered_on: null last_updated_on: null kyc_mode: null remarks: null raw_status: null cams: status: "not_checked" registered_on: null last_updated_on: null kyc_mode: null remarks: null raw_status: null karvy: status: "not_checked" registered_on: null last_updated_on: null kyc_mode: null remarks: null raw_status: null kfin: status: "not_checked" registered_on: null last_updated_on: null kyc_mode: null remarks: null raw_status: null '400': description: Missing or invalid pan_no content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: status: "failed" msg: "pan_no is required" '401': $ref: '#/components/responses/Unauthorized' '503': description: KYC service unavailable content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: status: "failed" msg: "KYC service unavailable. Please try again later." '504': description: KYC service timed out (CVL portal slow/unresponsive) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: status: "failed" msg: "KYC service timed out. Please try again." /v1/kyc/digilocker/account-lookup: post: tags: - KYC summary: Check if a DigiLocker account exists description: | Check whether a mobile number or Aadhaar number is already registered with DigiLocker. This is a pre-consent lookup: it helps you decide whether to start the user in `signin` or `signup` flow. Provide exactly one of `aadhaar_number` or `mobile`. **Credits:** Free. operationId: digilockerAccountLookup security: - ApiKeyAuth: [] - AccessTokenAuth: [] requestBody: required: true content: application/json: schema: type: object properties: aadhaar_number: type: string description: 12-digit Aadhaar number. example: "123456789012" mobile: type: string description: 10-digit Indian mobile number. example: "9876543210" reference_id: type: string description: Optional caller-side request identifier. responses: '200': description: Account lookup result content: application/json: schema: type: object properties: status: type: string example: "success" registered: type: boolean description: Whether the identifier has a DigiLocker account. digilocker_id: type: [string, "null"] description: DigiLocker username if registered, null otherwise. suggested_user_flow: type: string enum: [signin, signup] reference_id: type: [string, "null"] next_step: type: object properties: action: type: string example: "start_session" message: type: string endpoint: type: string example: "POST /v1/kyc/digilocker/session" '400': description: Validation error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: identifier_conflict: value: status: "failed" msg: "Provide only one of aadhaar_number or mobile." code: "identifier_conflict" identifier_required: value: status: "failed" msg: "Provide either aadhaar_number or mobile." code: "identifier_required" '401': $ref: '#/components/responses/Unauthorized' /v1/kyc/digilocker/session: post: tags: - KYC summary: Start DigiLocker session description: | Start a DigiLocker session to fetch the investor's government-issued documents (Aadhaar, PAN, etc.) with their consent. **How it works:** 1. Call this endpoint — you get back an `authorization_url` and a `session_id`. The `session_id` is the single identifier you use for everything else. 2. Redirect the investor to `authorization_url` to log in to DigiLocker and grant consent. 3. After consent, the investor is redirected back to your `redirect_url` with query parameters appended (see the table below). 4. Use the `session_id` (path parameter) on the `POST /v1/kyc/digilocker/result/{session_id}` endpoint to read the consented data — identity, documents, and parsed document content. **Redirect parameters appended to your `redirect_url`:** | Outcome | Appended query parameters | | --- | --- | | Consent granted | `?success=true&id={session_id}&state={state}&documents=pan+driving_licence&has_verified_data=1` | | Partial consent (e.g. PAN only) | `?success=true&id={session_id}&state={state}&documents=pan` | | Consent denied / failed | `?success=false&id={session_id}&state={state}&error={error}` | `documents` lists the documents the investor agreed to share; `has_verified_data=1` appears only when verified identity is available. **Credits:** Free. Billing happens when documents/identity are returned. operationId: startDigilockerSession security: - ApiKeyAuth: [] - AccessTokenAuth: [] requestBody: required: true content: application/json: schema: type: object required: - consent - consent_purpose - redirect_url properties: consent: type: boolean description: | Explicit end-user consent. Must be `true` to proceed (regulatory requirement under DPDP / RBI). Captured for audit. example: true consent_purpose: type: string minLength: 20 description: | Specific purpose for which consent is taken (min 20 chars). Stored in the session and returned as a consent receipt. example: "KYC for loan account opening" redirect_url: type: string format: uri description: Where to redirect the user after consent. Must be a valid http/https URL. example: "https://yourapp.com/digilocker/callback" state: type: string description: Opaque value echoed back to your redirect_url unchanged. example: "investor_42" documents: type: array items: type: string enum: [aadhaar, pan, driving_licence, email, mobile] description: Identities to verify and return in the response. example: ["aadhaar", "pan"] user_flow: type: string enum: [signin, signup] description: 'Use "signin" for users with an existing DigiLocker account, "signup" for account-on-the-fly.' example: "signin" prefill_mobile: type: string description: Pre-verified mobile to skip the signup OTP step (only applies with user_flow=signup). example: "9999999999" responses: '200': description: Session started; redirect the user to authorization_url content: application/json: schema: type: object properties: status: type: string example: "success" session_id: type: string description: | The single identifier for this verification. Use it as the path parameter in `POST /v1/kyc/digilocker/result/{session_id}` to retrieve identity + documents. example: "3f9a2c1e8b7d4e6fa1c2d3e4f5a6b7c8" authorization_url: type: string description: DigiLocker consent URL to redirect the investor to. expires_in: type: integer description: Seconds the authorization_url is valid for. example: 600 '400': description: | Validation error. `code` indicates which: `consent_required` (consent not true), `consent_purpose_required` (missing/too short), or `redirect_url_invalid`. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: consent_required: value: status: "failed" msg: "Explicit user consent is required. Pass consent: true." code: "consent_required" consent_purpose_required: value: status: "failed" msg: "A specific consent_purpose is required (min 20 characters)." code: "consent_purpose_required" redirect_url_invalid: value: status: "failed" msg: "Invalid or missing redirect_url. Must be a valid http/https URL." code: "redirect_url_invalid" '401': $ref: '#/components/responses/Unauthorized' '503': description: DigiLocker is temporarily unavailable content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /v1/kyc/digilocker/result/{session_id}: post: tags: - KYC summary: DigiLocker aggregate (one-call convenience) description: | Return everything available for a DigiLocker session in a single call. Each section is independent: if one is unavailable (e.g. the investor didn't consent to it), it's reported under `errors` and the rest of the response still succeeds. - `identity` — verified identity, when requested via `documents` (free). - `documents` — list of the investor's documents (free). Set `include_documents: false` to skip. - `fetched` — for each document in `fetch_documents`, the parsed document data. **Credits:** 0.25 per call, regardless of how many documents are fetched or whether identity is returned. operationId: digilockerResult security: - ApiKeyAuth: [] - AccessTokenAuth: [] parameters: - name: session_id in: path required: true schema: type: string description: The `session_id` returned by `/v1/kyc/digilocker/session`. requestBody: required: false content: application/json: schema: type: object properties: fetch_documents: type: array items: type: string enum: [pan, aadhaar, driving_licence] description: | Documents to additionally fetch + parse from the locker. Default: [] (no document fetch, no fetch billing). example: ["pan", "driving_licence"] include_documents: type: boolean default: true description: Include the issued-documents list. responses: '200': description: Aggregate result (sections present per availability) content: application/json: schema: type: object properties: status: type: string example: "success" identity: oneOf: - $ref: '#/components/schemas/DigilockerVerifiedIdentity' - type: "null" consent: $ref: '#/components/schemas/DigilockerConsent' consented_documents: type: array description: Friendly names of documents the user consented to share. items: type: string example: ["pan", "driving_licence"] documents: type: array items: $ref: '#/components/schemas/DigilockerDocument' fetched: type: object description: | Parsed documents keyed by the `fetch_documents` value (pan/aadhaar/driving_licence). Value is null if the document was requested but unavailable. Each parsed object also includes a `signature` block. properties: pan: oneOf: - $ref: '#/components/schemas/DigilockerPan' - type: "null" aadhaar: oneOf: - $ref: '#/components/schemas/DigilockerAadhaar' - type: "null" driving_licence: oneOf: - $ref: '#/components/schemas/DigilockerDrivingLicence' - type: "null" errors: type: object description: Per-section error codes (omitted when there are none). additionalProperties: type: string '401': $ref: '#/components/responses/Unauthorized' /v1/agent-auth/token/{token}/approve: post: tags: - Agent Auth summary: Approve Agent Authorization description: | Called by the frontend after the user signs in. Verifies the identity token, creates or looks up the user's API key, and stores it against the token for the agent to poll via `GET /v1/agent-auth/token/{token}`. **This endpoint is called by the browser, not by agents directly.** operationId: agentAuthApprove parameters: - name: token in: path required: true description: The token the agent generated and included in the approval URL. Must be 16-128 characters. schema: type: string minLength: 16 maxLength: 128 example: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2" requestBody: required: true content: application/json: schema: type: object required: - id_token properties: id_token: type: string description: Identity token obtained after the user signs in on the approval page client_name: type: string description: Name of the agent/application (stored in customer metadata for audit) example: "Claude Code" responses: '200': description: Authorization approved. The API key is now available for the agent to poll. content: application/json: schema: type: object properties: status: type: string example: "approved" email: type: string example: "user@example.com" '400': description: Invalid token length or invalid request content: application/json: schema: type: object properties: detail: type: string example: "Token must be 16-128 characters" '401': description: Invalid or expired identity token content: application/json: schema: type: object properties: detail: type: string example: "Invalid identity token" '403': description: Unsupported sign-in method content: application/json: schema: type: object properties: detail: type: string example: "Unsupported sign-in method" '409': description: Token was already approved (only the first approval is accepted) content: application/json: schema: type: object properties: detail: type: string example: "Token already approved or storage unavailable" /v1/agent-auth/token/{token}: get: tags: - Agent Auth summary: Poll for Agent Authorization Result description: | Agent polls this endpoint to check if the user has approved the authorization. Returns `{"status": "pending"}` while waiting for user approval. Returns `{"status": "approved", "api_key": "sk_...", "email": "..."}` once approved. **One-shot delivery:** the API key is returned exactly once. After delivery, the token is deleted and subsequent polls return `{"status": "pending"}`. **Recommended polling interval:** 5 seconds. Token expires after 10 minutes. operationId: agentAuthPoll parameters: - name: token in: path required: true description: The token the agent generated locally and included in the approval URL. schema: type: string minLength: 16 maxLength: 128 example: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2" responses: '200': description: Current authorization status content: application/json: schema: oneOf: - type: object title: Pending properties: status: type: string enum: ["pending"] example: status: "pending" - type: object title: Approved properties: status: type: string enum: ["approved"] api_key: type: string description: The issued API key (sk_ prefix) email: type: string description: Email of the user who approved example: status: "approved" api_key: "sk_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" email: "user@example.com" '400': description: Invalid token length content: application/json: schema: type: object properties: detail: type: string example: "Token must be 16-128 characters" # ============================================ # Core Endpoints (portfolio-parser server) # ============================================ /v4/cdsl/fetch: post: tags: - CAS Fetch summary: CDSL CAS Fetch - Step 1 (Request OTP) description: | **Step 1 of 2**: Request OTP for CDSL CAS fetch. This endpoint: 1. Solves reCAPTCHA automatically (~15-20 seconds) 2. Submits login credentials to CDSL portal 3. Triggers OTP to user's registered mobile number After user receives OTP, call `/v4/cdsl/fetch/{session_id}/verify` to complete. operationId: cdslFetchRequestOTP security: - ApiKeyAuth: [] requestBody: content: application/json: schema: type: object required: - pan - bo_id - dob properties: pan: type: string description: PAN number example: "ABCDE1234F" bo_id: type: string description: CDSL BO ID (16 digits) example: "1234567890123456" dob: type: string description: Date of birth (YYYY-MM-DD) example: "1990-01-15" responses: '200': description: OTP sent to registered mobile content: application/json: schema: type: object properties: status: type: string example: "success" session_id: type: string description: Session ID for verify step example: "550e8400-e29b-41d4-a716-446655440000" msg: type: string example: "OTP sent to registered mobile" '400': $ref: '#/components/responses/ParseBadRequest' '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalError' /v4/cdsl/fetch/{session_id}/verify: post: tags: - CAS Fetch summary: CDSL CAS Fetch - Step 2 (Verify OTP & Get Files) description: | **Step 2 of 2**: Verify OTP and retrieve CDSL CAS files. After successful verification, CAS PDFs are fetched from CDSL portal, uploaded to cloud storage, and returned as direct download URLs. operationId: cdslFetchVerifyOTP security: - ApiKeyAuth: [] parameters: - name: session_id in: path required: true schema: type: string description: Session ID from Step 1 requestBody: content: application/json: schema: type: object required: - otp properties: otp: type: string description: OTP received on mobile example: "123456" num_periods: type: integer description: Number of monthly statements to fetch (default 6) default: 6 example: 6 responses: '200': description: CAS files fetched successfully content: application/json: schema: type: object properties: status: type: string example: "success" msg: type: string example: "Fetched 6 CAS files" files: type: array items: type: object properties: filename: type: string example: "CDSL_CAS_1234567890123456_NOV2025.pdf" url: type: string description: Direct download URL (cloud storage) example: "https://cdn.casparser.in/cdsl-cas/session-id/CDSL_CAS_1234567890123456_NOV2025.pdf" '400': $ref: '#/components/responses/ParseBadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': description: Session not found or expired '500': $ref: '#/components/responses/InternalError' /v1/token: post: tags: - Portfolio Connect summary: Generate Access Token description: | Generate a short-lived access token from your API key. **Use this endpoint from your backend** to create tokens that can be safely passed to frontend/SDK. **Legacy path:** `/v1/access-token` (still supported) Access tokens: - Are prefixed with `at_` for easy identification - Valid for up to 60 minutes - Can be used in place of API keys on all v4 endpoints - Cannot be used to generate other access tokens operationId: generateAccessToken security: - ApiKeyAuth: [] requestBody: content: application/json: schema: type: object properties: expiry_minutes: type: integer description: Token validity in minutes (max 60) default: 60 maximum: 60 minimum: 1 example: 60 responses: '200': description: Access token generated successfully content: application/json: schema: type: object properties: access_token: type: string description: The at_ prefixed access token example: "at_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." token_type: type: string description: Always "api_key" - token is a drop-in replacement for x-api-key header example: "api_key" expires_in: type: integer description: Token validity in seconds example: 3600 '400': description: Bad request (e.g., trying to create token from access token) content: application/json: schema: type: object properties: detail: type: string example: "Cannot create access token from access token" '401': $ref: '#/components/responses/Unauthorized' /v1/token/verify: post: tags: - Portfolio Connect summary: Verify Access Token description: | Verify an access token and check if it's still valid. Useful for debugging token issues. operationId: verifyAccessToken security: - ApiKeyAuth: [] responses: '200': description: Token verification result content: application/json: schema: type: object properties: valid: type: boolean description: Whether the token is valid example: true masked_api_key: type: string description: Masked API key (only shown if valid) example: "abc1****ef23" error: type: string description: Error message (only shown if invalid) example: "Token has expired" '401': $ref: '#/components/responses/Unauthorized' # ============================================ # Agent Auth Endpoints # ============================================ /v1/credits: post: tags: - Authorization summary: Check API Credits description: | Check your remaining API credits and usage for the current billing period. Returns: - Number of API calls used and remaining credits - Credit limit and reset date - List of enabled features for your plan Credits reset at the start of each billing period. operationId: checkCredits security: - ApiKeyAuth: [] responses: '200': description: Credits information retrieved successfully content: application/json: schema: type: object properties: used: type: number description: Number of credits used this billing period example: 15.0 remaining: type: [number, "null"] description: Remaining credits (null if unlimited) example: 35.0 limit: type: integer description: Total credit limit for billing period example: 50 is_unlimited: type: boolean description: Whether the account has unlimited credits example: false resets_at: type: [string, "null"] format: date-time description: When credits reset (ISO 8601) example: "2026-02-15T00:00:00Z" enabled_features: type: array items: type: string description: List of API features enabled for your plan example: ["cams_kfintech_cas_parser", "cdsl_cas_parser", "nsdl_cas_parser"] '400': description: Bad request (e.g., using access token instead of API key) content: application/json: schema: type: object properties: detail: type: string example: "Access tokens cannot be used for credits API. Use your API key instead." '401': $ref: '#/components/responses/Unauthorized' /v1/usage: post: tags: - Authorization summary: Get API Usage Logs description: | Retrieve detailed API usage logs for your account. Returns a list of API calls with timestamps, features used, status codes, and credits consumed. Useful for monitoring usage patterns and debugging. **Legacy path:** `/logs` (still supported) operationId: getUsageLogs security: - ApiKeyAuth: [] requestBody: content: application/json: schema: type: object properties: start_time: type: string format: date-time description: Start time filter (ISO 8601). Defaults to 30 days ago. example: "2026-01-01T00:00:00Z" end_time: type: string format: date-time description: End time filter (ISO 8601). Defaults to now. example: "2026-01-31T23:59:59Z" limit: type: integer minimum: 1 maximum: 500 default: 100 description: Maximum number of logs to return responses: '200': description: Usage logs retrieved successfully content: application/json: schema: type: object properties: status: type: string example: "success" logs: type: array items: type: object properties: request_id: type: string description: Unique request identifier example: "req_2xYz7KpL8mN3Ab" feature: type: string description: API feature used example: "cdsl_cas_parser" path: type: string description: API endpoint path example: "/v4/cdsl/parse" status_code: type: integer description: HTTP response status code example: 200 credits: type: number description: Credits consumed for this request example: 1.0 timestamp: type: string format: date-time description: When the request was made example: "2026-01-15T14:30:00Z" count: type: integer description: Number of logs returned example: 25 '400': description: Bad request (e.g., using access token) content: application/json: schema: type: object properties: detail: type: string example: "Access tokens cannot be used for logs API. Use your API key instead." '401': $ref: '#/components/responses/Unauthorized' /v1/usage/summary: post: tags: - Authorization summary: Get Usage Summary description: | Get aggregated usage statistics grouped by feature. Useful for understanding which API features are being used most and tracking usage trends. **Legacy path:** `/logs/summary` (still supported) operationId: getUsageSummary security: - ApiKeyAuth: [] requestBody: content: application/json: schema: type: object properties: start_time: type: string format: date-time description: Start time filter (ISO 8601). Defaults to start of current month. end_time: type: string format: date-time description: End time filter (ISO 8601). Defaults to now. responses: '200': description: Usage summary retrieved successfully content: application/json: schema: type: object properties: status: type: string example: "success" summary: type: object properties: total_credits: type: number description: Total credits consumed in the period example: 45.5 total_requests: type: integer description: Total API requests made in the period example: 42 by_feature: type: array description: Usage breakdown by feature items: type: object properties: feature: type: string description: API feature name example: "cdsl_cas_parser" credits: type: number description: Credits consumed by this feature example: 15.0 requests: type: integer description: Number of requests for this feature example: 15 '400': description: Bad request (e.g., using access token) content: application/json: schema: type: object properties: detail: type: string example: "Access tokens cannot be used for logs API. Use your API key instead." '401': $ref: '#/components/responses/Unauthorized' # ============================================ # Portfolio Connect SDK Endpoints # ============================================ # ============================================ # Account & Authorization Endpoints # New paths: /v1/credits, /v1/usage, /v1/usage/summary, /v1/token, /v1/token/verify # Legacy paths still supported: /credits, /logs, /logs/summary, /v1/access-token, /v1/verify-token # ============================================ components: securitySchemes: ApiKeyAuth: type: apiKey in: header name: x-api-key description: | Your API key for authentication. Use `sandbox-with-json-responses` as Sandbox key. headers: X-Request-ID: description: | Unique request identifier for tracking and support. Format: `req_` (e.g., `req_2xYz7KpL8mN3Ab`) Use this ID when: - Contacting support about a specific request - Correlating API calls with usage logs - Debugging issues in your application You can also provide your own `X-Request-ID` header in the request (must start with `req_`) to use your own tracking ID. schema: type: string pattern: '^req_[A-Za-z0-9]+$' example: "req_2xYz7KpL8mN3Ab" requestBodies: CASParseRequest: required: true content: multipart/form-data: schema: type: object description: Provide either `pdf_file` OR `pdf_url` (one is required) properties: pdf_file: type: string format: binary description: CAS PDF file to parse (required if pdf_url not provided) pdf_url: type: string format: uri description: URL to the CAS PDF file (required if pdf_file not provided) password: type: string description: Password for the PDF file (if required) application/json: schema: type: object description: Provide either `pdf_file` OR `pdf_url` (one is required) properties: pdf_file: type: string format: base64 description: Base64 encoded CAS PDF file (required if pdf_url not provided) pdf_url: type: string format: uri description: URL to the CAS PDF file (required if pdf_file not provided) password: type: string description: Password for the PDF file (if required) ContractNoteParseRequest: required: true content: multipart/form-data: schema: type: object properties: pdf_file: type: string format: binary description: Contract note PDF file to parse password: type: string description: Password for the PDF file (usually PAN number) broker_type: type: string description: Optional broker type override enum: [zerodha, groww, upstox, icici] required: - pdf_file - password application/json: schema: type: object properties: pdf_file: type: string format: base64 description: Base64 encoded contract note PDF file example: "JVBERi0xLjQKMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwo..." pdf_url: type: string format: uri description: URL to the contract note PDF file example: "https://example.com/contract_note.pdf" password: type: string description: Password for the PDF file (usually PAN number for Zerodha) example: "FAXAK2545F" broker_type: type: string description: Optional broker type override. If not provided, system will auto-detect. enum: [zerodha, groww, upstox, icici] example: "zerodha" oneOf: - required: [pdf_file, password] - required: [pdf_url, password] responses: ParseSuccess: description: Successful operation content: application/json: schema: $ref: '#/components/schemas/UnifiedResponse' ParseBadRequest: description: Bad request (Invalid PDF file, missing parameters, or invalid password) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' Unauthorized: description: Unauthorized. This can happen if the `x-api-key` header is missing. content: application/json: schema: $ref: '#/components/schemas/AuthErrorResponse' example: status: "failed" msg: "Authentication failed: API key is missing. Please provide a valid API key in the x-api-key header." Forbidden: description: Forbidden. This can happen if the API key is invalid or the quota has been exceeded. content: application/json: schema: $ref: '#/components/schemas/AuthErrorResponse' example: status: "failed" msg: "Authentication failed: API quota exceeded or invalid API key. Please check your API key or quota limits." InternalError: description: Internal server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' schemas: AuthErrorResponse: type: object required: - status - msg properties: status: type: string description: The status of the error. enum: [failed] example: failed msg: type: string description: A descriptive message explaining the error. example: "Authentication failed: API key is missing." ErrorResponse: type: object required: - status - msg properties: status: type: string description: The status of the error. enum: [failed] example: failed msg: type: string description: A descriptive message explaining the error. example: "Invalid PDF file or password." UnifiedResponse: type: object properties: meta: type: object properties: cas_type: type: string enum: [NSDL, CDSL, CAMS_KFINTECH] description: Type of CAS detected and processed statement_period: type: object properties: from: type: string format: date description: Start date of the statement period to: type: string format: date description: End date of the statement period generated_at: type: string format: date-time description: Timestamp when the response was generated investor: type: object properties: name: type: string description: Name of the investor pan: type: string description: PAN (Permanent Account Number) of the investor email: type: string format: email description: Email address of the investor address: type: string description: Address of the investor pincode: type: string description: Postal code of the investor's address mobile: type: string description: Mobile number of the investor cas_id: type: string description: CAS ID of the investor (only for NSDL and CDSL) summary: type: object properties: total_value: type: number format: float description: Total portfolio value across all accounts accounts: type: object properties: demat: type: object properties: count: type: integer description: Number of demat accounts total_value: type: number format: float description: Total value of demat accounts mutual_funds: type: object properties: count: type: integer description: Number of mutual fund folios total_value: type: number format: float description: Total value of mutual funds insurance: type: object properties: count: type: integer description: Number of insurance policies total_value: type: number format: float description: Total value of insurance policies nps: type: object properties: count: type: integer description: Number of NPS accounts total_value: type: number format: float description: Total value of NPS accounts demat_accounts: type: array items: $ref: '#/components/schemas/DematAccount' mutual_funds: type: array items: $ref: '#/components/schemas/MutualFundFolio' insurance: type: object properties: life_insurance_policies: type: array items: $ref: '#/components/schemas/LifeInsurancePolicy' nps: type: array items: $ref: '#/components/schemas/NPSAccount' description: List of NPS accounts DematAccount: type: object properties: demat_type: type: string enum: [NSDL, CDSL] description: Type of demat account dp_id: type: string description: Depository Participant ID dp_name: type: string description: Depository Participant name client_id: type: string description: Client ID bo_id: type: string description: Beneficiary Owner ID (primarily for CDSL) value: type: number format: float description: Total value of the demat account linked_holders: type: array items: $ref: '#/components/schemas/LinkedHolder' description: List of account holders linked to this demat account holdings: type: object properties: equities: type: array items: $ref: '#/components/schemas/Equity' corporate_bonds: type: array items: $ref: '#/components/schemas/CorporateBond' government_securities: type: array items: $ref: '#/components/schemas/GovernmentSecurity' aifs: type: array items: $ref: '#/components/schemas/AIF' demat_mutual_funds: type: array items: $ref: '#/components/schemas/DematMutualFund' additional_info: type: object description: Additional information specific to the demat account type properties: bo_status: type: string description: Beneficiary Owner status (CDSL) bo_sub_status: type: string description: Beneficiary Owner sub-status (CDSL) bo_type: type: string description: Beneficiary Owner type (CDSL) bsda: type: string description: Basic Services Demat Account status (CDSL) email: type: string format: email description: Email associated with the demat account (CDSL) status: type: string description: Account status (CDSL) nominee: type: string description: Nominee details (CDSL) linked_pans: type: array items: type: string description: List of linked PAN numbers (NSDL) Equity: type: object properties: isin: type: string description: ISIN code of the equity name: type: string description: Name of the equity units: type: number format: float description: Number of units held value: type: number format: float description: Current market value of the holding transactions: type: array items: $ref: '#/components/schemas/Transaction' description: List of transactions for this holding (beta) additional_info: type: object description: Additional information specific to the equity properties: open_units: type: [number, "null"] format: float description: Opening balance units for the statement period (beta) close_units: type: [number, "null"] format: float description: Closing balance units for the statement period (beta) CorporateBond: type: object properties: isin: type: string description: ISIN code of the corporate bond name: type: string description: Name of the corporate bond units: type: number format: float description: Number of units held value: type: number format: float description: Current market value of the holding transactions: type: array items: $ref: '#/components/schemas/Transaction' description: List of transactions for this holding (beta) additional_info: type: object description: Additional information specific to the corporate bond properties: open_units: type: [number, "null"] format: float description: Opening balance units for the statement period (beta) close_units: type: [number, "null"] format: float description: Closing balance units for the statement period (beta) GovernmentSecurity: type: object properties: isin: type: string description: ISIN code of the government security name: type: string description: Name of the government security units: type: number format: float description: Number of units held value: type: number format: float description: Current market value of the holding transactions: type: array items: $ref: '#/components/schemas/Transaction' description: List of transactions for this holding (beta) additional_info: type: object description: Additional information specific to the government security properties: open_units: type: [number, "null"] format: float description: Opening balance units for the statement period (beta) close_units: type: [number, "null"] format: float description: Closing balance units for the statement period (beta) AIF: type: object properties: isin: type: string description: ISIN code of the AIF name: type: string description: Name of the AIF units: type: number format: float description: Number of units held value: type: number format: float description: Current market value of the holding transactions: type: array items: $ref: '#/components/schemas/Transaction' description: List of transactions for this holding (beta) additional_info: type: object description: Additional information specific to the AIF properties: open_units: type: [number, "null"] format: float description: Opening balance units for the statement period (beta) close_units: type: [number, "null"] format: float description: Closing balance units for the statement period (beta) DematMutualFund: type: object properties: isin: type: string description: ISIN code of the mutual fund name: type: string description: Name of the mutual fund units: type: number format: float description: Number of units held value: type: number format: float description: Current market value of the holding transactions: type: array items: $ref: '#/components/schemas/Transaction' description: List of transactions for this holding (beta) additional_info: type: object description: Additional information specific to the mutual fund properties: open_units: type: [number, "null"] format: float description: Opening balance units for the statement period (beta) close_units: type: [number, "null"] format: float description: Closing balance units for the statement period (beta) MutualFundFolio: type: object properties: folio_number: type: string description: Folio number amc: type: string description: Asset Management Company name registrar: type: string description: Registrar and Transfer Agent name value: type: number format: float description: Total value of the folio linked_holders: type: array items: $ref: '#/components/schemas/LinkedHolder' description: List of account holders linked to this mutual fund folio schemes: type: array items: $ref: '#/components/schemas/MutualFundScheme' additional_info: type: object description: Additional folio information properties: pan: type: string description: PAN associated with the folio pankyc: type: string description: PAN KYC status kyc: type: string description: KYC status of the folio MutualFundScheme: type: object properties: isin: type: string description: ISIN code of the scheme name: type: string description: Scheme name type: type: string enum: [Equity, Debt, Hybrid, Other] description: Type of mutual fund scheme units: type: number format: float description: Number of units held nav: type: number format: float description: Net Asset Value per unit value: type: number format: float description: Current market value of the holding cost: type: number format: float description: Cost of investment gain: type: object properties: absolute: type: number format: float description: Absolute gain or loss percentage: type: number format: float description: Percentage gain or loss transactions: type: array items: $ref: '#/components/schemas/Transaction' nominees: type: array items: type: string description: List of nominees additional_info: type: object description: Additional information specific to the scheme properties: advisor: type: string description: Financial advisor name (CAMS/KFintech) rta_code: type: string description: RTA code for the scheme (CAMS/KFintech) amfi: type: string description: AMFI code for the scheme (CAMS/KFintech) is_demat: type: [boolean, "null"] description: Whether the scheme is held in demat form (CAMS/KFintech). true = Demat, false = Non-Demat, null = not specified. open_units: type: [number, "null"] format: float description: Opening balance units for the statement period close_units: type: [number, "null"] format: float description: Closing balance units for the statement period Transaction: type: object description: Unified transaction schema for all holding types (MF folios, equities, bonds, etc.) properties: date: type: string format: date description: Transaction date (YYYY-MM-DD) description: type: string description: Transaction description/particulars type: type: string description: Transaction type. Possible values are PURCHASE, PURCHASE_SIP, REDEMPTION, SWITCH_IN, SWITCH_IN_MERGER, SWITCH_OUT, SWITCH_OUT_MERGER, DIVIDEND_PAYOUT, DIVIDEND_REINVEST, SEGREGATION, STAMP_DUTY_TAX, TDS_TAX, STT_TAX, MISC, REVERSAL, UNKNOWN. enum: [PURCHASE, PURCHASE_SIP, REDEMPTION, SWITCH_IN, SWITCH_IN_MERGER, SWITCH_OUT, SWITCH_OUT_MERGER, DIVIDEND_PAYOUT, DIVIDEND_REINVEST, SEGREGATION, STAMP_DUTY_TAX, TDS_TAX, STT_TAX, MISC, REVERSAL, UNKNOWN] amount: type: [number, "null"] format: float description: Transaction amount in currency (computed from units × price/NAV) units: type: number format: float description: Number of units involved in transaction nav: type: [number, "null"] format: float description: NAV/price per unit on transaction date balance: type: number format: float description: Balance units after transaction dividend_rate: type: [number, "null"] format: float description: Dividend rate (for DIVIDEND_PAYOUT transactions) additional_info: type: object description: Additional transaction-specific fields that vary by source properties: credit: type: number format: float description: Units credited (demat transactions) debit: type: number format: float description: Units debited (demat transactions) stamp_duty: type: number format: float description: Stamp duty charged order_no: type: string description: Order/transaction reference number (demat transactions) price: type: number format: float description: Price per unit (NSDL/CDSL MF transactions) income_distribution: type: number format: float description: Income distribution amount (CDSL MF transactions) capital_withdrawal: type: number format: float description: Capital withdrawal amount (CDSL MF transactions) LifeInsurancePolicy: type: object properties: policy_number: type: string description: Insurance policy number provider: type: string description: Insurance company name policy_name: type: string description: Name of the insurance policy life_assured: type: string description: Name of the life assured status: type: string description: Status of the policy (e.g., Active, Lapsed) sum_assured: type: number format: float description: Sum assured amount premium_amount: type: number format: float description: Premium amount premium_frequency: type: string description: Frequency of premium payment (e.g., Annual, Monthly) additional_info: type: object description: Additional information specific to the policy NPSAccount: type: object properties: pran: type: string description: Permanent Retirement Account Number (PRAN) cra: type: string description: Central Record Keeping Agency name value: type: number format: float description: Total value of the NPS account funds: type: array items: $ref: '#/components/schemas/NPSFund' linked_holders: type: array items: $ref: '#/components/schemas/LinkedHolder' description: List of account holders linked to this NPS account additional_info: type: object description: Additional information specific to the NPS account NPSFund: type: object properties: name: type: string description: Name of the NPS fund units: type: number format: float description: Number of units held nav: type: number format: float description: Net Asset Value per unit value: type: number format: float description: Current market value of the holding cost: type: number format: float description: Cost of investment additional_info: type: object description: Additional information specific to the NPS fund properties: tier: type: number enum: [1, 2, null] description: NPS tier (Tier I or Tier II) manager: type: string description: Fund manager name LinkedHolder: type: object properties: name: type: string description: Name of the account holder pan: type: string description: PAN of the account holder EmailCASFile: type: object description: A CAS file found in the user's email inbox properties: message_id: type: string description: Unique identifier for the email message (use for subsequent API calls) example: "18d4a2b3c4d5e6f7" filename: type: string description: Standardized filename (provider_YYYYMMDD_uniqueid.pdf) example: "cdsl_20250115_a1b2c3d4.pdf" original_filename: type: string description: Original attachment filename from the email example: "CDSL_CAS_Statement.pdf" message_date: type: string format: date description: Date the email was received example: "2025-01-15" cas_type: type: string enum: [cdsl, nsdl, cams, kfintech] description: Detected CAS provider based on sender email example: "cdsl" sender_email: type: string format: email description: Email address of the CAS authority (CDSL, NSDL, CAMS, or KFintech) who originally sent this statement example: "eCAS@cdslstatement.com" size: type: integer description: File size in bytes example: 245000 url: type: string format: uri description: Direct download URL (presigned, expires based on expires_in) example: "https://cdn.casparser.in/email-cas/user123/cdsl_20250115_a1b2c3d4.pdf" expires_in: type: integer description: | URL expiration time in seconds. Defaults vary by source: - Gmail Inbox Import: 86400 (24h) - Inbound Email with `callback_url` set: 172800 (48h) - Inbound Email without `callback_url`: aligned with the session TTL (~30 min) example: 86400 ReceivedEmailCASFile: description: | A CAS file that has arrived at an inbound email address. Matches `EmailCASFile` plus a `received_at` timestamp used as a monotonic cursor for polling. allOf: - $ref: '#/components/schemas/EmailCASFile' - type: object properties: received_at: type: string format: date-time description: | ISO 8601 timestamp (microsecond precision) when the file was persisted. Use as the `since` cursor on subsequent polls. example: "2025-02-21T10:45:12.000123+00:00" ContractNoteResponse: type: object properties: contract_note_info: type: object properties: contract_note_number: type: string description: Contract note reference number example: "CNT-25/26-73436720" trade_date: type: string format: date description: Date when trades were executed example: "2025-08-05" settlement_number: type: string description: Settlement reference number example: "2025149" settlement_date: type: string format: date description: Settlement date for the trades example: "2025-08-06" broker_info: type: object properties: broker_type: type: string description: Auto-detected or specified broker type enum: [zerodha, groww, upstox, icici, unknown] example: "zerodha" name: type: string description: Broker company name example: "Zerodha Broking Limited" sebi_registration: type: string description: SEBI registration number of the broker example: "INZ000031633" client_info: type: object properties: name: type: string description: Client name example: "VIRENDER KUMAR" pan: type: string description: Client PAN number example: "FAXAK2545F" ucc: type: string description: Unique Client Code example: "YS3654" place_of_supply: type: string description: GST place of supply example: "DELHI" gst_state_code: type: string description: GST state code example: "7" address: type: string description: Client address equity_transactions: type: array description: Summary of equity transactions grouped by security items: type: object properties: isin: type: string description: ISIN code of the security example: "INE172A01027" security_name: type: string description: Name of the security example: "CASTROLIND" security_symbol: type: string description: Trading symbol example: "CASTROLIND" buy_quantity: type: number format: float description: Total quantity purchased buy_wap: type: number format: float description: Weighted Average Price for buy transactions buy_total_value: type: number format: float description: Total value of buy transactions sell_quantity: type: number format: float description: Total quantity sold sell_wap: type: number format: float description: Weighted Average Price for sell transactions sell_total_value: type: number format: float description: Total value of sell transactions net_obligation: type: number format: float description: Net amount payable/receivable for this security derivatives_transactions: type: array description: Summary of derivatives transactions items: type: object properties: contract_description: type: string description: Derivatives contract description example: "NIFTY24802410DPE" buy_sell_bf_cf: type: string description: Transaction type (Buy/Sell/Bring Forward/Carry Forward) example: "B" quantity: type: number format: float description: Quantity traded wap_per_unit: type: number format: float description: Weighted Average Price per unit brokerage_per_unit: type: number format: float description: Brokerage charged per unit closing_rate_per_unit: type: number format: float description: Closing rate per unit net_total: type: number format: float description: Net total amount detailed_trades: type: array description: Detailed breakdown of all individual trades items: type: object properties: order_number: type: string description: Order reference number example: "1000000042939390" order_time: type: string description: Time when order was placed example: "13:13:13" trade_number: type: string description: Trade reference number example: "4006567" trade_time: type: string description: Time when trade was executed example: "13:13:13" security_description: type: string description: Security name with exchange and ISIN example: "CASTROLIND-EQ/INE172A01027" buy_sell: type: string description: Transaction type (B for Buy, S for Sell) example: "B" exchange: type: string description: Exchange name example: "NSE" quantity: type: number format: float description: Quantity traded brokerage: type: number format: float description: Brokerage charged for this trade net_rate_per_unit: type: number format: float description: Net rate per unit closing_rate_per_unit: type: number format: float description: Closing rate per unit net_total: type: number format: float description: Net total for this trade remarks: type: string description: Additional remarks or notes charges_summary: type: object description: Breakdown of various charges and fees properties: pay_in_pay_out_obligation: type: number format: float description: Net pay-in/pay-out obligation taxable_value_brokerage: type: number format: float description: Taxable brokerage amount exchange_transaction_charges: type: number format: float description: Exchange transaction charges cgst: type: number format: float description: Central GST amount sgst: type: number format: float description: State GST amount igst: type: number format: float description: Integrated GST amount securities_transaction_tax: type: number format: float description: Securities Transaction Tax sebi_turnover_fees: type: number format: float description: SEBI turnover fees stamp_duty: type: number format: float description: Stamp duty charges net_amount_receivable_payable: type: number format: float description: Final net amount receivable or payable # ============================================ # Inbound Email Schemas # ============================================ InboundEmail: type: object description: An inbound email address for receiving forwarded CAS emails properties: inbound_email_id: type: string description: Unique inbound email identifier example: "ie_a1b2c3d4e5f6" email: type: string format: email description: The inbound email address to forward CAS statements to example: "ie_a1b2c3d4e5f6@import.casparser.in" reference: type: [string, "null"] description: Your internal reference identifier example: "user_12345" callback_url: type: string format: uri description: | Webhook URL for email notifications. If set, we POST each parsed email here. If omitted, files are only retrievable via `GET /v4/inbound-email/{id}/files`. example: "https://api.yourapp.com/webhooks/cas-email" allowed_sources: type: array items: type: string enum: [cdsl, nsdl, cams, kfintech] description: Accepted CAS providers (empty = all) example: ["cdsl", "nsdl"] status: type: string enum: [active, paused] description: Current inbound email lifecycle status example: "active" metadata: type: object additionalProperties: type: string description: Custom key-value metadata example: plan: "premium" created_at: type: string format: date-time description: When the inbound email was created example: "2025-02-21T10:30:00Z" updated_at: type: string format: date-time description: When the inbound email was last updated example: "2025-02-21T10:30:00Z" KycPanStatusResponse: type: object description: Normalized KYC status response for a PAN number properties: status: type: string enum: [success] example: "success" pan: type: string description: The PAN number that was queried (uppercase) example: "ABCDE1234F" kyc_compliant: type: boolean description: | `true` if the investor has a `validated` or `registered` KYC record. Use this as the primary onboarding gate signal. example: true kyc_status: type: string description: | Normalized KYC status from the active KRA. Possible values: - `validated` — KYC complete, investor can onboard and transact freely - `registered` — KYC registered; some restrictions may apply depending on your platform's onboarding policy - `under_process` — Recently submitted KYC, still being processed by KRA - `on_hold` — KYC on hold due to discrepancy; check `remarks` on the KRA object - `rejected` — KYC rejected; investor must submit fresh KYC - `legacy` — Old/incomplete record; re-KYC recommended - `not_available` — No KYC record found at any KRA - `unknown` — Portal returned an unrecognized string; `raw_status` preserved enum: - validated - registered - under_process - on_hold - rejected - legacy - not_available - unknown example: "validated" kyc_mode: type: [string, "null"] description: | How the KYC was completed. Possible values: - `normal` — Physical/paper KYC - `digilocker` — DigiLocker-backed digital KYC - `ekyc` — Aadhaar eKYC (OTP-based) - `paper` — Offline paper submission - `unknown` — Mode string not recognized enum: - normal - digilocker - ekyc - paper - unknown example: "digilocker" active_kra: type: [string, "null"] description: Short key of the KRA where the active KYC record was found enum: - cvl - ndml - cams - karvy - kfin example: "cvl" registered_on: type: [string, "null"] format: date description: Date the KYC was first registered at the active KRA (ISO 8601) example: "2020-07-02" last_updated_on: type: [string, "null"] format: date description: Date the KYC record was last updated at the active KRA (ISO 8601) example: "2024-09-19" kras: type: object description: Per-KRA status breakdown. All five KRA keys are always present. required: [cvl, ndml, cams, karvy, kfin] properties: cvl: $ref: '#/components/schemas/KycKraStatus' ndml: $ref: '#/components/schemas/KycKraStatus' cams: $ref: '#/components/schemas/KycKraStatus' karvy: $ref: '#/components/schemas/KycKraStatus' kfin: $ref: '#/components/schemas/KycKraStatus' KycKraStatus: type: object description: | KYC status at a single KRA. All six keys are always present (never omitted). For `not_checked` and `not_available` KRAs, date/mode/remarks fields are `null`. required: [status, registered_on, last_updated_on, kyc_mode, remarks, raw_status] properties: status: type: string description: Normalized status for this KRA enum: - validated - registered - under_process - on_hold - rejected - legacy - not_available - not_checked - unknown example: "validated" registered_on: type: [string, "null"] format: date description: Date KYC was first registered at this KRA example: "2020-07-02" last_updated_on: type: [string, "null"] format: date description: Date KYC was last updated at this KRA example: "2024-09-19" kyc_mode: type: [string, "null"] description: KYC completion mode at this KRA enum: - normal - digilocker - ekyc - paper - unknown example: "digilocker" remarks: type: [string, "null"] description: | Remarks from the KRA. Only actionable when `status` is `on_hold` (e.g. `"APPLICANT PHOTO MISMATCH"`). For `validated` KRAs, remarks like `"FATCA NOT APPLICABLE"` can be safely ignored. Multiple remarks are comma-separated. example: "APPLICANT PHOTO MISMATCH IN THE APPLICATION,FATCA NOT APPLICABLE" raw_status: type: [string, "null"] description: | The original status string from the CVL portal. `null` for exact-matched statuses. Non-null when a fuzzy match or unknown string was encountered — preserve this for debugging and to help us expand coverage. example: "ON HOLD - New KYC" DigilockerAadhaar: type: object description: | e-Aadhaar (Aadhaar Paperless Offline eKYC) data parsed from DigiLocker XML. Any field may be null if not present in the source XML. properties: name: type: [string, "null"] description: Full name as on Aadhaar example: "Anjali Sharma" dob: type: [string, "null"] description: Date of birth (as returned by DigiLocker, typically DD-MM-YYYY) example: "16-05-1990" gender: type: [string, "null"] description: Gender (M, F, or T) example: "F" care_of: type: [string, "null"] description: Care-of / guardian field example: "S/O Rajesh Sharma" address: type: [string, "null"] description: Full address assembled from the Aadhaar address fields example: "12, MG Road, Indiranagar, Bengaluru, Karnataka, 560038, India" photo_base64: type: [string, "null"] description: Base64-encoded photograph from the Aadhaar XML masked_aadhaar: type: [string, "null"] description: Masked Aadhaar number (only last 4 digits visible) example: "XXXXXXXX1234" session_id: type: [string, "null"] description: DigiLocker/UIDAI reference id for this eKYC document DigilockerPan: type: object description: | PAN certificate data parsed from DigiLocker XML (returned as `parsed` when `parse=pan`). Any field may be null if absent in the source XML. properties: pan: type: [string, "null"] description: PAN number example: "ABCPD1234E" name: type: [string, "null"] description: PAN holder name example: "RAHUL SHARMA" father_name: type: [string, "null"] description: Father/spouse name (the `swd` field; often empty) example: null dob: type: [string, "null"] description: Date of birth (DD-MM-YYYY as returned) example: "22-03-1995" gender: type: [string, "null"] example: "MALE" status: type: [string, "null"] description: Certificate status (e.g. "A" = Active) example: "A" verified_on: type: [string, "null"] description: Timestamp DigiLocker verified the record example: "31-05-2026 03:55:02" signature: $ref: '#/components/schemas/DigilockerSignature' DigilockerDrivingLicence: type: object description: | Driving licence data parsed from DigiLocker XML (returned under `fetched.driving_licence`). Any field may be null if absent in the source XML. properties: dl_number: type: [string, "null"] example: "KA0120190008989" name: type: [string, "null"] example: "Rahul Sharma" father_name: type: [string, "null"] example: "Suresh Sharma" dob: type: [string, "null"] description: Date of birth, normalized to ISO 8601. example: "1995-03-22" gender: type: [string, "null"] example: "M" status: type: [string, "null"] example: "A" issued_at: type: [string, "null"] example: "RTO, Bengaluru" issue_date: type: [string, "null"] format: date valid_from: type: [string, "null"] format: date expiry_date: type: [string, "null"] format: date present_address: type: [string, "null"] permanent_address: type: [string, "null"] categories: type: array description: Vehicle classes the holder is licensed for. items: type: object properties: code: type: string abbreviation: type: string description: type: string issue_date: type: string format: date signature: $ref: '#/components/schemas/DigilockerSignature' DigilockerSignature: type: object description: | Signature provenance for a fetched document. Reports whether a signed XML block with an X.509 certificate is present, plus the signing certificate's details. Note: this is provenance reporting, not full XML-DSIG cryptographic verification (the trust model is direct-from-source retrieval, consistent with the industry). properties: signature_present: type: boolean example: true certificate: type: [object, "null"] properties: subject: type: string example: "C=IN,O=DIGITAL INDIA CORPORATION,CN=DS DIGITAL INDIA CORPORATION" issuer: type: string valid_from: type: string format: date-time valid_until: type: string format: date-time serial_number: type: string DigilockerConsent: type: object description: | Consent receipt captured at session start. Carried through the flow for audit (DPDP / RBI compliance). properties: purpose: type: [string, "null"] description: The consent_purpose provided at /session. example: "KYC for loan account opening" consented_at: type: [string, "null"] format: date-time description: ISO 8601 timestamp (UTC) when consent was captured. example: "2026-05-30T22:37:17+00:00" DigilockerVerifiedIdentity: type: object description: | The investor's verified identity. Only fields the investor shared are populated. `verified` contains the authenticated document identifiers (e.g. PAN, Aadhaar number). properties: name: type: [string, "null"] example: "Anjali Sharma" dob: type: [string, "null"] example: "1990-05-16" gender: type: [string, "null"] example: "F" email: type: [string, "null"] example: "anjali@example.com" mobile: type: [string, "null"] example: "98XXXXXX10" digilocker_id: type: [string, "null"] example: "rahul22031995" username: type: [string, "null"] description: DigiLocker preferred username example: "rahul22031995" care_of: type: [string, "null"] example: "S/O Rajesh Sharma" address: type: [string, "null"] verified: type: object description: Authenticated document identifiers (keys present only when verified). properties: pan: type: string example: "ABCDE1234F" aadhaar: type: string description: Usually masked example: "XXXXXXXX1234" driving_licence: type: string example: "KA01-20110012345" additionalProperties: true DigilockerDocument: type: object description: Metadata for a single issued document in the user's DigiLocker. properties: name: type: string description: Document name example: "PAN Verification Record" type: type: string example: "file" doctype: type: string description: 5-character DigiLocker document type code example: "PANCR" issuer: type: string example: "Income Tax Department" issuerid: type: string example: "in.gov.pan" uri: type: string description: Unique DigiLocker identifier for the document example: "in.gov.pan-PANCR-xxxxxxxxxxxx" mime: type: array items: type: string example: ["application/pdf", "application/xml"] date: type: [string, "null"] format: date description: Last modified date in DigiLocker, normalized to ISO 8601. example: "2024-09-19" InboundEmailWebhookPayload: type: object description: Webhook payload sent to your callback_url when an investor forwards a CAS email properties: inbound_email_id: type: string description: The inbound email address ID that received this email example: "ie_a1b2c3d4e5f6" forwarded_by: type: string format: email description: Email address of the investor who forwarded the CAS email example: "investor@gmail.com" reference: type: [string, "null"] description: Your reference from inbound email creation example: "user_12345" metadata: type: object additionalProperties: type: string description: Your metadata from inbound email creation example: plan: "premium" files: type: array description: CAS files from the email (matches EmailCASFile schema) items: $ref: '#/components/schemas/EmailCASFile' count: type: integer description: Number of files example: 1