osrs-data-hub

API overview

Authentication, conventions, limits and errors of the hub's read-only REST API (v1).

A read-only JSON API over the OSRS account data your guild's RuneLite plugins send to the hub. It is pull-only: poll /snapshot and follow the /events cursor feed. There are no webhooks.

  • Base URL: https://<your hub>/api/v1
  • OpenAPI 3.1 document: https://<your hub>/api/v1/openapi.json (public), or the copy on this site
  • On your hub: an interactive reference at https://<your hub>/docs/api

The pages in this section are generated from the hub's OpenAPI document. Each one lists parameters and response schemas, and has a playground.

Using the playground

Set the hub server variable to your hub's origin (such as https://hub.example.com) and paste an API key. Your browser then calls your hub directly: the key goes only from your browser to your hub, not to this site. The hub allows this because every API response carries Access-Control-Allow-Origin: *.

Authentication

Send a key on every request:

GET /api/v1/me
Authorization: Bearer ohub_<prefix>_<secret>

Members create keys on API keys, admins create integration keys on Admin → Integrations. See Which key?. Cookies are ignored.

These all get the same 401 unauthorized with WWW-Authenticate: Bearer: a missing, malformed, unknown, revoked or expired key, or a key whose creator left the guild.

What a key can read is worked out on every request: its categories, its accounts, and what its creator can see right now. An integration key reads what the guild audience sees.

Conventions

  • Success: { "data": …, "meta": { "generated_at": "…" } }. Lists add meta.count.
  • Errors: { "error": { "code": "…", "message": "…" } }. A 400 adds details: [{ path, message }].
  • Names: every key the hub defines is snake_case. Data passes through unchanged: skill names ("Attack"), equipment slots, item and account names, and an event's data object (the plugin's own camelCase).
  • Timestamps are ISO-8601 UTC. Ids are opaque strings.
  • Query parameters: lists are comma-separated (skills=attack,defence), dates are ISO-8601 with Z or an offset, booleans are true/false. Unknown parameters are ignored.
  • Evolution: v1 only changes additively. Ignore fields you don't know.
  • Not found vs not readable: anything the key can't read answers 404 not_found, exactly like an unknown id.
  • Omitted vs not shared: on /accounts/{id} and /snapshot, a section the key can't read is left out. A section the key may read but the plugin never sent is { "shared": false, "updated_at": null } on /accounts/{id}, or null on /snapshot.
  • Stale locations: a live location older than 2 minutes has stale: true.

Rate limits

LimitValueOn excess
Per key, all endpoints120 requests per sliding minute for a personal key; 600 (or what the admin set) for an integration key429 rate_limited with Retry-After
Per key, /snapshot1 request per second429 with Retry-After: 1
Failed authentications per client IP30 per minuteevery request from that IP gets 429 until the window passes

Every authenticated response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds until a request frees up, not a timestamp). Retry-After is always whole seconds, and a 304 counts too.

Caching

Cache-Control: no-store everywhere, except:

  • /snapshot: private, no-cache with a weak ETag. Send it back as If-None-Match to get 304.
  • /openapi.json: public, max-age=300.

Errors

StatuscodeWhen
400invalid_requesta malformed or out-of-range parameter (details names it)
401unauthorizedno valid key
404not_foundan unknown or unreadable account, or an unknown path
429rate_limitedover a limit; wait Retry-After seconds
503unavailablethe hub is busy or its database is unreachable; retry after Retry-After
500internal_errora bug; nothing about it is revealed
{"error":{"code":"invalid_request","message":"The request is invalid.","details":[{"path":"from","message":"must be an ISO-8601 date-time with Z or an offset"}]}}

Endpoints

EndpointCategoryWhat it returns
GET /meanythe key, its creator and how many accounts it sees
GET /accountsthe visible accounts, filterable by names, ids, online
GET /accounts/{id}per sectionone account's current state
GET /snapshotper fieldevery visible account's current state, for polling
GET /accounts/{id}/xp, GET /xpstatsXP series of one or several accounts
GET /accounts/{id}/gainsstatsXP gained per skill over a period
GET /eventseventsthe events cursor feed
GET /accounts/{id}/sessionsactivityplay sessions
GET /accounts/{id}/equipment-historyequipmentevery change of the worn set
GET /accounts/{id}/wealthinventorycarried value per day
GET /accounts/{id}/locations, GET /locationslocation_historylocation trails
GET /leaderboards/gainsstatstop 10 gainers per skill
GET /leaderboards/looteventsthe most valuable drops of a period
GET /openapi.jsonnonethe OpenAPI document

Pick an endpoint in the sidebar for its parameters, response schema and playground.

On this page