Keep the key server-side
A key in browser JavaScript is a published key. Call the API from your own backend and pass the result down.
Create a key, send it as X-API-Key, and read a response whose shape every other endpoint repeats. No SDK, no OAuth exchange, no card.
Your first authenticated call
Two lines of request. The data object below is the whole success body — nothing is nested behind a second call.
{
"success": true,
"data": {
"symbol": "EURUSD",
"timeframe": "H1",
"indicator": "RSI_14",
"value": 58.43,
"bid": 1.08432,
"ask": 1.08445
}
} Signup mints an API key and 200 requests a day against $2.50 of prepaid credit. No card, and no overage — when the credit runs out the key returns 429, it does not bill you.
Keys start with tk_. Put it in an environment variable, not in source control — the snippets below all read TICKATLAS_API_KEY.
One header on every REST call. There is no OAuth exchange, no bearer token and no refresh cycle to implement.
Success is {"success": true, "data": {…}}. Every non-2xx is {"success": false, "error": {"code", "message"}} — one shape to deserialise for the whole API.
Swapping indicator=RSI_14 for ATR_14, or timeframe=H1 for M15, reaches all 42 indicator series through the same request.
curl -H "X-API-Key: YOUR_API_KEY" \ "https://tickatlas.com/v1/indicator?symbol=EURUSD&indicator=RSI_14&timeframe=H1"
200 OK
{
"success": true,
"data": {
"symbol": "EURUSD",
"timeframe": "H1",
"indicator": "RSI_14",
"value": 58.43,
"bid": 1.08432,
"ask": 1.08445,
"updated_at": 1711548000,
"server_time": "2024-03-27T14:00:00+00:00"
}
}
No client library is involved in any of these — each one uses its language's own
HTTP facility and reads the key from an environment variable. Copy one, set
TICKATLAS_API_KEY, and run it.
import os, requests
response = requests.get(
"https://tickatlas.com/v1/indicator",
headers={"X-API-Key": os.environ["TICKATLAS_API_KEY"]},
params={"symbol": "EURUSD", "indicator": "RSI_14", "timeframe": "H1"},
timeout=10,
)
response.raise_for_status()
data = response.json()["data"]
print(f"{data['indicator']} = {data['value']} at {data['server_time']}") const url = new URL("https://tickatlas.com/v1/indicator");
url.search = new URLSearchParams({
symbol: "EURUSD",
indicator: "RSI_14",
timeframe: "H1",
});
const res = await fetch(url, {
headers: { "X-API-Key": process.env.TICKATLAS_API_KEY },
});
if (!res.ok) throw new Error((await res.json()).error.code);
const { data } = await res.json();
console.log(`${data.indicator} = ${data.value} at ${data.server_time}`); <?php
$query = http_build_query([
'symbol' => 'EURUSD',
'indicator' => 'RSI_14',
'timeframe' => 'H1',
]);
$ch = curl_init("https://tickatlas.com/v1/indicator?$query");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: ' . getenv('TICKATLAS_API_KEY')],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);
printf("%s = %s at %s\n",
$body['data']['indicator'], $body['data']['value'], $body['data']['server_time']); package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
type indicatorResponse struct {
Success bool `json:"success"`
Data struct {
Indicator string `json:"indicator"`
Value float64 `json:"value"`
ServerTime string `json:"server_time"`
} `json:"data"`
}
func main() {
req, _ := http.NewRequest("GET",
"https://tickatlas.com/v1/indicator?symbol=EURUSD&indicator=RSI_14&timeframe=H1", nil)
req.Header.Set("X-API-Key", os.Getenv("TICKATLAS_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var out indicatorResponse
json.NewDecoder(res.Body).Decode(&out)
fmt.Printf("%s = %.2f at %s\n", out.Data.Indicator, out.Data.Value, out.Data.ServerTime)
}
This is the complete success payload of GET /v1/indicator — not an
excerpt. The indicator arrives with its price context and its timestamps, so a
consumer can judge freshness without a second call.
| Field | Type | What it carries |
|---|---|---|
success | boolean | true on a 2xx. Present on every response, including errors, where it is false. |
data.symbol | string | The canonical symbol. |
data.timeframe | string | The timeframe served, upper-cased. Defaults to H1 when the request omits it. |
data.indicator | string | The indicator key echoed back, e.g. RSI_14, MACD_main, EMA_20. |
data.value | number | The computed value. Its scale is the indicator’s own — RSI is 0–100, ATR is in price units. |
data.bid | number | null | Live bid at read time, so the value arrives with its price context. null when no price is cached for the symbol. |
data.ask | number | null | Live ask at read time. null under the same condition as bid. |
data.updated_at | integer | Unix epoch seconds of the last indicator recalculation. Use it to decide whether a value is fresh enough for your purpose. |
data.server_time | string | The same instant in ISO-8601 UTC, e.g. 2024-03-27T14:00:00+00:00. |
Other endpoints add their own keys — /v1/summary returns
bias, confidence and key levels; /v1/heatmap
returns strongest, weakest and a strength map — but the
success + data wrapper never changes.
Every non-2xx — including a 404 from the router itself and a 422 from parameter
validation — is folded into the same envelope, so you deserialise one type for the
whole API. Branch on error.code: it is the contract, while
error.message is prose that may be reworded.
HTTP/1.1 401 Unauthorized
X-Request-ID: 9f3c1a7e52b64d0e8a17c4d3b6e09f21
{
"success": false,
"error": {
"code": "MISSING_API_KEY",
"message": "API key is required. Include it in the X-API-Key header.",
"docs": "https://tickatlas.com/docs#authentication"
}
} | Status | error.code | Cause, and what to do |
|---|---|---|
| 401 | MISSING_API_KEY | No X-API-Key header. The message names the header and links to the authentication docs. |
| 401 | INVALID_API_KEY | The key is not recognised. Check you copied the whole tk_ string. |
| 403 | IP_NOT_ALLOWED | The key has an IP allowlist and the caller is not on it. |
| 403 | PERMISSION_DENIED | The key exists but lacks the permission the endpoint requires. |
| 403 | PLAN_UPGRADE_REQUIRED | The endpoint is gated above your plan — how GET /v1/ticks answers a pay-as-you-go or Starter key. |
| 400 | INVALID_TIMEFRAME | Not one of the seven timeframes. The body lists the valid set, so you can surface it directly. |
| 404 | DATA_NOT_FOUND | The symbol resolved but no cached data exists for that symbol and timeframe. |
| 404 | INDICATOR_NOT_FOUND | The symbol has data but not that indicator key. The body lists the available keys. |
| 422 | VALIDATION_ERROR | A parameter failed its bounds check. error.details carries the field-level diagnostics. |
| 429 | RATE_LIMIT_EXCEEDED | Per-minute limit hit. Retry-After and X-RateLimit-Reset both tell you when to retry. |
| 429 | QUOTA_EXCEEDED | Daily quota or prepaid credit exhausted. error.details says which, so you can alert rather than blindly retry. |
The badge is the endpoint's weight: what one call costs in pay-as-you-go credit units. Every call counts once against a daily quota. This is the whole REST surface, plus the one socket.
/v1/quote Live bid, ask and spread for one symbol.
/v1/quotes POST a symbol list, get one quote each plus a not_found array.
/v1/symbols Every tradable instrument with category, digits and base/quote currency.
/v1/sessions Which market sessions are open now, and the overlaps between them.
/v1/indicator One indicator value for one symbol and timeframe. The call on this page.
/v1/indicators Every cached indicator for a symbol at once, optionally filtered by category.
/v1/spread Spread statistics over a 1h, 24h, 7d or 30d window.
/v1/ohlc OHLCV candles for a symbol and timeframe.
/v1/multi Several indicators across several symbols in one round trip.
/v1/screener Filter every symbol by one indicator’s value instead of looping yourself.
/v1/heatmap Currency strength, or the correlation matrix with type=correlation.
/v1/calendar Economic events with impact, forecast, previous and released actuals.
/v1/ticks Raw tick-level bid/ask history over a bounded time range.
/v1/summary Aggregated market-state summary: bias, confidence, scores and key levels.
/v1/indicator/history A time series of one indicator. PAYG and up.
/ws/v1/quotes Streaming bid/ask. Authenticate, then subscribe to symbols.
200 OK to something you can leave running.Six behaviours the API already gives you, each one cheaper to use than to reimplement.
A key in browser JavaScript is a published key. Call the API from your own backend and pass the result down.
Every /v1 response carries X-RateLimit-Limit, -Remaining and -Reset. The starting grant is 30 requests a minute; do not hardcode it.
A 429 tells you exactly how long to wait. Back off on that number rather than on a guess, and never retry a 4xx that is not 429.
The code is the contract; the message is prose and may be reworded. INVALID_API_KEY needs a human, QUOTA_EXCEEDED needs a top-up, DATA_NOT_FOUND needs neither.
It is set on every /v1 response and echoed from your request if you send one. Quote it and a failure becomes traceable instead of anecdotal.
Quota is charged per weight, not per request: one /v1/summary costs five, one /v1/quote costs one. Budget the mix, not the count.
Four groups, ordered the way an integration actually grows.
The three documents that turn this first call into a working integration.
Per-endpoint parameters, response schemas and worked examples.
Worked end-to-end projects rather than endpoint documentation.
Diagnosis before escalation, and a record of what changed.
Almost every first-request failure is one of four things: no header, a truncated key, a timeframe that is not in the valid set, or a symbol with no cached data yet. Troubleshooting walks them in that order.
Include the endpoint, the HTTP status, the X-Request-ID from the
response headers and a redacted request. That turns a report into something
traceable in the logs.
The questions that come up between reading the first snippet and running it against your own key.
An account and the API key it mints. That is all — the key works immediately against 200 requests a day and $2.50 of prepaid credit, with no card on file and no approval step.
M1, M5, M15, M30, H1, H4, D1. Omitting the parameter gives you H1. Anything else returns 400 INVALID_TIMEFRAME with the valid set in the body.
GET /v1/indicator returns one point — the current one. A time series comes from GET /v1/indicator/history, which takes from, to and limit; its limit parameter defaults to 500 and is then bounded per timeframe by how far back that timeframe is retained. It is available on pay-as-you-go, Starter, Pro and Enterprise; it counts once against a daily quota and costs five weighted units of pay-as-you-go credit.
No. The API is plain REST with one header, so any HTTP client works — the five snippets above use nothing but the standard library of each language. See /docs/sdks for what is packaged.
The next call returns 429 with error.code QUOTA_EXCEEDED and error.details naming the limit that was reached. Nothing is billed past your prepaid credit, so a runaway loop costs you availability rather than money.
You can, but the key would ship to every visitor. Keep the key on a server you control and expose your own endpoint to the browser instead.
Signup mints a working key and $2.50 of prepaid credit. Paste one of the snippets above, and the next thing you read is your own data.