# Approve Agent Authorization Source: https://casparser.in/docs/api-reference/agent-auth/approve-agent-authorization /api-reference/openapi.yaml post /v1/agent-auth/token/{token}/approve 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.** # Poll for Agent Authorization Result Source: https://casparser.in/docs/api-reference/agent-auth/poll-for-agent-authorization-result /api-reference/openapi.yaml get /v1/agent-auth/token/{token} 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. # Check API Credits Source: https://casparser.in/docs/api-reference/authorization/check-api-credits /api-reference/openapi.yaml post /v1/credits 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. # Get API Usage Logs Source: https://casparser.in/docs/api-reference/authorization/get-api-usage-logs /api-reference/openapi.yaml post /v1/usage 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) # Get Usage Summary Source: https://casparser.in/docs/api-reference/authorization/get-usage-summary /api-reference/openapi.yaml post /v1/usage/summary 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) # CDSL CAS Fetch - Step 1 (Request OTP) Source: https://casparser.in/docs/api-reference/cas-fetch/cdsl-cas-fetch--step-1-request-otp /api-reference/openapi.yaml post /v4/cdsl/fetch **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. # CDSL CAS Fetch - Step 2 (Verify OTP & Get Files) Source: https://casparser.in/docs/api-reference/cas-fetch/cdsl-cas-fetch--step-2-verify-otp-&-get-files /api-reference/openapi.yaml post /v4/cdsl/fetch/{session_id}/verify **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. # CAS Generator (Email Mailback) Source: https://casparser.in/docs/api-reference/cas-generator/cas-generator-email-mailback /api-reference/openapi.yaml post /v4/generate 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. `cas_authority` picks which RTA sends the email. The portfolio data is the same either way — only the PDF password policy differs. With `cams`, a PAN is a valid password. # Parse CAMS/KFintech CAS PDF Source: https://casparser.in/docs/api-reference/cas-parser/parse-camskfintech-cas-pdf /api-reference/openapi.yaml post /v4/cams_kfintech/parse 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. # Parse CDSL CAS PDF Source: https://casparser.in/docs/api-reference/cas-parser/parse-cdsl-cas-pdf /api-reference/openapi.yaml post /v4/cdsl/parse 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. # Parse NSDL CAS PDF Source: https://casparser.in/docs/api-reference/cas-parser/parse-nsdl-cas-pdf /api-reference/openapi.yaml post /v4/nsdl/parse 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. # Smart Parse CAS PDF Source: https://casparser.in/docs/api-reference/cas-parser/smart-parse-cas-pdf /api-reference/openapi.yaml post /v4/smart/parse 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. # Parse Contract Note PDF Source: https://casparser.in/docs/api-reference/contract-note-parser/parse-contract-note-pdf /api-reference/openapi.yaml post /v4/contract_note/parse 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. # Check Email Connection Status Source: https://casparser.in/docs/api-reference/email-import/check-email-connection-status /api-reference/openapi.yaml post /v4/inbox/status 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). # Connect Email Provider (Initiate OAuth) Source: https://casparser.in/docs/api-reference/email-import/connect-email-provider-initiate-oauth /api-reference/openapi.yaml post /v4/inbox/connect 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. The token is long-lived (it stores an encrypted refresh token), so a single OAuth connect gives ongoing access to both historical and future CAS statements in the user's inbox. Reuse the same token until the user revokes access via `/v4/inbox/disconnect` or their provider's account settings. # Disconnect Email Provider Source: https://casparser.in/docs/api-reference/email-import/disconnect-email-provider /api-reference/openapi.yaml post /v4/inbox/disconnect 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. # List CAS Files from Email Inbox Source: https://casparser.in/docs/api-reference/email-import/list-cas-files-from-email-inbox /api-reference/openapi.yaml post /v4/inbox/cas 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). # Create Inbound Email Source: https://casparser.in/docs/api-reference/inbound-email/create-inbound-email /api-reference/openapi.yaml post /v4/inbound-email 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. # Delete Inbound Email Source: https://casparser.in/docs/api-reference/inbound-email/delete-inbound-email /api-reference/openapi.yaml delete /v4/inbound-email/{inbound_email_id} 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. # Get Inbound Email Details Source: https://casparser.in/docs/api-reference/inbound-email/get-inbound-email-details /api-reference/openapi.yaml get /v4/inbound-email/{inbound_email_id} Retrieve details of a specific inbound email including statistics. # List Files Received Source: https://casparser.in/docs/api-reference/inbound-email/list-files-received /api-reference/openapi.yaml get /v4/inbound-email/{inbound_email_id}/files 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. # List Inbound Emails Source: https://casparser.in/docs/api-reference/inbound-email/list-inbound-emails /api-reference/openapi.yaml get /v4/inbound-email List all inbound emails associated with your API key. Returns active and paused inbound emails (deleted ones are excluded). # API Reference Source: https://casparser.in/docs/api-reference/introduction Complete API documentation for CAS Parser. ## Base URL ``` https://api.casparser.in ``` ## Authentication All API requests require an `x-api-key` header: ```bash theme={null} curl -X POST https://api.casparser.in/v4/smart/parse \ -H "x-api-key: YOUR_API_KEY" \ -F "file=@cas.pdf" \ -F "password=ABCDE1234F" ``` **Sandbox:** Use `sandbox-with-json-responses` for testing without consuming credits. ## Access Tokens For frontend/SDK use, generate short-lived tokens from your backend: ```python theme={null} response = requests.post( "https://api.casparser.in/v1/token", headers={"x-api-key": "YOUR_API_KEY"}, json={"expiry_minutes": 30} ) token = response.json()["access_token"] # at_xxx ``` Access tokens (`at_` prefix) can replace API keys on all `/v4/*` endpoints. ## Endpoints ### Parsing | Method | Endpoint | Description | | - | - | - | | POST | `/v4/smart/parse` | Auto-detect CAS type and parse | | POST | `/v4/cdsl/parse` | Parse CDSL eCAS | | POST | `/v4/nsdl/parse` | Parse NSDL eCAS | | POST | `/v4/cams_kfintech/parse` | Parse CAMS/KFintech CAS | | POST | `/v4/contract_note/parse` | Parse broker contract notes | ### Data Retrieval | Method | Endpoint | Description | | - | - | - | | POST | `/v4/generate` | Request CAS via email (KFintech + CAMS) | | POST | `/v4/cdsl/fetch` | CDSL OTP request (Step 1) | | POST | `/v4/cdsl/fetch/{session_id}/verify` | CDSL OTP verify (Step 2) | ### Email Import | Method | Endpoint | Description | | - | - | - | | POST | `/v4/inbox/connect` | Get email OAuth URL (Gmail, Outlook, Zoho) | | POST | `/v4/inbox/status` | Check connection status | | POST | `/v4/inbox/cas` | List/parse CAS from inbox | | POST | `/v4/inbox/disconnect` | Revoke access | ### Account | Method | Endpoint | Description | | - | - | - | | POST | `/v1/credits` | Check remaining credits | | POST | `/v1/usage` | Get usage logs | | POST | `/v1/usage/summary` | Get aggregated usage | | POST | `/v1/token` | Generate access token | | POST | `/v1/token/verify` | Verify access token | ## Response Format All parsing endpoints return a unified JSON structure: ```json theme={null} { "meta": { "cas_type": "CDSL", "statement_period": { "from": "2024-01-01", "to": "2024-01-31" }, "generated_at": "2024-02-01T10:30:00Z" }, "investor": { "name": "John Doe", "pan": "ABCDE1234F" }, "summary": { "total_value": 2500000.00, "accounts": { "demat": { "count": 2, "total_value": 1500000.00 }, "mutual_funds": { "count": 5, "total_value": 1000000.00 } } }, "demat_accounts": [...], "mutual_funds": [...], "insurance": {...}, "nps": [...] } ``` ## Error Handling ```json theme={null} { "status": "failed", "msg": "Invalid password for PDF" } ``` | Status | Meaning | Retry? | | - | - | - | | 400 | Bad request | No — fix request | | 401 | Invalid API key | No — check key | | 402 | Insufficient credits | No — upgrade plan | | 500 | Server error | Yes — exponential backoff | ## Request ID Every response includes `X-Request-ID`: ``` X-Request-ID: req_2xYz7KpL8mN3Ab ``` Include this when contacting support. # Check if a DigiLocker account exists Source: https://casparser.in/docs/api-reference/kyc/check-if-a-digilocker-account-exists /api-reference/openapi.yaml post /v1/kyc/digilocker/account-lookup 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. # Check KYC PAN Status Source: https://casparser.in/docs/api-reference/kyc/check-kyc-pan-status /api-reference/openapi.yaml post /v1/kyc/pan/status 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. # DigiLocker aggregate (one-call convenience) Source: https://casparser.in/docs/api-reference/kyc/digilocker-aggregate-one-call-convenience /api-reference/openapi.yaml post /v1/kyc/digilocker/result/{session_id} 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. # Start DigiLocker session Source: https://casparser.in/docs/api-reference/kyc/start-digilocker-session /api-reference/openapi.yaml post /v1/kyc/digilocker/session 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. # Generate Access Token Source: https://casparser.in/docs/api-reference/portfolio-connect/generate-access-token /api-reference/openapi.yaml post /v1/token 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 # Verify Access Token Source: https://casparser.in/docs/api-reference/portfolio-connect/verify-access-token /api-reference/openapi.yaml post /v1/token/verify Verify an access token and check if it's still valid. Useful for debugging token issues. # Normalize mutual-fund RTA files Source: https://casparser.in/docs/api-reference/rta-sync/normalize-mutual-fund-rta-files /api-reference/openapi.yaml post /v1/rta-sync/normalize Upload one or more CAMS or KFintech reverse-feed files and receive typed canonical data sets. The endpoint accepts CSV, XLS, XLSX, and DBF files, detects the source report from the headers, applies provider-specific cleanup rules, and returns normalized records with warnings and a batch summary. RTA Sync preserves reported holdings and brokerage payouts as source-backed records. It does not calculate portfolio valuations or commissions. # Reconcile normalized RTA batches Source: https://casparser.in/docs/api-reference/rta-sync/reconcile-normalized-rta-batches /api-reference/openapi.yaml post /v1/rta-sync/reconcile Compare a current normalized RTA batch with a previous normalized batch supplied by the caller. The endpoint returns record-level classifications for new, unchanged, changed, duplicate, reversed, removed, and unmatched records. Removed classifications are emitted only for complete snapshot comparisons. It does not retain either batch. # Verify a SEBI-registered intermediary Source: https://casparser.in/docs/api-reference/verification/verify-a-sebi-registered-intermediary /api-reference/openapi.yaml post /v1/verify/sebi Verify any SEBI-registered intermediary. Provide a `registration_number` and the category is auto-detected from its sequence (e.g. `INA…` → Investment Adviser, `INP…` → Portfolio Manager, `INZ…` → Stock Broker). Alternatively, provide a `name` together with a `type` slug. `type` can also override detection, and is required for fund/FPI registration numbers that do not encode a category. `verified` is `true` only when a current registration is found and its status is `ACTIVE`. A missing registration returns HTTP 200 with `verified: false` and `registration_status: NOT_FOUND`. **Credits:** 0.25 per successful verification. # Verify an AMFI mutual-fund distributor (ARN) Source: https://casparser.in/docs/api-reference/verification/verify-an-amfi-mutual-fund-distributor-arn /api-reference/openapi.yaml post /v1/verify/mfd Verify a mutual-fund distributor by ARN. The ARN (and its EUIN) is screened against AMFI's suspended, terminated, and terminated-EUIN lists; a hit sets `registration_status` to `SUSPENDED`/`TERMINATED`, `negative_list_hit` to `true`, and `verified` to `false`. **Credits:** 0.25 per successful verification. # CAS Generator for Mutual Funds Source: https://casparser.in/docs/guides/cas-generator Request mutual fund CAS statements programmatically. Get 50+ years of transaction history from both KFintech and CAMS RTAs. ## Overview CAS Generator triggers a mutual fund CAS statement to be emailed to your user — no manual portal visits needed. **Coverage:** Uses the KFintech or CAMS email mailback service. KFintech and CAMS have a mutual partnership for data sharing and consolidation, so either one retrieves mutual fund data from **both RTAs** (Registrar and Transfer Agents), covering all mutual funds in India. **Statement type:** 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. ```mermaid theme={null} flowchart LR A[Your App] --> B[CAS Parser
Generator] B --> C[KFintech
Portal] C --> D[User Email
CAS PDF] ``` **Use cases:** * Client onboarding (request 50+ years of transaction history) * Automated monthly portfolio updates * Filling gaps in transaction history The CAS is emailed to the user's registered email within a few minutes. The CAS is not returned in the API response. ## Request a CAS statement ```python theme={null} import requests response = requests.post( "https://api.casparser.in/v4/generate", headers={"x-api-key": "YOUR_API_KEY"}, json={ "email": "investor@example.com", "from_date": "1990-01-01", "to_date": "2024-01-15", "password": "Abcdefghi12$" } ) data = response.json() # {"status": "success", "msg": "CAS request submitted. Check email shortly."} ``` ## Parameters | Parameter | Required | Description | | - | - | - | | `email` | Yes | User's email registered with their RTA (KFintech/CAMS) | | `pan_no` | No | User's PAN number (optional) | | `from_date` | Yes | Start date (YYYY-MM-DD) | | `to_date` | Yes | End date (YYYY-MM-DD) | | `password` | Yes | Password that protects the emailed PDF. Must satisfy the chosen authority's policy — see below. | | `cas_authority` | No | Which RTA sends the mailback: `kfintech` (default) or `cams`. Case-insensitive. | ## Choosing the CAS authority `cas_authority` decides **which RTA sends the email** — nothing else. You get the **same portfolio** either way. KFintech and CAMS share folio data with each other, so both authorities report the investor's holdings and transactions across every AMC in India. Switching authorities will not surface folios the other one missed, and will not change the fields you get back after parsing. What it *does* change is the **PDF password policy**, because each RTA enforces its own rule on the self-extraction password: | Authority | Password policy | Can you use the PAN? | | - | - | - | | `kfintech` (default) | KFintech's portal policy — stricter, expects a mix of upper and lowercase letters, digits and a special character | No | | `cams` | 6-20 characters containing at least two digits | **Yes** | ### Why the CAMS password rule matters A PAN like `ABCDE1234F` is 10 characters with four digits, so it satisfies the CAMS rule. That means you can set the password **to the investor's PAN**: ```python theme={null} requests.post( "https://api.casparser.in/v4/generate", headers={"x-api-key": "YOUR_API_KEY"}, json={ "email": "investor@example.com", "pan_no": "ABCDE1234F", "from_date": "1990-01-01", "to_date": "2024-01-15", "password": "ABCDE1234F", # PAN doubles as the PDF password "cas_authority": "cams", }, ) ``` This removes a whole piece of state from your integration. With `kfintech` you have to invent a password, store it against the user, and look it up again when the PDF arrives so you can pass it to `/v4/smart/parse`. With `cams` the password is derivable from data you already hold. The password is **not** validated against the investor's real PAN by the RTA — it is just a string that locks the PDF. Using the PAN is a convention that makes the password predictable to you, not an authentication step. If you don't already collect PAN, or you'd rather not reuse it as a password, stay on the default `kfintech`. ## Integration patterns ### Pattern 1: Generate + Email Import Combine CAS Generator with Email Inbox Import for a seamless flow: ```python theme={null} # 1. Request CAS generation requests.post( "https://api.casparser.in/v4/generate", headers={"x-api-key": API_KEY}, json={ "email": "user@gmail.com", "from_date": "2020-01-01", "to_date": "2024-12-31", "password": "Abcdefghi12$" } ) # 2. Wait for email (1-2 minutes) time.sleep(120) # 3. Import from inbox (Gmail/Outlook/Zoho) response = requests.post( "https://api.casparser.in/v4/inbox/cas", headers={ "x-api-key": API_KEY, "x-inbox-token": inbox_token } ) ``` ### Pattern 2: User-initiated Let users trigger generation and upload manually: ```jsx theme={null} // Frontend const requestCAS = async () => { await fetch('/api/generate-cas', { method: 'POST', body: JSON.stringify({ email: user.email, pan: user.pan }) }); alert('CAS will arrive in your email in 1-2 minutes. Upload it here when received.'); }; ``` ## Timing expectations | Step | Duration | | - | - | | API request | \< 1 second | | RTA processing | 1-2 minutes | | Email delivery | Depends on email provider | ## Date range options ```python theme={null} from datetime import datetime, timedelta # Last 1 year requests.post(..., json={ "email": "user@example.com", "from_date": (datetime.now() - timedelta(days=365)).strftime("%Y-%m-%d"), "to_date": datetime.now().strftime("%Y-%m-%d"), "password": "Abcdefghi12$" }) # Complete transaction history (50+ years) requests.post(..., json={ "email": "user@example.com", "from_date": "1970-01-01", "to_date": datetime.now().strftime("%Y-%m-%d"), "password": "Abcdefghi12$" }) ``` ## Credit usage | Operation | Credits | | - | - | | Generate CAS | 0.5 | ## Comparison: CDSL Fetch vs CAS Generator | Feature | CDSL Fetch | CAS Generator | | - | - | - | | **Speed** | Real-time (\~20s) | 1-2 minutes | | **Data source** | CDSL eCAS (all holdings) | KFintech + CAMS (all MFs) | | **History** | Current holdings | 50+ years transactions | | **User input** | PAN + BO ID + DOB + OTP | Email + date range | | **Asset classes** | Demat + Non-Demat (stocks, bonds, MFs, etc.) | Mutual funds | **Important:** CDSL eCAS statements contain holdings from **both CDSL and NSDL** depositories. Similarly, NSDL eCAS contains both NSDL and CDSL holdings. This is a consolidated view across all depositories, not limited to the issuing depository. ## Next steps Import the generated CAS Parse uploaded CAS files # CDSL OTP Fetch Source: https://casparser.in/docs/guides/cdsl-fetch Fetch CAS PDF files directly from CDSL via OTP verification. Returns download URLs for up to 6 monthly statements. ## Overview CDSL OTP Fetch retrieves eCAS (electronic Consolidated Account Statement) PDF files directly from CDSL's CAS portal ([https://www.cdslindia.com/cas/](https://www.cdslindia.com/cas/)) via OTP authentication. ```mermaid theme={null} sequenceDiagram participant App as Your App participant API as CAS Parser
Fetch API participant CDSL as CDSL
CAS Portal participant User as User Phone App->>API: Step 1: Request OTP API->>CDSL: Trigger OTP CDSL->>User: SMS with OTP User->>App: User enters OTP App->>API: Step 2: Verify OTP API-->>App: CAS PDF URLs Note over App: Parse each URL with
/v4/smart/parse ``` **What you get:** * Download URLs for up to 6 monthly eCAS PDFs * **All holdings:** Demat + Non-Demat assets (stocks, bonds, mutual funds, AIFs, etc.) * **Cross-depository:** Includes holdings from **both CDSL and NSDL** accounts * No manual portal login required **Critical:** CDSL eCAS statements are **not limited to CDSL demat accounts**. They contain a consolidated view of all holdings across **both CDSL and NSDL** depositories, including both demat and non-demat assets. Similarly, NSDL eCAS also contains holdings from both depositories. **What you need:** * PAN number * CDSL BO ID (16-digit Client ID) * Date of birth * **At least one CDSL demat account** (required to access CDSL CAS portal) **Portal access requirement:** Only users with at least one CDSL demat account can access the CDSL CAS portal. NSDL does not have an equivalent OTP-based fetch portal yet. ## Two-step flow ### Step 1: Request OTP ```python theme={null} import requests response = requests.post( "https://api.casparser.in/v4/cdsl/fetch", headers={"x-api-key": "YOUR_API_KEY"}, json={ "pan": "ABCDE1234F", "bo_id": "1234567890123456", # 16-digit CDSL BO ID "dob": "1990-01-15" # YYYY-MM-DD format }, timeout=50 # Captcha solving takes ~15-20 seconds ) data = response.json() session_id = data["session_id"] # User receives OTP on registered mobile ``` Step 1 takes **15-20 seconds** due to automated captcha solving. Set your timeout accordingly. ### Step 2: Verify OTP and get PDF URLs ```python theme={null} # User provides OTP from their phone otp = "123456" response = requests.post( f"https://api.casparser.in/v4/cdsl/fetch/{session_id}/verify", headers={"x-api-key": "YOUR_API_KEY"}, json={ "otp": otp, "num_periods": 6 # Number of monthly statements (default: 6) } ) data = response.json() # Response includes array of PDF download URLs # { # "status": "success", # "msg": "Fetched 6 CAS files", # "files": [ # { # "filename": "CDSL_CAS_1234567890123456_NOV2025.pdf", # "url": "https://cdn.casparser.in/cdsl-cas/session-id/CDSL_CAS_1234567890123456_NOV2025.pdf" # }, # ... # ] # } ``` ## Full example ```python theme={null} import requests import time API_KEY = "YOUR_API_KEY" def fetch_cdsl_cas_files(pan: str, bo_id: str, dob: str, otp_callback): """ Fetch CDSL CAS PDF files via OTP. Args: pan: PAN number bo_id: 16-digit CDSL BO ID (Client ID) dob: Date of birth (YYYY-MM-DD) otp_callback: A function that prompts user for OTP and returns it Returns: List of PDF download URLs """ # Step 1: Request OTP response = requests.post( "https://api.casparser.in/v4/cdsl/fetch", headers={"x-api-key": API_KEY}, json={"pan": pan, "bo_id": bo_id, "dob": dob}, timeout=50 ) data = response.json() if data.get("status") != "success": raise Exception(f"Failed to request OTP: {data.get('msg')}") session_id = data["session_id"] # User receives OTP on their registered mobile otp = otp_callback() # Your UI prompts user for OTP # Step 2: Verify OTP and get PDF URLs response = requests.post( f"https://api.casparser.in/v4/cdsl/fetch/{session_id}/verify", headers={"x-api-key": API_KEY}, json={"otp": otp, "num_periods": 6} ) result = response.json() # Now parse each PDF file parsed_files = [] for file in result.get("files", []): parse_response = requests.post( "https://api.casparser.in/v4/smart/parse", headers={"x-api-key": API_KEY}, json={"pdf_url": file["url"], "password": pan} ) parsed_files.append(parse_response.json()) return parsed_files # Usage def prompt_otp(): return input("Enter OTP received on your phone: ") cas_files = fetch_cdsl_cas_files( pan="ABCDE1234F", bo_id="1234567890123456", dob="1990-01-15", otp_callback=prompt_otp ) # Process each CAS file for cas_data in cas_files: print(f"Total Value: ₹{cas_data['summary']['total_value']:,.2f}") ``` ## Response format ```json theme={null} { "status": "success", "msg": "Fetched 6 CAS files", "files": [ { "filename": "CDSL_CAS_1234567890123456_NOV2025.pdf", "url": "https://cdn.casparser.in/cdsl-cas/session-id/CDSL_CAS_1234567890123456_NOV2025.pdf" }, { "filename": "CDSL_CAS_1234567890123456_OCT2025.pdf", "url": "https://cdn.casparser.in/cdsl-cas/session-id/CDSL_CAS_1234567890123456_OCT2025.pdf" } ] } ``` The response contains **download URLs only**. To get parsed data, pass each URL to `/v4/smart/parse` with `pdf_url` parameter. ## Credit usage | Step | Credits | | - | - | | Request OTP (Step 1) | 0 | | Verify OTP (Step 2) | 0.5 | You're only charged when Step 2 succeeds. Failed OTP attempts are not charged. ## Error handling | Error | Cause | Solution | | - | - | - | | `Invalid PAN format` | PAN doesn't match pattern | Validate PAN: `[A-Z]{5}[0-9]{4}[A-Z]` | | `No CDSL account found` | PAN not registered with CDSL | User doesn't have CDSL BO ID (may only use NSDL) | | `OTP expired` | OTP older than 5 minutes | Request new OTP (repeat Step 1) | | `Invalid OTP` | Wrong OTP entered | Ask user to re-enter | ## Using with Portfolio Connect SDK ```jsx theme={null} console.log(data)} /> ``` ## Next steps Generate CAS via email Frontend widget with CDSL fetch # Contract Notes Source: https://casparser.in/docs/guides/contract-notes Parse broker contract notes from Zerodha, Groww, Upstox, and ICICI Direct. Extract trade details automatically. ## Overview Contract notes are daily trade confirmations from brokers. CAS Parser extracts structured trade data from these PDFs. ```mermaid theme={null} flowchart LR A[Contract Note PDF
any broker] --> B[CAS Parser
Auto-detect] B --> C[Trade Details
JSON Response] ``` ## Supported Brokers | Broker | Auto-detected | Password | | - | - | - | | **Zerodha** | ✅ | PAN number | | **Groww** | ✅ | PAN number | | **Upstox** | ✅ | PAN number | | **ICICI Direct** | ✅ | PAN number | The API auto-detects the broker — no need to specify which broker issued the contract note. ## Parse a contract note ```python Python theme={null} import requests response = requests.post( "https://api.casparser.in/v4/contract_note/parse", headers={"x-api-key": "YOUR_API_KEY"}, files={"pdf_file": open("contract_note.pdf", "rb")}, data={"password": "ABCDE1234F"} ) result = response.json() if result["status"] == "success": data = result["data"] for txn in data["equity_transactions"]: print(f"{txn['security_symbol']}: {txn['buy_quantity']} @ ₹{txn['buy_wap']}") ``` ```javascript Node.js theme={null} const form = new FormData(); form.append('pdf_file', fs.createReadStream('contract_note.pdf')); form.append('password', 'ABCDE1234F'); const response = await fetch('https://api.casparser.in/v4/contract_note/parse', { method: 'POST', headers: { 'x-api-key': 'YOUR_API_KEY' }, body: form }); const result = await response.json(); const data = result.data; ``` ```bash cURL theme={null} curl -X POST https://api.casparser.in/v4/contract_note/parse \ -H "x-api-key: YOUR_API_KEY" \ -F "pdf_file=@contract_note.pdf" \ -F "password=ABCDE1234F" ``` ## Response format ```json theme={null} { "status": "success", "msg": "success", "data": { "contract_note_info": { "contract_note_number": "CNT-25/26-73436720", "trade_date": "2025-08-05", "settlement_number": "2025149", "settlement_date": "2025-08-06" }, "broker_info": { "broker_type": "zerodha", "name": "Zerodha Broking Limited", "sebi_registration": "INZ000031633" }, "client_info": { "name": "JOHN DOE", "pan": "ABCDE1234F", "ucc": "AB1234", "place_of_supply": "DELHI", "gst_state_code": "7" }, "equity_transactions": [ { "isin": "INE002A01018", "security_name": "RELIANCE", "security_symbol": "RELIANCE", "buy_quantity": 10, "buy_wap": 2450.50, "buy_total_value": 24505.00, "sell_quantity": 0, "sell_wap": 0, "sell_total_value": 0, "net_obligation": 24547.43 } ], "derivatives_transactions": [], "detailed_trades": [ { "order_number": "1000000042939390", "order_time": "13:13:13", "trade_number": "4006567", "trade_time": "13:13:13", "security_description": "RELIANCE-EQ/INE002A01018", "buy_sell": "B", "exchange": "NSE", "quantity": 10, "brokerage": 12.25, "net_rate_per_unit": 2450.50, "closing_rate_per_unit": 2450.50, "net_total": 24547.43 } ], "charges_summary": { "pay_in_pay_out_obligation": 24505.00, "taxable_value_brokerage": 12.25, "exchange_transaction_charges": 5.00, "cgst": 1.10, "sgst": 1.10, "igst": 0, "securities_transaction_tax": 24.51, "sebi_turnover_fees": 0.25, "stamp_duty": 2.45, "net_amount_receivable_payable": 24547.43 } } } ``` ## Response Fields ### Contract Note Info | Field | Description | | - | - | | `contract_note_number` | Contract note reference number | | `trade_date` | Date when trades were executed | | `settlement_number` | Settlement reference | | `settlement_date` | Settlement date | ### Equity Transactions (Summary) | Field | Description | | - | - | | `isin` | ISIN code of the security | | `security_name` | Name of the security | | `security_symbol` | Trading symbol | | `buy_quantity` | Total quantity purchased | | `buy_wap` | Weighted Average Price for buys | | `buy_total_value` | Total buy value | | `sell_quantity` | Total quantity sold | | `sell_wap` | Weighted Average Price for sells | | `sell_total_value` | Total sell value | | `net_obligation` | Net amount payable/receivable | ### Detailed Trades | Field | Description | | - | - | | `order_number` | Order reference number | | `trade_number` | Trade reference number | | `security_description` | Security with exchange and ISIN | | `buy_sell` | B (Buy) or S (Sell) | | `exchange` | NSE, BSE, etc. | | `quantity` | Quantity traded | | `brokerage` | Brokerage for this trade | | `net_rate_per_unit` | Net rate per unit | | `net_total` | Net total for this trade | ### Charges Summary | Field | Description | | - | - | | `pay_in_pay_out_obligation` | Net pay-in/pay-out | | `taxable_value_brokerage` | Taxable brokerage amount | | `securities_transaction_tax` | STT amount | | `stamp_duty` | Stamp duty charges | | `cgst` / `sgst` / `igst` | GST components | | `net_amount_receivable_payable` | Final settlement amount | ## Credit usage | Operation | Credits | | - | - | | Parse contract note | 0.5 | Contract notes cost **half a credit** compared to CAS parsing (1 credit). ## Use cases * **Portfolio tracking** — Automatically import trades into your app * **Tax calculation** — Extract capital gains data for ITR filing * **Trade analytics** — Analyze trading patterns and costs * **Reconciliation** — Match broker records with demat statements ## Next steps Parse portfolio statements View all endpoints # Email Inbox Import Source: https://casparser.in/docs/guides/gmail-inbox Import CAS files directly from user email inboxes via OAuth. Supports Gmail, Outlook, and Zoho. No password sharing required. ## Overview Email Inbox Import lets users import CAS statements directly from their email — no file uploads or password sharing needed. Supports Gmail, Outlook, and Zoho Mail. ```mermaid theme={null} flowchart LR A[User
Gmail / Outlook / Zoho] --> B[OAuth
Consent] B --> C[CAS Parser
Inbox API] C --> D[Your App
Callback] ``` **Security features:** * Read-only access (cannot send emails) * OAuth-based consent flow * User can revoke access anytime * Tokens encrypted server-side ## Supported providers Pass `provider` in the connect request to choose the mail service. Defaults to `gmail`. | Provider | Value | Covers | | - | - | - | | Gmail | `gmail` | `@gmail.com` and Google Workspace domains | | Outlook | `outlook` | Personal Microsoft accounts: `@outlook.com`, `@hotmail.com`, `@live.com`, `@msn.com`, localised variants (`@hotmail.co.uk`, `@live.in`, `@hotmail.fr`), and custom domains registered as personal Microsoft accounts | | Zoho Mail | `zoho` | Zoho-hosted mailboxes, including custom domains | Any unrecognised value is treated as `gmail`. The resolved `provider` is returned in the connect response and reported by `/v4/inbox/status`. ## Integration flow ### Step 1: Initiate OAuth ```python theme={null} import requests response = requests.post( "https://api.casparser.in/v4/inbox/connect", headers={"x-api-key": "YOUR_API_KEY"}, json={ "redirect_uri": "https://yourapp.com/oauth/callback", "provider": "outlook", # "gmail" (default), "outlook", or "zoho" } ) data = response.json() oauth_url = data["oauth_url"] # data["provider"] == "outlook" # Redirect user to oauth_url ``` ### Step 2: User grants consent The user is redirected to their provider's OAuth consent screen. After granting access, they're redirected back to your `redirect_uri` with an `inbox_token`: ``` https://yourapp.com/oauth/callback?inbox_token=eyJhbGciOiJIUzI1NiIs... ``` The `inbox_token` is long-lived — it stores an encrypted refresh token. A single OAuth connect gives ongoing access to both historical and future CAS statements in the user's inbox. Reuse the same token for every `/v4/inbox/cas` call until the user revokes access via `/v4/inbox/disconnect` or their provider's account settings. ### Step 3: List CAS files from inbox ```python theme={null} inbox_token = request.args.get("inbox_token") response = requests.post( "https://api.casparser.in/v4/inbox/cas", headers={ "x-api-key": "YOUR_API_KEY", "x-inbox-token": inbox_token } ) data = response.json() for cas_file in data["files"]: print(f"Found: {cas_file['filename']} ({cas_file['cas_type']})") print(f"Download URL: {cas_file['url']}") # Parse the CAS file parse_response = requests.post( "https://api.casparser.in/v4/smart/parse", headers={"x-api-key": "YOUR_API_KEY"}, json={ "pdf_url": cas_file["url"], "password": "YOUR_PAN_NUMBER" } ) parsed_data = parse_response.json() print(f"Portfolio value: ₹{parsed_data['summary']['total_value']:,.2f}") ``` ### Step 4: Disconnect (optional) ```python theme={null} requests.post( "https://api.casparser.in/v4/inbox/disconnect", headers={ "x-api-key": "YOUR_API_KEY", "x-inbox-token": inbox_token } ) ``` ## API endpoints | Endpoint | Description | | - | - | | `POST /v4/inbox/connect` | Get OAuth URL for user consent | | `POST /v4/inbox/status` | Check if inbox\_token is still valid | | `POST /v4/inbox/cas` | List and optionally parse CAS files | | `POST /v4/inbox/disconnect` | Revoke access and delete tokens | ## Response format ```json theme={null} { "status": "success", "files": [ { "message_id": "18d4a2b3c4d5e6f7", "filename": "cdsl_20250115_a1b2c3d4.pdf", "original_filename": "CDSL_CAS_Statement.pdf", "message_date": "2025-01-15", "cas_type": "cdsl", "sender_email": "ecas@cdslstatement.com", "size": 245000, "url": "https://cdn.casparser.in/email-cas/user123/cdsl_20250115_a1b2c3d4.pdf", "expires_in": 86400 } ], "count": 1 } ``` Download URLs expire in **24 hours**. The response contains URLs only — parse each file with `/v4/smart/parse`. ## Credit usage | Operation | Credits | | - | - | | Connect (OAuth) | Free | | List files (`/v4/inbox/cas`) | 0.2 per pull (any number of files) | | Parse file (`/v4/smart/parse`) | 1.0 per file | ## Using with Portfolio Connect SDK The Portfolio Connect SDK has built-in inbox import across all supported providers: ```jsx theme={null} console.log(data)} /> ``` ## Next steps Real-time holdings via OTP Drop-in frontend widget # Inbound Email API Source: https://casparser.in/docs/guides/inbound-email Create dedicated email addresses for investors to forward CAS statements ## Overview The Inbound Email API lets you create unique email addresses (like `ie_xyz@import.casparser.in`) where your users can forward their CAS statements. When an email arrives, we validate the sender, upload attachments to cloud storage, and deliver the result to your app. **Use Case**: Lower-friction alternative to OAuth or manual file upload. *** ## Delivery options `callback_url` is **optional**. It controls how you receive parsed files: We `POST` parsed files to your URL whenever an investor forwards a CAS. Files also remain retrievable via `GET /v4/inbound-email/{id}/files` for 48h. **Best for:** server-side workflows, recurring statement ingestion. Retrieve files via `GET /v4/inbound-email/{id}/files`. The address is scoped to a 30-min sliding session (each poll extends it). Used by the Portfolio Connect widget when `enableInboundEmail: true`. **Best for:** onboarding flows, frontend-heavy apps with no backend. Sender validation, file format, and security guarantees are identical regardless of `callback_url`. *** ## How It Works ```mermaid theme={null} sequenceDiagram participant Dev as Your App participant API as CAS Parser API participant User as Investor participant Email as Email Provider participant Webhook as Your Webhook Dev->>API: POST /v4/inbound-email API-->>Dev: ie_xyz@import.casparser.in Dev->>User: Display: "Forward CAS to ie_xyz@..." User->>Email: Forwards CAS email Email->>API: Email received at ie_xyz@... API->>API: Validate sender (CDSL/NSDL/CAMS/KFintech) API->>Webhook: POST with email details + attachment URLs Webhook-->>API: 200 OK ``` *** ## Quick Start The Quick Start below provides a `callback_url` for webhook delivery. For a frontend-only flow with no backend, see [Use with Portfolio Connect](#use-with-portfolio-connect). ### 1. Create an Inbound Email ```python Python theme={null} import requests response = requests.post( "https://api.casparser.in/v4/inbound-email", headers={"x-api-key": "your-api-key"}, json={ "callback_url": "https://yourapp.com/webhooks/cas-email", "allowed_sources": ["cdsl", "nsdl"], # Optional filter "reference": "user_12345", # Your internal ID "metadata": {"plan": "premium"} # Optional key-value pairs } ) data = response.json() print(f"Forward emails to: {data['email']}") # ie_a1b2c3d4e5f6@import.casparser.in ``` ```javascript Node.js theme={null} const response = await fetch('https://api.casparser.in/v4/inbound-email', { method: 'POST', headers: { 'x-api-key': 'your-api-key', 'Content-Type': 'application/json', }, body: JSON.stringify({ callback_url: 'https://yourapp.com/webhooks/cas-email', allowed_sources: ['cdsl', 'nsdl'], reference: 'user_12345', metadata: { plan: 'premium' } }) }); const data = await response.json(); console.log(`Forward emails to: ${data.email}`); // ie_a1b2c3d4e5f6@import.casparser.in ``` ```bash cURL theme={null} curl -X POST https://api.casparser.in/v4/inbound-email \ -H "x-api-key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "callback_url": "https://yourapp.com/webhooks/cas-email", "allowed_sources": ["cdsl", "nsdl"], "reference": "user_12345", "metadata": {"plan": "premium"} }' ``` **Response (`201 Created`):** ```json theme={null} { "inbound_email_id": "ie_a1b2c3d4e5f6", "email": "ie_a1b2c3d4e5f6@import.casparser.in", "callback_url": "https://yourapp.com/webhooks/cas-email", "allowed_sources": ["cdsl", "nsdl"], "reference": "user_12345", "metadata": {"plan": "premium"}, "status": "active", "created_at": "2025-02-22T20:00:00Z", "updated_at": "2025-02-22T20:00:00Z" } ``` `status` here is the inbound email's lifecycle (`active` / `paused`). *** ### 2. Display to User Show the email address in your UI: ``` 📧 Forward your CAS statement to: ie_a1b2c3d4e5f6@import.casparser.in ``` *** ### 3. Handle Webhook When the user forwards an email, we POST to your `callback_url`: ```python Python (Flask) theme={null} from flask import Flask, request, jsonify import requests app = Flask(__name__) @app.route('/webhooks/cas-email', methods=['POST']) def handle_cas_webhook(): data = request.get_json() # Process the inbound email print(f"Forwarded by: {data['forwarded_by']}") # Investor's email print(f"User reference: {data['reference']}") print(f"Files received: {data['count']}") # Download files (URLs expire in 48 hours) for file in data['files']: url = file['url'] filename = file['filename'] cas_type = file['cas_type'] # Download to your storage response = requests.get(url) if response.status_code == 200: # Save to disk or upload to your storage with open(f"/tmp/{filename}", 'wb') as f: f.write(response.content) print(f"Downloaded {cas_type}: {filename}") return jsonify({"status": "success"}) ``` ```javascript Node.js (Express) theme={null} const express = require('express'); const fetch = require('node-fetch'); const fs = require('fs'); const app = express(); app.use(express.json()); app.post('/webhooks/cas-email', async (req, res) => { const data = req.body; console.log(`Forwarded by: ${data.forwarded_by}`); // Investor's email console.log(`User reference: ${data.reference}`); console.log(`Files received: ${data.count}`); // Download files (URLs expire in 48 hours) for (const file of data.files) { const url = file.url; const filename = file.filename; const casType = file.cas_type; // Download to your storage const response = await fetch(url); if (response.ok) { const buffer = await response.buffer(); // Save to disk or upload to your storage fs.writeFileSync(`/tmp/${filename}`, buffer); console.log(`Downloaded ${casType}: ${filename}`); } } res.json({ status: 'success' }); }); ``` **Webhook Payload:** ```json theme={null} { "inbound_email_id": "ie_a1b2c3d4e5f6", "forwarded_by": "investor@gmail.com", "reference": "user_12345", "metadata": {"plan": "premium"}, "files": [ { "message_id": "att_xyz789", "filename": "cdsl_20250222_att_xyz789.pdf", "original_filename": "JAN2026_AA03773313_TXN.pdf", "message_date": "2025-02-22", "cas_type": "cdsl", "sender_email": "ecas@cdslstatement.com", "size": 245000, "url": "https://storage.casparser.in/inbound-email/...", "expires_in": 172800 } ], "count": 1 } ``` *** ## Retrieving Files The `GET /v4/inbound-email/{id}/files` endpoint returns every file that has been forwarded to a given inbound email address. It's **available on every eligible inbound email**, regardless of whether you configured a `callback_url`. Common uses: * **Frontend polling** — the Portfolio Connect widget polls it automatically when `enableInboundEmail: true` (see below). * **Backend polling** — use it instead of (or alongside) a webhook if a pull model fits your stack better. * **Replay / backfill** — fetch files that were delivered via webhook if your endpoint was down or needs to re-process them. ```bash theme={null} GET /v4/inbound-email/{inbound_email_id}/files?since=&limit=20 ``` **Response:** ```json theme={null} { "status": "success", "files": [ { "message_id": "att_xyz789", "filename": "cdsl_20250222_att_xyz789.pdf", "cas_type": "cdsl", "url": "https://storage.casparser.in/inbound-email/...", "expires_in": 172800, "received_at": "2025-02-22T20:01:12.000123+00:00" } ], "count": 1, "cursor": "2025-02-22T20:01:12.000123+00:00" } ``` **Cursor pagination.** Pass the returned `cursor` as `since` on your next call to receive only files that arrived after it. Timestamps are guaranteed strictly monotonic at microsecond precision, so no entry is returned twice. Persist the cursor on your side between polls. `expires_in` differs depending on whether `callback_url` was set: long-lived presigned URLs (48h) when set, aligned with the session TTL when omitted. Treat the value as authoritative and refresh by re-polling if a download fails. **Retention.** * With `callback_url` set: files retained **48 hours** from arrival. * Without `callback_url`: files retained for the sliding session TTL; each call extends it. *** ## Use with Portfolio Connect For frontend-only apps, the Portfolio Connect widget can drive the entire flow: it creates a short-lived inbound email (without a `callback_url`), displays it to the user, polls the retrieval endpoint above, and auto-parses the forwarded PDF. No webhook required. ```tsx React theme={null} import { PortfolioConnect } from '@cas-parser/connect'; { // metadata.source === 'INBOUND_EMAIL' // metadata.received_at === ISO timestamp console.log('Parsed:', data); }} /> ``` The widget automatically: * Creates an inbound email via `POST /v4/inbound-email` (no `callback_url`). * Displays the address with a copy button. * Polls `GET /v4/inbound-email/{id}/files` every few seconds. * Parses the first file it receives and emits `onSuccess`. * Deletes the inbound email on widget close (or lets the 30-min TTL expire it). *** ## Sender Validation We automatically verify that forwarded emails originated from trusted CAS authorities: * **CDSL** → [eCAS@cdslstatement.com](mailto:eCAS@cdslstatement.com) * **NSDL** → [NSDL-CAS@nsdl.co.in](mailto:NSDL-CAS@nsdl.co.in) * **CAMS** → [donotreply@camsonline.com](mailto:donotreply@camsonline.com) * **KFintech** → [samfS@kfintech.com](mailto:samfS@kfintech.com) **Forwarded emails** are supported — we extract the original sender from email headers and body. *** ## Managing Inbound Emails ### List All ```bash theme={null} GET /v4/inbound-email?status=active&limit=50&offset=0 ``` ### Get Details ```bash theme={null} GET /v4/inbound-email/ie_a1b2c3d4e5f6 ``` ### Delete ```bash theme={null} DELETE /v4/inbound-email/ie_a1b2c3d4e5f6 ``` *** ## Best Practices ### 1. Download Attachments Promptly Presigned URLs expire in **48 hours**. Download and store attachments in your own storage. ### 2. Use HTTPS for Callbacks Production callback URLs must use HTTPS (HTTP is allowed for `localhost` during development). ### 3. Use `reference` for Correlation Store your user ID in `reference` to map inbound emails back to your users. ### 4. Filter by `allowed_sources` If you only need CDSL statements, set `"allowed_sources": ["cdsl"]` to reject others automatically. *** ## Billing **0.2 credits** per validated email received, regardless of whether `callback_url` was set — charged on successful webhook delivery, or when the file is stored for polling. Notes: * Inbound email creation, listing, and polling reads (`GET /files`): **free**. * Emails from unknown senders: **not billed**. * Webhook-delivery failures (after retries): **still billed** — the email was valid. * The Portfolio Connect widget incurs an additional **1.0 credit** when it auto-parses the received file (standard smart-parse cost). *** ## Error Handling ### Common Issues | Error | Cause | Solution | | - | - | - | | `callback_url must be HTTPS` | Using HTTP in production | Use HTTPS or test with `localhost` | | `Inbound email not found` | Wrong ID or deleted | Check ID or recreate | | `alias already taken` | Another inbound email uses this alias | Choose a different alias | ### Webhook Delivery Failures If your webhook endpoint is down or returns an error, we retry automatically with exponential backoff. *** ## Security ### Email Authentication We validate: * **Exact email whitelist**: Only emails from official CAS authority addresses * **Header parsing**: Extract original sender from forwarded emails ### Webhook Security * **HTTPS required**: Production callbacks must use HTTPS * **Automatic retries**: Failed webhook deliveries are retried automatically * **Attachment URLs**: Time-limited presigned URLs (48h expiry) ### Data Retention * Inbound email configs with `callback_url` set: active indefinitely, marked inactive after 30 days without emails. * Inbound email configs without `callback_url`: auto-expire after **30 minutes** of no polling activity (sliding TTL). * Received file records (for polling): retained **48 hours** when `callback_url` is set; aligned with the session TTL otherwise. *** ## Use Cases 1. **Onboarding Flow**: "Forward your CAS to get started" — simpler than OAuth 2. **Recurring Updates**: Users forward monthly statements → auto-sync portfolios 3. **Offline Users**: Works for investors without OAuth access or app login *** ## Next Steps * [Parse CAS PDFs](/docs/guides/parsing) — parse the downloaded files * [Email Inbox Import](/docs/guides/gmail-inbox) — alternative: pull CAS from user's inbox (Gmail, Outlook, Zoho) * [CDSL Fetch](/docs/guides/cdsl-fetch) — alternative: fetch CAS directly via OTP # KYC DigiLocker Source: https://casparser.in/docs/guides/kyc-digilocker Fetch an investor's PAN, Aadhaar, and driving licence from DigiLocker, with their consent. Pull an investor's government documents straight from DigiLocker after they sign in and consent. You get their verified identity plus parsed PAN, Aadhaar, and driving licence data. This is not the same as [KYC PAN Status](/docs/guides/kyc-pan-status). PAN Status is a passive KRA lookup. DigiLocker is an active flow where the investor logs in and shares documents. ## How it works 1. (Optional) Look up whether the investor already has a DigiLocker account. 2. Start a session. You get a `session_id` and an `authorization_url`. 3. Redirect the investor to `authorization_url`. They sign in and consent. 4. DigiLocker sends them back to your `redirect_url`. 5. Read the result with the `session_id`. The `session_id` is the only thing you carry between steps. Consent is mandatory. You must pass `consent: true` and a `consent_purpose` of at least 20 characters. We store both as a consent receipt. ## 1. Account lookup (optional) Decide whether to send the investor down a `signin` or `signup` path. Pass one of `mobile` or `aadhaar_number`, not both. ```python theme={null} import requests r = requests.post( "https://api.casparser.in/v1/kyc/digilocker/account-lookup", headers={"x-api-key": "YOUR_API_KEY"}, json={"mobile": "9876543210"}, timeout=30, ) r.json()["suggested_user_flow"] # "signin" or "signup" ``` Free. ## 2. Start a session ```python theme={null} r = requests.post( "https://api.casparser.in/v1/kyc/digilocker/session", headers={"x-api-key": "YOUR_API_KEY"}, json={ "consent": True, "consent_purpose": "KYC for investment account opening", "redirect_url": "https://yourapp.com/digilocker/callback", "documents": ["aadhaar", "pan"], }, timeout=30, ) data = r.json() # data["session_id"], data["authorization_url"] ``` Body fields: * `consent` (required) — must be `true`. * `consent_purpose` (required) — min 20 characters. * `redirect_url` (required) — where the investor lands after consent. * `documents` — any of `aadhaar`, `pan`, `driving_licence`, `email`, `mobile`. * `state` — echoed back to you unchanged. * `user_flow` — `signin` or `signup`. * `prefill_mobile` — pre-verified mobile to skip the signup OTP (with `signup`). Free. ## 3. Consent redirect After consent, the investor returns to your `redirect_url` with query params: * Granted: `?success=true&id={session_id}&documents=pan+aadhaar&has_verified_data=1` * Denied: `?success=false&id={session_id}&error={error}` `id` is the `session_id`. `documents` lists what they agreed to share. ## 4. Read the result One call returns everything the investor consented to. Missing pieces show up under `errors`; the rest still comes back. ```python theme={null} r = requests.post( f"https://api.casparser.in/v1/kyc/digilocker/result/{session_id}", headers={"x-api-key": "YOUR_API_KEY"}, json={"fetch_documents": ["pan"]}, timeout=60, ) data = r.json() # data["identity"], data["documents"], data["fetched"]["pan"] ``` Body fields: * `fetch_documents` — documents to parse: `pan`, `aadhaar`, `driving_licence`. Default none. * `include_documents` — include the issued-documents list. Default `true`. Response: ```json theme={null} { "status": "success", "identity": { "name": "Anjali Sharma", "dob": "1990-05-16", "verified": { "pan": "ABCDE1234F", "aadhaar": "XXXXXXXX1234" } }, "consent": { "purpose": "KYC for investment account opening", "consented_at": "..." }, "documents": [ { "name": "PAN Verification Record", "doctype": "PANCR", "issuer": "Income Tax Department" } ], "fetched": { "pan": { "pan": "ABCPD1234E", "name": "RAHUL SHARMA", "dob": "22-03-1995" } }, "errors": {} } ``` Each parsed document also carries a `signature` block with the issuer's certificate details. Costs 0.25 credits per call, no matter how many documents you fetch. ## Credits | Call | Credits | | - | - | | Account lookup | 0 | | Start session | 0 | | Result | 0.25 | | Failed calls | 0 | ## Errors | Status | Cause | | - | - | | `400` | Bad request. Check `code`: `consent_required`, `consent_purpose_required`, `redirect_url_invalid`, `identifier_conflict`, `identifier_required`. | | `401` | Bad or missing key. | | `503` | DigiLocker is down. Retry with backoff. | ## Frontend note Don't ship your API key to the browser. Mint a short-lived access token (`at_`) from your backend with `POST /v1/token` and use that. ## Next steps Check a PAN's KRA registration status Parse portfolio statements # KYC PAN Status Source: https://casparser.in/docs/guides/kyc-pan-status Check KYC registration status for any PAN number across all five SEBI-registered KRAs in a single API call. ## Overview The KYC PAN Status API queries the CVL KRA public portal to retrieve KYC registration status for a given PAN number across all five SEBI-registered KRAs (CVL, NDML, CAMS, Karvy, KFin). **What you get:** * A normalized `kyc_status` enum — no raw portal strings to parse * A single `kyc_compliant` boolean for quick onboarding gate decisions * Per-KRA breakdown with dates, mode, and remarks **What you need:** * PAN number (10 characters) This endpoint queries the CVL KRA portal and may take up to 60 seconds. Set your client timeout to at least 60 seconds. ## Quick example ```python theme={null} import requests response = requests.post( "https://api.casparser.in/v1/kyc/pan/status", headers={"x-api-key": "YOUR_API_KEY"}, json={"pan_no": "ABCDE1234F"}, timeout=60 ) data = response.json() if data["kyc_compliant"]: print(f"KYC verified — status: {data['kyc_status']}, KRA: {data['active_kra']}") else: print(f"KYC not compliant — status: {data['kyc_status']}") ``` ## Response structure ```json theme={null} { "status": "success", "pan": "ABCDE1234F", "kyc_compliant": true, "kyc_status": "validated", "kyc_mode": "digilocker", "active_kra": "cvl", "registered_on": "2020-07-02", "last_updated_on": "2024-09-19", "kras": { "cvl": { "status": "validated", "registered_on": "2020-07-02", "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", ... }, "karvy": { "status": "not_checked", ... }, "kfin": { "status": "not_checked", ... } } } ``` All six keys on every KRA object are always present — `null` when not applicable. ## `kyc_status` values | Status | Meaning | `kyc_compliant` | | - | - | - | | `validated` | KYC complete, investor can transact freely | `true` | | `registered` | KYC registered; some restrictions may apply — check your platform's policy | `true` | | `under_process` | Recently submitted, still being processed by KRA | `false` | | `on_hold` | Discrepancy detected — check `remarks` on the KRA object | `false` | | `rejected` | KYC rejected — investor must submit fresh KYC | `false` | | `legacy` | Old/incomplete record — re-KYC recommended | `false` | | `not_available` | KRA was queried but has no record for this PAN | `false` | | `not_checked` | KRA was not queried by the portal | `false` | | `unknown` | Unrecognized portal string — `raw_status` field preserved | `false` | ## Handling `on_hold` When `kyc_status` is `on_hold`, check `remarks` on the active KRA object for the reason: ```python theme={null} data = response.json() if data["kyc_status"] == "on_hold": active = data["active_kra"] remarks = data["kras"][active]["remarks"] # e.g. "APPLICANT PHOTO MISMATCH IN THE APPLICATION" print(f"KYC on hold: {remarks}") ``` `remarks` may contain multiple comma-separated reasons (e.g. `"APPLICANT PHOTO MISMATCH IN THE APPLICATION,FATCA NOT APPLICABLE"`). `FATCA NOT APPLICABLE` is informational and not an error. ## `raw_status` field For exact-matched statuses (the common case), `raw_status` is `null`. `raw_status` is non-null when the portal returned a string the API hadn't seen before and used a fuzzy match instead of an exact one. If you see a non-null `raw_status` in production, please [report it](https://casparser.in/contact) so we can add it to the exact map. ## Full example ```python theme={null} import requests API_KEY = "YOUR_API_KEY" def check_kyc_status(pan: str) -> dict: """ Check KYC status for a PAN number. Returns the normalized response or raises on error. """ response = requests.post( "https://api.casparser.in/v1/kyc/pan/status", headers={"x-api-key": API_KEY}, json={"pan_no": pan}, timeout=60 # CVL KRA portal queries can take up to 60s ) response.raise_for_status() return response.json() # Basic onboarding gate data = check_kyc_status("ABCDE1234F") if data["kyc_compliant"]: # Allow onboarding print(f"KYC verified ({data['kyc_status']}) via {data['active_kra'].upper()} KRA") print(f"Registered on: {data['registered_on']}") else: status = data["kyc_status"] if status == "on_hold": active = data["active_kra"] remarks = data["kras"][active]["remarks"] print(f"KYC on hold — reason: {remarks}") elif status == "not_available": print("No KYC record found. Investor needs to complete KYC.") elif status == "rejected": print("KYC rejected. Investor must submit fresh KYC.") elif status == "under_process": print("KYC is being processed. Check again later.") elif status == "legacy": print("Old KYC record. Re-KYC recommended.") else: print(f"KYC status: {status}") ``` ## Credit usage | Operation | Credits | | - | - | | Successful lookup | 0.25 | | Failed lookup (any error) | 0 | ## Error handling | HTTP status | Cause | Action | | - | - | - | | `400` | `pan_no` missing or empty | Validate request body | | `401` | Invalid or missing API key | Check `x-api-key` header | | `503` | CVL KRA service unavailable | Retry with exponential backoff | | `504` | CVL KRA portal timed out | Retry with exponential backoff | ```python theme={null} try: data = check_kyc_status("ABCDE1234F") except requests.exceptions.Timeout: print("Request timed out — CVL KRA portal is slow. Retry.") except requests.exceptions.HTTPError as e: if e.response.status_code in (503, 504): print("KYC service unavailable — retry in a few seconds") else: raise ``` ## Next steps Parse portfolio statements after KYC is verified Fetch CAS files directly from CDSL # MCP Server Source: https://casparser.in/docs/guides/mcp-server Model Context Protocol server for AI agents. Use CAS Parser tools directly in Claude, Cursor, Windsurf, and VS Code. The official CAS Parser MCP server provides all API endpoints as tools for AI agents. Built on the [Model Context Protocol](https://modelcontextprotocol.io/) standard. ## Installation ```bash theme={null} npm install -g cas-parser-node-mcp ``` Or use directly via npx (no install required): ```bash theme={null} npx -y cas-parser-node-mcp@latest ``` ## Configuration ### Claude Desktop Add to `~/.claude.json`: ```json theme={null} { "mcpServers": { "cas_parser_node_mcp": { "command": "npx", "args": ["-y", "cas-parser-node-mcp@latest"], "env": { "CAS_PARSER_API_KEY": "your-api-key" } } } } ``` Or via CLI: ```bash theme={null} claude mcp add cas_parser_node_mcp -e CAS_PARSER_API_KEY=your-api-key -- npx -y cas-parser-node-mcp@latest ``` ### Cursor Add to Cursor Settings → Tools & MCP → `mcp.json`: ```json theme={null} { "mcpServers": { "cas_parser_node_mcp": { "command": "npx", "args": ["-y", "cas-parser-node-mcp@latest"], "env": { "CAS_PARSER_API_KEY": "your-api-key" } } } } ``` ### Windsurf 1. Open Windsurf Settings 2. Navigate to **Cascade → MCP** 3. Click **Add Server** 4. Enter: * **Name:** `cas_parser_node_mcp` * **Type:** `command` * **Command:** `npx -y cas-parser-node-mcp@latest` * **Environment:** `CAS_PARSER_API_KEY=your-api-key` ### VS Code Add to your VS Code MCP settings: ```json theme={null} { "mcpServers": { "cas_parser_node_mcp": { "command": "npx", "args": ["-y", "cas-parser-node-mcp@latest"], "env": { "CAS_PARSER_API_KEY": "your-api-key" } } } } ``` ### Remote Server (Streamable HTTP) If you prefer not to install the npm package locally: ```json theme={null} { "mcpServers": { "cas_parser_node_mcp": { "url": "https://cas-parser.stlmcp.com", "headers": { "x-api-key": "your-api-key" } } } } ``` ## Available Tools The MCP server exposes all CAS Parser API endpoints as tools: | Tool | Description | | - | - | | `smart_parse` | Auto-detect CAS type and parse PDF | | `cdsl_parse` | Parse CDSL eCAS | | `nsdl_parse` | Parse NSDL eCAS | | `cams_kfintech_parse` | Parse CAMS/KFintech CAS | | `contract_note_parse` | Parse broker contract notes | | `kfintech_generate` | Request CAS via KFintech email | | `cdsl_fetch` | CDSL OTP request (Step 1) | | `cdsl_fetch_verify` | CDSL OTP verify (Step 2) | | `inbox_connect` | Get email OAuth URL (Gmail, Outlook, Zoho) | | `inbox_cas` | List/parse CAS from inbox | | `inbox_disconnect` | Revoke email access | | `credits` | Check remaining credits | | `token_generate` | Generate access token | ## Code Mode The MCP server includes **Code Mode** — agents can write TypeScript SDK code that runs in a sandboxed environment: ```typescript theme={null} // Agent-generated code example const response = await casparser.smartParse({ file: pdfBuffer, password: "ABCDE1234F" }); console.log(`Portfolio value: ₹${response.summary.total_value}`); ``` ## Doc Search Tool The server includes a documentation search tool that lets agents query CAS Parser docs: ``` Agent: "How do I parse a CDSL CAS file?" Tool: doc_search("CDSL CAS parsing") → Returns relevant documentation snippets ``` ## Example Prompts Once configured, you can ask your AI agent: * "Parse this CAS PDF and show me my portfolio summary" * "What are my top holdings by value?" * "Check my CAS Parser credit balance" * "Import my latest CAS from my email" * "Fetch my CDSL holdings using OTP" ## Sandbox Testing Use the sandbox API key for testing: ```json theme={null} { "env": { "CAS_PARSER_API_KEY": "sandbox-with-json-responses" } } ``` The sandbox returns sample data without consuming credits. ## Troubleshooting | Issue | Solution | | - | - | | "API key invalid" | Check `CAS_PARSER_API_KEY` env var | | "Tool not found" | Restart your IDE after config change | | "Connection refused" | Ensure npx can access npm registry | | "Timeout" | Parsing takes \~30-60s; increase timeout | ## Links View on npm registry Templates and examples # Parsing CAS PDFs Source: https://casparser.in/docs/guides/parsing Upload CAS PDF files and extract structured portfolio data. Supports CDSL, NSDL, CAMS, and KFintech formats. ## Overview CAS Parser extracts structured data from Consolidated Account Statement (CAS) PDFs issued by Indian depositories and registrars. ```mermaid theme={null} flowchart LR A[CAS PDF
any type] --> B[CAS Parser
Smart API] B --> C[Unified JSON
Response] ``` ## Supported CAS types | Source | Format | Password | Coverage | | - | - | - | - | | **CDSL** | eCAS from [www.cdslindia.com/cas/logincas.aspx](http://www.cdslindia.com/cas/logincas.aspx) | PAN number | All holdings (CDSL + NSDL) | | **NSDL** | eCAS from nsdlcas.nsdl.com/ecas/ecas.php | PAN number | All holdings (NSDL + CDSL) | | **CAMS** | CAS from camsonline.com | PAN number | Mutual funds only | | **KFintech** | CAS from kfintech.com | User-defined password | Mutual funds only | Use `/v4/smart/parse` — it auto-detects the CAS type and applies the correct parser. **Cross-depository coverage:** CDSL and NSDL eCAS statements contain holdings from **both** depositories (CDSL + NSDL), not just the issuing depository. This includes demat and non-demat assets across all accounts. ## Parse a PDF file ```python Python theme={null} import requests response = requests.post( "https://api.casparser.in/v4/smart/parse", headers={"x-api-key": "YOUR_API_KEY"}, files={"file": open("cas.pdf", "rb")}, data={"password": "ABCDE1234F"} ) data = response.json() ``` ```javascript Node.js theme={null} const form = new FormData(); form.append('file', fs.createReadStream('cas.pdf')); form.append('password', 'ABCDE1234F'); const response = await fetch('https://api.casparser.in/v4/smart/parse', { method: 'POST', headers: { 'x-api-key': 'YOUR_API_KEY' }, body: form }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://api.casparser.in/v4/smart/parse \ -H "x-api-key: YOUR_API_KEY" \ -F "file=@cas.pdf" \ -F "password=ABCDE1234F" ``` ## Parse from URL If your CAS PDF is hosted online: ```python theme={null} response = requests.post( "https://api.casparser.in/v4/smart/parse", headers={ "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, json={ "pdf_url": "https://example.com/cas.pdf", "password": "ABCDE1234F" } ) ``` ## Response format All CAS types return a unified JSON structure: ```json theme={null} { "meta": { "cas_type": "CDSL", "statement_period": { "from": "2024-01-01", "to": "2024-01-31" }, "generated_at": "2024-02-01T10:30:00Z" }, "investor": { "name": "John Doe", "pan": "ABCDE1234F", "email": "john@example.com", "mobile": "9876543210", "address": "123 Main St, Mumbai", "pincode": "400001", "cas_id": "CAS123456" }, "summary": { "total_value": 2500000.00, "accounts": { "demat": { "count": 2, "total_value": 1500000.00 }, "mutual_funds": { "count": 5, "total_value": 900000.00 }, "insurance": { "count": 1, "total_value": 50000.00 }, "nps": { "count": 1, "total_value": 50000.00 } } }, "demat_accounts": [ { "demat_type": "CDSL", "dp_id": "12345678", "dp_name": "HDFC Bank", "client_id": "12345678901234", "bo_id": "12345678901234", "value": 1500000.00, "linked_holders": [...], "holdings": { "equities": [...], "corporate_bonds": [...], "government_securities": [...], "aifs": [...], "demat_mutual_funds": [...] }, "additional_info": {...} } ], "mutual_funds": [ { "folio_number": "1234567890", "amc": "HDFC Mutual Fund", "registrar": "CAMS", "value": 450000.00, "linked_holders": [...], "schemes": [...], "additional_info": {...} } ], "insurance": { "life_insurance_policies": [...] }, "nps": [...] } ``` ## Asset classes extracted CAS Parser extracts **9 asset classes** from your CAS: | Asset Class | Source | Fields | | - | - | - | | Equities | CDSL, NSDL | ISIN, name, units, value | | Mutual Funds (Demat) | CDSL, NSDL, CAMS, KFintech | ISIN, scheme, units, NAV | | Mutual Funds (Non-Demat) | CAMS, KFintech | Folio, scheme, units, NAV, transactions | | Corporate Bonds | CDSL, NSDL | ISIN, issuer, coupon, maturity | | G-Secs | CDSL, NSDL | ISIN, name, units, value | | AIFs | CDSL, NSDL | ISIN, fund, units, value | | Insurance | CDSL, NSDL | Policy, provider, sum assured | | NPS | CDSL, NSDL | PRAN, tier, fund, units | | ETFs | CDSL, NSDL | ISIN, name, units, value | ## Password requirements | CAS Type | Password Format | Example | | - | - | - | | CDSL eCAS | PAN number | `ABCDE1234F` | | NSDL eCAS | PAN number | `ABCDE1234F` | | CAMS CAS | Plain PAN | `ABCDE1234F` | | KFintech CAS | User-defined password | `Min 8 chars (1 upper, 1 lower, 1 number, 1 special)` | For all CAS types except KFintech, the password is the investor's PAN number. If parsing fails, verify the PAN and check that the PDF is an original digitally-generated statement. ## Error handling ```python theme={null} response = requests.post(...) data = response.json() if data.get("status") == "failed": error = data.get("msg") # Common errors: # - "Invalid password" — wrong PAN/encryption # - "Invalid PDF" — corrupted or scanned file # - "Unsupported format" — not a CAS file ``` ## Next steps Import CAS from user email Request CAS programmatically # Portfolio Links Source: https://casparser.in/docs/guides/portfolio-links Create branded collection pages for clients to upload CAS statements. No code required. ## Overview Portfolio Links let advisors and wealth managers create branded collection pages where clients can upload their CAS PDFs. The parsed portfolio data is emailed to the advisor automatically. **Use case:** Collect CAS from clients without building any integration. Share a link, clients upload, you get parsed data. *** ## How It Works ```mermaid theme={null} sequenceDiagram participant Advisor as Advisor participant Portal as Web Portal participant Client as Client participant Page as link.casparser.in participant API as CAS Parser Advisor->>Portal: Create branded link Portal-->>Advisor: link.casparser.in/acme-wealth Advisor->>Client: Share link Client->>Page: Visit link, upload CAS PDF Page->>API: Parse CAS API-->>Advisor: Email with parsed portfolio data ``` *** ## Getting Started 1. Go to [app.casparser.in/portfolio-links](https://app.casparser.in/portfolio-links) 2. Click **Create Link** 3. Enter your company name, slug, and notification email 4. Optionally upload a logo for your branded page 5. Share your link: `link.casparser.in/{your-slug}` That's it. No API integration needed. Everything is managed through the web portal. *** ## Use Cases | Use Case | Approach | | - | - | | **Per advisor/RM** | Each relationship manager creates their own link | | **Per client segment** | Different links for HNI, retail, corporate clients | | **Per campaign** | Track submissions by marketing campaign | | **Per branch/city** | Separate links for different office locations | *** ## Billing Portfolio Links submissions consume **0.2 credits** per submission. *** ## Related Create and manage your Portfolio Links Need a code-based integration? Use the embeddable SDK instead # RTA Sync for CAMS and KFintech Reverse Feeds Source: https://casparser.in/docs/guides/rta-sync Reverse-feed infrastructure for CAMS and KFintech holdings, transactions, SIPs, commissions, and reporting data. ## Overview > Get RTA files in. Get clean investment records out. RTA Sync is reverse-feed infrastructure for CAMS and KFintech, India's leading Registrar and Transfer Agents (RTAs). It receives RTA files, cleans provider- specific data, and delivers consistent records to MFD and wealth platforms. The product contract is intentionally simple: ```text theme={null} RTA reverse feed -> RTA Sync Engine -> clean canonical records ``` The engine detects the source format, cleans the rows, applies provider-specific transaction rules, and returns a consistent JSON response for your portfolio or wealth platform. In the back-office market, this is commonly called a **reverse feed**: CAMS and KFintech send updated holdings and transaction data to the platform that serves the distributor. RTA Sync focuses on the quality of that data transfer and the records produced from it. ## How the product works RTA reports are commonly delivered to the distributor's registered email inbox. RTA Sync can receive those files through either of these delivery paths: ```mermaid theme={null} flowchart LR A[CAMS or KFintech RTA] --> B[RTA report email] B --> C[Gmail / Outlook inbox integration] B --> D[Email Forwarding API] C --> E[RTA Sync Engine] D --> E F[Direct file upload] --> E E --> G[Clean canonical records] G --> H[Your portfolio or wealth platform] ``` The delivery connectors get the file to the engine. The RTA Sync Engine does the data work: 1. Detect the RTA and file format. 2. Map provider fields into a common vocabulary. 3. Normalize dates, numbers, signs, and transaction semantics. 4. Handle provider-specific reversal, switch, and transfer markers. 5. Return clean records, warnings, and a batch summary. Use direct file upload for the simplest integration. Inbox and email forwarding connectors deliver files to the same engine. ## Is this an RTA file reader? Yes, but the output is more useful than a file listing. The engine reads the source file and returns canonical investment records that your application can use without maintaining separate CAMS and KFintech field mappings. ## Who it is for RTA Sync is designed for: * wealth and portfolio platforms; * mutual-fund distributors and RIAs; * fintech applications onboarding investor portfolios; * engineering teams maintaining CAMS/KFintech feed pipelines. ## Two operating models ### Platforms serving multiple distributors Use RTA Sync as a data layer behind your MFD or wealth platform: * route each file to the right platform tenant, distributor, or branch; * remove separate CAMS/KFintech parsing logic from your product; * expose consistent transactions, AUM, SIP, commission, and exception data; * keep the distributor-facing product focused on onboarding, portfolio views, research, and client servicing. ### Distributor and sub-distributor networks Use RTA Sync to keep each advisor's book current: * attribute records to the correct distributor, sub-distributor, broker code, or EUIN when the source provides it; * update client, folio, scheme, AUM, SIP, transaction, and commission views; * surface rejected, pending, missing, and unmatched records for operations; * give every advisor clean portfolio data without asking them to manually clean RTA files. Each RTA report family maps to a typed data set. Transaction records are shown in this guide; the same file-level envelope carries investor, folio, AUM, SIP, NAV, exception, brokerage, mandate, and settlement-exception objects. Bulk SOA and PDF statements use the CAS document APIs. ## What RTA Sync powers RTA Sync is a data layer for the back-office functions that depend on RTA data: * portfolio tracking and current holdings; * client reporting and historical transactions; * commission and brokerage reconciliation; * CRM updates for investors, folios, SIPs, and KYC status; * operational queues for rejected, pending, missing, or unmatched records. RTA Sync does not execute buy, sell, switch, SIP, STP, or SWP orders. It keeps the data behind those workflows accurate. ## Why reverse-feed quality matters Small source errors become visible product failures. A missed dividend reinvestment, incorrectly paired switch, stale AUM snapshot, or unresolved brokerage payout can make a distributor distrust the entire back office. RTA Sync preserves source fields, emits warnings, and separates transactions, snapshots, systematic instructions, brokerage payouts, and exceptions instead of forcing every report into one row shape. ## RTA report families RTA Sync supports more than transaction files. Each report family maps to the data objects and workflows it contains: | Report family | Representative reports | RTA Sync data object | Primary use | | - | - | - | - | | Investor and folio master | KFintech MFSD211, MFSD311 | `investor`, `folio` | Client and account servicing. | | Transactions | MFSD201, MFSD221, MFSD223, MFSD307; CAMS WBR2 family | `transaction` | Portfolio activity and history. | | AUM and holdings | MFSD202, MFSD203, MFSD219, MFSD310 | `position_snapshot` | AUM, holdings, and portfolio views. | | SIP/STP/SWP lifecycle | MFSD227-MFSD231, MFSD243, MFSD244, MFSD313, MFSD314, MFSD327, MFSD331 | `systematic_instruction` | Registrations, pauses, expiries, terminations, and rejections. | | NAV reference | MFSD301, MFSD308, MFSD217 | `nav` | Scheme valuation and historical NAV. | | Dividend and bonus | MFSD309, MFSD213 | `corporate_action` | Distribution and corporate-action history. | | Rejections and pending work | MFSD218, MFSD236, MFSD241, MFSD315, MFSD316 | `exception` | Operations and remediation queues. | | KYC and data quality | MFSD232, MFSD239, MFSD240, MFSD247, MFSD258, MFSD262, MFSD263, MFSD339 | `exception`, `investor` | Missing, invalid, or compliance data. | | Brokerage and payouts | MFSD205-MFSD208, MFSD222, MFSD238, MFSD245, MFSD249, MFSD250, MFSD260; CAMS WBR77 | `brokerage_payout` | Commission, TDS, payout, cheque, and warrant workflows. | | Mandates and settlement | MFSD347A, MFSD348 | `mandate`, `settlement_exception` | Bank mandate and undelivered payment tracking. | | Non-commercial and transfer activity | MFSD237, MFSD252, MFSD352 | `transaction`, `exception` | TI/TO and non-commercial activity. | | Analytics and market reports | MFSD209, MFSD212, MFSD214, MFSD215, MFSD225, MFSD302, MFSD303 | `analytics_report` | Rankings, market size, procurement, and management views. | | Statement documents | MFSD248 and CAS documents | CAS document APIs | Bulk SOA and PDF statements are routed through document parsing rather than structured feed normalization. | The report code identifies the provider file. It does not define the data object by itself. A report such as MFSD221 can produce both investor-master and transaction data sets. CAMS WBR2 reports map to transactions, while WBR77 reports map to brokerage payouts. ## Normalize one or more files ### Request `POST /v1/rta-sync/normalize` The request uses `multipart/form-data`: | Field | Required | Description | | - | - | - | | `files` | Yes | One or more related CAMS or KFintech feed files. Send one item for a single-file request. | | `reference` | Yes | Your opaque tenant, distributor, sub-distributor, or batch reference, echoed in the response. | | `report_code` | No | Report identifier when all submitted files are the same report type. | | `provider` | No | `auto`, `cams`, or `kfintech`. Defaults to `auto`. | | `include_source` | No | Include all original source fields in record provenance. Defaults to `false`. | Supported file formats: * CSV * XLS * XLSX * DBF ### cURL ```bash cURL theme={null} curl --fail-with-body \ -X POST https://api.casparser.in/v1/rta-sync/normalize \ -H "x-api-key: $CASPARSER_API_KEY" \ -F "files=@kfintech-feed.csv" \ -F "reference=jan-2026-mfd-001" \ -F "report_code=MFSD221" \ -F "provider=auto" \ -F "include_source=true" ``` ### Python ```python Python theme={null} import os import requests with open("kfintech-feed.csv", "rb") as feed_file: response = requests.post( "https://api.casparser.in/v1/rta-sync/normalize", headers={"x-api-key": os.environ["CASPARSER_API_KEY"]}, files=[("files", ("kfintech-feed.csv", feed_file, "text/csv"))], data={ "reference": "jan-2026-mfd-001", "report_code": "MFSD221", "provider": "auto", "include_source": "true", }, timeout=60, ) response.raise_for_status() result = response.json() for file_result in result["data"]["files"]: for data_set in file_result["data_sets"]: for record in data_set["records"]: print(data_set["object_type"], record["record_key"]) ``` ### Node.js ```javascript Node.js theme={null} import fs from "node:fs"; const form = new FormData(); form.append( "files", new Blob([fs.readFileSync("kfintech-feed.csv")], { type: "text/csv" }), "kfintech-feed.csv", ); form.append("reference", "jan-2026-mfd-001"); form.append("report_code", "MFSD221"); form.append("provider", "auto"); form.append("include_source", "true"); const response = await fetch("https://api.casparser.in/v1/rta-sync/normalize", { method: "POST", headers: { "x-api-key": process.env.CASPARSER_API_KEY }, body: form, }); if (!response.ok) { throw new Error(`RTA Sync failed: ${response.status}`); } const result = await response.json(); console.log(result.data.files); ``` ## Response The response is a batch envelope. Even a one-file request returns a `files` array so the same contract works for multiple attachments. ```json theme={null} { "status": "success", "data": { "schema_version": "rta-sync.v1", "key_version": "rta-key.v1", "batch_key": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "reference": "jan-2026-mfd-001", "files": [ { "file_index": 0, "filename": "kfintech-feed.csv", "provider": "kfintech", "report_code": "MFSD221", "report_family": "transaction_investor_master", "detection_confidence": "strong", "source_sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "mapping_version": "rta-sync-2026-08", "source_schema": "kfintech_format1", "data_sets": [ { "object_type": "transaction", "record_count": 1, "records": [ { "record_type": "transaction", "record_key": "kfintech:1227:0:91046479506:2026-01-15", "source_record_key": "BSE000123", "folio_number": "91046479506", "product_code": "HDFC", "scheme_code": "HDFC0001", "transaction_id": "BSE000123", "transaction_number": "1227", "parent_transaction_number": null, "transaction_date": "2026-01-15", "nav_date": "2026-01-15", "processing_date": "2026-01-16", "nav": "100.25", "units": "10.000000", "amount": "1002.50", "currency": "INR", "transaction_type": "P", "action": "buy", "action_tag": "purchase", "effect": "increase_units", "cash_effect": "cash_outflow", "transaction_mode": "N", "transaction_status": "successful", "is_reversal": false, "reversal_resolution": null, "pan": "ABCDE1234F", "investor_name": "Example Investor", "broker_code": "ARN-12345", "sub_broker_code": null, "euin": null, "identity_quality": "strong", "relations": [], "provenance": { "source_rows": [ {"row_number": 2, "sheet": null, "source_row_key": "1227"} ], "source_fields": { "TD_PURRED": "P", "TRFLAG": "" } }, "warnings": [] } ], "warnings": [] } ], "rejected_rows": [], "summary": { "source_rows": 1, "output_records": 1, "rejected_rows": 0, "warning_count": 0 }, "warnings": [] } ], "summary": { "input_files": 1, "source_rows": 1, "output_records": 1, "rejected_rows": 0, "warning_count": 0 }, "warnings": [] } } ``` ## Canonical record fields Each file result identifies what was received and how it was mapped: | Field | Description | | - | - | | `file_index` | Zero-based upload position. Results preserve request order. | | `provider` | Detected RTA: `cams` or `kfintech`. | | `report_code` | Provider report identifier such as `MFSD221` or `WBR2`. | | `report_family` | Normalized report family. | | `detection_confidence` | `strong` or `weak`; ambiguous detection fails the batch. | | `source_sha256` | SHA-256 hash of the source file. | | `mapping_version` | Version of the mapping rules used for the file. | | `source_schema` | Detected provider schema. | | `data_sets` | Typed records emitted from the file. | Every canonical record has shared identity and provenance fields: | Field | Description | | - | - | | `record_type` | Canonical object type, such as `transaction` or `position_snapshot`. | | `record_key` | Object-specific canonical identity. Investor, folio, and scheme keys are report-family independent; event keys use provider source identity. | | `source_record_key` | Provider-native row or event identity when available. | | `identity_quality` | `strong`, `weak`, or `ambiguous` source identity. | | `relations` | Explicit reversal, supersession, switch, transfer, or lineage links. | | `provenance.source_rows` | Every source row used to produce the canonical record. | | `provenance.source_fields` | Original source fields when `include_source=true`. | | `warnings` | Row-level mapping or data-quality warnings. | Transaction records add these fields: | Field | Description | | - | - | | `folio_number` | Mutual-fund folio or account number. | | `product_code` | Provider product or AMC code when available. | | `scheme_code` | Provider scheme or plan code. | | `transaction_id` | Provider transaction or order ID when available. | | `transaction_number` | Provider transaction or line number. | | `parent_transaction_number` | Parent transaction reference, typically used for reversals. | | `transaction_date` | Normalized transaction date in `YYYY-MM-DD` format. | | `nav_date` | NAV date when supplied separately from the transaction date. | | `processing_date` | RTA processing date when supplied. | | `nav` | Decimal NAV returned as a string to preserve precision. | | `units` | Decimal unit quantity returned as a string to preserve precision. | | `amount` | Decimal transaction amount returned as a string to preserve precision. | | `transaction_type` | Original provider transaction type. | | `action` | Canonical action: `buy`, `sell`, or `no_effect`. | | `action_tag` | Canonical detail such as `purchase`, `redemption`, `switch_in`, or `reversal`. | | `effect` | Ledger effect after reversal handling: `increase_units`, `decrease_units`, or `no_effect`. | | `cash_effect` | Cash direction after reversal handling: inflow, outflow, no effect, or unknown. | | `currency` | Currency for financial amounts. RTA mutual-fund records use `INR`. | | `transaction_status` | `successful`, `reversed`, `pending`, `failed`, or `unknown`. | | `transaction_mode` | Original provider transaction mode. | | `is_reversal` | Whether the source row is a reversal. | | `reversal_resolution` | `resolved`, `unresolved`, or `null` for non-reversal records. | | `pan` | PAN when supplied by the source. | | `investor_name` | Investor name when supplied by the source. | | `broker_code` | Broker or sub-broker code when supplied by the source. | | `sub_broker_code` | Sub-distributor code when supplied by the source. | | `euin` | Employee Unique Identification Number when supplied by the source. | Fields that are not available in the source are returned as `null`. The engine does not infer transaction identity from a display name or silently convert a brokerage amount into an investment transaction amount. ## Financial value semantics * NAV, units, amounts, AUM, commissions, and TDS are decimal strings. * Canonical decimal values are non-negative. `action` and `effect` represent unit direction; `cash_effect` represents money direction instead of relying on a negative sign. * `currency` is explicit on financial records and is `INR` for the supported mutual-fund reverse feeds. * `transaction_date` is the business transaction date. NAV, processing, payout, and snapshot dates use separate fields when present. * Rounding is not applied during normalization; source precision is preserved. * Pending, failed, and unknown transactions have no unit effect. Pending and failed transactions have no cash effect. ## Identity and provenance * `schema_version` versions the response contract. * `key_version` versions the deterministic record-key algorithm. * `batch_key` is lowercase SHA-256 over the UTF-8 string `schema_version + "\n" + key_version + "\n" + pairs`, where `pairs` is the newline-joined, lexicographically sorted list of `source_sha256:mapping_version` values. File order does not change it; duplicate pairs are preserved. * `record_key` remains stable when the same source identity is reprocessed. * `identity_quality` tells you whether the key is strong, weak, or ambiguous. * `provenance.source_rows` lists every row used to create a record, including records aggregated from multiple switch or transfer rows. ## Data object categories RTA Sync uses typed data objects so report-specific fields do not get forced into a transaction row: * investor and folio masters; * AUM and holdings snapshots; * SIP, STP, and SWP instructions; * NAV and dividend/bonus reference data; * rejection, KYC, and other operational exceptions; * brokerage payouts, mandates, and settlement exceptions. PDF statements and bulk SOAs use the CAS document APIs rather than the structured reverse-feed normalization contract. Do not map those rows into the transaction object. ## Batch behavior Send one file when you have one report. Send multiple files when an email or reporting period gives you related attachments: * every input file gets its own result, source schema, typed data sets, summary, and warnings; * file results preserve upload order and include `file_index`, so duplicate filenames remain distinguishable; * `reference` is echoed for your correlation and is not used as server-side state; * RTA Sync does not merge files or compare them with earlier batches; * use the reconciliation operation for cross-file comparison and change detection. * A file-level failure fails the entire request; row-level warnings do not. For a file-level failure, the API returns `400` with an `errors[]` entry for the affected file and no successful file results. ## Provider-specific cleanup The canonical output hides source-specific rules while preserving the original values: * KFintech transfer and switch flags take precedence over the purchase or redemption purpose field. * Reversal rows retain the business action, set `is_reversal`, expose the final ledger `effect`, and link to the original record through `relations` when the parent can be resolved. * An unresolved reversal remains in the output with `reversal_resolution: "unresolved"` and an `UNMATCHED_REVERSAL` warning. * CAMS and KFintech switch and transfer rows may require different aggregation rules before output. * Unknown source columns are available through `provenance.source_fields` when requested. ## Warnings and skipped rows The API returns warnings instead of silently discarding information whenever it can continue safely. Example warning: ```json theme={null} { "row_number": 42, "code": "MISSING_TRANSACTION_ID", "message": "The source row has no provider transaction ID; record identity may be weak.", "fields": ["transaction_id"] } ``` Rows are rejected when they cannot be normalized safely. Every rejected source row appears once in the file-level `rejected_rows`; its length equals `summary.rejected_rows`. Review rejected rows and warnings before writing records into a production ledger. ## Reconciliation RTA Sync reconciliation compares a current normalized batch with a previous batch supplied by your platform and classifies new, unchanged, changed, duplicate, reversed, removed, and unmatched records. `POST /v1/rta-sync/reconcile` ```json theme={null} { "reference": "distributor_123", "current": { "schema_version": "rta-sync.v1", "key_version": "rta-key.v1", "batch_key": "sha256:1111111111111111111111111111111111111111111111111111111111111111", "reference": "jan-2026-mfd-001", "files": [ { "file_index": 0, "filename": "aum-2026-01.csv", "provider": "kfintech", "report_code": "MFSD310", "report_family": "aum", "detection_confidence": "strong", "source_sha256": "1111111111111111111111111111111111111111111111111111111111111111", "mapping_version": "rta-sync-2026-08", "source_schema": "kfintech_aum", "data_sets": [ { "object_type": "position_snapshot", "record_count": 1, "records": [ { "record_type": "position_snapshot", "record_key": "position:folio-1:scheme-1", "identity_quality": "strong", "provenance": {"source_rows": [{"row_number": 2}]}, "relations": [], "warnings": [], "as_of_date": "2026-01-31", "folio_number": "folio-1", "scheme_code": "scheme-1", "units": "100.000000", "nav": "20.00", "market_value": "2000.00", "currency": "INR" } ], "warnings": [] } ], "rejected_rows": [], "summary": {"source_rows": 1, "output_records": 1, "rejected_rows": 0, "warning_count": 0}, "warnings": [] } ], "summary": {"input_files": 1, "source_rows": 1, "output_records": 1, "rejected_rows": 0, "warning_count": 0}, "warnings": [] }, "previous": { "schema_version": "rta-sync.v1", "key_version": "rta-key.v1", "batch_key": "sha256:2222222222222222222222222222222222222222222222222222222222222222", "reference": "dec-2025-mfd-001", "files": [ { "file_index": 0, "filename": "aum-2025-12.csv", "provider": "kfintech", "report_code": "MFSD310", "report_family": "aum", "detection_confidence": "strong", "source_sha256": "2222222222222222222222222222222222222222222222222222222222222222", "mapping_version": "rta-sync-2026-08", "source_schema": "kfintech_aum", "data_sets": [ { "object_type": "position_snapshot", "record_count": 1, "records": [ { "record_type": "position_snapshot", "record_key": "position:folio-1:scheme-1", "identity_quality": "strong", "provenance": {"source_rows": [{"row_number": 2}]}, "relations": [], "warnings": [], "as_of_date": "2025-12-31", "folio_number": "folio-1", "scheme_code": "scheme-1", "units": "100.000000", "nav": "19.00", "market_value": "1900.00", "currency": "INR" } ], "warnings": [] } ], "rejected_rows": [], "summary": {"source_rows": 1, "output_records": 1, "rejected_rows": 0, "warning_count": 0}, "warnings": [] } ], "summary": {"input_files": 1, "source_rows": 1, "output_records": 1, "rejected_rows": 0, "warning_count": 0}, "warnings": [] }, "scope": { "mode": "snapshot", "providers": ["cams", "kfintech"], "report_families": ["aum", "transaction"], "current": {"complete": true, "as_of_date": "2026-01-31"}, "previous": {"complete": true, "as_of_date": "2025-12-31"} }, "mapping_policy": "require_same", "object_types": ["transaction", "position_snapshot"] } ``` The response returns per-record classifications and field-level changes: ```json theme={null} { "status": "success", "data": { "reference": "distributor_123", "scope": { "mode": "snapshot", "providers": ["cams", "kfintech"], "report_families": ["aum", "transaction"], "current": {"complete": true, "as_of_date": "2026-01-31"}, "previous": {"complete": true, "as_of_date": "2025-12-31"} }, "mapping_policy": "require_same", "summary": { "total": 1000, "new_count": 20, "unchanged_count": 950, "changed_count": 10, "duplicate_count": 5, "reversed_count": 3, "removed_count": 2, "unmatched_count": 10 }, "results": [ { "object_type": "transaction", "classification": "changed", "current_record_key": "transaction-123", "previous_record_key": "transaction-123", "changes": [ {"field": "/amount", "before": "1000.00", "after": "1002.50"} ] } ] } } ``` Reconciliation is stateless: both comparison sides are supplied in the request. The API does not retain a prior batch or decide which platform record should win. Use `event_stream` scope for observed transaction/activity files; absence from a later event feed is not a removal. Use `snapshot` with `complete: true` only when both sides contain the full declared scope. The `removed` classification is emitted only for that complete-snapshot case. Mapping, key, and schema versions must match unless `mapping_policy` is `allow_compatible`. ## Errors | Status | Meaning | | - | - | | `400` | A file is invalid, unsupported, empty, or does not match the selected provider. The entire batch fails. | | `401` | API key is missing or invalid. | | `403` | API quota or account permissions do not allow the operation. | | `500` | The RTA Sync Engine failed. Retry with backoff. | Use the `X-Request-ID` response header when contacting support. ## Common feed issues RTA files can fail for different reasons. The API reports file-level and row-level warnings so you can distinguish format problems from data problems. * Header, delimiter, encoding, or spreadsheet layout differs. * The same attachment is delivered more than once. * A report contains multiple attachments or mixed report types. * A reversal has no parent reference or uses a changed transaction number. * Switch and transfer rows are split across multiple source rows. * A scheme code, folio, PAN, or transaction ID is missing or ambiguous. * AUM or closing assets are mistaken for transaction amounts. * SIP, STP, rejection, KYC, or brokerage rows are not transaction records. * Broker, sub-broker, EUIN, and investor attribution are mixed together. Use the warning `code` and `fields` to decide whether to accept the record, retry the file, or send it to your review workflow. ## Scope The file API processes one or more RTA feed files per request. It returns clean records and typed data sets without calculating positions or brokerage payouts inside the normalization step. For inbox delivery, see [Email Inbox Import](/docs/guides/gmail-inbox) and [Inbound Email API](/docs/guides/inbound-email). Those connectors deliver files; RTA Sync cleans them. # Registry Verification Source: https://casparser.in/docs/guides/verify Verify that an advisor or distributor is genuinely registered with SEBI or AMFI, in a single API call — before you onboard them. ## Overview Registry Verification confirms a financial intermediary's regulatory registration against the official public registers. Two endpoints, one job — a yes/no answer you can gate onboarding on: * **`POST /v1/verify/sebi`** — any SEBI-registered intermediary (Investment Adviser, Research Analyst, Portfolio Manager, Stock Broker, Merchant Banker, RTA, Mutual Fund, AIF, and more). * **`POST /v1/verify/mfd`** — an AMFI Mutual Fund Distributor by ARN, screened against AMFI's suspended, terminated, and terminated-EUIN lists. **What you get:** * A single `verified` boolean to gate on * A normalized `registration_status` (`ACTIVE`, `EXPIRED`, `SUSPENDED`, `TERMINATED`, `NOT_FOUND`) * The registrant's name, validity, and category — straight from the regulator's register **What you need:** * A registration number (SEBI) or an ARN (AMFI). That's it — the category is auto-detected. A "not found" result is a successful verification: you get HTTP 200 with `verified: false` and `registration_status: NOT_FOUND`. It's billed like any other lookup. Only genuine service errors return 5xx (and those are free). ## Verify a SEBI intermediary Send the registration number — the category is detected from its sequence. No need to tell us whether it's an adviser, broker, or portfolio manager. ```python theme={null} import requests response = requests.post( "https://api.casparser.in/v1/verify/sebi", headers={"x-api-key": "YOUR_API_KEY"}, json={"registration_number": "INA000000888"}, timeout=30, ) data = response.json() if data["verified"]: print(f"{data['name']} — {data['category_label']}, {data['registration_status']}") else: print(f"Not verified — {data['registration_status']}") ``` ### Categories & auto-detection `/v1/verify/sebi` covers **every SEBI intermediary register**. When you send a `registration_number`, the character after `IN` selects the category automatically — no `type` needed: | Prefix | Category | `type` slug | | - | - | - | | `INA` | Investment Adviser (RIA) | `investment-adviser` | | `INH` | Research Analyst | `research-analyst` | | `INP` | Portfolio Manager (PMS) | `portfolio-manager` | | `INM` | Merchant Banker | `merchant-banker` | | `INR` | Registrar & Share Transfer Agent | `registrar-transfer-agent` | | `INB` `INE` `INF` `INZ` | Stock Broker | `stock-broker` | The remaining registers don't encode a category in the number (funds, FPIs, trusts, etc.), so pass `type` explicitly. These are all supported: | `type` slug | Category | | - | - | | `mutual-fund` | Mutual Fund | | `alternative-investment-fund` | Alternative Investment Fund (AIF) | | `credit-rating-agency` | Credit Rating Agency | | `custodian` | Custodian | | `debenture-trustee` | Debenture Trustee | | `venture-capital-fund` | Venture Capital Fund | | `foreign-venture-capital-investor` | Foreign Venture Capital Investor | | `foreign-portfolio-investor` | Foreign Portfolio Investor (FPI) | | `kyc-registration-agency` | KYC Registration Agency (KRA) | | `infrastructure-investment-trust` | Infrastructure Investment Trust (InvIT) | | `real-estate-investment-trust` | Real Estate Investment Trust (REIT) | | `esg-rating-provider` | ESG Rating Provider | | `vault-manager` | Vault Manager | Short aliases are accepted anywhere a `type` is expected: `ria`, `ia`, `ra`, `pms`, `broker`, `rta`, `mf`, `aif`, `cra`, `vcf`, `fvci`, `fpi`, `kra`, `invit`, `reit`. `type` is also how you **search by name** (there's no number to detect from), or **override** detection: ```python theme={null} # Search by name — type is required when there's no registration number requests.post( "https://api.casparser.in/v1/verify/sebi", headers={"x-api-key": "YOUR_API_KEY"}, json={"name": "Motilal Oswal", "type": "portfolio-manager"}, timeout=30, ) ``` The registration number is authoritative. If you send both `registration_number` and `name`, the name is ignored — we never let a mismatched name turn a real registration into a false "not found". ### SEBI response ```json theme={null} { "status": "success", "verified": true, "authority": "SEBI", "category": "investment-adviser", "category_label": "Investment Adviser (RIA)", "registration_number": "INA000000888", "name": "360 ONE Investment Adviser and Trustee Services Limited", "registration_status": "ACTIVE", "valid_from": "2014-01-22", "valid_till": null, "is_perpetual": true, "address": "IIFL Centre, Kamala Mills, Lower Parel, MUMBAI, MAHARASHTRA, 400013", "email": "advcompliance@example.com", "telephone": "0912345678", "contact_person": "Compliance Officer", "detected_by": "registration_number" } ``` Every key is always present on a 200 response — `null` when it doesn't apply (e.g. a `NOT_FOUND` result carries the same keys with nulls). Use `verified` as your gate. ## Verify an AMFI distributor (ARN) ```python theme={null} import requests response = requests.post( "https://api.casparser.in/v1/verify/mfd", headers={"x-api-key": "YOUR_API_KEY"}, json={"arn": "89762"}, timeout=30, ) data = response.json() print(data["registration_status"]) # ACTIVE / SUSPENDED / TERMINATED / NOT_FOUND ``` The ARN — and the EUIN attached to it — is screened against AMFI's adverse lists. A distributor who has been suspended or terminated for cause is reported as such, not merely "not found": ```json theme={null} { "status": "success", "verified": false, "authority": "AMFI", "category": "mutual-fund-distributor", "category_label": "Mutual Fund Distributor (ARN)", "arn": "89762", "name": null, "euin": null, "kyd_compliant": null, "registration_status": "SUSPENDED", "valid_from": null, "valid_till": null, "is_perpetual": false, "address": null, "pincode": null, "negative_list_hit": true, "negative_list": { "type": "suspended", "since": "May 26, 2006" } } ``` Only an **exact** ARN matches. A partial or non-existent ARN returns `NOT_FOUND` — we never resolve a fuzzy search to some other distributor's record. An active distributor returns `verified: true`, `kyd_compliant`, and the ARN's `valid_from`/`valid_till`. `negative_list_hit` is `false` and `negative_list` is `null`. ### Adverse-list screening Every ARN is checked against three AMFI lists. A hit populates `negative_list` and overrides `registration_status`: | `negative_list.type` | Source list | `registration_status` | | - | - | - | | `suspended` | ARN suspended from mutual fund business | `SUSPENDED` | | `terminated` | ARN terminated | `TERMINATED` | | `euin_terminated` | The EUIN attached to the ARN was terminated | `TERMINATED` | `negative_list.since` carries the effective date AMFI published (e.g. `"May 26, 2006"`). On an active record, three AMFI-specific fields matter: * **`kyd_compliant`** — the distributor's Know Your Distributor (KYD) status. `true` when compliant. * **`euin`** — the Employee Unique Identification Number of the individual under the ARN. * **`valid_from` / `valid_till`** — the ARN's current validity window (ARNs are renewed, never perpetual). ## `registration_status` values | Status | Meaning | `verified` | | - | - | - | | `ACTIVE` | Currently registered and in good standing | `true` | | `EXPIRED` | Registration validity end date has passed | `false` | | `SUSPENDED` | On AMFI's suspended list (see `negative_list`) | `false` | | `TERMINATED` | On AMFI's terminated / terminated-EUIN list | `false` | | `NOT_FOUND` | No current registration in this register | `false` | `SUSPENDED` and `TERMINATED` are MFD-only. SEBI's register lists current registrations, so a lapsed or cancelled SEBI registration simply reads `NOT_FOUND`. ## Credit usage | Operation | Credits | | - | - | | Successful verification (found **or** not found) | 0.25 | | Failed lookup (any 5xx) | 0 | Both endpoints bill under a single `verify` feature. The register the lookup hit (SEBI vs AMFI) is recorded on the usage event, so you can still break usage down by source. ## Error handling | HTTP status | Cause | Action | | - | - | - | | `400` | Missing input, unknown `type`, or `name` without `type` | Fix the request body | | `401` | Invalid or missing API key | Check the `x-api-key` header | | `403` | `verify` not enabled on your plan, or quota exhausted | Check your plan / top up | | `422` | Category couldn't be detected from the registration number | Pass an explicit `type` | | `500` | The verification service is temporarily unavailable | Retry with backoff | ```python theme={null} try: r = requests.post( "https://api.casparser.in/v1/verify/sebi", headers={"x-api-key": "YOUR_API_KEY"}, json={"registration_number": "INA000000888"}, timeout=30, ) r.raise_for_status() data = r.json() except requests.exceptions.HTTPError as e: if e.response.status_code == 500: print("Register unavailable — retry in a few seconds") else: raise ``` ## Next steps Check an investor's KYC status across all five KRAs Parse portfolio statements once the intermediary is verified # Introduction Source: https://casparser.in/docs/index The portfolio import API for Indian fintech. Parse CAS PDFs, fetch holdings via OTP, import from email. 9 asset classes. 5-minute integration. CAS Parser is the portfolio import API for Indian fintech. Trusted by **100+ companies** including Angel One, Dezerv, and Grip Invest. Parse any CAS PDF, fetch live holdings via CDSL OTP, or import directly from email (Gmail, Outlook, Zoho) — all through one API with **99.9% uptime**. 10 free credits/month. No credit card required. 15-min call to discuss your integration ## Quickstart Parse your first CAS in 5 lines of code Get started with JavaScript/TypeScript Make API calls from the command line Drop-in React widget for frontend apps ## How it works ```mermaid theme={null} flowchart LR subgraph Input A1[PDF Upload] A2[Gmail Import] A3[CDSL OTP] A4[MF CAS Generator] end subgraph CASParser["CAS Parser"] B1[CDSL Parser] B2[NSDL Parser] B3[CAMS Parser] B4[KFintech Parser] end subgraph Output C1[Equities] C2[Mutual Funds] C3[Bonds & AIFs] C4[NPS/Insurance] end Input --> CASParser CASParser --> Output Output --> D[Unified JSON] ``` **One API, any CAS format.** Send a PDF, get structured JSON. Works with all Indian depositories and registrars — CDSL, NSDL, CAMS, KFintech. **Cross-depository coverage:** CDSL and NSDL eCAS statements contain holdings from **both** depositories, not just the issuing one. This consolidated view includes all demat and non-demat assets across CDSL and NSDL accounts. ## Explore Upload PDFs and extract portfolio data Read CAMS or KFintech files and get clean investment records Import CAS files from Gmail, Outlook, or Zoho Real-time holdings via OTP verification Request CAS statements programmatically ## Why CAS Parser? | | CAS Parser | Account Aggregator | Build In-House | | - | - | - | - | | **Time to integrate** | 5 minutes | 5-10 months | 3-6 months | | **Monthly cost** | From ₹999/mo | ₹5-25 lakhs setup + recurring | ₹10-50 lakhs setup + team | | **Regulatory approval** | None needed | NBFC-AA license required | None | | **Asset classes** | 9 (demat + non-demat) | All financial data | Custom scope | | **Data freshness** | Real-time (OTP fetch) | Consent-based | Manual | | **Tamper detection** | Built-in | No | Custom build | | **On-premise option** | Available | No | N/A | 10 free credits/month. No credit card required. Questions about integration? We can help. # FAQ Source: https://casparser.in/docs/knowledge-base/faq Frequently asked questions about CAS Parser API, pricing, and integration. ## General CAS Parser is an API that extracts structured data from Indian financial portfolio statements (CAS PDFs). It supports CDSL, NSDL, CAMS, and KFintech formats. * **Fintech apps** — Portfolio tracking, wealth management * **Banks** — Client onboarding, KYC verification * **Tax platforms** — Capital gains calculation * **Research firms** — Portfolio analysis * **Internal audit** — Compliance and verification CAS Parser extracts **9 asset classes**: 1. Equities 2. Mutual Funds (demat & non-demat) 3. Corporate Bonds 4. Government Securities 5. AIFs (Alternative Investment Funds) 6. ETFs 7. Insurance 8. NPS (National Pension System) 9. Contract Notes (broker trades) ## Pricing & Credits | Plan | Credits/Month | Price | | - | - | - | | **Free** | 10 | ₹0 | | **Lite 50** | 50 | ₹999/month | | **Pro 200** | 200 | ₹3,500/month | | **Ultra 750** | 750 | ₹10,500/month | [View full pricing](https://casparser.in/pricing) | Operation | Credits | | - | - | | CAS parsing (any type) | 1.0 | | Contract note parsing | 0.5 | | CDSL OTP fetch | 0.5 | | KFintech CAS generation | 0.5 | | Gmail inbox (per pull, any number of files) | 0.2 | | Inbound email (per processed email) | 0.2 | | Portfolio Links (per submission) | 0.2 | No. Credits reset monthly on your billing date. Unused credits don't carry forward. Refunds are subject to contract terms. Please reach out to [support](https://casparser.in/contact) to discuss your specific situation. ## Technical **Supported:** * Original digital PDFs from CDSL, NSDL, CAMS, KFintech * Password-protected PDFs **Not supported:** * Scanned PDFs (no OCR) * Photographed documents * Tampered or modified PDFs | CAS Type | Password | | - | - | | **CDSL eCAS** | Your PAN number | | **NSDL eCAS** | Your PAN number | | **CAMS CAS** | Your PAN number | | **KFintech CAS** | User-defined password (set during generation) | The password is typically mentioned in the email that delivered the CAS. | Operation | Duration | | - | - | | CAS parsing | 5-30 seconds | | CDSL OTP fetch (Step 1) | 15-20 seconds | | CDSL OTP fetch (Step 2) | 2-5 seconds | | KFintech generation | 1-2 minutes | 10MB per file. Larger files will be rejected. No. PDFs are deleted immediately after parsing. We don't store any uploaded files. Yes. We use: * TLS 1.3 encryption in transit * AES-256 encryption at rest * India-hosted infrastructure (DigitalOcean Bangalore) * No data retention after parsing * SOC 2 compliance (in progress) [Read more about security](/docs/resources/security) ## Integration CAS Parser is a REST API — works with any language that can make HTTP requests. **Official SDKs:** * Portfolio Connect SDK (React, Next.js, vanilla JS) **Community examples:** * Python (requests) * Node.js (fetch, axios) * PHP, Ruby, Go, Java, .NET [View examples](/docs/quickstart) Yes, but use **access tokens** instead of API keys: 1. Generate token from your backend (`POST /v1/token`) 2. Pass `at_` token to frontend 3. Use Portfolio Connect SDK for best UX [Learn about authentication](/docs/learn/authentication) Use the sandbox API key: ``` sandbox-with-json-responses ``` Returns sample data for all endpoints. Rate limited to 10 requests/minute. [Sandbox guide](/docs/resources/sandbox) * **Standard plans:** 60 requests/minute * **Enterprise:** Custom limits ## Features Yes. Use the **CDSL OTP Fetch** feature: 1. `POST /v4/cdsl/fetch` — Request OTP 2. User enters OTP from SMS 3. `POST /v4/cdsl/fetch/{session_id}/verify` — Get CAS [CDSL Fetch guide](/docs/guides/cdsl-fetch) Yes. The **Email Inbox Import** feature uses OAuth to import CAS files directly from a user's inbox. Supports Gmail, Outlook, and Zoho. [Email Import guide](/docs/guides/gmail-inbox) Yes. Use the **CAS Generator** to trigger email mailback (KFintech + CAMS): ```python theme={null} POST /v4/generate ``` The CAS is emailed to the user within 1-2 minutes. [CAS Generator guide](/docs/guides/cas-generator) Yes. Parse broker contract notes from: * Zerodha * Groww * Upstox * ICICI Direct [Contract Notes guide](/docs/guides/contract-notes) ## Support * **Email:** [Contact Support](https://casparser.in/contact) (\< 24hr response) * **AI Assistant:** [PortfolioGPT](https://chatgpt.com/g/g-698778fc35f881919a8e57c537e211c6-india-portfoliogpt-powered-by-casparser) * **Documentation:** You're reading it! [Contact support](/docs/resources/support) Yes. Enterprise customers can request: * Custom integrations * On-premise deployment * White-label solutions * Dedicated support [Speak to an engineer](https://cal.com/sameer-kumar/casparser-1-1-with-sameer) to discuss your requirements, or email us at [casparser.in/contact](https://casparser.in/contact). 1. Check the error message in the response 2. Verify PDF password (check CAS email) 3. Ensure PDF is original (not scanned) 4. Contact support with the `X-Request-ID` header [Error handling guide](/docs/learn/error-handling) ## Compliance CAS Parser is a **parsing service**, not a financial service. We don't require RBI approval. We parse publicly available CAS files that investors already possess. Yes. We comply with India's Digital Personal Data Protection Act (DPDPA): * No data retention after parsing * User consent required for email OAuth * Data hosted in India * Encrypted storage and transmission Yes. CAS Parser includes **tamper detection** — modified or scanned PDFs are rejected. This makes it suitable for: * Loan underwriting * KYC verification * Credit assessment [Speak to an engineer](https://cal.com/sameer-kumar/casparser-1-1-with-sameer) if you want to discuss tamper detection for your underwriting workflow. # On-Premise Deployment Source: https://casparser.in/docs/knowledge-base/on-premise Self-hosted CAS Parser for enterprise customers. Parse portfolios locally without external API calls. ## Overview On-premise deployment allows you to run CAS Parser on your own infrastructure — all parsing happens locally within your network. No data leaves your servers. **Use cases:** * Banks and financial institutions with strict data policies * Compliance requirements for on-shore data processing * High-volume parsing (eliminate API rate limits) * Air-gapped environments On-premise deployment is **available for enterprise customers only**. Contact [sales](https://casparser.in/contact) to discuss licensing. ## Architecture ```mermaid theme={null} flowchart LR A[Your Application] --> B[CAS Parser
Docker Container] B --> C[Parsed JSON
Response] style B fill:#e8f5e9 Note1[No external
network calls] ``` **What you get:** * Docker image with CAS Parser runtime * License key for activation * Local parsing (no external API calls) * All features: CDSL, NSDL, CAMS, KFintech **What you don't get:** * CDSL OTP Fetch (requires CDSL portal access) * Email Inbox Import (requires OAuth with our credentials) * CAS Generator (requires calls to RTAs) ## Deployment ### 1. Receive Docker Image We'll provide a private Docker image URL and credentials: ```bash theme={null} docker login registry.casparser.in docker pull registry.casparser.in/casparser:enterprise ``` ### 2. Run Container ```bash theme={null} docker run -d \ -p 8080:8080 \ -e LICENSE_KEY="your-license-key" \ -e MAX_WORKERS=4 \ --name casparser \ registry.casparser.in/casparser:enterprise ``` ### 3. Parse CAS PDFs ```python theme={null} import requests response = requests.post( "http://localhost:8080/v4/smart/parse", files={"file": open("cas.pdf", "rb")}, data={"password": "ABCDE1234F"} ) data = response.json() print(data['investor']['name']) ``` ## Configuration | Environment Variable | Description | Default | | - | - | - | | `LICENSE_KEY` | Your enterprise license key (required) | - | | `MAX_WORKERS` | Gunicorn worker processes | 4 | | `WORKER_TIMEOUT` | Request timeout in seconds | 120 | | `LOG_LEVEL` | Logging verbosity | `INFO` | ## License Validation The container validates your license key on startup: * **Valid license** → Container starts, parsing enabled * **Invalid/expired license** → Container exits with error * **Network check** → License verified once at startup (one-time call to `license.casparser.in`) After initial validation, the container runs fully offline. No subsequent external calls are made. ## Supported Features | Feature | On-Premise | Cloud API | | - | - | - | | CDSL CAS parsing | ✅ | ✅ | | NSDL CAS parsing | ✅ | ✅ | | CAMS CAS parsing | ✅ | ✅ | | KFintech CAS parsing | ✅ | ✅ | | Contract Notes | ✅ | ✅ | | CDSL OTP Fetch | ❌ | ✅ | | Email Inbox Import | ❌ | ✅ | | KFintech Generator | ❌ | ✅ | ## Scaling **Horizontal scaling:** ```yaml theme={null} # docker-compose.yml version: '3.8' services: casparser: image: registry.casparser.in/casparser:enterprise environment: LICENSE_KEY: your-license-key deploy: replicas: 3 ports: - "8080-8082:8080" ``` **Load balancing:** Use Nginx to distribute traffic across multiple containers. ## Updates We release quarterly updates with bug fixes and new features: * **Automatic updates:** Not available (air-gapped) * **Manual updates:** Pull new image version, restart container * **Version pinning:** Recommended for production (`casparser:enterprise-v2.1.0`) Contact support for update notifications. ## Monitoring The container exposes health endpoints: ```bash theme={null} # Health check curl http://localhost:8080/health # {"status": "ok", "version": "2.1.0"} # License status curl http://localhost:8080/license # {"valid": true, "expires": "2026-12-31"} ``` Integrate with your existing monitoring (Prometheus, Datadog, etc.). ## Pricing On-premise licenses are based on: * **Concurrent workers** (not request volume) * **Annual licensing** (no per-parse fees) * **Support SLA** Includes unlimited parsing within worker limits. ## Getting Started Email [casparser.in/contact](https://casparser.in/contact) with your requirements (workers, deployment environment, SLA needs) We'll provision a license key and share Docker registry credentials Pull image, run container, validate parsing with your CAS samples Deploy to production with our support team standing by ## Support Enterprise license includes: * **Email support** (SLA-based response times) * **Slack/Teams channel** (for critical issues) * **Quarterly health checks** (our team reviews your setup) * **Version upgrade assistance** (guided migration) ## FAQ Yes. After initial license validation, the container runs fully offline. Copy the Docker image to your air-gapped network via USB/DVD. The container will stop accepting new parsing requests. Existing deployments continue running, but you'll need to renew to process new PDFs. No. The image is proprietary and license-protected. Modifications will invalidate your license. On-premise deployment uses the same battle-tested code as our cloud API. Source code access is not included, but we can discuss custom development for strategic partners. ## Get Started Walk through deployment, licensing, and architecture Send us your requirements and we'll follow up # Use Cases Source: https://casparser.in/docs/knowledge-base/use-cases Real-world applications of CAS Parser across fintech, wealth management, and financial services. Used by **100+ companies** across portfolio tracking, wealth management, credit underwriting, and tax filing. ## Portfolio Tracking Apps ### Challenge Users have investments across multiple platforms (Zerodha, Groww, Kuvera, etc.). Manually entering transaction history is tedious and error-prone. ### Solution Let users upload their CAS PDF to import their entire portfolio history in seconds. ### Implementation ```python theme={null} # User uploads CAS PDF cas_data = casparser.parse(pdf_file, password) # Import to your database for folio in cas_data['mutual_funds']: for scheme in folio['schemes']: for txn in scheme['transactions']: db.insert_transaction({ 'user_id': user.id, 'date': txn['date'], 'scheme': scheme['scheme'], 'amount': txn['amount'], 'units': txn['units'], 'nav': txn['nav'] }) ``` ### Benefits * **70% faster onboarding** — Import years of data in seconds * **100% accurate** — No manual entry errors * **Better UX** — Users see complete portfolio immediately ### Customers Angel One, Neo Wealth, Dezerv *** ## Wealth Management Platforms ### Challenge Advisors need to understand client portfolios before making recommendations. Clients often have scattered investments across 5-10 platforms. ### Solution Request CAS via KFintech API, parse automatically, and generate consolidated reports. ### Implementation ```python theme={null} # Step 1: Request CAS generation casparser.generate_cas( email=client.email, pan=client.pan, from_date="2020-01-01", to_date="2024-12-31" ) # Step 2: Client receives CAS in email, uploads to portal # Step 3: Parse and analyze cas_data = casparser.parse(pdf_file, password) # Generate asset allocation report allocation = calculate_allocation(cas_data) recommendations = generate_recommendations(allocation, client.risk_profile) ``` ### Benefits * **Comprehensive view** — All investments in one place * **Faster advice** — Automated portfolio analysis * **Better decisions** — Data-driven recommendations ### Customers Grip Invest, KFintech *** ## Advisor & Distributor Onboarding ### Challenge Platforms and networks that onboard financial advisors and mutual fund distributors must confirm each one is genuinely registered — an RIA with SEBI, a distributor with AMFI — and hasn't been suspended or terminated. Checking the regulators' websites by hand doesn't scale. ### Solution Verify a registration number against the official register in one API call, before onboarding. `verified` gates the decision, and suspended or terminated distributors are flagged — not just reported as "not found". ### Implementation ```python theme={null} # Verify a SEBI Registered Investment Adviser (category auto-detected from the number) ria = casparser.verify_sebi(registration_number="INA000000888") if not ria["verified"]: reject(f"Not an active RIA — {ria['registration_status']}") # Verify a mutual fund distributor by ARN, with AMFI adverse-list screening mfd = casparser.verify_mfd(arn="89762") if mfd["registration_status"] in ("SUSPENDED", "TERMINATED"): escalate(f"ARN on AMFI {mfd['registration_status']} list") ``` ### Benefits * **Compliant onboarding** — Confirm SEBI/AMFI registration before granting access * **Catch bad actors** — Suspended and terminated distributors are flagged automatically * **Zero manual lookups** — One call replaces checking the regulators' portals by hand *** ## Tax Filing Platforms ### Challenge Calculating capital gains requires transaction-level data. Users don't have organized records. ### Solution Parse CAS to extract all buy/sell transactions, calculate FIFO/LIFO gains automatically. ### Implementation ```python theme={null} cas_data = casparser.parse(pdf_file, password) # Extract equity transactions equity_txns = [] for account in cas_data['demat_accounts']: for equity in account['equities']: for txn in equity.get('transactions', []): equity_txns.append({ 'isin': equity['isin'], 'date': txn['date'], 'type': txn['type'], 'quantity': txn['quantity'], 'price': txn['price'] }) # Calculate capital gains gains = calculate_capital_gains(equity_txns, method='FIFO') ``` ### Benefits * **Accurate ITR** — No missed transactions * **Time savings** — Automated gain calculation * **Audit trail** — Complete transaction history *** ## Credit Underwriting ### Challenge Verifying borrower's financial status requires authentic portfolio data. Self-reported data is unreliable. ### Solution Request CAS directly from CDSL via OTP, verify authenticity, assess creditworthiness. ### Implementation ```python theme={null} # Step 1: Request OTP from CDSL session = casparser.cdsl_fetch(pan=borrower.pan, dob=borrower.dob) # Step 2: Borrower enters OTP cas_urls = casparser.cdsl_verify(session_id, otp) # Step 3: Parse and assess cas_data = casparser.parse_from_url(cas_urls['detailed']) # Calculate credit score factors total_portfolio_value = cas_data['summary']['total_value'] equity_exposure = calculate_equity_percentage(cas_data) loan_eligibility = assess_eligibility(total_portfolio_value, equity_exposure) ``` ### Benefits * **Tamper-proof** — Direct from depository * **Real-time** — Instant verification * **Accurate** — No self-reporting bias *** ## Financial Research ### Challenge Analyzing investment trends requires aggregated portfolio data. Individual CAS files are unstructured. ### Solution Parse thousands of CAS files, aggregate data, identify patterns. ### Implementation ```python theme={null} # Batch processing results = [] for cas_file in cas_files: data = casparser.parse(cas_file, password) results.append({ 'total_value': data['summary']['total_value'], 'equity_count': len(data['demat_accounts'][0]['equities']), 'mf_count': sum(len(f['schemes']) for f in data['mutual_funds']), 'age_group': classify_age(data['investor']['name']) }) # Aggregate insights avg_portfolio_value = statistics.mean(r['total_value'] for r in results) popular_stocks = Counter(e['isin'] for r in results for e in r['equities']).most_common(10) ``` ### Benefits * **Large-scale analysis** — Process thousands of CAS files * **Market insights** — Identify trends and patterns * **Research reports** — Data-driven publications *** ## Robo-Advisors ### Challenge Providing personalized advice requires understanding current portfolio allocation. ### Solution Parse CAS, analyze allocation, suggest rebalancing. ### Implementation ```python theme={null} cas_data = casparser.parse(pdf_file, password) # Current allocation current = { 'equity': calculate_equity_value(cas_data), 'debt': calculate_debt_value(cas_data), 'gold': calculate_gold_value(cas_data) } # Target allocation (based on risk profile) target = {'equity': 0.6, 'debt': 0.3, 'gold': 0.1} # Rebalancing suggestions suggestions = generate_rebalancing_plan(current, target) ``` ### Benefits * **Personalized advice** — Based on actual holdings * **Automated rebalancing** — Data-driven suggestions * **Better outcomes** — Optimized portfolios *** ## Insurance Platforms ### Challenge Assessing insurance needs requires knowing existing coverage and assets. ### Solution Parse CAS to extract insurance policies and portfolio value, recommend coverage. ### Implementation ```python theme={null} cas_data = casparser.parse(pdf_file, password) # Extract existing insurance existing_coverage = sum(p['sum_assured'] for p in cas_data['insurance']) # Calculate recommended coverage portfolio_value = cas_data['summary']['total_value'] recommended_coverage = portfolio_value * 10 # 10x rule # Gap analysis coverage_gap = recommended_coverage - existing_coverage ``` ### Benefits * **Accurate needs** — Based on real assets * **Gap identification** — Underinsured users * **Cross-sell** — Targeted recommendations *** ## Get Started 10 free credits. No credit card. Have questions? Jump on a quick call. Explore all endpoints # Authentication Source: https://casparser.in/docs/learn/authentication API keys, access tokens, and security best practices for CAS Parser API. ## API Keys All CAS Parser API requests require authentication via the `x-api-key` header. ### Getting Your API Key #### From the dashboard 1. Sign up at [app.casparser.in](https://app.casparser.in) 2. Navigate to **Developers** → **API Keys** 3. Click **Generate API Key** 4. Copy and store securely #### From a coding agent (automated) If you're using a coding agent (Claude Code, Cursor, etc.), it can obtain an API key for you automatically. The agent generates a one-time URL, you open it in your browser and approve, and the agent receives the key — no copy-paste needed. ```bash theme={null} # 1. Agent generates a random token TOKEN=$(openssl rand -hex 32) # 2. You open this URL and sign in # https://app.casparser.in/agent-auth?token=${TOKEN}&client_name=YOUR_AGENT_NAME # 3. Agent polls until you approve curl -s https://api.casparser.in/v1/agent-auth/token/${TOKEN} # → {"status": "approved", "api_key": "sk_...", "email": "..."} ``` See the [API reference](/docs/api-reference/introduction) for full details on the agent auth endpoints. **Never expose your API key in client-side code.** Use access tokens for frontend applications. ### Using API Keys ```python Python theme={null} import requests response = requests.post( "https://api.casparser.in/v4/smart/parse", headers={"x-api-key": "YOUR_API_KEY"}, files={"file": open("cas.pdf", "rb")}, data={"password": "ABCDE1234F"} ) ``` ```javascript Node.js theme={null} const response = await fetch('https://api.casparser.in/v4/smart/parse', { method: 'POST', headers: { 'x-api-key': 'YOUR_API_KEY' }, body: formData }); ``` ```bash cURL theme={null} curl -X POST https://api.casparser.in/v4/smart/parse \ -H "x-api-key: YOUR_API_KEY" \ -F "file=@cas.pdf" \ -F "password=ABCDE1234F" ``` ## Access Tokens For frontend/SDK applications, use short-lived access tokens instead of exposing your API key. ### Token Flow ```mermaid theme={null} flowchart LR A[Backend
API Key] --> B[CAS Parser
/v1/token] B --> C[Frontend
at_ token] ``` ### Generate Access Token **Backend (server-side):** ```python theme={null} import requests response = requests.post( "https://api.casparser.in/v1/token", headers={"x-api-key": "YOUR_API_KEY"}, json={"expiry_minutes": 30} ) access_token = response.json()["access_token"] # Returns: at_xxxxxxxxxxxxxxxxxx ``` **Frontend (client-side):** ```javascript theme={null} // Get token from your backend const { access_token } = await fetch('/api/casparser/token').then(r => r.json()); // Use token in place of API key const response = await fetch('https://api.casparser.in/v4/smart/parse', { method: 'POST', headers: { 'x-api-key': access_token }, // Use token here body: formData }); ``` ### Token Properties | Property | Value | | - | - | | **Prefix** | `at_` | | **Max TTL** | 60 minutes | | **Scope** | All `/v4/*` endpoints | | **Restrictions** | Cannot generate other tokens, cannot access billing | ### Verify Token ```python theme={null} response = requests.post( "https://api.casparser.in/v1/token/verify", headers={"x-api-key": "YOUR_API_KEY"}, json={"access_token": "at_xxx"} ) data = response.json() # {"valid": true, "expires_at": "2024-01-15T11:30:00Z"} ``` ## Security Best Practices ### 1. Environment Variables **Never hardcode API keys:** ```python theme={null} # ❌ Bad API_KEY = "sk_live_abc123" # ✅ Good import os API_KEY = os.environ.get("CASPARSER_API_KEY") ``` ### 2. Backend Token Generation **Create a backend endpoint:** ```python theme={null} # Flask example from flask import Flask, jsonify import requests import os app = Flask(__name__) @app.route('/api/casparser/token') def get_token(): response = requests.post( 'https://api.casparser.in/v1/token', headers={'x-api-key': os.environ['CASPARSER_API_KEY']}, json={'expiry_minutes': 30} ) return jsonify(response.json()) ``` ### 3. Rate Limiting Implement rate limiting on your backend: ```python theme={null} from flask_limiter import Limiter limiter = Limiter(app, default_limits=["60 per minute"]) @app.route('/api/parse') @limiter.limit("10 per minute") def parse_cas(): # Your parsing logic pass ``` ### 4. HTTPS Only Always use HTTPS for API requests. The API will reject HTTP requests. ### 5. Key Rotation Rotate API keys periodically: 1. Generate new API key in dashboard 2. Update environment variables 3. Deploy updated code 4. Delete old API key ## Sandbox Key For testing and development: ``` sandbox-with-json-responses ``` * Returns sample data * No credit consumption * Rate limited to 10 requests/minute ## Troubleshooting | Error | Cause | Solution | | - | - | - | | `401 Unauthorized` | Invalid API key | Check key in dashboard | | `401 Unauthorized` | Missing header | Add `x-api-key` header | | `403 Forbidden` | Expired token | Generate new access token | | `403 Forbidden` | Quota exceeded | Check credits with `/v1/credits` | ## Next Steps Make your first API call Handle authentication errors # Error Handling Source: https://casparser.in/docs/learn/error-handling Best practices for handling errors in CAS Parser API. Retry logic, timeouts, and error codes. ## Error Response Format All errors return a consistent structure: ```json theme={null} { "status": "failed", "msg": "Invalid password for PDF", "code": "INVALID_PASSWORD" } ``` ## HTTP Status Codes | Status | Meaning | Retry? | | - | - | - | | **200** | Success | N/A | | **400** | Bad request | No — fix request | | **401** | Invalid API key | No — check credentials | | **402** | Insufficient credits | No — upgrade plan | | **422** | Invalid PDF/password | No — check inputs | | **429** | Rate limited | Yes — exponential backoff | | **500** | Server error | Yes — retry with backoff | | **502/503** | Service unavailable | Yes — retry with backoff | ## Error Codes ### Client Errors (4xx) | Code | HTTP | Description | Solution | | - | - | - | - | | `INVALID_API_KEY` | 401 | API key missing or invalid | Check `x-api-key` header | | `INSUFFICIENT_CREDITS` | 402 | No credits remaining | Upgrade plan or wait for reset | | `INVALID_PASSWORD` | 422 | Wrong PDF password | Verify PAN format for CDSL/NSDL | | `INVALID_PDF` | 422 | Corrupted or scanned PDF | Use original digital PDF | | `UNSUPPORTED_FORMAT` | 422 | Not a CAS file | Upload CAS, not bank statement | | `FILE_TOO_LARGE` | 422 | PDF exceeds 10MB | Compress or split PDF | | `RATE_LIMITED` | 429 | Too many requests | Wait 60s or implement backoff | ### Server Errors (5xx) | Code | HTTP | Description | Solution | | - | - | - | - | | `PARSING_ERROR` | 500 | Internal parsing failure | Retry or contact support | | `TIMEOUT` | 504 | Request took too long | Retry with longer timeout | ## Retry Logic ### When to Retry **Retry these errors:** * 429 (Rate Limited) * 500 (Server Error) * 502/503 (Service Unavailable) * 504 (Timeout) **Don't retry these:** * 400 (Bad Request) * 401 (Invalid API Key) * 402 (Insufficient Credits) * 422 (Invalid PDF/Password) ### Exponential Backoff ```python Python theme={null} import requests import time def parse_with_retry(file_path, password, max_retries=3): for attempt in range(max_retries): try: response = requests.post( "https://api.casparser.in/v4/smart/parse", headers={"x-api-key": API_KEY}, files={"file": open(file_path, "rb")}, data={"password": password}, timeout=60 ) # Don't retry client errors if 400 <= response.status_code < 500 and response.status_code != 429: response.raise_for_status() # Retry server errors and rate limits if response.status_code >= 500 or response.status_code == 429: if attempt < max_retries - 1: wait_time = 2 ** attempt # 1s, 2s, 4s time.sleep(wait_time) continue response.raise_for_status() return response.json() except requests.exceptions.Timeout: if attempt < max_retries - 1: time.sleep(2 ** attempt) continue raise raise Exception("Max retries exceeded") ``` ```javascript Node.js theme={null} async function parseWithRetry(filePath, password, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { try { const form = new FormData(); form.append('file', fs.createReadStream(filePath)); form.append('password', password); const response = await fetch('https://api.casparser.in/v4/smart/parse', { method: 'POST', headers: { 'x-api-key': API_KEY }, body: form, signal: AbortSignal.timeout(60000) }); // Don't retry client errors (except 429) if (response.status >= 400 && response.status < 500 && response.status !== 429) { throw new Error(`Client error: ${response.status}`); } // Retry server errors and rate limits if (response.status >= 500 || response.status === 429) { if (attempt < maxRetries - 1) { await new Promise(r => setTimeout(r, 2 ** attempt * 1000)); continue; } throw new Error(`Server error: ${response.status}`); } return await response.json(); } catch (error) { if (error.name === 'TimeoutError' && attempt < maxRetries - 1) { await new Promise(r => setTimeout(r, 2 ** attempt * 1000)); continue; } throw error; } } throw new Error('Max retries exceeded'); } ``` ## Timeout Configuration | Operation | Recommended Timeout | | - | - | | CAS parsing | 60 seconds | | CDSL fetch (Step 1) | 50 seconds | | CDSL fetch (Step 2) | 10 seconds | | Credits check | 5 seconds | | Token generation | 5 seconds | CDSL fetch Step 1 takes **15-20 seconds** due to automated captcha solving. Set timeout to at least 50s. ## Graceful Degradation ```python theme={null} def parse_cas_safe(file_path, password): """Parse CAS with graceful error handling.""" try: response = requests.post( "https://api.casparser.in/v4/smart/parse", headers={"x-api-key": API_KEY}, files={"file": open(file_path, "rb")}, data={"password": password}, timeout=60 ) data = response.json() if data.get("status") == "failed": error_code = data.get("code", "UNKNOWN") if error_code == "INVALID_PASSWORD": return { "error": "Wrong password. Please check your CAS email for the correct password.", "retry": False } elif error_code == "INVALID_PDF": return { "error": "Invalid PDF. Please upload the original digital PDF, not a scanned copy.", "retry": False } elif error_code == "INSUFFICIENT_CREDITS": return { "error": "No credits remaining. Please upgrade your plan.", "retry": False, "upgrade_url": "https://app.casparser.in/billing" } else: return { "error": f"Parsing failed: {data.get('msg')}", "retry": True } return {"success": True, "data": data} except requests.exceptions.Timeout: return { "error": "Request timed out. The PDF may be too large or complex.", "retry": True } except requests.exceptions.ConnectionError: return { "error": "Network error. Please check your internet connection.", "retry": True } except Exception as e: return { "error": f"Unexpected error: {str(e)}", "retry": False } ``` ## Request Tracing Every response includes an `X-Request-ID` header: ```python theme={null} response = requests.post(...) request_id = response.headers.get("X-Request-ID") print(f"Request ID: {request_id}") # req_2xYz7KpL8mN3Ab ``` **Include this ID when contacting support** for faster resolution. ## Monitoring & Alerts ### Track Error Rates ```python theme={null} import logging def parse_with_logging(file_path, password): request_id = None try: response = requests.post(...) request_id = response.headers.get("X-Request-ID") data = response.json() if data.get("status") == "failed": logging.error(f"Parsing failed: {data.get('msg')} (Request ID: {request_id})") return data except Exception as e: logging.error(f"Exception: {str(e)} (Request ID: {request_id})") raise ``` ### Set Up Alerts Monitor these metrics: * **Error rate** — Alert if greater than 5% of requests fail * **Timeout rate** — Alert if greater than 2% timeout * **Credit balance** — Alert when less than 10 credits remain ## Next Steps Understand the response structure Contact support # Response Schema Source: https://casparser.in/docs/learn/response-schema Complete reference for CAS Parser API response structure. Understand every field in the JSON output. ## Overview All CAS parsing endpoints return a unified JSON structure regardless of source (CDSL, NSDL, CAMS, KFintech). ## Top-Level Structure ```json theme={null} { "meta": { ... }, "investor": { ... }, "summary": { ... }, "demat_accounts": [ ... ], "mutual_funds": [ ... ], "insurance": { ... }, "nps": [ ... ] } ``` ## Meta Object Contains metadata about the CAS document and parsing operation. ```json theme={null} { "meta": { "cas_type": "CDSL", "statement_period": { "from": "2024-01-01", "to": "2024-01-31" }, "generated_at": "2024-02-01T10:30:00Z" } } ``` | Field | Type | Description | | - | - | - | | `cas_type` | string | `CDSL`, `NSDL`, or `CAMS_KFINTECH` (uppercase) | | `statement_period` | object | Period covered by the statement | | `statement_period.from` | string | Start date of statement (YYYY-MM-DD) | | `statement_period.to` | string | End date of statement (YYYY-MM-DD) | | `generated_at` | string | When the response was generated (ISO 8601) | ## Investor Object Contains personal details of the investor extracted from the CAS. ```json theme={null} { "investor": { "name": "John Doe", "pan": "ABCDE1234F", "email": "john@example.com", "address": "123 Main St, Mumbai 400001", "pincode": "400001", "mobile": "9876543210", "cas_id": "CAS123456" } } ``` | Field | Type | Description | | - | - | - | | `name` | string | Full name of investor | | `pan` | string | PAN (Permanent Account Number) | | `email` | string | Email address (may be null) | | `address` | string | Full postal address (may be null) | | `pincode` | string | Postal/ZIP code (may be null) | | `mobile` | string | Mobile number (may be null) | | `cas_id` | string | CAS ID (only for NSDL and CDSL, may be null) | ## Summary Object Provides aggregated portfolio metrics and account counts. ```json theme={null} { "summary": { "total_value": 2547832.50, "accounts": { "demat": { "count": 2, "total_value": 1500000.00 }, "mutual_funds": { "count": 5, "total_value": 950000.00 }, "insurance": { "count": 1, "total_value": 50000.00 }, "nps": { "count": 1, "total_value": 47832.50 } } } } ``` | Field | Type | Description | | - | - | - | | `total_value` | number | Total portfolio value across all accounts | | `accounts` | object | Breakdown by account type | | `accounts.demat.count` | integer | Number of demat accounts | | `accounts.demat.total_value` | number | Total value of demat holdings | | `accounts.mutual_funds.count` | integer | Number of mutual fund folios | | `accounts.mutual_funds.total_value` | number | Total value of MF holdings | | `accounts.insurance.count` | integer | Number of insurance policies | | `accounts.insurance.total_value` | number | Total value of insurance | | `accounts.nps.count` | integer | Number of NPS accounts | | `accounts.nps.total_value` | number | Total value of NPS holdings | ## Demat Accounts Array Array of demat (dematerialized) accounts holding equities, bonds, ETFs, and other securities. ```json theme={null} { "demat_accounts": [ { "demat_type": "CDSL", "dp_id": "12345678", "dp_name": "HDFC Bank Limited", "client_id": "1234567890123456", "bo_id": "1234567890123456", "value": 1500000.00, "linked_holders": [ { "name": "John Doe", "pan": "ABCDE1234F" } ], "holdings": { "equities": [ ... ], "corporate_bonds": [ ... ], "government_securities": [ ... ], "aifs": [ ... ], "demat_mutual_funds": [ ... ] }, "additional_info": { "bo_status": "Active", "bo_type": "Individual", "bsda": "No", "email": "john@example.com", "status": "Active", "nominee": "Jane Doe" } } ] } ``` ### Demat Account Fields | Field | Type | Description | | - | - | - | | `demat_type` | string | `NSDL` or `CDSL` | | `dp_id` | string | Depository Participant ID | | `dp_name` | string | DP name (e.g., "HDFC Bank") | | `client_id` | string | Client ID | | `bo_id` | string | Beneficiary Owner ID (primarily for CDSL) | | `value` | number | Total value of this demat account | | `linked_holders` | array | Account holders linked to this account | | `holdings` | object | Holdings grouped by asset class | | `additional_info` | object | Account-specific metadata (varies by type) | ### Equity Object Holdings in equities (stocks). ```json theme={null} { "isin": "INE002A01018", "name": "Reliance Industries Ltd", "units": 100.0, "value": 250000.00, "transactions": [ { "date": "2023-06-15", "type": "PURCHASE", "description": "Purchase", "units": 50.0, "nav": 2400.00, "amount": 120000.00, "balance": 100.0 } ], "additional_info": { "open_units": 50.0, "close_units": 100.0 } } ``` | Field | Type | Description | | - | - | - | | `isin` | string | ISIN code (12 characters) | | `name` | string | Security name | | `units` | number | Number of shares currently held | | `value` | number | Current market value | | `transactions` | array | Transaction history (beta feature) | | `additional_info` | object | Opening/closing units for statement period | ### Corporate Bond Object Holdings in corporate bonds. ```json theme={null} { "isin": "INE123A01234", "name": "HDFC Ltd 8.5% Bond", "units": 10.0, "value": 10500.00, "transactions": [ ... ], "additional_info": { "open_units": 10.0, "close_units": 10.0 } } ``` | Field | Type | Description | | - | - | - | | `isin` | string | ISIN code | | `name` | string | Bond name/description | | `units` | number | Number of units held | | `value` | number | Current market value | | `transactions` | array | Transaction history (beta feature) | | `additional_info` | object | Opening/closing units for statement period | ## Mutual Funds Array Array of mutual fund folios with schemes and transactions. ```json theme={null} { "mutual_funds": [ { "folio_number": "1234567890", "amc": "HDFC Mutual Fund", "registrar": "Computer Age Management Services", "value": 451000.88, "linked_holders": [ { "name": "John Doe", "pan": "ABCDE1234F" } ], "schemes": [ { "isin": "INF179K01234", "name": "HDFC Equity Fund - Growth", "type": "Equity", "units": 1000.50, "nav": 450.75, "value": 451000.88, "cost": 400000.00, "gain": { "absolute": 51000.88, "percentage": 12.75 }, "transactions": [ ... ], "nominees": ["Jane Doe"], "additional_info": { "advisor": "XYZ Advisors", "rta_code": "HDC001", "amfi": "100123", "open_units": 900.50, "close_units": 1000.50 } } ], "additional_info": { "pan": "ABCDE1234F", "pankyc": "OK", "kyc": "OK" } } ] } ``` ### Mutual Fund Folio Fields | Field | Type | Description | | - | - | - | | `folio_number` | string | Folio number | | `amc` | string | Asset Management Company name | | `registrar` | string | Registrar and Transfer Agent name | | `value` | number | Total value of the folio | | `linked_holders` | array | Account holders linked to this folio | | `schemes` | array | List of schemes in this folio | | `additional_info` | object | PAN, KYC status, etc. | ### Scheme Object | Field | Type | Description | | - | - | - | | `isin` | string | ISIN code | | `name` | string | Scheme name | | `type` | string | `Equity`, `Debt`, `Hybrid`, or `Other` | | `units` | number | Units held | | `nav` | number | Net Asset Value per unit | | `value` | number | Current value (units × NAV) | | `cost` | number | Cost of investment | | `gain` | object | Gain/loss metrics | | `gain.absolute` | number | Absolute gain or loss | | `gain.percentage` | number | Percentage gain or loss | | `transactions` | array | Transaction history | | `nominees` | array | List of nominee names | | `additional_info` | object | Advisor, RTA code, AMFI code, opening/closing units | ### Transaction Object Unified transaction schema for all holdings (mutual funds, equities, bonds, etc.). ```json theme={null} { "date": "2023-06-15", "description": "Purchase", "type": "PURCHASE", "amount": 10000.00, "units": 22.18, "nav": 450.86, "balance": 1000.50, "dividend_rate": null, "additional_info": { "stamp_duty": 5.00, "order_no": "ORD123456" } } ``` | Field | Type | Description | | - | - | - | | `date` | string | Transaction date (YYYY-MM-DD) | | `description` | string | Transaction description/particulars | | `type` | string | Transaction type (see below) | | `amount` | number | Transaction amount in currency (may be null) | | `units` | number | Number of units involved | | `nav` | number | NAV/price per unit on transaction date (may be null) | | `balance` | number | Balance units after transaction | | `dividend_rate` | number | Dividend rate (for DIVIDEND\_PAYOUT, may be null) | | `additional_info` | object | Additional transaction-specific fields | **Transaction Types:** `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` ## Insurance Object Contains insurance policies (currently only life insurance). ```json theme={null} { "insurance": { "life_insurance_policies": [ { "policy_number": "POL123456", "provider": "HDFC Life", "policy_name": "Click 2 Protect Life", "life_assured": "John Doe", "status": "Active", "sum_assured": 10000000, "premium_amount": 15000, "premium_frequency": "Annual", "additional_info": { } } ] } } ``` | Field | Type | Description | | - | - | - | | `policy_number` | string | Insurance policy number | | `provider` | string | Insurance company name | | `policy_name` | string | Name of the insurance policy | | `life_assured` | string | Name of the life assured | | `status` | string | Policy status (e.g., Active, Lapsed) | | `sum_assured` | number | Sum assured amount | | `premium_amount` | number | Premium amount | | `premium_frequency` | string | Frequency (Annual, Monthly, etc.) | | `additional_info` | object | Additional policy-specific information | ## NPS Array National Pension System accounts (only available in NSDL CAS). ```json theme={null} { "nps": [ { "pran": "123456789012", "cra": "NSDL", "value": 228472.84, "funds": [ { "name": "SBI Equity Fund", "units": 5000.50, "nav": 45.67, "value": 228472.84, "cost": 200000.00, "additional_info": { "tier": 1, "manager": "SBI Pension Funds" } } ], "linked_holders": [ { "name": "John Doe", "pan": "ABCDE1234F" } ], "additional_info": { } } ] } ``` | Field | Type | Description | | - | - | - | | `pran` | string | Permanent Retirement Account Number | | `cra` | string | Central Record Keeping Agency name | | `value` | number | Total value of the NPS account | | `funds` | array | List of NPS funds | | `linked_holders` | array | Account holders linked to this NPS account | | `additional_info` | object | Additional NPS account metadata | ### NPS Fund Object | Field | Type | Description | | - | - | - | | `name` | string | Name of the NPS fund | | `units` | number | Number of units held | | `nav` | number | Net Asset Value per unit | | `value` | number | Current market value | | `cost` | number | Cost of investment | | `additional_info.tier` | number | NPS tier (1, 2, or null) | | `additional_info.manager` | string | Fund manager name | ## Error Response When parsing fails, the API returns a `failed` status with a descriptive error message. ```json theme={null} { "status": "failed", "msg": "Invalid password for PDF" } ``` | Field | Type | Description | | - | - | - | | `status` | string | `failed` for API errors, `error` for authentication errors | | `msg` | string | Human-readable error message | **Note:** All responses include an `X-Request-ID` header (format: `req_`) for debugging and support. ## Common Error Messages | Message | Cause | Solution | | - | - | - | | `Invalid password for PDF` | Wrong PDF password | Verify password format (often PAN-based) | | `Invalid PDF file` | Corrupted or scanned PDF | Use original digitally-generated PDF | | `Unsupported CAS type` | Wrong endpoint for CAS type | Use `/v4/smart/parse` to auto-detect | | `API quota exceeded` | Credit limit reached | Upgrade plan or wait for reset | | `Authentication failed` | Missing/invalid API key | Check `x-api-key` header | ## Next Steps Start parsing CAS files Handle errors gracefully # Quickstart Source: https://casparser.in/docs/quickstart Get started with CAS Parser in under 5 minutes. Choose your preferred language. ## Prerequisites 1. **Get an API key** from [app.casparser.in/developers](https://app.casparser.in/developers), or let your coding agent [obtain one automatically](/docs/learn/authentication#from-a-coding-agent-automated) 2. **Have a CAS PDF** ready, or use our sandbox key for testing For testing, use `sandbox-with-json-responses` as your API key. It returns sample data without consuming credits. ## Choose your language requests library fetch or axios Command line ## What you'll build In this quickstart, you'll parse a CAS PDF and receive structured JSON containing: * **Investor details** — Name, email, PAN, mobile * **Portfolio summary** — Total value, account counts * **Demat holdings** — Equities, bonds, AIFs, ETFs * **Mutual funds** — Folios with transactions (50+ years of transaction history from CAMS/KFintech) * **Insurance & NPS** — Policy details and pension accounts ## API endpoints | Endpoint | Description | | - | - | | `POST /v4/smart/parse` | Auto-detect CAS type and parse (recommended) | | `POST /v4/cdsl/parse` | Parse CDSL eCAS specifically | | `POST /v4/nsdl/parse` | Parse NSDL eCAS specifically | | `POST /v4/cams_kfintech/parse` | Parse CAMS/KFintech CAS specifically | Use `/v4/smart/parse` for most cases. It automatically detects the CAS type and returns a unified response format. # cURL Quickstart Source: https://casparser.in/docs/quickstart/curl Parse CAS PDFs from the command line using cURL. ## Prerequisites * cURL installed * [Get an API key](https://app.casparser.in/developers) * Have a CAS PDF ready (or use sandbox key) ## 1. Parse a CAS PDF ```bash theme={null} curl -X POST https://api.casparser.in/v4/smart/parse \ -H "x-api-key: YOUR_API_KEY" \ -F "file=@cas.pdf" \ -F "password=YOUR_PAN_NUMBER" ``` ## 2. Parse from URL ```bash theme={null} curl -X POST https://api.casparser.in/v4/smart/parse \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "pdf_url": "https://example.com/cas.pdf", "password": "YOUR_PAN_NUMBER" }' ``` ## 3. Try it yourself Test without consuming credits: ```bash theme={null} curl -X POST https://api.casparser.in/v4/smart/parse \ -H "x-api-key: sandbox-with-json-responses" \ -F "file=@cas.pdf" \ -F "password=test" ``` ## Check your credits ```bash theme={null} curl -X POST https://api.casparser.in/v1/credits \ -H "x-api-key: YOUR_API_KEY" ``` Response: ```json theme={null} { "used": 15, "remaining": 35, "limit": 50, "reset_date": "2024-02-01", "features": ["cdsl", "nsdl", "cams_kfintech", "inbox", "cdsl_fetch"] } ``` ## Response headers Every API response includes: | Header | Description | | - | - | | `X-Request-ID` | Unique request ID (e.g., `req_abc123`) for support | | `X-Credits-Used` | Credits consumed by this request | | `X-Credits-Remaining` | Remaining credits for billing period | ## Error handling ```bash theme={null} # Check response status response=$(curl -s -w "\n%{http_code}" -X POST https://api.casparser.in/v4/smart/parse \ -H "x-api-key: YOUR_API_KEY" \ -F "file=@cas.pdf" \ -F "password=WRONG_PASSWORD") http_code=$(echo "$response" | tail -n1) body=$(echo "$response" | sed '$d') if [ "$http_code" -ne 200 ]; then echo "Error: $body" exit 1 fi echo "$body" | jq '.summary.total_value' ``` ## Common errors | Status | Error | Solution | | - | - | - | | 400 | Invalid password | Check PAN format (PAN for CDSL/NSDL/CAMS) | | 401 | Invalid API key | Verify your API key at app.casparser.in | | 402 | Insufficient credits | Upgrade plan or wait for reset | | 422 | Invalid PDF | Ensure PDF is not scanned/tampered | ## Next steps Explore all endpoints Test with sample data # Node.js Quickstart Source: https://casparser.in/docs/quickstart/nodejs Parse your first CAS PDF with Node.js using fetch or axios. ## Prerequisites * Node.js 18+ * [Get an API key](https://app.casparser.in/developers) * Have a CAS PDF ready (or use sandbox key) ## 1. Using fetch (Node.js 18+) ```javascript theme={null} import fs from 'fs'; const formData = new FormData(); formData.append('file', new Blob([fs.readFileSync('cas.pdf')])); formData.append('password', 'YOUR_PAN_NUMBER'); const response = await fetch('https://api.casparser.in/v4/smart/parse', { method: 'POST', headers: { 'x-api-key': 'YOUR_API_KEY' }, body: formData }); const data = await response.json(); console.log(`Investor: ${data.investor.name}`); console.log(`Total Value: ₹${data.summary.total_value.toLocaleString()}`); ``` ## 2. Using axios ```bash theme={null} npm install axios form-data ``` ```javascript theme={null} import axios from 'axios'; import FormData from 'form-data'; import fs from 'fs'; const form = new FormData(); form.append('file', fs.createReadStream('cas.pdf')); form.append('password', 'YOUR_PAN_NUMBER'); const response = await axios.post( 'https://api.casparser.in/v4/smart/parse', form, { headers: { 'x-api-key': 'YOUR_API_KEY', ...form.getHeaders() }, timeout: 60000 } ); console.log(response.data); ``` ## TypeScript types ```typescript theme={null} interface CASResponse { meta: { cas_type: 'CDSL' | 'NSDL' | 'CAMS_KFINTECH'; statement_period: { from: string; // YYYY-MM-DD to: string; // YYYY-MM-DD }; generated_at: string; // ISO 8601 }; investor: { name: string; pan: string; email?: string; mobile?: string; address?: string; pincode?: string; cas_id?: string; }; summary: { total_value: number; accounts: { demat: { count: number; total_value: number }; mutual_funds: { count: number; total_value: number }; insurance: { count: number; total_value: number }; nps: { count: number; total_value: number }; }; }; demat_accounts: DematAccount[]; mutual_funds: MutualFundFolio[]; insurance: { life_insurance_policies: LifeInsurancePolicy[]; }; nps: NPSAccount[]; } interface DematAccount { demat_type: 'CDSL' | 'NSDL'; dp_id: string; dp_name: string; client_id: string; bo_id?: string; value: number; linked_holders: Array<{ name: string; pan: string }>; holdings: { equities: Equity[]; corporate_bonds: CorporateBond[]; government_securities: GovernmentSecurity[]; aifs: AIF[]; demat_mutual_funds: DematMutualFund[]; }; additional_info: object; } interface Equity { isin: string; name: string; units: number; value: number; } ``` ## Full example with error handling ```typescript theme={null} import axios from 'axios'; import FormData from 'form-data'; import fs from 'fs'; const API_KEY = process.env.CASPARSER_API_KEY; const API_URL = 'https://api.casparser.in/v4/smart/parse'; async function parseCAS(pdfPath: string, password: string): Promise { const form = new FormData(); form.append('file', fs.createReadStream(pdfPath)); form.append('password', password); const response = await axios.post(API_URL, form, { headers: { 'x-api-key': API_KEY, ...form.getHeaders() }, timeout: 60000 }); if (response.data.status === 'failed') { throw new Error(`Parsing failed: ${response.data.msg}`); } return response.data; } // Usage const data = await parseCAS('cas.pdf', 'ABCDE1234F'); console.log(`Investor: ${data.investor.name}`); console.log(`Total Value: ₹${data.summary.total_value.toLocaleString()}`); ``` ## Next steps Drop-in React widget Import from user email # Python Quickstart Source: https://casparser.in/docs/quickstart/python Parse your first CAS PDF with Python in 5 lines of code. ## Prerequisites * Python 3.7+ * [Get an API key](https://app.casparser.in/developers) * Have a CAS PDF ready (or use sandbox key) ## 1. Install ```bash theme={null} pip install requests ``` ## 2. Parse a CAS PDF ```python theme={null} import requests response = requests.post( "https://api.casparser.in/v4/smart/parse", headers={"x-api-key": "YOUR_API_KEY"}, files={"file": open("cas.pdf", "rb")}, data={"password": "YOUR_PAN_NUMBER"} ) data = response.json() print(f"Investor: {data['investor']['name']}") print(f"Total Value: ₹{data['summary']['total_value']:,.0f}") ``` ## 3. Try it yourself Use `sandbox-with-json-responses` as your API key to test without consuming credits. ## Response structure ```json theme={null} { "meta": { "cas_type": "CDSL", "statement_period": { "from": "2024-01-01", "to": "2024-01-31" }, "generated_at": "2024-02-01T10:30:00Z" }, "investor": { "name": "John Doe", "pan": "ABCDE1234F", "email": "john@example.com", "mobile": "9876543210" }, "summary": { "total_value": 1234567.89, "accounts": { "demat": { "count": 2, "total_value": 800000.00 }, "mutual_funds": { "count": 5, "total_value": 400000.00 }, "insurance": { "count": 1, "total_value": 25000.00 }, "nps": { "count": 1, "total_value": 9567.89 } } }, "demat_accounts": [...], "mutual_funds": [...], "insurance": {...}, "nps": [...] } ``` ## Full example with error handling ```python theme={null} import requests import os API_KEY = os.environ.get("CASPARSER_API_KEY") API_URL = "https://api.casparser.in/v4/smart/parse" def parse_cas(pdf_path: str, password: str) -> dict: """Parse a CAS PDF and return structured data.""" with open(pdf_path, "rb") as f: response = requests.post( API_URL, headers={"x-api-key": API_KEY}, files={"file": f}, data={"password": password}, timeout=60 ) result = response.json() if result.get("status") == "failed": raise Exception(f"Parsing failed: {result.get('msg')}") return result # Usage if __name__ == "__main__": data = parse_cas("cas.pdf", "ABCDE1234F") # Print summary print(f"Investor: {data['investor']['name']}") print(f"PAN: {data['investor']['pan']}") print(f"Total Value: ₹{data['summary']['total_value']:,.2f}") # Print demat holdings for account in data.get("demat_accounts", []): print(f"\nDemat Account: {account['dp_name']} ({account['demat_type']})") for equity in account.get("holdings", {}).get("equities", []): print(f" {equity['name']}: {equity['units']} units @ ₹{equity['value']:,.2f}") ``` ## Next steps Import CAS directly from user email Real-time holdings via OTP # Release Notes Source: https://casparser.in/docs/release-notes Latest updates from CAS Parser **Last Updated:** August 2026\ **API Documentation:** [API Reference](/docs/api-reference/introduction) *** ## 📅 August 2026 ### Registry Verification — SEBI & AMFI Verify that a financial advisor or distributor is genuinely registered — before you onboard them. * `POST /v1/verify/sebi` — verify any SEBI-registered intermediary (Investment Adviser, Research Analyst, Portfolio Manager, Stock Broker, Merchant Banker, RTA, Mutual Fund, AIF, and more). The category is **auto-detected** from the registration number's sequence; pass `type` to search by name instead. * `POST /v1/verify/mfd` — verify an AMFI Mutual Fund Distributor by ARN, screened against AMFI's suspended, terminated, and terminated-EUIN lists. Both return a single `verified` boolean to gate onboarding on, plus a normalized `registration_status` (`ACTIVE`, `EXPIRED`, `SUSPENDED`, `TERMINATED`, `NOT_FOUND`). A "not found" is a successful `200` with `verified: false`. **Credits:** 0.25 per successful verification. Failed lookups are free. **Start here:** [Registry Verification Guide](/docs/guides/verify) *** ## 📅 July 2026 ### Email Inbox Import — Outlook and Zoho `POST /v4/inbox/connect` now supports Microsoft Outlook and Zoho Mail in addition to Gmail. Pass `provider` in the request body to choose the mail service. * `gmail` (default) — `@gmail.com` and Google Workspace domains * `outlook` — personal Microsoft accounts: `@outlook.com`, `@hotmail.com`, `@live.com`, `@msn.com`, localised variants, and custom domains registered as personal Microsoft accounts * `zoho` — Zoho Mail accounts, including custom domains hosted on Zoho The resolved `provider` is returned in the connect response and reported by `POST /v4/inbox/status`. The rest of the flow (list CAS files, download, parse, disconnect) is unchanged. Billing stays at 0.2 credits per inbox pull. **Start here:** [Email Inbox Import Guide](/docs/guides/gmail-inbox) *** ### CAS Generator — `cas_authority: "cams"` `POST /v4/generate` now accepts a `cas_authority` parameter to select which RTA sends the mailback: `kfintech` (default) or `cams`. This only changes the sender, not the data — both RTAs share folio data, so either authority returns the same consolidated portfolio. What it does change is the PDF password policy: CAMS accepts 6-20 characters with at least two digits, so you can use the investor's **PAN as the password** and skip storing a separate secret to reopen the PDF at parse time. The default behaviour is unchanged. **Start here:** [CAS Generator Guide](/docs/guides/cas-generator) *** ## 📅 June 2026 ### KYC DigiLocker Consent-based flow to fetch an investor's government-issued documents (Aadhaar, PAN, driving licence) directly from DigiLocker. A separate feature from KYC PAN Status. * `POST /v1/kyc/digilocker/account-lookup` — pre-consent check (free) * `POST /v1/kyc/digilocker/session` — start a consent session, get `authorization_url` (free) * `POST /v1/kyc/digilocker/result/{session_id}` — one-call aggregate: verified identity, issued documents, and parsed PAN/Aadhaar/driving licence * Explicit `consent` + `consent_purpose` captured as a consent receipt (DPDP / RBI) **Credits:** account-lookup and session are free; `result` is 0.25 per call. **Start here:** [KYC DigiLocker Guide](/docs/guides/kyc-digilocker) *** ## 📅 May 2026 ### KYC PAN Status New endpoint to verify KYC registration status for any PAN number across all five SEBI-registered KRAs — in a single call, with normalized output. * `POST /v1/kyc/pan/status` — returns `kyc_compliant` boolean + full per-KRA breakdown * Raw CVL portal strings normalized to clean enums (`validated`, `registered`, `on_hold`, etc.) * All KRA objects always return the same six keys (no missing fields to guard against) **Credits:** 0.25 per successful lookup. Failed lookups are not billed. **Start here:** [KYC PAN Status Guide](/docs/guides/kyc-pan-status) *** ### Frontend-only Inbound Email Investors can now forward their CAS email to a unique address and get parsed data back in-browser — no download, no upload, no webhook to build. * `callback_url` is now optional on `POST /v4/inbound-email` * New `GET /v4/inbound-email/{id}/files` for polling (cursor-paginated by `received_at`) * Portfolio Connect picks this up via `enableInboundEmail: true` **Billing:** 0.2 credits per validated email received, regardless of delivery mode. **Start here:** [Inbound Email Guide](/docs/guides/inbound-email) *** ## 📅 April 2026 ### Portfolio Connect 2.0 Major redesign of the [`@cas-parser/connect`](https://www.npmjs.com/package/@cas-parser/connect) widget focused on end-user conversion. * Plain-language copy — no more "CAS", "depository", "BO ID", "KFintech" in user-facing screens * Demat path reordered: broker first, method second (CDSL brokers get instant OTP fetch; NSDL go straight to upload) * New `enableInboundEmail` flag adds an in-widget "forward from email" path * Cross-flow handoffs replace dead ends (mailback → forward, empty inbox → request fresh, upload ↔ forward) ```bash theme={null} npm install @cas-parser/connect@^2 ``` ### Portfolio Connect 2.1 — `onSubmit` now covers every flow `onSubmit` used to fire only on file upload. It now covers file upload, Gmail inbox, inbound email, and CDSL fetch — so you can intercept PDFs from any path and send them to your own backend. ```ts theme={null} onSubmit={async (input, password) => { if (input.kind === 'file') { // input.file: File — source: 'UPLOAD' } else { // input.pdfUrl: string — source: 'INBOX' | 'INBOUND_EMAIL' | 'CDSL_FETCH' } }} ``` ### Teams & Collaboration Collaborate with your team under one account. Invite colleagues by email, manage roles, and share a single billing plan. **How it works:** 1. Create a team from the [Teams page](https://app.casparser.in/teams) 2. Invite members by email 3. Members join and start working — usage is billed to the team owner **Details:** * Role-based access: Owners manage billing and team; Members get scoped access * Up to 50 members per team ### Test Mode Try any CAS Parser endpoint without signing up, uploading a PDF, or spending credits. One toggle, instant sample data. **Web Portal:** Global "Test Mode" switch in the header. When active, the sandbox key (`sandbox-with-json-responses`) is injected automatically. File upload is replaced with a "Get Sample Portfolio" button. **API:** All endpoints now short-circuit in sandbox mode — no PDF, password, or form fields required. Previously, sandbox users still needed to provide a valid file. *** ## 📅 March 2026 ### Web Portal Overhaul Complete redesign of the [Web Portal](https://app.casparser.in) with professional-grade financial data views and multi-portfolio support. **Data views:** New finance-themed tables across all sections (Demat, Mutual Funds, Insurance, NPS) with pinned totals and improved readability. **Multi-portfolio tabs:** Upload and compare multiple CAS portfolios side-by-side. Tab labels show investor name + total value. Summary cards for Total, Mutual Funds, Demat, and Insurance values. All data stored locally in the browser. **Analytics & Insights:** * Portfolio analytics with interactive charts and visualizations * Advisor Insights: health score, risk warnings, redundancy detection, STCG/LTCG tax analysis, SIP detection * XLSX/CSV/JSON export now covers all API fields including demat transactions and NPS (separate sheets) ### Multi-lingual CDSL Support CDSL eCAS PDFs containing regional language text are now parsed correctly. The parser now supports all 22 official Indian languages — Hindi, Gujarati, Bengali, Telugu, Kannada, Malayalam, Tamil, Punjabi, Odia, Urdu, Manipuri, Santhal, and more. *** ## 📅 February 2026 ### Inbound Email API Create dedicated email addresses for investors to forward CAS statements. No OAuth, no file upload — just forward and done. **How it works:** 1. Create an inbound email: `POST /v4/inbound-email` with your `callback_url` 2. We return a unique address like `@import.casparser.in` 3. Investor forwards their CAS email to this address 4. We validate the sender (CDSL/NSDL/CAMS/KFintech), extract attachments, and POST to your webhook **Webhook payload includes:** * `forwarded_by`: Investor's email address * `files`: Array of CAS attachments with download URLs (48h expiry) * `reference` and `metadata`: Your data from creation time **Billing:** 0.2 credits per successfully processed email. **Start here:** [Inbound Email Guide](/docs/guides/inbound-email) ### Portfolio Links Branded portfolio collection pages for wealth managers and advisors. Share a custom link with clients — they upload CAS statements that are emailed directly to you. **Use cases:** * Client onboarding (collect 50+ years of transaction history) * Portfolio reviews * Compliance documentation **How it works:** 1. Create a branded link: `link.casparser.in/{your-company}` 2. Share via WhatsApp, email, or SMS 3. Clients upload CAS PDFs (with optional name/email) 4. Receive parsed data via email instantly **Features:** * Custom branding (logo, company name) * Privacy-first (noindex, no search visibility) * Upload-only mode (simplified UI) * Email delivery with PDF attachment \*\* Start here: [Portfolio Links](https://app.casparser.in/portfolio-links) *** ### AI Agent Toolkit The [Agent Toolkit](https://github.com/CASParser/agent-toolkit) helps empower AI agents with CAS parsing capabilities. Includes Model Context Protocol (MCP) support for seamless integration with LLM clients like Claude Desktop. ### Usage Tracking & Analytics Monitor API consumption via new endpoints and the Web Portal dashboard. **API Endpoints:** | Endpoint | Description | | - | - | | `POST /v1/usage` | Detailed request-level usage logs with timestamps, status codes, and credits consumed | | `POST /v1/usage/summary` | Aggregated analytics grouped by feature — ideal for overview | **Web Portal** ([app.casparser.in](https://app.casparser.in)): * Visual usage dashboard with charts and trends * Filter by date range and feature type Response includes: `X-Request-ID`, feature name, credits, status code, and timestamp for each API call. *** ## 📅 January 2026 ### Portfolio Connect SDK Drop-in widget for importing Indian investment portfolios — go live without building UI. ```bash theme={null} npm install @cas-parser/connect ``` **What it does:** * Pre-built UI for file upload, CDSL OTP fetch, and MF statement generation * Works with React, Next.js, Angular, Vue, Vanilla JS, React Native, and Flutter * Customizable branding (logo, title, colors) * Event callbacks for analytics integration ```jsx theme={null} console.log(data)} > {({ open }) => } ``` Bundle sizes: 54KB gzipped (standalone) ### Short-Lived Access Tokens Securely pass parsing capability to client-side applications without exposing your API key. **How it works:** 1. Generate token from your backend: `POST /v1/token` 2. Pass the `at_` prefixed token to frontend/SDK 3. Token works as drop-in replacement for `x-api-key` **Properties:** * Expires in 60 minutes (configurable) * Cannot generate other tokens * Verify validity: `POST /v1/token/verify` ### Inbox Integration - Gmail Import Import CAS files directly from user email inboxes via OAuth. CASA verified by Google for secure, compliant email access. **Flow:** 1. `POST /v4/inbox/connect` → Get OAuth URL 2. User authorizes read-only email access 3. `POST /v4/inbox/cas` → List and download CAS attachments **Supported senders:** CDSL, NSDL, CAMS, KFintech **Security:** * Google CASA Audit approved * Read-only access (cannot send emails) * Tokens encrypted server-side * User can revoke via `/v4/inbox/disconnect` ### CDSL Instant Fetch Retrieve CDSL statements directly via OTP authentication — no email wait required. **Two-step flow:** 1. `POST /v4/cdsl/fetch` — Submit credentials, receive OTP on registered mobile 2. `POST /v4/cdsl/fetch/{session_id}/verify` — Submit OTP, receive download URLs Returns: Up to 6 monthly CAS PDFs as direct download URLs. *** ## 📅 December 2025 ### Demat Transaction History CDSL and NSDL statements now include transaction-level detail for demat holdings. **Available in:** `/v4/smart/parse`, `/v4/cdsl/parse`, `/v4/nsdl/parse` Each holding now includes a `transactions` array with: * Transaction date and type (buy/sell/corporate action) * Quantity changes (open/close units) * Value at transaction time **Web Portal:** Expandable transaction rows in the results table for demat holdings. ### Request Tracing All API responses now include a `X-Request-ID` header in the format `req_`. Use this ID to: * Correlate requests with usage logs * Reference specific calls when contacting support * Debug issues in your application *** ## 📅 September 2025 ### NPS Account Support National Pension System accounts are now captured in NSDL statements. **Response includes:** * PRAN (Permanent Retirement Account Number) * Fund-wise holdings with gain/loss * Tier classification (Tier I / Tier II) * Fund type (Equity, Corporate Bond, Government Securities, etc.) ### Holders Summary NSDL statements now capture account holder information with linked PAN details for joint accounts and nominee tracking. *** ## 📅 July 2025 ### Mobile-Optimized Web Portal The Web Portal ([app.casparser.in](https://app.casparser.in)) is now fully responsive with touch-friendly controls, optimized form elements, and improved layouts for mobile devices. ### Security Trust Indicators Enhanced upload area with prominent security messaging — no file storage after processing, end-to-end encryption, and India-hosted infrastructure. ### Corporate Bonds Support (CDSL) CDSL statements now parse corporate bond holdings alongside equities. **Bond data includes:** * ISIN and company name * Coupon rate and maturity date * Face value, market value, and total value * Number of bonds held ```json theme={null} { "holdings": { "corporate_bonds": [ { "isin": "INE123A01234", "name": "Company Name", "coupon_rate": "8.5%", "maturity_date": "2027-12-31", "units": 10, "value": 105000 } ] } } ``` *** ## 📅 June 2025 ### Smart Parse Endpoint Single endpoint that auto-detects CAS type and returns a unified response format. ``` POST /v4/smart/parse ``` **Benefits:** * No need to pre-identify if the PDF is from CDSL, NSDL, or CAMS/KFintech * Consistent response schema regardless of source * Reduces integration complexity ### CAS Generator Programmatically request CAS statements via email mailback. KFintech and CAMS have a mutual partnership for data sharing and consolidation, so this retrieves mutual fund data from both RTAs. ``` POST /v4/generate ``` Parameters: `email`, `from_date`, `to_date`, `password`, optional `pan_no` The investor receives the password-protected CAS PDF in their email within minutes. ### Live Chat Support In-app chat support for real-time assistance with API integration and troubleshooting. ### Sandbox Mode Test API integrations without consuming credits using the sandbox key `sandbox-with-json-responses`. Returns realistic sample responses for all endpoints. *** ## 📅 April 2025 ### Passwordless Authentication Sign in to the Web Portal using email magic links — no password required. Enter your email, click the link sent to your inbox, and you're in. *** ## API Endpoints Summary | Category | Endpoint | Description | | - | - | - | | **Parsing** | `POST /v4/smart/parse` | Auto-detect and parse any CAS | | | `POST /v4/nsdl/parse` | Parse NSDL CAS | | | `POST /v4/cdsl/parse` | Parse CDSL CAS | | | `POST /v4/cams_kfintech/parse` | Parse CAMS/KFintech CAS | | **Generation** | `POST /v4/generate` | Request CAS via email (KFintech + CAMS) | | **Fetch** | `POST /v4/cdsl/fetch` | CDSL OTP request | | | `POST /v4/cdsl/fetch/{id}/verify` | CDSL OTP verify + download | | **Email Import** | `POST /v4/inbox/connect` | Initiate OAuth | | | `POST /v4/inbox/cas` | List CAS files from inbox | | | `POST /v4/inbox/disconnect` | Revoke email access | | **Access Tokens** | `POST /v1/token` | Generate short-lived token | | | `POST /v1/token/verify` | Validate token | | **SDK** | `@cas-parser/connect` | Drop-in widget (npm package) | | **Usage** | `POST /v1/credits` | Check remaining credits | | | `POST /v1/usage` | Detailed usage logs | | | `POST /v1/usage/summary` | Aggregated analytics | We've been in business since 2020, this is just an overview of recent releases :) *** ## Getting Started 1. Sign up at [app.casparser.in](https://app.casparser.in) to get your API key 2. Use `sandbox-with-json-responses` as a test key for development 3. Explore the interactive API docs at [API Reference](/docs/api-reference/introduction) Questions? Contact us at [casparser.in/contact](https://casparser.in/contact) # Sandbox Source: https://casparser.in/docs/resources/sandbox Test CAS Parser APIs without consuming credits using the sandbox API key. ## Sandbox API Key Use this key for development and testing: ``` sandbox-with-json-responses ``` The sandbox returns sample data for all endpoints without consuming credits. ## Usage ```python Python theme={null} import requests response = requests.post( "https://api.casparser.in/v4/smart/parse", headers={"x-api-key": "sandbox-with-json-responses"}, files={"file": open("any-pdf.pdf", "rb")}, data={"password": "test"} ) # Returns sample portfolio data print(response.json()) ``` ```bash cURL theme={null} curl -X POST https://api.casparser.in/v4/smart/parse \ -H "x-api-key: sandbox-with-json-responses" \ -F "file=@any-pdf.pdf" \ -F "password=test" ``` ## Sample Response The sandbox returns realistic sample data: ```json theme={null} { "meta": { "cas_type": "CDSL", "statement_period": { "from": "2024-01-01", "to": "2024-01-31" }, "generated_at": "2024-02-01T10:30:00Z" }, "investor": { "name": "John Doe", "pan": "ABCDE1234F", "email": "john.doe@example.com", "mobile": "9876543210", "address": "123 Main St, Mumbai", "pincode": "400001", "cas_id": "CAS123456" }, "summary": { "total_value": 2547832.50, "accounts": { "demat": { "count": 2, "total_value": 1500000.00 }, "mutual_funds": { "count": 5, "total_value": 950000.00 }, "insurance": { "count": 1, "total_value": 50000.00 }, "nps": { "count": 1, "total_value": 47832.50 } } }, "demat_accounts": [ { "demat_type": "CDSL", "dp_id": "12345678", "dp_name": "HDFC Bank Limited", "client_id": "1234567890123456", "bo_id": "1234567890123456", "value": 1500000.00, "linked_holders": [ { "name": "John Doe", "pan": "ABCDE1234F" } ], "holdings": { "equities": [ { "isin": "INE002A01018", "name": "Reliance Industries Ltd", "units": 100.0, "value": 250000.00 } ] }, "additional_info": {} } ], "mutual_funds": [], "insurance": { "life_insurance_policies": [] }, "nps": [] } ``` ## What sandbox covers | Endpoint | Sandbox Behavior | | - | - | | `/v4/smart/parse` | Returns sample CDSL data | | `/v4/cdsl/parse` | Returns sample CDSL data | | `/v4/nsdl/parse` | Returns sample NSDL data | | `/v4/cams_kfintech/parse` | Returns sample MF data | | `/v4/generate` | Returns success (no email sent) | | `/v4/cdsl/fetch` | Returns mock session\_id | | `/v4/inbox/*` | Returns mock OAuth flow | | `/v1/credits` | Returns unlimited credits | | `/v1/token` | Returns working access token | ## Moving to Production 1. **Get an API key** at [app.casparser.in/developers](https://app.casparser.in/developers) 2. **Replace** `sandbox-with-json-responses` with your real API key 3. **Test** with a real CAS PDF to verify integration The sandbox PDF password validation is disabled. In production, you must provide the correct password for each CAS type. ## Rate Limits | Environment | Rate Limit | | - | - | | Sandbox | 10 requests/minute | | Production | Based on your plan | ## Next steps Build your first integration Explore all endpoints # Security Source: https://casparser.in/docs/resources/security How CAS Parser protects your data. Zero retention, India-hosted, encrypted. ## Data Handling | Aspect | Policy | | - | - | | **Data Retention** | PDFs deleted immediately after parsing | | **Password Storage** | Never stored — used only for decryption | | **Response Storage** | Not stored — returned directly to you | | **Logs** | Request metadata only (no PII) | CAS Parser is a **stateless parsing service**. We process your PDFs and return structured data — we don't store portfolios. ## Infrastructure | Feature | Details | | - | - | | **Hosting** | India-based infrastructure (DigitalOcean Bangalore) | | **Encryption** | TLS 1.3 in transit, AES-256 at rest | | **Network** | Private VPC, no public database access | | **Monitoring** | 24/7 uptime monitoring | ## Fraud Prevention CAS Parser includes built-in tamper detection: * **Rejects scanned PDFs** — Only original digital documents accepted * **Detects modifications** — Tampered PDFs are rejected * **Validates signatures** — Checks PDF digital signatures when present This makes CAS Parser suitable for **credit underwriting** and **KYC** workflows where document authenticity matters. ## API Security ### Authentication All API requests require an `x-api-key` header: ```bash theme={null} curl -H "x-api-key: YOUR_API_KEY" https://api.casparser.in/v1/credits ``` ### Access Tokens For frontend/SDK use, generate short-lived access tokens: ```python theme={null} # Backend response = requests.post( "https://api.casparser.in/v1/token", headers={"x-api-key": "YOUR_API_KEY"}, json={"expiry_minutes": 30} ) access_token = response.json()["access_token"] # at_xxx # Frontend can use this token safely ``` Access tokens: * Cannot generate other tokens * Cannot access billing/usage endpoints * Expire automatically * Can be revoked ### IP Whitelisting Enterprise plans support IP whitelisting. Contact support to configure. ## Compliance | Standard | Status | | - | - | | **DPDPA** | Compliant (India Data Protection) | | **Google CASA** | Certified (External security audit) | | **SOC 2** | Planned | | **ISO 27001** | Planned | ## Email Inbox Security For email inbox import (Gmail, Outlook, Zoho): | Feature | Details | | - | - | | **Scope** | Read-only (cannot send emails) | | **OAuth** | Standard OAuth 2.0 via the provider (Google, Microsoft, Zoho) | | **Token Storage** | Encrypted server-side | | **Revocation** | User can revoke via `/v4/inbox/disconnect` or their provider's account settings | ## Reporting Vulnerabilities Found a security issue? Email [security@casparser.in](mailto:security@casparser.in). We respond to reports within 24 hours and follow responsible disclosure practices. ## Questions? Reach out for security questions # Support Source: https://casparser.in/docs/resources/support Get help with CAS Parser. Email, docs, and AI assistant. ## Contact | Channel | Response Time | Best For | | - | - | - | | **Email** | \< 24 hours | Technical issues, billing | | **AI Assistant** | Instant | Quick questions, code help | | **Documentation** | Self-serve | Guides, API reference | ## Email Support Get help from our team Include in your email: * **Request ID** (`X-Request-ID` header from API response) * **Error message** (full JSON response) * **Code snippet** (if applicable) ## AI Assistant Ask questions about CAS Parser in natural language: ChatGPT-powered assistant trained on CAS Parser docs ## Self-Service Resources Complete endpoint documentation Test without consuming credits Templates for AI agents Try Portfolio Connect SDK ## Pricing | Plan | Credits/Month | Price | | - | - | - | | **Free** | 10 | ₹0 | | **Lite 50** | 50 | ₹999/month | | **Pro 200** | 200 | ₹3,500/month | | **Ultra 750** | 750 | ₹10,500/month | | **Enterprise** | Custom | Contact us | See all features and credit costs ## Credit Costs | Operation | Credits | | - | - | | CAS parsing (any type) | 1.0 | | Contract note parsing | 0.5 | | CDSL OTP fetch | 0.5 | | KFintech CAS generation | 0.5 | | Gmail inbox (per pull, any number of files) | 0.2 | | Inbound email (per processed email) | 0.2 | | Portfolio Links (per submission) | 0.2 | ## Enterprise For high-volume or custom requirements: * **Volume discounts** — Lower per-credit pricing * **On-premise deployment** — Run on your infrastructure * **SLA guarantees** — 99.9% uptime commitment * **Dedicated support** — Slack/Teams channel * **IP whitelisting** — Network security Discuss your requirements on a quick call Send us your requirements and we'll follow up # SDK Configuration Source: https://casparser.in/docs/sdk/configuration Complete reference for Portfolio Connect SDK props, config, events, and framework integration. This page covers `@cas-parser/connect` v2.x. For the underlying APIs the widget calls, see the [API Reference](/docs/api-reference/introduction). ## Authentication Never expose your raw API key to the browser. Mint a short-lived **access token** (`at_*`) on your backend via [`POST /v1/token`](/docs/api-reference/endpoint/access-tokens) and pass that to the widget instead. The widget accepts either credential — both are sent as the `x-api-key` header. At least one of `accessToken` / `apiKey` is required. ```python theme={null} # Backend — Python (Flask) import os, requests from flask import jsonify @app.route('/api/casparser/token') def get_token(): res = requests.post( 'https://api.casparser.in/v1/token', headers={'x-api-key': os.environ['CASPARSER_API_KEY']}, json={'expiry_minutes': 30}, ) return jsonify(res.json()) ``` Access tokens: * Prefix: `at_` * TTL: 1–60 minutes (default: 60) * Drop-in replacement for `x-api-key` on `/v4/*` endpoints * Cannot mint other tokens *** ## Props | Prop | Type | Required | Description | | - | - | - | - | | `accessToken` | `string` | One of these two | Short-lived token from `POST /v1/token`. Recommended for browser embeds. | | `apiKey` | `string` | One of these two | Raw API key. For server-side or trusted-environment use only. | | `apiBaseUrl` | `string` | No | Override API base URL (for dedicated / self-hosted instances). Defaults to `https://api.casparser.in`. | | `config` | `PortfolioConnectConfig` | No | Feature flags, branding, prefill, theme, etc. See [Config](#config). | | `onSuccess` | `(data, metadata) => void` | **Yes** | Called when an import completes successfully. | | `onError` | `(error) => void` | No | Called on any structured error. | | `onExit` | `() => void` | No | Called when the widget closes (regardless of success/cancel). | | `onEvent` | `(event, metadata) => void` | No | Fires for every widget event. Useful for analytics. | | `onSubmit` | `(input, password, onProgress?) => Promise` | No | **v2.1+** — replaces the default parse API call across all intercept-capable flows. See [`onSubmit`](#onsubmit-collect-pdfs-without-parsing). | | `children` | `(renderProps) => ReactNode` | **Yes** (React) | Render-prop. Receives `{ open, isReady, isOpen }`. | *** ## Config ```typescript theme={null} interface PortfolioConnectConfig { // Import methods enableGenerator?: boolean; // MF statement via email (default: false) enableCdslFetch?: boolean; // CDSL OTP fetch (default: false) enableInbox?: boolean; // Email OAuth import — Gmail, Outlook, Zoho (default: false) enableInboundEmail?: boolean; // Forward-email flow (default: false) // Restrict portfolio types surfaced in the UI allowedTypes?: ('CAMS_KFINTECH' | 'CDSL' | 'NSDL')[]; // Home-screen layout homeLayout?: 'actions' | 'asset-type' | 'unified'; // 'actions' (default) — three action cards: File / Email / Fetch // 'asset-type' — two tiles: Mutual Funds / Stocks & Demat (advisor-friendly) // 'unified' — single drop zone with secondary chips below // Demat broker picker showBrokerPicker?: boolean; // default: true brokers?: BrokerInfo[]; // override the default broker list showShortcuts?: boolean; // email-search shortcuts (default: true) showPortalLinks?: boolean; // links to CAS generation portals (default: true) // Pre-fill user details (used across CDSL OTP, MF email, inbound forms) prefill?: { pan?: string; email?: string; phone?: string; boId?: string; // CDSL BO ID (16 digits) dob?: string; // YYYY-MM-DD }; // Sub-flow configs generator?: { fromDate?: string; // YYYY-MM-DD (default: 2000-01-01) toDate?: string; // YYYY-MM-DD (default: today) password?: string; // PDF password (default: 'Abcdefghi12$') }; inbox?: { redirectUri: string; // REQUIRED — OAuth callback (must call SDK's handleInboxCallback) casTypes?: ('cdsl' | 'nsdl' | 'cams' | 'kfintech')[]; startDate?: string; // YYYY-MM-DD (default: 30 days ago) endDate?: string; // YYYY-MM-DD (default: today) }; inboundEmail?: { callbackUrl?: string; // optional webhook; SDK still receives the file in-widget existingId?: string; // inject an existing inbound email — SDK won't create or delete email?: string; // pair with existingId to skip a network call allowedSources?: ('cdsl' | 'nsdl' | 'cams' | 'kfintech')[]; reference?: string; // your internal user/account ID metadata?: Record; // max 10 keys pollIntervalMs?: number; // default: 3000 (3s) sessionTimeoutMs?: number; // default: 1800000 (30 min, matches backend TTL) }; // Branding logoUrl?: string; title?: string; // default: "Import Your Investments" subtitle?: string; // default: "Mutual Funds, Stocks, Bonds — all in one place" // Theme — emits CSS variables on the modal root theme?: { mode?: 'light' | 'dark' | 'auto'; primary?: string; primaryHover?: string; primaryForeground?: string; accent?: string; radius?: number; fontFamily?: string; }; // Success screen successBehavior?: 'summary' | 'close'; // default: 'close' successAutoCloseMs?: number; // default: 10000 (10s) — used when behavior === 'summary' successCta?: { label: string; onClick: () => void; }; // Misc hideFooter?: boolean; // default: false. Please keep visible unless co-branded. } ``` ### Inbound email config When `enableInboundEmail: true`, the widget creates a one-time forwarding address via `POST /v4/inbound-email`, polls `GET /files`, and parses the first file it receives. This works without a `callback_url`. You can also pass an `existingId` (and optionally `email`) to inject an inbound email you've already created on your backend — useful when you want a stable address per user, or when you're driving the API yourself and only need the SDK for the polling/UI. The `callbackUrl` here is the underlying API's `callback_url` — set it if you want **both** webhook delivery to your backend **and** in-widget reception. Omit for a pure-frontend integration. *** ## Events ### `onSuccess` ```typescript theme={null} onSuccess: (data: ParsedData, metadata: ParseMetadata) => void; interface ParsedData { cas_type: 'CAMS_KFINTECH' | 'CDSL' | 'NSDL'; status: 'success' | 'failed'; investor_info?: { name: string; email?: string; mobile?: string; pan?: string; address?: string }; folios?: any[]; holdings?: any[]; summary?: { total_value: number; as_on_date: string }; raw_response?: any; // full API response } interface ParseMetadata { filename: string; parser_type: 'CAMS_KFINTECH' | 'CDSL' | 'NSDL'; parse_duration_ms: number; } ``` Fires once per successful import. The signature is **two arguments**, not a single payload object. ### `onError` ```typescript theme={null} onError: (error: PortfolioConnectError) => void; interface PortfolioConnectError { code: string; // see codes below; type widened to string for forward compat message: string; title?: string; // short headline suitable for a banner remediation?: string; // what the user can try next retryable?: boolean; details?: any; // raw API payload when available } ``` Error codes the SDK emits: | Code | When it fires | | - | - | | `INVALID_PASSWORD` | API response message explicitly mentions "password" | | `AUTHENTICATION` | HTTP 401/403 | | `RATE_LIMITED` | HTTP 429 | | `NETWORK` | Network failure / timeout | | `PARSE_ERROR` | `/v4/*/parse` endpoint failed | | `GENERATOR_ERROR` | KFintech mailback failed | | `CDSL_FETCH_ERROR` | CDSL OTP / fetch flow failed | | `INBOX_ERROR` | Gmail inbox flow failed | | `INBOUND_EMAIL_ERROR` | Inbound email provisioning / polling failed | | `UNKNOWN` | Catch-all | The type is widened to `| string`, so future codes won't break exhaustive switches. ### `onExit` ```typescript theme={null} onExit: () => void; ``` Fires whenever the widget closes — after success, after error, or on user cancel. No argument is passed; combine with state set in `onSuccess` / `onError` if you need to distinguish. ### `onEvent` ```typescript theme={null} onEvent: (event: PortfolioConnectEvent | string, metadata: EventMetadata) => void; ``` Fires for every widget event. Useful for analytics integrations. Selected events: * **Lifecycle**: `WIDGET_OPENED`, `WIDGET_CLOSED`, `MODE_SWITCHED`, `ASSET_SELECTED`, `BROKER_SELECTED` * **Upload**: `FILE_SELECTED`, `FILE_REMOVED`, `UPLOAD_STARTED`, `UPLOAD_PROGRESS`, `PARSE_STARTED`, `PARSE_SUCCESS`, `PARSE_ERROR` * **Generator**: `GENERATOR_STARTED`, `GENERATOR_SUCCESS`, `GENERATOR_ERROR` * **CDSL fetch**: `CDSL_FETCH_STARTED`, `CDSL_OTP_SENT`, `CDSL_OTP_VERIFIED`, `CDSL_FETCH_SUCCESS`, `CDSL_FETCH_ERROR` * **Gmail inbox**: `INBOX_CONNECT_STARTED`, `INBOX_CONNECTED`, `INBOX_FILES_LOADED`, `INBOX_FILE_SELECTED`, `INBOX_DISCONNECTED`, `INBOX_ERROR` * **Inbound email**: `INBOUND_EMAIL_CREATED`, `INBOUND_EMAIL_COPIED`, `INBOUND_EMAIL_POLLING`, `INBOUND_EMAIL_FILE_RECEIVED`, `INBOUND_EMAIL_TIMEOUT`, `INBOUND_EMAIL_ERROR` * **Cross-flow handoffs** — measure how often the widget's nudges convert: `GENERATOR_TO_INBOUND_HANDOFF`, `INBOX_TO_GENERATOR_HANDOFF`, `INBOUND_TO_UPLOAD_HANDOFF`, `UPLOAD_TO_INBOUND_HANDOFF` The `metadata` object always includes `timestamp` and may include event-specific fields. *** ## `onSubmit` — collect PDFs without parsing **v2.1+.** When `onSubmit` is provided, the SDK routes every intercept-capable flow through it instead of calling the parse API itself. Useful for: * Forwarding PDFs to your own backend without burning parse credits * Custom retry / queueing logic * Storing a copy of the PDF before parsing ```typescript theme={null} type SubmitInput = | { kind: 'file'; file: File; filename: string; source: 'UPLOAD' } | { kind: 'url'; pdfUrl: string; // presigned, valid 24–48h filename: string; source: 'INBOX' | 'INBOUND_EMAIL' | 'CDSL_FETCH'; metadata?: Record; // cas_type, message_id, received_at, etc. }; onSubmit={async (input, password, onProgress) => { if (input.kind === 'file') { // User uploaded the PDF directly const form = new FormData(); form.append('file', input.file); form.append('password', password); return fetch('/api/portfolio/upload', { method: 'POST', body: form }).then(r => r.json()); } // input.kind === 'url' — fetched from inbox / inbound email / CDSL return fetch('/api/portfolio/from-url', { method: 'POST', body: JSON.stringify({ pdfUrl: input.pdfUrl, password, source: input.source, }), }).then(r => r.json()); }} ``` The MF generator (KFintech mailback) flow stays out of scope — the CAS is delivered to the investor's email out of band, so the SDK never sees a PDF. The return value is forwarded to `onSuccess`. Throw to trigger `onError`. *** ## Framework integration ### React / Next.js ```tsx theme={null} import { PortfolioConnect } from '@cas-parser/connect'; function ImportButton({ accessToken }: { accessToken: string }) { return ( { console.log(`Total: ₹${data.summary?.total_value} (${metadata.parser_type})`); }} onError={(err) => console.error(err.code, err.message)} onExit={() => console.log('widget closed')} > {({ open, isReady }) => ( )} ); } ``` For **Next.js App Router**, mark the parent component `'use client'` and fetch the access token from a server route. ### Vanilla JS / Angular / Vue (Imperative API) The standalone bundle ships its own React copy — no host-page React required. ```html theme={null} ``` If your page already has React 18+, use the lighter UMD bundle: ```html theme={null} ``` **Bundle sizes (gzipped):** * Standalone: \~71 KB (includes React) * UMD: \~27 KB (requires React 18+ on the page) The imperative `open()` accepts the same `accessToken` / `apiKey` / `apiBaseUrl` / `config` / `onSubmit` / `onEvent` as the React props, plus an optional `onOpen` lifecycle hook. The success / error / cancel callbacks are intentionally dropped — the returned `OpenResult` covers all three outcomes. If you need manual control (open the widget later, dispose without opening), use `PortfolioConnect.create(config)` which returns a `{ open, destroy }` handle. ### React Native / Flutter Both are supported via WebView. See the [`@cas-parser/connect` examples](https://github.com/CASParser/cas-connect/tree/main/examples) for working setups. *** ## Theming The recommended way to brand the widget is the `config.theme` object — it emits CSS variables on the modal root and derives the full palette (hover, soft, surface, border, text) from your primary colour automatically. ```tsx theme={null} config={{ theme: { mode: 'auto', // 'light' | 'dark' | 'auto' (follows prefers-color-scheme) primary: '#10B981', primaryHover: '#059669', // optional — derived if omitted primaryForeground: '#FFFFFF', accent: '#F59E0B', radius: 12, // base radius in px; sm/md/lg/xl/pill are derived fontFamily: 'Inter, system-ui, sans-serif', }, }} ``` ### CSS variables If you need to override individual tokens beyond what `theme` exposes, set the CSS variables directly. The full list emitted on the modal root: ```css theme={null} /* Primary palette */ --pc-color-primary --pc-color-primary-hover --pc-color-primary-foreground --pc-color-primary-soft /* low-alpha tint of primary */ --pc-color-primary-soft-border --pc-color-accent /* Surfaces & borders (driven by mode) */ --pc-color-surface, --pc-color-surface-subtle, --pc-color-surface-muted, --pc-color-surface-strong --pc-color-border, --pc-color-border-strong /* Text */ --pc-color-text, --pc-color-text-muted, --pc-color-text-faded /* Status */ --pc-color-success, --pc-color-success-bg, --pc-color-success-border --pc-color-error, --pc-color-error-bg, --pc-color-error-border --pc-color-warning, --pc-color-warning-bg, --pc-color-warning-border /* Geometry & font */ --pc-radius-sm, --pc-radius-md, --pc-radius-lg, --pc-radius-xl, --pc-radius-pill --pc-font-family --pc-shadow-card, --pc-shadow-button ``` `theme.mode: 'auto'` follows the user's `prefers-color-scheme` media query. *** ## Next steps Forward-email flow used by `enableInboundEmail` Endpoints the widget calls under the hood # Portfolio Connect SDK Source: https://casparser.in/docs/sdk/portfolio-connect Drop-in React widget for portfolio import. PDF upload, email inbox (Gmail, Outlook, Zoho), CDSL OTP — all in one modal. Portfolio Connect is a white-label UI widget for importing Indian portfolios. One component handles file upload, email inbox import (Gmail, Outlook, Zoho), and CDSL OTP fetch. Try the widget at connect.casparser.in Drag and drop CAS PDFs or browse files Import CAS files directly from email via OAuth (Gmail, Outlook, Zoho) Fetch live demat holdings via OTP verification ### Supported Statements | Statement | Covers | Methods | | - | - | - | | **CAMS / KFintech CAS** | Mutual Funds (Demat + Non-Demat) | PDF upload, Email request via KFintech, Email Inbox integration | | **CDSL eCAS** | All holdings (CDSL + NSDL): Stocks, ETFs, Bonds, SGBs, Demat/Non-Demat MFs, AIFs | PDF upload, OTP fetch, Email Inbox integration | | **NSDL eCAS** | All holdings (NSDL + CDSL): Stocks, ETFs, Bonds, SGBs, Demat/Non-Demat MFs, AIFs | PDF upload, Email Inbox integration | **Cross-depository coverage:** Both CDSL and NSDL eCAS statements contain holdings from **both** depositories, not just the issuing depository. ## Installation ```bash theme={null} npm install @cas-parser/connect ``` ```bash theme={null} yarn add @cas-parser/connect ``` ```html theme={null} ``` ## Quick Start ### React / Next.js ```jsx theme={null} import { PortfolioConnect } from '@cas-parser/connect'; function App() { return ( console.log(data.holdings)} > {({ open }) => } ); } ``` ### Vanilla JavaScript ```html theme={null} ``` ## Access Tokens **Never expose your API key to the frontend.** Generate short-lived access tokens from your backend. ```python theme={null} # Backend: Generate access token import requests response = requests.post( "https://api.casparser.in/v1/token", headers={"x-api-key": "your_api_key"}, json={"expiry_minutes": 60} ) access_token = response.json()["access_token"] # Pass this at_ prefixed token to your frontend ``` 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 ## Response Data See full schema: [Response Schema](/docs/learn/response-schema) ```typescript theme={null} interface ParsedData { meta: { cas_type: 'CAMS_KFINTECH' | 'CDSL' | 'NSDL'; statement_period: { from: string; // YYYY-MM-DD to: string; // YYYY-MM-DD }; generated_at: string; // ISO 8601 }; investor: { name: string; pan: string; email?: string; mobile?: string; address?: string; pincode?: string; cas_id?: string; }; summary: { total_value: number; accounts: { demat: { count: number; total_value: number }; mutual_funds: { count: number; total_value: number }; insurance: { count: number; total_value: number }; nps: { count: number; total_value: number }; }; }; demat_accounts: Array; mutual_funds: Array; insurance: { life_insurance_policies: Array; }; nps: Array; } ``` ## Security | Feature | Details | | - | - | | **Data Hosting** | 100% India-hosted (DigitalOcean Bangalore) | | **Encryption** | TLS 1.3 in transit, AES-256 at rest | | **Data Retention** | PDFs deleted immediately after parsing | | **Credentials** | We never store user passwords | Complete configuration options, events, error handling, and framework examples