Skip to content

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

https://trru.atapinar.com/v1

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

export TRRU_KEY=trru_YOUR_KEY
curl -s -H "Authorization: Bearer $TRRU_KEY" \
  "https://trru.atapinar.com/v1/series/ru.key_rate?start=2026-11-01"
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.

Where to go next