Docs

Using your key

One key reaches every endpoint. What it limits is which territories you may ask about — never how often you ask, and never which routes you may call.

One key, every endpoint

There is no per-endpoint permission and nothing to enable. The same key answers a market calendar and a national one:

curl -H "X-API-Key: $TIMESYS_API_KEY" \
  "https://api.timesys.pro/v1/calendar?jurisdictions=ES&from=2027-01-01&to=2027-12-31"

curl -H "X-API-Key: $TIMESYS_API_KEY" \
  "https://api.timesys.pro/v1/exchanges/XETR/calendar?from=2027-01-01&to=2027-12-31"

The header is X-API-Key, not Authorization: Bearer. That is the single commonest integration error, and it returns 401 with a message saying so.

Where the key goes in your application

In an environment variable read at start-up, on the server. Nowhere else.

# .env — never committed
TIMESYS_API_KEY=ts_live_xxxxxxxxxxxxxxxxxxxx
// Node
const res = await fetch(
  `https://api.timesys.pro/v1/calendar?jurisdictions=ES&from=${from}&to=${to}`,
  { headers: { "X-API-Key": process.env.TIMESYS_API_KEY } });

# Python
requests.get(url, headers={"X-API-Key": os.environ["TIMESYS_API_KEY"]})

Never put it in a browser

A key in front-end JavaScript is a public key, whatever the file is called. Anyone can read it from the network tab, and it carries your whole territory scope. If your front end needs this data, call us from your server and serve the result from your own endpoint — which you want anyway, because you should be caching.

The same goes for a mobile app: a key compiled into a binary is a key somebody can extract. Put it behind your own API.

If a key leaks

Rotate it. POST /v1/account/rotate, authenticated by the key being replaced, mints a new one and revokes the old in the same transaction. Your plan, your Stripe subscription and every territory you have claimed carry over, so nothing you have deployed loses scope — only the string changes.

What the key limits

LimitedNot limited
Which territories you may ask aboutHow many calls you make
Whether subdivisions are includedWhich endpoints you may call
How far ahead a free key may lookHow much data per response

Ask for a territory outside your scope and the answer is 403 naming the territory and the plan that would cover it — not a silent empty list. An empty list means we hold nothing; a 403 means you may not see it. Those are different answers and the Calendar API never conflates them.

Two limits that will bite

A calendar range is capped at 400 days. Split a multi-year sync by year. This is not a rate limit — it bounds one response so a five-year request cannot quietly become a fifteen-megabyte one.

A bare country code returns national rows only. Asking for GB gives you the United Kingdom's national calendar and not Scotland's. If you serve English users, ask for GB-ENG. This caught our own first customer: syncing GB silently dropped Easter Monday and the Summer Bank Holiday for England.

Cache. Please cache.

Calls are unlimited on every plan, and the reason is that this data changes on a published schedule rather than continuously. Editions are monthly. A holiday calendar for 2027 is the same answer today and next Tuesday.

Fetch on a schedule, store the result, and read from your own store. A payroll system checking holidays quarterly makes roughly forty thousand calls a year if it polls and about twelve if it caches — and both cost the same, which is the point. rate_per_minute exists as abuse protection; hitting it returns 429 telling you to slow down, and never an invoice.

Read the assurance field

Every row carries assurance: verified against the instrument, corroborated by two independent sources, or compiled from a published rule. It is a field you can branch on, and it is the reason to use this rather than a holiday list.

// Do not print an estimate as a hard date
if (row.assurance === "verified" || row.precision === "exact") render(row.date);
else renderWithCaveat(row);

Filter server-side with min_assurance=verified if you only want dates confirmed against an authority.

If you are citing dates, record what answered

Calendar responses do not carry an edition string today — that is worth saying plainly, because it is the sort of thing people assume. What you can record is GET /v1/health, which returns the running version and db_mtime, the corpus build the answer came from.

{"status":"ok","version":"v1.36.1","observances":3990,
 "keystore":"ok","db_mtime":1787131781}

If you are producing something dated — a report, a filing, a printed planner — store those two alongside the dates. Dossiers are different: each PDF carries its own edition on the cover, and the edition you cited stays retrievable.

Errors

CodeMeansDo
401No key, wrong header, or a rotated keyCheck it is X-API-Key
403Outside your territory scopeThe message names the plan that covers it
400Range over 400 days, or a malformed dateSplit by year; dates are YYYY-MM-DD
404Unknown jurisdiction or exchange codeCheck against /v1/jurisdictions
429Abuse protection, not billingBack off; you are almost certainly not caching

Build the call without writing it

The configurator assembles a request against the live corpus, shows the response, and gives you the URL to copy. Signed in on a paid plan, Use my key mints a temporary key for that browser — separate from yours, expiring in an hour, so nothing you have deployed is touched.