# 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