REST API¶
The UBI REST API is the whole point of this project. It is a Flask application that serves one combined view of ten broker accounts. You log in to it once a day with an API key and secret. After that, every request carries one access-token header, whichever broker ends up doing the work.
This page is the map. It lists every route, explains the conventions that all of them share, and links to one page per group.
All endpoints¶
The table below lists all 29 routes in 36 rows, because the seven instrument routes that also take a list of instruments by POST appear once for each method. The badge shows the HTTP method, and the lock column shows which routes need the access-token header.
| Method | Endpoint | What it does | Token |
|---|---|---|---|
| GET | /api/ |
Says hello, so a client can tell the API is up | |
| POST | /api/session/connect |
Exchanges the API key and secret for the day's access token | |
| DELETE | /api/session/disconnect |
Revokes the access token | |
| GET | /api/session/status |
Says whether the token is valid and when it expires | |
| GET | /api/users/details |
The account holder's profile and each broker's profile | |
| GET | /api/brokers/details |
Reference details for every broker | |
| GET | /api/exchanges/details |
Exchanges, trading hours and holidays | |
| GET | /api/instruments/segments |
Every exchange segment and the fields that name an instrument in it | |
| GET | /api/instruments/master |
The whole instrument list of a segment, streamed | |
| GET | /api/instruments/search |
Searches one segment by symbol or name | |
| GET | /api/instruments/details |
One instrument and how each broker names it | |
| POST | /api/instruments/details |
The same for a list of instruments in one request | |
| GET | /api/instruments/additional_details |
Extra broker-specific attributes of one instrument | |
| POST | /api/instruments/additional_details |
The same for a list of instruments in one request | |
| GET | /api/instruments/ltp |
Last traded price | |
| POST | /api/instruments/ltp |
The same for a list of instruments in one request | |
| GET | /api/instruments/ohlc |
The day's open, high, low, close and volume | |
| POST | /api/instruments/ohlc |
The same for a list of instruments in one request | |
| GET | /api/instruments/quote |
The full quote, including market depth | |
| POST | /api/instruments/quote |
The same for a list of instruments in one request | |
| GET | /api/instruments/prices |
Historical candles, adjusted for corporate actions if asked | |
| POST | /api/instruments/prices |
The same for a list of instruments in one request | |
| GET | /api/instruments/ticks |
Every recorded tick between two times, streamed | |
| POST | /api/instruments/ticks |
The same for a list of instruments in one request | |
| GET | /api/portfolio/funds |
Cash and margin, summed across brokers | |
| GET | /api/portfolio/holdings |
Long-term holdings, combined and priced | |
| GET | /api/portfolio/positions |
Open positions, combined | |
| GET | /api/orders/details |
Today's orders at every broker | |
| GET | /api/orders/trades |
Today's trades at every broker | |
| POST | /api/orders/place |
Places an order at a broker the API chooses, or each order of a list | |
| GET | /api/orders/intents/<intent_id> |
The order engine's answer for one order, read after the place request stopped waiting | |
| GET | /api/orders/parents |
One of the order engine's parents, or every one still open | |
| DELETE | /api/orders/parents |
Cancels one of the engine's parents with its resting legs, or each of a list | |
| PUT | /api/orders/modify |
Changes an open order, or each order of a list | |
| DELETE | /api/orders/cancel |
Cancels an open order, an engine parent or one part of a plan, or each of a list | |
| POST | /api/orders/flatten |
Cancels every open order and closes every position, everywhere |
Four routes move real money
place, modify, cancel and flatten send real requests to real broker accounts. Every one of them accepts "dry_run": true, which builds and returns the broker request without sending it. Use that first.
How the groups relate¶
The diagram below shows the order in which a client normally uses the groups. It logs in first, looks an instrument up, and only then reads prices or places orders.
flowchart LR
S["Session<br/>connect"] --> I["Instruments<br/>search / details"]
I --> Q["Market quotes<br/>ltp / ohlc / quote"]
I --> H["Historical data<br/>prices / ticks"]
I --> O["Orders<br/>place / modify / cancel"]
S --> P["Portfolio<br/>funds / holdings / positions"]
S --> D["Details<br/>users / brokers / exchanges"]
O -.-> F["Flatten<br/>(emergency)"]
Base URL¶
The API listens on 127.0.0.1:8080 by default, and every route lives under /api. The host and port come from UNIFIED_BROKER_INTERFACE_API_HOST and UNIFIED_BROKER_INTERFACE_API_PORT (see Configuration).
Start it with bin/rest-api, which runs gunicorn with two worker processes and four threads each. bin/rest-api --dev runs Flask's development server instead.
Authentication¶
Authentication happens in two steps. The key and secret are exchanged for a token once a day, and the token goes on every other request.
sequenceDiagram
autonumber
participant C as Your program
participant A as UBI API
participant M as MongoDB
participant R as Redis
C->>A: POST /api/session/connect<br/>api-key, api-secret headers
A->>M: read settings for unified_broker_interface
A->>A: compare key and secret in constant time
alt stored token was issued after 07:00 today and is unexpired
A-->>C: 200 the same token
else otherwise
A->>M: store a new token
A->>R: copy it to the last_login hash
A-->>C: 200 a new token
end
C->>A: GET /api/portfolio/positions<br/>access-token header
A->>R: read last_login
A-->>C: 200 positions
| Header | Sent to | Value |
|---|---|---|
api-key |
POST /api/session/connect only |
The key stored in MongoDB settings for unified_broker_interface |
api-secret |
POST /api/session/connect only |
The matching secret |
access-token |
Every other route except GET /api/ |
The token returned by connect |
There is only one token for the whole application. Issuing a new one replaces the old one, so any other client using the old token starts getting 401. A token issued before 07:00 IST is replaced by the first connect after 07:00.
Request format¶
GET and DELETE routes take their parameters in the query string. POST and PUT routes take a JSON body with Content-Type: application/json. The order routes modify and cancel also accept order_id, broker and dry_run in the query string, as a convenience.
Response format¶
Every response is JSON. A successful call returns the data directly, with no envelope around it. A failed call returns an object with an error key, sometimes with extra fields that help you act on the failure.
Two routes, /api/instruments/master and /api/instruments/ticks, can return very large arrays. They stream the JSON array in chunks of about 64 KB rather than building it in memory. If something fails after streaming has started, the array simply ends early and the status stays 200, so check the count you received.
Status codes¶
The API uses a small set of HTTP status codes, and each one means the same thing on every route. Errors and status codes lists which route returns which.
| Status | Meaning |
|---|---|
| 200 | Done. For orders, the broker accepted the request. |
| 202 | Order engine only: a synthetic order is armed and waiting for its trigger. |
| 207 | Flatten only: some cancels or closes failed. |
| 400 | The request is malformed or breaks a rule, such as a quantity that is not a multiple of the lot size. |
| 401 | The token or the key and secret are missing, wrong or expired. |
| 403 | Order engine only: the daily loss limit is reached. |
| 404 | The instrument, order or document does not exist. |
| 409 | The request conflicts with the current state, for example the order is already complete. |
| 422 | The broker rejected the order. |
| 429 | Order engine only: the broker's daily order cap, from unified.broker_order_costs or UNIFIED_BROKER_INTERFACE_API_ORDER_DAILY_CAPS, has no room left for this kind of order. |
| 500 | The API itself is not configured. |
| 501 | The broker cannot modify that field. |
| 502 | The stored order book, trade book or portfolio document exists, but no broker in it has usable data. |
| 503 | A store or a background process that the route depends on is missing or stale. |
| 504 | The broker or the order engine did not answer in time, so the outcome is unknown. |
504 on an order means unknown, not failed
When an order route returns 504, the order may well have reached the broker. Read GET /api/orders/details before sending it again, or you may end up with two orders.
Where the answers come from¶
Most routes never call a broker. They read a document that a background script keeps up to date in Redis. The table below shows which routes read stored data and which ones talk to a broker while you wait.
| Route group | Reads | Calls a broker while you wait? |
|---|---|---|
| Session | MongoDB settings, last_login |
No |
| Details | Redis unified:details:*, falling back to MongoDB |
No |
| Instruments | Redis instrument cache, falling back to TimescaleDB | No |
| Market quotes | Redis live quotes | Only when no live quote is fresh |
| Historical data | Redis candle cache and TimescaleDB | No |
| Portfolio | Redis unified:portfolio:* |
No |
| Orders (read) | Redis unified:orders:* |
No |
| Orders (place, modify, cancel, flatten) | Redis, then the broker | Yes |
Because of this, a read route can fail with 503 if the script that fills its Redis key has stopped. The message names the key, so you know which service to restart.
Checking the API is up¶
GET /api/no token needed
This route needs no token, so it is the quickest way to see whether the API is running.
Trying it by hand¶
bin/rest-api-app opens a Streamlit page that calls every read route with a click. See Test page.