VPS 14 min read ·
Dhan API in Python: Static IP, Access Token and First Order
Use the Dhan API from Python: get the 24-hour access token, register your static IP, place a first LIMIT order with dhanhq 2.3 and fix DH-905 errors.
To use the Dhan API from Python you need three things: a daily access token, a static IP registered with Dhan, and the official dhanhq library (version 2.3.0 as of October 2026). Every Dhan access token now lasts 24 hours, and Dhan accepts order placement, modification and cancellation only from a static IP you have whitelisted. The Trading APIs are free; live market data is a paid add-on.
This guide covers tokens, IP registration, a first LIMIT order, the live feed and common errors, all on a Linux server. Prices, limits and rules come from Dhan's own documentation and help pages, checked in October 2026.
What the Dhan API costs and what you need first
Dhan sells DhanHQ as two products:
- Trading APIs: free. Placing and managing orders, and reading your portfolio and funds. Dhan's pricing page lists them "for FREE".
- Data APIs: ₹499 a month plus applicable taxes, debited from your trading ledger every 30 days, according to Dhan's help centre. This covers the live market feed over WebSocket, market quotes, and historical and intraday data. The old free-data offer for active traders has ended. A yearly plan exists, but Dhan's help pages don't give its price.
You also need an active Dhan account with your segments switched on, a server with a fixed public IP, and Python 3.10 or newer.
Dhan publishes these rate limits (as of October 2026):
| API group | Per second | Per minute | Per hour | Per day |
|---|---|---|---|---|
| Order APIs | 10 | 250 | 1,000 | 7,000 |
| Data APIs | 5 | – | – | 100,000 |
| Quote APIs | 1 | Unlimited | Unlimited | Unlimited |
| Non-trading APIs | 20 | Unlimited | Unlimited | Unlimited |
Each order can be modified at most 25 times. Ten orders a second is also the exchanges' threshold above which a retail algo must be registered, as our guide to SEBI's algo trading rules explains.
Choose how your bot gets its daily access token
Every request carries an access token. Dhan's help centre says the maximum validity is now 24 hours, in line with exchange and regulator requirements. Its authentication docs describe three ways to get one:
| Method | How it works | Validity | Hands-free? |
|---|---|---|---|
| Token from Dhan Web | web.dhan.co → My Profile → Access DhanHQ APIs → Generate Access Token, with an app name and an optional postback URL | 24 hours; the RenewToken call extends a still-active one by 24 hours | No, you paste it in yourself |
| API key and secret | Create a key with an app name and redirect URL. Each day you log in once in a browser with 2FA; Dhan sends a tokenId to the redirect URL and your code swaps it for a token | Key and secret: 12 months. Token: 24 hours | Partly: a person logs in each day |
| PIN and TOTP | Turn on TOTP under Setup TOTP, then send your client ID, PIN and the current TOTP code to the generateAccessToken endpoint | 24 hours | Yes |
Some Dhan help pages call the menu "Get Trading & Data APIs". You can hold up to 5 active tokens at once.
The PIN and TOTP route in Python
When you set up TOTP, Dhan shows a QR code and a text code. That text code is the TOTP secret, and pyotp (preinstalled on the Algo VPS) turns it into the current 6-digit code:
import os
import pyotp
from dhanhq import DhanLogin
login = DhanLogin(os.environ["DHAN_CLIENT_ID"])
code = pyotp.TOTP(os.environ["DHAN_TOTP_SECRET"]).now()
result = login.generate_token(os.environ["DHAN_PIN"], code)
token = result["accessToken"]
print("New token, valid until", result["expiryTime"])
Weigh the trade-off: your PIN and TOTP secret together are enough to log in to your Dhan account. A server holding both needs SSH-key logins only, secrets that only root can read, and a fresh TOTP setup if you ever suspect a leak. Pasting a web token each morning is more effort, but leaves nothing long-lived on the server.
Whichever route you choose, your bot needs a fresh token every trading day. Our guide to running a trading bot 24/7 shows how to schedule that step with a systemd timer before the market opens.
Register your server's static IP with Dhan
On an Algo VPS, run algo-ip: it prints the IPv4 address to register and the address Python actually connects from. The "Register your static IP" card in your HostEthical dashboard shows the same address with a link to Dhan's API settings.
In Dhan Web, the steps are: Profile → Get Trading & Data APIs → scroll down → Add IP. Or do it from code: dhanhq wraps Dhan's setIP, modifyIP and getIP endpoints in its DhanLogin class. This check script shows your token's expiry, your data plan and your registered IPs:
import os
from dhanhq import DhanLogin
login = DhanLogin(os.environ["DHAN_CLIENT_ID"])
token = os.environ["DHAN_ACCESS_TOKEN"]
profile = login.user_profile(token)
print("Token valid until:", profile.get("tokenValidity"))
print("Data plan:", profile.get("dataPlan"))
print("Registered IPs:", login.get_ip(token))
# First time only, with the IPv4 address that algo-ip prints:
# print(login.set_ip(token, "203.0.113.10", "PRIMARY"))
The rules, from Dhan's documentation:
- A primary and an optional secondary IP, each IPv4 or IPv6.
- A 7-day lock. Once an IP is whitelisted, it can't be edited for the next 7 days.
getIPreturnsmodifyDatePrimaryandmodifyDateSecondary, the dates from which each can be changed. - Only order calls are checked. Placing, modifying and cancelling orders, including Super Orders and Forever Orders, need the whitelisted IP. Fetching orders, trades and market data does not.
- A new IP needs a new token. If you added the IP after generating your token, generate the token again.
The IPv6 trap and how Dhan compares with other brokers are covered in our static IP for algo trading guide.
Dhan's family-IP rule
Dhan's API docs say "each individual needs to have a unique static IP". Its help centre adds one exception: eligible family member accounts can be mapped to a single static IP, subject to verification and approval. To request it, email apihelp@dhan.co from your registered email address, with the registered address of every family member's account in CC. Those pages don't define "family". The exchange rules use SEBI's definition: spouse, dependent children and dependent parents. Never share the IP with friends or clients.
Install dhanhq and connect with DhanContext
On an Algo VPS, dhanhq is already in the virtual environment at /opt/algo/venv. Check which version you have, and upgrade if a newer one is out:
/opt/algo/venv/bin/pip show dhanhq
/opt/algo/venv/bin/pip install --upgrade dhanhq
On another server, create a virtual environment and run pip install dhanhq. Version 2.3.0 came out on 8 October 2026; Ubuntu 24.04 ships Python 3.12, which meets its 3.10 minimum.
The constructor has changed. Tutorials written for version 2.0 and earlier create the client with dhanhq("client_id", "access_token") and stream prices with marketfeed.DhanFeed. From version 2.1 onwards you build a DhanContext and pass it in, and the old two-argument call fails with a TypeError:
from dhanhq import DhanContext, dhanhq
ctx = DhanContext(client_id, access_token)
dhan = dhanhq(ctx)
Keep your credentials out of the code. On an Algo VPS they belong in /opt/algo/.env, which is created with chmod 600 so only root can read it:
DHAN_CLIENT_ID=your-client-id
DHAN_ACCESS_TOKEN=paste-todays-token-here
# Only if you use the PIN and TOTP login:
DHAN_PIN=
DHAN_TOTP_SECRET=
The algo@ service loads this file automatically. When you run a script by hand, load it into your shell first:
cd /opt/algo
set -a; . ./.env; set +a
venv/bin/python dhan_check.py
Run the check script this way before your first order: it catches an expired token or a missing IP while nothing is at stake.
Place a LIMIT order and check its status
This script places one LIMIT buy on NSE and checks its status every few seconds until it is filled, rejected, cancelled or expired. It refuses to run until you set your own limit price.
"""Place one LIMIT order on Dhan and follow its status. Run it by hand."""
import os
import sys
import time
from dhanhq import DhanContext, dhanhq
SECURITY_ID = "1333" # HDFC Bank, NSE equity (from Dhan's instrument list)
QUANTITY = 1
LIMIT_PRICE = 0.0 # set your own limit price before running
TAG = "first-order-001" # your correlation ID, up to 30 characters
if LIMIT_PRICE <= 0:
sys.exit("Set LIMIT_PRICE first.")
ctx = DhanContext(os.environ["DHAN_CLIENT_ID"], os.environ["DHAN_ACCESS_TOKEN"])
dhan = dhanhq(ctx)
resp = dhan.place_order(
security_id=SECURITY_ID,
exchange_segment=dhan.NSE, # NSE_EQ
transaction_type=dhan.BUY,
quantity=QUANTITY,
order_type=dhan.LIMIT,
product_type=dhan.CNC, # delivery; dhan.INTRA for intraday
price=LIMIT_PRICE,
validity=dhan.DAY,
tag=TAG,
)
if resp["status"] != "success":
sys.exit(f"Order not accepted: {resp['remarks']}") # DH-905, DH-906 ...
order_id = resp["data"]["orderId"]
print("Order", order_id, resp["data"]["orderStatus"])
for _ in range(10):
time.sleep(3)
r = dhan.get_order_by_id(order_id)
if r["status"] != "success":
print("Lookup failed:", r["remarks"])
continue
order = r["data"][0] if isinstance(r["data"], list) else r["data"]
status = order["orderStatus"]
print(status, "filled", order.get("filledQty"), "of", order.get("quantity"))
if status == "REJECTED":
print("Reason:", order.get("omsErrorDescription"))
if status in ("TRADED", "REJECTED", "CANCELLED", "EXPIRED"):
break
What to know about it:
- Security IDs come from Dhan's instrument list. Look them up in the compact CSV (column
SEM_SMST_SECURITY_ID) or withdhan.fetch_security_list("compact"). The same number can exist in several segments (1333 is also a currency contract), so always pair it with the rightexchange_segment. - Every SDK call returns a dictionary with
status,remarksanddata. When a call fails,remarksholds Dhan'serror_code,error_typeanderror_message. - Order statuses are TRANSIT, PENDING, PART_TRADED, TRADED, REJECTED, CANCELLED and EXPIRED. A rejected order carries the reason in
omsErrorDescription. To cancel an open order, calldhan.cancel_order(order_id). - The
tagbecomes the order's correlation ID.dhan.get_order_by_correlationID(TAG)finds the order even if your script lost the order ID. - Polling is fine for a test. A bot can instead listen with the SDK's
OrderUpdateclass, which receives changes to your orders over Dhan's order update WebSocket.
Stream live prices with Dhan's market feed
The live feed needs the Data APIs subscription; without it, the connection closes with code 806. The static IP isn't checked for market data.
"""Print live prices from Dhan's market feed (needs the Data API plan)."""
import os
from dhanhq import DhanContext, MarketFeed
ctx = DhanContext(os.environ["DHAN_CLIENT_ID"], os.environ["DHAN_ACCESS_TOKEN"])
instruments = [
(MarketFeed.NSE, "1333", MarketFeed.Ticker), # (segment, security ID, mode)
]
feed = MarketFeed(ctx, instruments, "v2")
feed.run_forever() # connects and subscribes, then returns
while True:
tick = feed.get_data() # waits for the next packet
if tick and tick.get("type") == "Ticker Data":
print(tick["security_id"], tick["LTP"], flush=True)
The limits from Dhan's feed documentation, as of October 2026:
- Up to 5 WebSocket connections per user. Opening a sixth disconnects your first one with code 805.
- Up to 5,000 instruments per connection, and at most 100 in a single subscription message.
- Three modes: Ticker (last price and time), Quote (adds volume, OHLC and buy/sell quantities) and Full (adds market depth).
- A ping every 10 seconds. The server drops clients that stay silent for more than 40 seconds. The SDK answers for you and decodes Dhan's binary packets.
If the connection drops, get_data() raises an exception and the script stops. Under the service described next, systemd simply starts it again.
Run your Dhan bot 24/7 with the algo@ service
Every Algo VPS has a systemd template called algo@<name>. Save your script as /opt/algo/dhan_feed.py, then run, as root:
systemctl enable --now algo@dhan_feed
journalctl -u algo@dhan_feed -f # live logs
systemctl restart algo@dhan_feed # after you put a new token in .env
The template runs the script with the Python in /opt/algo/venv, loads /opt/algo/.env, starts the bot at boot and restarts it 10 seconds after a crash. Three things matter once real orders are involved:
- A restart can repeat an order. If your script places an order and then crashes, systemd starts it again and the same code may place a second order. Give each order a fixed correlation ID and look it up with
get_order_by_correlationIDbefore placing it. - A permanent error keeps looping. With an expired token (DH-901), the script fails, restarts 10 seconds later and fails again, all day. Catch errors that a retry can't fix, send yourself an alert and exit cleanly. The 24/7 bot guide covers alerts, crash loops and kill switches.
- Flush your output. Python buffers
printwhen it isn't writing to a terminal, so useflush=Trueorlogging, or lines reachjournalctllate.
Dhan's kill switch API can also block trading for the rest of the day, once your positions are closed and no orders are pending.
You don't need the Algo VPS for any of this. A plain Cloud VPS starts at ₹860 a month, or about ₹774.17 a month when billed yearly. Mumbai adds a monthly location fee, and you install Python, the SDK, the IPv4 preference and the service yourself. The Algo VPS, with all of that done at first boot, costs ₹1,510 a month, or about ₹1,325 a month when billed yearly. Our guide to choosing a VPS for algo trading compares both with cloud and broker-hosted options.
Fixing DH-905 and other Dhan API errors
The codes and descriptions below are from Dhan's error annexure, with what to check first.
| Code | Dhan's description | What to check |
|---|---|---|
| DH-901 | Client ID or access token invalid or expired | Generate a new token, check the client ID in .env, restart the service |
| DH-902 | No Data API subscription, or no Trading API access | The Data APIs tab in Dhan Web |
| DH-903 | Account error, such as a segment that isn't activated | Your segments and account status in Dhan |
| DH-904 | Rate limit breached | Slow down; see the limits table above |
| DH-905 | Input exception: missing or bad parameters. IP rejections also arrive as "DH-905 Invalid IP" | The IP checklist below, then your order fields |
| DH-906 | Incorrect order request | Price and tick size, quantity and lot size, product type, market hours |
| 805 / 806 / 807 | Feed: too many connections / Data APIs not subscribed / token expired | Close extra connections, subscribe, or renew the token |
For "DH-905 Invalid IP", work through these in order:
- Compare addresses on the server. Run
algo-ipand the check script. If the address Python uses differs from the IPv4 you registered, your requests are leaving over IPv6. Make the server prefer IPv4 or register the IPv6 address as your secondary IP, as the static IP guide explains. - Send the order from the server, not from your laptop, and with no VPN or proxy in the path.
- Generate a new token if you added the IP after creating the old one. This is the fix Dhan suggests.
- Use Dhan's own check. In Dhan Web go to Profile → Get Trading & Data APIs → Sandbox (top right) → IP Setup → Get Static IP, and send your latest token. The response shows your primary IP, secondary IP, the detected IP and whether orders are allowed.
- Check the 7-day lock if you changed the IP recently. Dhan can't turn off IP whitelisting or allow a dynamic IP, but apihelp@dhan.co can help with the setup.
Dhan's own TradingView webhooks: when you may not need a bot
For a plain "alert fires, order is placed" setup, Dhan has a no-code feature. Its webhook guide gives the steps:
- In web.dhan.co, go to Orders → Webhooks, click Manage under TV Webhook URL and generate a URL.
- Build the order in Dhan's webhook order window (equity, F&O, currency, commodity, multi-segment or baskets) and click Generate JSON.
- In the TradingView alert, turn on Webhook URL, paste Dhan's URL and put only that JSON in the Message box.
You need a TradingView Pro, Pro+ or Premium plan. Each JSON carries a secret tied to your webhook URL, so if you generate a new URL you must update every alert. TradingView only sends the alert: it doesn't know whether Dhan placed the order, so check your order book.
Dhan's webhook guide doesn't mention a static IP. In an October 2026 reply on MadeForTrade, Dhan's community forum, the account t7support said webhook orders need neither API access nor IP whitelisting. We couldn't find that in Dhan's documentation, so confirm it with Dhan before you rely on it.
If you need logic between alert and order, such as position checks or more than one broker, run your own receiver on your static-IP server; our TradingView webhook guide builds one. If Dhan's webhooks or a broker-hosted platform already do the job, you don't need a server at all.
Frequently asked questions
Is the Dhan API free?
The Trading APIs are free, but the Data APIs are not. As of October 2026, Dhan charges ₹499 a month plus applicable taxes for the Data APIs, debited from your trading ledger every 30 days. That subscription covers the live market feed, market quotes, and historical and intraday data. Placing, modifying and cancelling orders, and fetching positions, holdings and funds, costs nothing extra.
How long is a Dhan API access token valid?
A Dhan access token is valid for 24 hours, whichever way you generate it: on Dhan Web, through the API key and secret login, or with your PIN and a TOTP code. Your bot therefore needs a fresh token every trading day. You can hold up to five active tokens at once, and Dhan's RenewToken call can extend a still-active token that was generated on Dhan Web.
Why does Dhan reject my API order with DH-905 Invalid IP?
DH-905 Invalid IP means the address your order came from does not match the static IP you whitelisted with Dhan. Common causes are requests leaving over IPv6 when you registered IPv4, testing from a laptop instead of the server, a VPN, or a token generated before you added the IP. Check the address on the server, generate a new token and try again.
Can family members use the same static IP with Dhan?
Yes, but only with Dhan's approval. Dhan's API docs say each individual needs a unique static IP, while its help centre says eligible family member accounts can be mapped to one IP after verification. To ask for it, email apihelp@dhan.co from your registered address with each family member's registered email in CC. Never share the IP with friends or clients.