Limits and errors

Rate limit

120 requests per minute per IP address, and the two APIs share it: a request to Huntera spends the same budget as a request to Tibia. Every response carries what is left:

x-ratelimit-limit: 120
x-ratelimit-remaining: 114

Go over and the API answers 429. There is no key to raise the limit with; it is the same for everyone.

If you are pulling a lot, the cheapest thing you can do is read GET /v1/meta first and fetch again only when it moves:

  • Tibia changes when a new TibiaWiki snapshot is imported, every few months. Cache on dump.version.
  • Huntera changes when a new capture of the game is imported, which can be most days. Cache on capture.synced_at, which moves only when the capture found something new, and keep an eye on version, which moves when the fields themselves do (see Huntera).

A client that re-reads everything on a schedule is spending its budget on answers it already has.

Errors

Errors are JSON with a message, and the status is the whole story.

404: not found

{ "message": "Not found." }

The same body for an unknown row, an unknown route, and an article the wiki has retired. Deliberately plain: the alternative leaked internal model names into the response. The one addition is a hint, when a Tibia route is handed a client id where it expects an article id; see Looking things up.

400: you asked for something the endpoint does not offer

{
  "message": "Requested filter(s) `banana` are not allowed. Allowed filter(s) are `name, status, is_boss, ...`."
}

An unknown filter, sort or include is an error, not a thing to ignore. The message names everything the endpoint does accept, so the fix is in the response.

A value the filter cannot read answers the same way, and says what it wanted instead:

{ "message": "[filter[bestiary_level]] must be one of: harmless, trivial, easy, medium, hard, challenging." }

429: over the rate limit

Wait for the window to roll. x-ratelimit-remaining tells you how much room is left before you get here.

CORS

Neither API currently sends Access-Control-Allow-Origin, so a browser application served from another origin cannot call them directly. Server-side code, mobile clients, and anything that is not a browser are unaffected.

Caching

Responses are cached on the server, and x-cache says whether yours was a HIT or a MISS. The cache is emptied every time new data is imported, so a hit never answers with older data than a miss would. Nothing is cached at the edge: data responses go out with Cache-Control: no-cache, private.

Each GET /v1/openapi.yaml is served with Cache-Control: public, max-age=300 and a Last-Modified, so a client that revalidates gets a 304 instead of several megabytes.


Did this page help you?