Automated OAuth token refresh for Dhan trading API. Single Python file, no framework dependencies.
Dhan access tokens expire every 24 hours and require browser-based login with TOTP 2FA. This tool automates the entire flow using Playwright — set it on a cron job and never manually refresh again.
If you use Dhan's trading or data APIs, you know the pain:
- Tokens expire daily at ~8:30 AM IST
- Refreshing requires browser login → mobile number → TOTP → redirect
- No programmatic token refresh endpoint exists
- Miss a refresh = your algo/dashboard goes down
This solves it with a single file you can drop into any project.
# Install
pip install playwright pyotp requests python-dateutil
playwright install chromium
# Set credentials
export DHAN_CLIENT_ID=your_client_id
export DHAN_API_KEY=your_oauth_app_id
export DHAN_API_SECRET=your_oauth_app_secret
export DHAN_MOBILE=your_mobile_number
export DHAN_TOTP_SECRET=YOUR_BASE32_SECRETfrom dhan_auth import DhanAuth
auth = DhanAuth(
client_id="your_client_id",
api_key="your_app_id",
api_secret="your_app_secret",
mobile="your_mobile_number",
totp_secret="YOUR_BASE32_SECRET",
)
# Get token (auto-refreshes if expired)
token = auth.token()
# Use with dhanhq
from dhanhq import dhanhq
dhan = dhanhq("your_client_id", token) ┌─────────────────────────────┐
│ auth.token() / auth.refresh()│
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ 1. Generate Consent ID │
│ POST auth.dhan.co/app/ │
│ generate-consent │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ 2. Playwright Browser Login │
│ • Enter mobile number │
│ • Generate & enter TOTP │
│ • Enter PIN (if required) │
│ • Capture redirect tokenId │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ 3. Consume Consent │
│ POST auth.dhan.co/app/ │
│ consumeApp-consent │
│ → Returns accessToken │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ 4. Save to token.json │
│ Valid for ~24 hours │
└─────────────────────────────┘
- Single file — just copy
dhan_auth.pyinto your project - Auto-refresh — call
auth.token()and it handles expiry automatically - TOTP automation — generates TOTP codes using
pyotp, handles timing edge cases - Multiple input strategies — handles Dhan's various OTP input formats (individual boxes, single field, tel inputs)
- 30-min cooldown — prevents burning Dhan's daily consent limit
- Token persistence — saves to JSON file, survives restarts
- Manual flow support — generate consent URL for browser-based auth when headless isn't possible
- Headless & headed — works on servers (EC2, Docker) and desktops
Initialize the authenticator.
| Param | Description |
|---|---|
client_id |
Your Dhan client ID (from profile) |
api_key |
OAuth app ID (from Dhan API portal) |
api_secret |
OAuth app secret |
mobile |
Registered mobile number |
totp_secret |
Base32 TOTP secret (from authenticator app setup) |
pin |
6-digit PIN, optional (only if Dhan asks for it) |
token_file |
Path to save token JSON (default: data/token.json) |
| Method | Returns | Description |
|---|---|---|
auth.token() |
str |
Get valid access token. Auto-refreshes if expired. |
auth.refresh() |
dict |
Force full OAuth refresh via Playwright. Returns token data. |
auth.status() |
dict |
Token status: is_valid, hours_remaining, expires_at |
auth.get_consent_url() |
str |
Generate consent URL for manual browser login |
auth.consume(token_id) |
dict |
Exchange tokenId from redirect for access token |
# Add to crontab (8:55 AM IST = 3:25 UTC)
crontab -e
25 3 * * 1-5 /path/to/refresh.sh --force >> /var/log/dhan_refresh.log 2>&1The included refresh.sh script:
- Checks if backend is running
- Skips if token is fresh (>20h remaining)
- Calls
/api/auth/refreshendpoint - Prevents concurrent runs with lock file
# Use with APScheduler, Celery, or any scheduler
from dhan_auth import DhanAuth
auth = DhanAuth(...)
def daily_refresh():
info = auth.status()
if not info["is_valid"] or info["hours_remaining"] < 2:
auth.refresh()
print(f"Refreshed! Expires: {auth.status()['expires_at']}")# /etc/systemd/system/dhan-refresh.timer
[Unit]
Description=Dhan token refresh
[Timer]
OnCalendar=Mon..Fri 08:55 Asia/Kolkata
Persistent=true
[Install]
WantedBy=timers.targetIf you want HTTP endpoints for token management:
pip install fastapi uvicorn
uvicorn server:app --port 8000Endpoints:
| Route | Method | Description |
|---|---|---|
/api/auth/status |
GET | Token status |
/api/auth/refresh |
POST | Force refresh |
/api/auth/token |
GET | Get current token |
/api/auth/consent/generate |
POST | Manual auth URL |
/api/auth/consent/consume |
POST | Exchange tokenId |
/api/health |
GET | Health check |
# Install Playwright + Chromium
pip install playwright
playwright install chromium
playwright install-deps # System dependencies (Ubuntu/Debian)
# For ARM (Graviton/t4g instances)
# Playwright auto-detects architectureFROM python:3.11-slim
RUN pip install playwright pyotp requests python-dateutil
RUN playwright install chromium && playwright install-deps
COPY dhan_auth.py .
COPY your_app.py .
CMD ["python", "your_app.py"]- Client ID: Login to web.dhan.co → Profile → Client ID
- API Key & Secret: Go to dhanhq.co → Create App → Get
app_idandapp_secret - TOTP Secret: When setting up TOTP in Dhan app, copy the Base32 secret (the text code, not the QR). If already set up, you may need to reset TOTP to get the secret again.
- Mobile: The mobile number registered with your Dhan account
| Issue | Solution |
|---|---|
Consent generation failed: 401 |
Check api_key and api_secret are correct |
Timeout waiting for redirect |
TOTP might be wrong — verify totp_secret with an authenticator app |
TOTP not auto-submitted |
Dhan changed their UI — the code handles this with explicit button click fallback |
Token refresh failed after midnight |
Dhan resets consent limits at midnight IST — retry after 12:00 AM |
No chromium on server |
Run playwright install chromium && playwright install-deps |
| Works locally, fails on EC2 | Add --no-sandbox flag (already included in the code) |
Dhan doesn't have a standard OAuth refresh token flow. Instead:
- Consent API (
/app/generate-consent) creates a one-time login session - Browser login is mandatory — Dhan requires actual browser interaction (no pure API login)
- TOTP is required — can't use OTP (would need SMS access)
- After successful login, Dhan redirects to your
redirect_urlwith atokenIdquery parameter - Consume API (
/app/consumeApp-consent) exchangestokenIdforaccessToken - Token is valid for ~24 hours (until next day ~8:30 AM IST)
The Playwright automation mimics a human logging in — fills mobile, enters TOTP digits one by one with keyboard events (React needs this), and captures the redirect.
MIT — use it however you want.
PRs welcome! Especially for:
- Handling new Dhan login UI changes
- Adding support for OTP-based login (if you have SMS automation)
- Better error recovery and retry logic