Authentication and scopes
Exchange your API key for a token, and see what the scopes of your key allow.
Every route except POST /auth/token and the public catalogue GET /live/public expects a bearer
JWT (Authorization: Bearer <token>). The JWT is short-lived and comes from exchanging your API
key, issued in the web app, at POST /auth/token. The key itself is accepted on that one route
only.
Getting a token
curl -X POST https://api.qtsurfer.net/v1/auth/token
-H "X-API-Key: <your-api-key>" {
"access_token": "eyJhbGciOiJIUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scopes": { "backtests": ["read", "write", "execute"], "exchanges": ["read"] },
"tier": "free"
} | Field | Notes |
|---|---|
access_token | send it as Authorization: Bearer <token> on every other call |
token_type | always Bearer |
expires_in | seconds until the token expires. Ask for a new one before then, or on a 401 |
scopes | what your key may do, below |
tier | the subscription tier the token was issued for; it sets your limits, see Account |
A key that is unknown, revoked or expired is answered 401; the body is AuthTokenError in the
API reference. Where a deployment requires scopes, a key that carries none is answered 403 with
the code apikey_without_scopes and no token: create a key with at least one scope in the web app.
Scopes
A key carries scopes: a map from a resource to the actions it may take on it, in lower case.
{ "backtests": ["read", "write", "execute"], "exchanges": ["read"] } - A scope is a resource and an action: a resource is listed only with at least one action. A key either may do something or it may not; a key with no scope is
{}. - You see them in two places, as the same map: the
scopesof the token answer above, and thescopesofGET /account, which needs no new token. - The token carries the scopes your key had when it was issued. Change the key’s scopes and the
change shows from the next token on, at most
expires_inseconds later. - An operation that needs a scope your key does not carry is refused with
403and the codeinsufficient_scope(below). The tables below say which scope each operation requires.
Resources and actions
| Resource | Actions | Covers |
|---|---|---|
exchanges | read | the list of exchanges, their instruments and pools |
market_data | read | the hourly ticker and kline downloads |
strategies | read, write | list, inspect and read the source of a strategy; compile, validate and delete one |
backtests | read, write, execute | prepare, run, sweep and cancel backtests; read their status and results |
datasets | read, write | list and read your datasets; create, upload, import and delete them |
live | read, write, execute | read live runs, their signals and paper accounts; change a run; start, stop and command one, and manage its stream |
read lists and gets. write creates, changes, cancels or deletes without running anything. execute starts a run: a backtest, a sweep, a live run.
The scope each operation requires
Exchanges and market data — Market data
| Operation | Scope |
|---|---|
GET /exchanges, GET /exchange/{exchangeId}/instruments, GET /exchange/{exchangeId}/{segment}/instruments | exchanges : read |
GET /exchange/{exchangeId}/tickers/{base}/{quote}, GET /exchange/{exchangeId}/klines/{base}/{quote} | market_data : read |
Strategies — Strategies
| Operation | Scope |
|---|---|
GET /strategies, GET /strategy/{strategyId}, GET /strategy/{strategyId}/code | strategies : read |
POST /strategy, POST /strategy/{strategyId}/validate, DELETE /strategy/{strategyId} | strategies : write |
Backtests and sweeps — Backtests, Parameter sweeps
| Operation | Scope |
|---|---|
POST …/prepare, DELETE …/execute/{jobId}, DELETE …/executeSweep/{requestId}/{sweepId} | backtests : write |
POST …/execute, POST …/executeSweep/{requestId} | backtests : execute |
GET …/prepare/{jobId}, GET …/execute/{jobId}, GET …/executeSweep/… (result, sensitivity, equity curve) | backtests : read |
Datasets — Datasets
| Operation | Scope |
|---|---|
GET /datasets, GET /datasets/{datasetId}, GET /datasets/{datasetId}/uploads/{uploadId}, GET /datasets/{datasetId}/imports/{importId} | datasets : read |
POST /datasets, DELETE /datasets/{datasetId}, POST /datasets/{datasetId}/uploads, POST /datasets/{datasetId}/uploads/{uploadId}/finalize, POST /datasets/imports | datasets : write |
Live runs — Live execution, Paper trading
| Operation | Scope |
|---|---|
GET /live, GET /live/{runId}, GET /strategy/{strategyId}/live, GET /live/{runId}/signals, GET /live/{runId}/paper, GET /live/{runId}/paper/equity, POST /live/token | live : read |
PATCH /live/{runId}, PUT /live/{runId}/params | live : write |
POST /strategy/{strategyId}/live, DELETE /strategy/{strategyId}/live, POST /live/{runId}/commands, POST /live/{runId}/stream, DELETE /live/{runId}/stream | live : execute |
No scope: GET /account and GET /account/usage need a valid token and nothing more; GET /live/public needs no token at all.
When a scope is missing
{
"code": "insufficient_scope",
"message": "This API key does not have the scope datasets:write.",
"required": "datasets:write"
} The answer is 403 and required names the scope the operation needs, as resource:action. Give
the key that scope in the web app, then ask for a new token: the scopes of a token are the ones the
key had when it was issued, so a token you already hold keeps the old ones until it expires.