REST reference: overview¶
This section describes every REST endpoint with narrative, real example responses and edge cases. The complete parameter schema is generated separately: see the interactive reference.
Base URL and versioning¶
All data endpoints live under /v1. The prefix is the major version: fields may be added to responses inside /v1, so parse JSON by key and ignore keys you do not know. A breaking change would ship under a new prefix. The MCP server is at /mcp and is documented in the MCP section.
Only GET is used for REST. Any other method returns 405 (Allow: GET) in the normal error shape. There are no CORS headers: call the API from a server or script, not from a browser page.
Authentication, envelope, errors¶
Every endpoint except /v1/health needs Authorization: Bearer trru_YOUR_KEY and a key with scope data. Details: Authentication, Responses and sources, Errors, Rate limits.
In short, a success response is:
{
"data": { "rows": [], "row_count": 0, "truncated": false },
"meta": {
"sources": [{ "source_id": "cbr", "fetched_at": "2026-11-13T17:30:30.332163+00:00", "as_of": "2026-11-13" }],
"request_id": "d6ad2bac8fbe490497fee29ce822951b",
"truncated": false
}
}
An error is {"error": {"code": "...", "message": "..."}} with the matching HTTP status. Codes: bad_request (400), unauthorized (401), forbidden (403), not_found (404), method_not_allowed (405), rate_limited (429), internal (500), timeout (504); the full table is in Errors. meta.truncated is true when data.truncated is true, that is, when the server cut a result list at its row cap.
Parameter conventions¶
| Convention | Rule |
|---|---|
| Types | Every query parameter is a string on the wire. Integers (limit, days, years, ...) must parse as whole numbers; otherwise you get 400 invalid or missing parameter(s): <name>. |
| Empty means no filter | ?country= is the same as leaving country out. All optional text filters default to the empty string. |
| Booleans | Send true or false (only_active=true). |
| Dates | YYYY-MM-DD (start=2026-10-01). /v1/changes also accepts an ISO datetime; a naive datetime is read as UTC. |
| Lists | Only /v1/latest?ids= takes a comma-separated list. Spaces around items and empty items are ignored. |
| Case | country (series, calendar) is upper-cased by the server and category (series) lower-cased, so ru and RU both work. News beat and lang ignore case and surrounding spaces. Most other filters are case-sensitive: see each page. |
| Encoding | URL-encode values. Cyrillic and Turkish text is fine (UTF-8, percent-encoded); curl -G --data-urlencode and the params= argument of requests do this for you. |
| Limits | Each endpoint has its own limit default and cap. Values above a cap are clamped silently on some endpoints and rejected with 400 on others (news): see the page of the endpoint. |
| Path ids | /v1/series/{series_id}, /v1/companies/{inn}, /v1/restrictions/{hs}, /v1/products/{hs6} take the value in the path. |
Empty results are not errors
A filter that matches nothing returns 200 with "rows": [] and "row_count": 0. Only malformed input returns 400, and a well-formed but unknown company INN returns 404.
Endpoint index¶
All endpoints are GET. Scope data is required for everything except /v1/health.
| Path | Scope | Purpose | Page |
|---|---|---|---|
/v1/series |
data | Search the catalogue of macro series (id, names, unit, frequency, country, category, first and last observation) | Series |
/v1/series/{series_id} |
data | Observations of one series, optional date window and resampling | Series |
/v1/latest |
data | Latest observation of every series, or of the listed ids | Series |
/v1/companies/search |
data | Search ~3.5M Russian legal entities by name, INN, OKVED, region, size, revenue, status | Companies |
/v1/companies/{inn} |
data (contacts: admin) | Full company card with structure, geo, people and sanctions screening | Companies |
/v1/sanctions/check |
data | Screen a name and/or INN against EU, OFAC, UK, UN and US CSL lists | Sanctions |
/v1/restrictions/{hs} |
data | US priority-item list, EU Regulation 833/2014 annexes and Russian parallel-import list for an HS code | Sanctions |
/v1/products/{hs6} |
data | One-call dossier on a 6-digit HS product: suppliers, prices, seasonality, restrictions, buyers | Trade |
/v1/border/arrivals |
data | Foreign visitor entries to Türkiye by nationality, gate, province, transport | Tourism |
/v1/calendar |
data | Upcoming (and recent) dated events: central-bank meetings, OFAC licence expiries | Calendar, docs, news |
/v1/docs/search |
data | Full-text search over stored legal documents | Calendar, docs, news |
/v1/news/search |
data | Search recent Russia and Türkiye news headlines | Calendar, docs, news |
/v1/news/latest |
data | Newest headlines, optionally one beat | Calendar, docs, news |
/v1/health |
none | Liveness plus database check (cached 10 s) | Sources and changes |
/v1/sources |
data | Registered data sources and last successful refresh | Sources and changes |
/v1/sources/freshness |
data | Per-source freshness with a computed stale flag |
Sources and changes |
/v1/changes |
data | Change feed: what changed in the data since a date | Sources and changes |
Only the company card behaves differently by scope: a key with scope admin additionally receives contact details of people attached to a company. Everything else is identical for data and admin keys.
Request template¶
import os
import requests
BASE = "https://trru.atapinar.com"
HEADERS = {"Authorization": f"Bearer {os.environ['TRRU_KEY']}"}
def get(path, **params):
r = requests.get(BASE + path, params=params, headers=HEADERS, timeout=60)
body = r.json()
if r.status_code != 200:
raise RuntimeError(f"{r.status_code} {body['error']['code']}: {body['error']['message']}")
return body
body = get("/v1/series/ru.key_rate", start="2026-11-01")
print(body["data"]["rows"][-1], body["meta"]["sources"])
const BASE = "https://trru.atapinar.com";
const HEADERS = { Authorization: `Bearer ${process.env.TRRU_KEY}` };
async function get(path, params = {}) {
const res = await fetch(`${BASE}${path}?${new URLSearchParams(params)}`, { headers: HEADERS });
const body = await res.json();
if (!res.ok) throw new Error(`${res.status} ${body.error.code}: ${body.error.message}`);
return body;
}
const body = await get("/v1/series/ru.key_rate", { start: "2026-11-01" });
console.log(body.data.rows.at(-1), body.meta.sources);
The same get helper is assumed in the examples on the following pages.
Speed¶
Most calls answer in about 1 to 2 seconds. Company searches that filter only by a band (for example size_band=large alone) and the product dossier can take 3 to 10 seconds. Use a client timeout of at least 30 seconds. The server stops any query that runs longer than 30 seconds and answers 504 timeout.