Skip to main content

Documentation Index

Fetch the complete documentation index at: https://casparser.in/docs/llms.txt

Use this file to discover all available pages before exploring further.

This page covers @cas-parser/connect v2.x. For the underlying APIs the widget calls, see the API Reference.

Authentication

Never expose your raw API key to the browser. Mint a short-lived access token (at_*) on your backend via POST /v1/token 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.
# 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

PropTypeRequiredDescription
accessTokenstringOne of these twoShort-lived token from POST /v1/token. Recommended for browser embeds.
apiKeystringOne of these twoRaw API key. For server-side or trusted-environment use only.
apiBaseUrlstringNoOverride API base URL (for dedicated / self-hosted instances). Defaults to https://api.casparser.in.
configPortfolioConnectConfigNoFeature flags, branding, prefill, theme, etc. See Config.
onSuccess(data, metadata) => voidYesCalled when an import completes successfully.
onError(error) => voidNoCalled on any structured error.
onExit() => voidNoCalled when the widget closes (regardless of success/cancel).
onEvent(event, metadata) => voidNoFires for every widget event. Useful for analytics.
onSubmit(input, password, onProgress?) => Promise<any>Nov2.1+ — replaces the default parse API call across all intercept-capable flows. See onSubmit.
children(renderProps) => ReactNodeYes (React)Render-prop. Receives { open, isReady, isOpen }.

Config

interface PortfolioConnectConfig {
  // Import methods
  enableGenerator?: boolean;     // MF statement via email (default: false)
  enableCdslFetch?: boolean;     // CDSL OTP fetch (default: false)
  enableInbox?: boolean;         // Gmail OAuth import (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<string, string>;  // 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

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

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:
CodeWhen it fires
INVALID_PASSWORDAPI response message explicitly mentions “password”
AUTHENTICATIONHTTP 401/403
RATE_LIMITEDHTTP 429
NETWORKNetwork failure / timeout
PARSE_ERROR/v4/*/parse endpoint failed
GENERATOR_ERRORKFintech mailback failed
CDSL_FETCH_ERRORCDSL OTP / fetch flow failed
INBOX_ERRORGmail inbox flow failed
INBOUND_EMAIL_ERRORInbound email provisioning / polling failed
UNKNOWNCatch-all
The type is widened to | string, so future codes won’t break exhaustive switches.

onExit

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

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
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<string, unknown>;  // 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

import { PortfolioConnect } from '@cas-parser/connect';

function ImportButton({ accessToken }: { accessToken: string }) {
  return (
    <PortfolioConnect
      accessToken={accessToken}
      config={{
        enableCdslFetch: true,
        enableInboundEmail: true,
        homeLayout: 'asset-type',
        theme: { primary: '#10B981', mode: 'auto' },
      }}
      onSuccess={(data, metadata) => {
        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 }) => (
        <button onClick={open} disabled={!isReady}>
          Import Portfolio
        </button>
      )}
    </PortfolioConnect>
  );
}
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.
<script src="https://unpkg.com/@cas-parser/connect/dist/portfolio-connect.standalone.min.js"></script>

<button id="import-btn">Import Portfolio</button>

<script>
  document.getElementById('import-btn').onclick = async () => {
    // open() resolves with a discriminated result — never rejects on close.
    const result = await PortfolioConnect.open({
      accessToken: 'at_xxxxx',
      config: { enableCdslFetch: true },
    });

    if (result.status === 'success') {
      console.log('Portfolio:', result.data);
    } else if (result.status === 'closed') {
      console.log('User cancelled');
    } else {
      console.error('Error:', result.error);
    }
  };
</script>
If your page already has React 18+, use the lighter UMD bundle:
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@cas-parser/connect/dist/portfolio-connect.umd.min.js"></script>
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 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.
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:
/* 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

Inbound Email Guide

Forward-email flow used by enableInboundEmail

API Reference

Endpoints the widget calls under the hood