Short answer: To use the OANDA API from Python, you need a v20 account, a personal access token created under Manage API Access, and HTTPS calls with that token to api-fxpractice.oanda.com for practice or api-fxtrade.oanda.com for live. OANDA's documentation, read on 5 November 2026, says v20 is available to all divisions except OANDA Global Markets and OANDA TMS BROKERS S.A., and allows 120 requests per second per IP. The official v20 package was last released on 28 September 2018 and failed on import here until PyYAML was added, so plain requests is a fair alternative.
Work out lots, units and pip values with your own figures: Forex position size calculator and Pip calculator.
Leveraged FX and CFDs can lose money quickly: the banner on OANDA's developer pages, as shown on 5 November 2026, says 76.6% of retail investor accounts lose money when trading CFDs with this provider, and their footer says 76.8%. Treat everything here as software documentation, not a reason to trade. No broker is recommended, ranked or linked with a referral; rules depend on your country, and account types and API access vary.
Official pages were read on 5 November 2026. No token or practice account was available, so all code ran against a local mock built from the documented responses, and the footer dates on the developer pages run from 1996 to 2019, so recheck anything that matters.
Section 3 of TradingView's Terms of Use (opens in a new tab), read on 5 November 2026, licenses charts, alerts and webhooks for display-only use, lists automated trading and automated order generation among prohibited uses, and bars third-party products built for that purpose. That applies to any TradingView bridge to OANDA; it is a contract question between you and TradingView, so read the terms and take legal advice.
OANDA API key: how to get a v20 token
OANDA calls the key a personal access token, and you generate it from your account after accepting the API license.
- Open a v20 account, live or practice.
- Log in and find the token page. The developer introduction (opens in a new tab) says My Services, then Manage API Access; a help article (opens in a new tab) says the Hub, under Tools, then API. Layouts differ, so look for both.
- Agree to the license and generate the token.
- Store it at once: the authentication page (opens in a new tab) says OANDA does not retain it, and a lost token must be revoked and replaced. See API key security.
- Send it as
Authorization: Bearerand callGET /v3/accountsto read your account IDs.
| Message | What the pages say |
|---|---|
Insufficient authorization to perform request. | Listed under 401 (bad token or wrong URL) and under 403 (account cannot trade), so check the token and host first |
The provided request was forbidden. | Listed under 401 on the developer page; the help article names no status and says it usually means a wrong account ID |
The authentication page says one token reaches all sub-accounts, while the help article says new sub-accounts need a new token. If a new sub-account returns an error, try that first.
OANDA API pricing: who can use v20 and what it costs
Availability depends on your OANDA division, and no page read lists a fee for API access itself.
The introduction names two divisions that are excluded: OANDA Global Markets and OANDA TMS BROKERS S.A. The Singapore REST API page (opens in a new tab) advertises a free demo environment and requires an application, approval, funding and acceptance of an API agreement, with no stated minimum. Check the page for your own division. Accounts linked to MT4 have a further rule: the order definitions say not to set client extensions on them.
Exchange rates are a separate product. developer.oanda.com/exchange-rates-api/ redirected on 5 November 2026 to api.corporatefxservices.com, whose OpenAPI file describes a separate API key from its own portal. OANDA's API plans page (opens in a new tab), read the same day, lists Lite at $450 a month or $4,850 a year up to Premium + Crypto at $1,680 a month or $17,000 a year. A v20 token belongs to your trading account, and nothing read says it covers commercial display of rates, so check the API agreement.
OANDA API limits, and where the documents disagree
The Development Guide gives firm numbers, the Best Practices page gives a lower recommendation, and neither explains what happens after a 429. The OANDA API documentation lives at developer.oanda.com.
| Item | Value | Source |
|---|---|---|
| REST requests | 120 a second per IP, then HTTP 429 | Development Guide (opens in a new tab) |
| REST on one connection | 100 a second recommended | Best Practices (opens in a new tab) |
| New connections | 2 a second | Both pages |
| Active streams | 20 per IP; more are rejected | Development Guide |
| Stream prices | At most 4 a second per instrument; heartbeat every 5 seconds | Pricing (opens in a new tab) |
Hosts: REST on api-fxpractice.oanda.com and api-fxtrade.oanda.com, streams on stream-fxpractice.oanda.com and stream-fxtrade.oanda.com. Design to 100 a second on a kept-alive connection. Other gaps:
- The pages read mention no
Retry-Afterheader, so choose your own back-off. - The Singapore page mentions WebSockets; the developer pages show chunked HTTP on the stream hosts, and that is what the sample below reads.
- Prices skipped within a 250-millisecond window never arrive, so the stream is not a full tick record.
OANDA v20 Python binding or plain requests?
Both reached the same mock responses, so the choice is about upkeep rather than features.
| Check | v20 3.0.25.0 | requests |
|---|---|---|
| Last release | 28 September 2018; the newest egg on GitHub targets Python 3.7, and PyPI classifiers name 2.7 and 3.5 | 2.34.2 as installed |
| Fresh install | import v20 failed with No module named 'yaml'; worked after pip install pyyaml | Worked |
| Order and stream calls | Worked; r.get(field, 201) raises ResponseUnexpectedStatus on any other status | Worked; you read fields yourself |
| License signal | MIT on GitHub and in PyPI's license field; PyPI's classifier says Other/Proprietary | Apache 2.0 |
The binding gives typed responses but tracks no API change after 2018; plain requests means you own the field names. The community package oandapyV20 (opens in a new tab), last released as 0.7.2 on 27 August 2021, was not run; OANDA's help article calls it third-party and unsupported.
OANDA v20 Python example: orders and prices
This client treats HTTP 201 as a created order, not a fill, and never resends after a timeout or dropped connection without looking first. Create it with V20(os.environ["OANDA_TOKEN"], account_id).
import json, requests
class V20:
"""Plain requests client; keep the token out of the code."""
def __init__(self, token, account, rest="https://api-fxpractice.oanda.com",
stream="https://stream-fxpractice.oanda.com"):
self.account, self.rest, self.stream_host = account, rest, stream
self.http = requests.Session() # keep-alive, as Best Practices asks
self.http.headers.update({"Authorization": f"Bearer {token}",
"Accept-Datetime-Format": "RFC3339"})
def market(self, instrument, units, client_id, timeout=10):
url = f"{self.rest}/v3/accounts/{self.account}/orders"
order = {"type": "MARKET", "instrument": instrument, "units": str(units),
"timeInForce": "FOK", "positionFill": "DEFAULT",
"clientExtensions": {"id": client_id}}
try:
r = self.http.post(url, json={"order": order}, timeout=timeout)
except (requests.Timeout, requests.ConnectionError):
return self.find(client_id) # never resend blind
body = r.json() if "json" in r.headers.get("Content-Type", "") else {}
if r.status_code == 201: # 201 does not mean filled
if "orderFillTransaction" in body: # its price field is deprecated
return "FILLED", body["orderFillTransaction"].get("fullVWAP")
if "orderCancelTransaction" in body:
return "CANCELLED", body["orderCancelTransaction"]["reason"]
return "OPEN", body["orderCreateTransaction"]["id"]
if r.status_code == 429:
return "RATE_LIMITED", None # back off, do not loop
reject = body.get("orderRejectTransaction", {}).get("rejectReason")
return f"HTTP_{r.status_code}", reject or body.get("errorMessage")
def find(self, client_id):
url = f"{self.rest}/v3/accounts/{self.account}/orders/@{client_id}"
r = self.http.get(url, timeout=10)
if r.status_code == 404:
return "NOT_FOUND", None
r.raise_for_status() # a 429 or 5xx says nothing about the order
return f"FOUND_{r.json()['order']['state']}", None
def prices(self, instruments, read_timeout=15):
"""Yield PRICE dicts; read_timeout is three of the documented 5 s heartbeats."""
url = f"{self.stream_host}/v3/accounts/{self.account}/pricing/stream"
with self.http.get(url, params={"instruments": instruments}, stream=True,
timeout=(5, read_timeout)) as r:
r.raise_for_status()
for line in filter(None, r.iter_lines()):
msg = json.loads(line)
if msg["type"] == "PRICE":
yield msgOutput against the mock, including the failure paths. The account ID, token and prices are synthetic; not run on a practice or live account, so try it there first as described in paper trading API:
fill ('FILLED', '1.15012')
cancelled ('CANCELLED', 'MARKET_HALTED')
reject ('HTTP_400', 'UNITS_PRECISION_EXCEEDED')
429 ('RATE_LIMITED', None)
timeout ('FOUND_FILLED', None)
dropped ('FOUND_FILLED', None)
lookup 503 raises HTTPError
price 1.15000
bad token ('HTTP_401', 'Insufficient authorization to perform request.')
bad acct ('HTTP_401', 'The provided request was forbidden.')
stream stalled: ConnectionErrorWhat each case shows:
- 201 is not a fill. The order endpoint (opens in a new tab) returns 201 with
orderCancelTransactionwhen a fill-or-kill order is canceled, for example with reasonMARKET_HALTED. The transaction definitions (opens in a new tab) mark the fill'spricedeprecated, so the code readsfullVWAP. - Units are signed strings. Positive is long, negative is short.
- Timeouts and dropped connections. The order specifier accepts
@clientID, so the code looks the order up. Whether a filled market order stays retrievable that way could only be run on the mock, so confirm it on practice. Any lookup status except 404 raises, and a 404 right after a timeout does not prove the order never arrived, so look again before resending. - Duplicates.
CLIENT_ORDER_ID_ALREADY_EXISTSis defined against another pending order, and a filled order is no longer pending, so keep your own duplicate check; see client order ID. - Stalled stream. With no heartbeat inside the read timeout,
requestsraisedConnectionErrorhere, so wrapprices()in a reconnect loop with back-off. - Position fill.
DEFAULTreduces first on non-hedging accounts and opens a new trade on hedging accounts, so one code path can behave differently per account.
Hypothetical example. An alert sends 10,000 units. The POST times out at 10 seconds, and the worker resends. If the first request had filled, the account now holds 20,000 units at twice the intended risk. With the lookup, the worker finds the order and stops. Limits on size belong outside this code, as in trading bot risk management.
How to connect TradingView to OANDA
TradingView lists OANDA as a broker for trading from the chart, but that link is not automation; alerts need your own bridge calling v20.
TradingView's broker directory (opens in a new tab) has an OANDA profile, and its Help Center (opens in a new tab) says automated strategy trading with a brokerage account is not available yet. A bridge is alert, your HTTPS receiver, then the order code above; see TradingView webhook bot. Use alerts from rules you wrote, on your own account, never other people's. Order design beyond this page is in broker API integration.
Leveraged FX rules by country, including Turkey
Whether you may use a given broker depends on where you live, so check your own regulator before you open an account or write a line of code.
| Where | What the regulator says |
|---|---|
| Turkey | SPK said on 23 February 2023 (opens in a new tab) that residents may carry out leveraged FX trades only through firms it has authorized; offering them without authorization is a crime under Article 109(2) of Law No. 6362 (opens in a new tab), with two to five years in prison and a judicial fine. Article 99(4) lets SPK block such sites, and Communiqué III-37.1 (opens in a new tab) Articles 9 and 27 cap leverage at authorized firms at 10:1 and treat a Turkish website or marketing as activity aimed at residents. Take legal advice before using a foreign firm |
| United States | The CFTC advisory (opens in a new tab) lists leverage above the US legal limit (2 percent on major pairs, 5 percent on others) as a warning sign, warns about unregistered offshore dealers and points to NFA BASIC for checks |
| United Kingdom | The FCA's rules (opens in a new tab), announced 1 July 2019 and applying from 1 August 2019, cap retail CFD leverage, including rolling spot forex, at 30:1 to 2:1 |
| European Union | ESMA's temporary 2018 measures (opens in a new tab) set 30:1 on major pairs, and national regulators found 74% to 89% of retail accounts lose money on CFDs |
Fincode quotes integration work through its exchange API integration page, for foreign customers under their own law, so it has an interest here. A Turkish resident asking for a foreign leveraged FX account is declined or sent to a lawyer first. No prior OANDA project is claimed.
Frequently asked questions
Is the OANDA API free?
No page read on 5 November 2026 lists a fee for API access, and the Singapore page advertises a free demo environment. Trading costs on the account are separate, terms can differ by division, and the exchange rates API is a separate paid product.
Where do I find my OANDA API token?
Under Manage API Access in your account, or under Tools then API in the Hub, depending on the page you follow. OANDA does not retain it, so a lost token must be revoked and replaced.
Why do I get a 401 or 403 from the v20 API?
Check first that the host matches the account's environment, that the Bearer header is set and that the account ID came from GET /v3/accounts. OANDA's developer page lists one message under both codes, so do not branch on the text alone.
Can I test without real money?
Yes, a practice account uses api-fxpractice.oanda.com. A practice run shows how the code behaves, not how it would fill or perform live.
Sources
- OANDA Developer: Introduction (v20 requirements, divisions, Manage API Access) (opens in a new tab)
- OANDA Developer: Authentication (opens in a new tab)
- OANDA Developer: Development Guide (hosts, rate limits) (opens in a new tab)
- TradingView: Terms of Use, section 3 (read 5 November 2026) (opens in a new tab)
- OANDA Developer: Troubleshooting and Errors (opens in a new tab)
- OANDA Developer: Order endpoints (opens in a new tab)
- OANDA Developer: Order definitions (MarketOrderRequest, OrderSpecifier, position fill) (opens in a new tab)
- OANDA Developer: Transaction definitions (reject and cancel reasons) (opens in a new tab)
- SPK (Capital Markets Board): Press notice on unauthorized leveraged transactions, 23 February 2023 (Turkish) (opens in a new tab)
- OANDA Developer: Sample Code and libraries (opens in a new tab)
- OANDA Developer: Release Notes (3.0.25, 28 September 2018) (opens in a new tab)
- OANDA Help: REST v20 API troubleshooting guide (opens in a new tab)
- OANDA: v20-python repository (opens in a new tab)
- PyPI: v20 3.0.25.0 (opens in a new tab)
- OANDA Exchange Rates API documentation (own API key, read through its openapi.json) (opens in a new tab)
This guide is general information about software and markets, not investment, legal or tax advice. Automated trading can lose money, past or backtested performance does not guarantee future results, and the rules that apply depend on your jurisdiction and your exchange's terms. Sources, checks and corrections: see the editorial policy.