Menu

Thottbot Forever API

Everything on this site is available as JSON. The API is public for reads and uses bearer tokens for writes. The OpenAPI 3.1 document describes every endpoint and field.

Base URL
https://thottbot-forever.com/api/v1
Format
JSON, UTF-8. Timestamps are ISO 8601 in UTC, money is in copper, durations in milliseconds.

Every path below is relative to the base URL.

curl "https://thottbot-forever.com/api/v1/items/19019"

Responses

A response that worked has the result in data. One that did not has error instead, with an HTTP status to match. Both carry meta, whose requestId is worth quoting when you report a problem.

One entry

GET /items/19019 200 OK
{
  "data": {
    "type": "item",
    "item": {
      "id": 19019,
      "name": "Thunderfury, Blessed Blade of the Windseeker",
      "slug": "thunderfury-blessed-blade-of-the-windseeker",
      "quality": 5,
      "itemLevel": 80,
      …
    },
    "droppedBy": [],
    "soldBy": [],
    "rewardFrom": [],
    …
  },
  "meta": {
    "requestId": "a556f622888a7b74",
    "generatedAt": "2026-11-04T22:12:40.132Z"
  }
}

A listing

Listings add how they are paged and sorted to meta: page, perPage, total, totalPages, sort and dir.

GET /items?quality=5&sort=ilvl&dir=desc&perPage=2 200 OK
{
  "data": [
    { "id": 22632, "name": "Atiesh, Greatstaff of the Guardian", "itemLevel": 90, … },
    { "id": 22631, "name": "Atiesh, Greatstaff of the Guardian", "itemLevel": 90, … }
  ],
  "meta": {
    "requestId": "a556f6292b8cdf73",
    "generatedAt": "2026-11-04T22:12:40.862Z",
    "page": 1,
    "perPage": 2,
    "total": 28,
    "totalPages": 14,
    "sort": "ilvl",
    "dir": "desc",
    "type": "item"
  }
}

An error

error.code is the one to act on: it is fixed, while message is for people and may change. details is there only when there is more to say.

GET /items?perPage=abc 422 Unprocessable Content
{
  "error": {
    "code": "validation_failed",
    "message": "Request validation failed",
    "details": {
      "issues": [
        { "path": "perPage", "message": "Invalid input: expected number, received NaN" }
      ]
    }
  },
  "meta": {
    "requestId": "a556f63208358aaa"
  }
}
CodeStatusWhen
bad_request400The request is not one the endpoint can act on.
unauthorized401No token, or one that is not valid.
forbidden403Signed in, but not allowed: somebody else’s comment, or a moderator’s endpoint.
upgrade_required403The desktop uploader is older than its newest release.
not_found404No such entry, or one you may not see.
conflict409It clashes with what is already there: a name that is taken, say.
payload_too_large413The body is over the endpoint’s limit.
validation_failed422A parameter or field is wrong; details.issues says which.
rate_limited429Over the rate limit; Retry-After says when to try again.
internal500Something broke on this side.
unavailable503Something the request needs is paused or down for now.

Caching, limits and CORS

  • Public reads send Cache-Control and a weak ETag; send it back in If-None-Match to get a 304 when nothing changed.
  • Anonymous reads may make 120 requests a minute per IP address, and requests with a token 600 a minute. Writes are limited separately. Where you stand is reported in X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset.
  • CORS is open (Access-Control-Allow-Origin: *). A browser session (a cookie) counts only on requests from this site; everything else authenticates with a token.

Authentication

Create a token on your account page and send it as a bearer token:

curl "https://thottbot-forever.com/api/v1/me" -H "Authorization: Bearer tbf_…"

There is no way to exchange credentials for a token over the API: identity comes from Battle.net, there is no password to send, and an endpoint that traded something weaker for a token would undo the point of that.

A token never carries a moderator’s or an administrator’s role. What only they can do — the /admin endpoints, hiding somebody else’s comment — needs a browser session on this site that has passed its second factor, and an administrator’s changes a sign-in through Battle.net from the last ten minutes too.

Game data

Paged collections

Each takes q (part of the name), sort, dir (asc or desc), page and perPage (up to 200), and the filters its page on this site has. /{id} is the game’s id, and returns the entry with everything its page shows.

EndpointFilters
GET /items
GET /items/{id}
As on Items: quality, class, spec, slot, minlevel, maxlevel, stat, minvalue, source, binding, usable, reputation, standing, set, missing
GET /spells
GET /spells/{id}
As on Spells: class, school, category, minlevel, maxlevel
GET /quests
GET /quests/{id}
As on Quests: zone, side, type, minlevel, maxlevel, reputation, missing
GET /npcs
GET /npcs/{id}
As on NPCs: zone, type, classification, faction, reputation, service, family, tameable, minlevel, maxlevel, missing
GET /zones
GET /zones/{id}
As on Zones: territory, type, continent
GET /objects
GET /objects/{id}
As on Objects: zone, type, skill, missing

Whole lists

These are short enough to come back in one piece, so they are not paged. One entry is found by its slug or its id.

EndpointReturns
GET /professions
GET /professions/{key}
Every profession; one with its recipes, nodes and trainers. alchemy or 171. ?skill=150 narrows it to what that skill level opens, and &skillups=1 to the recipes still worth points.
GET /classes
GET /classes/{key}
Every class; one with its abilities, proficiencies and sets. mage or 8. ?level= narrows it to what a character of that level has.
GET /races
GET /races/{key}
Every playable race; one with its classes and racial traits. night-elf or 4.
GET /item-sets
GET /item-sets/{key}
Every item set, filtered by class, minlevel and maxlevel; one with its pieces and bonuses. 61 or defias-leather: the id is the one to keep, since two sets can share a slug.
GET /factions
GET /factions/{key}
Every reputation faction; one with what raises it and what it rewards. 529 or argent-dawn.
GET /dungeonsEvery dungeon and raid, lowest level first: the level to go at, group size, lockout, and how many bosses and drops are recorded. Each names its zone, and /zones/{id} has the bosses in order and the first 400 rows of the loot table.
GET /flight-pointsEvery flight point, by continent and zone. side and zone narrow it.

Search and the rest

EndpointReturns
GET /search?q=…Every collection searched by name, best first, as short references. type keeps one collection; limit and page page it, and meta.total counts every match.
GET /items/compare?i=…&i=…Up to five items as one table, with the difference from the first on every line. A value of null means the item has no such line, which is not the same as 0; the delta beside it counts a missing line as zero, because that is what switching costs you. Nothing says which item is better.
GET /latestEverything in the order it was first recorded, newest first. Pass meta.older back as before for the next page.
GET /wantedWhat nobody has recorded yet, counted per listing filter and zone.
GET /differentWhat players found different in Forever from what the site said, newest first; entity and id narrow it to one entry.
GET /screenshots?entity=…&id=…An entry’s approved screenshots.
GET /statsDatabase totals.
GET /uploaderThe desktop uploader’s releases.

Examples

# The highest item level legendaries
curl "https://thottbot-forever.com/api/v1/items?quality=5&sort=ilvl&dir=desc"

# One item, with everything its page shows
curl "https://thottbot-forever.com/api/v1/items/19019"

# Search every collection
curl "https://thottbot-forever.com/api/v1/search?q=thunderfury"

# The bosses of Molten Core
curl "https://thottbot-forever.com/api/v1/npcs?zone=2717&classification=boss"

# Two items side by side
curl "https://thottbot-forever.com/api/v1/items/compare?i=19019&i=18816"

Comments and screenshots

Token means a bearer token, or a browser session on this site; session means a browser session only.

EndpointAuthDoes
GET /comments?entity=item&id=19019—An entry’s thread. sort is top, new or old.
POST /commentstokenPosts { "entity", "id", "body", "parentId?" } and returns the rendered comment.
GET /comments/{id}—One comment.
PATCH /comments/{id}token{ "body" } edits your comment. A moderator may send { "status": "hidden" | "visible" }, from a session.
DELETE /comments/{id}tokenDeletes your comment.
POST /comments/{id}/votetoken{ "value": 1 | -1 | 0 }; 0 takes your vote back.
POST /comments/{id}/reportstokenReports a comment to the moderators.
GET /comments/recent—The newest comments on the site, or one reader’s with user.
GET /comments/search?q=…—Searches what people wrote.
POST /screenshotstokenSends a PNG, JPEG or WebP of an entry, up to 2 MB, for a moderator to approve.
GET /screenshots/{id}—One screenshot.
DELETE /screenshots/{id}tokenDeletes your screenshot.

Comment markup

Bodies are plain text with a small BBCode-style dialect. The API returns both the raw body and a sanitized bodyHtml.

Formatting
[b], [i], [u], [code], [quote]
Links
[url=https://…]text[/url]
Entries
[item=19019], [spell=133], [quest=7848], [npc=11502], [zone=2717], [object=179703]

Account

Signing in is a browser flow through Battle.net at /login, not an API call.

EndpointAuthDoes
POST /auth/device
POST /auth/device/token
—Signs in a program on your computer, such as the desktop uploader: it shows a code, you sign in and allow it at /login/device, and it collects a token.
POST /auth/revoketokenRevokes the token the request is made with: signing a program out.
POST /auth/logoutsessionEnds the browser session.
GET /metokenWho you are.
PATCH /mesessionChanges the name shown beside your comments.
GET /me/tokenstokenYour tokens.
POST /me/tokens
DELETE /me/tokens/{id}
sessionMakes a token, whose secret is shown this once; revokes one.
GET /me/commentstokenYour comments.
GET /me/replies
POST /me/replies/read
tokenReplies in the conversations you are in; marks them read.
GET /me/screenshotstokenEvery screenshot you sent, whatever became of it.
GET /me/contributionstokenYour uploads, and what each applied.
GET /me/uploadstokenWhat your uploads recorded, changed and counted.

Contributions

POST /contributions accepts batches of observations from the in-game collector (or any tool). See Contribute for the format. A batch is stored as received, acknowledged with 202, and merged into the database shortly after, one uploader’s batches at a time; GET /me/contributions then reports how many records were applied and why any were rejected. It takes a token and nothing else, as the file upload below does: a browser session cannot upload.

POST /contributions/files takes the addon’s SavedVariables file as the game wrote it, gzipped, with its SHA-256 in X-Content-SHA256: the site checks the hash before it reads anything, then reads the file and stores it in batches. It is what the desktop uploader sends, a large file in parts; GET /uploader lists the uploader’s releases, and uploading needs the newest.

Two rules govern the merge. Existing data is never overwritten, so a record creates an entity when it is missing and otherwise only fills fields that are still empty. Drop rates are computed from reported loot tables rather than taken from a record, which is why drop responses carry observed and encounters alongside dropChance.

Moderators can inspect and retry the queue with GET and POST /admin/contributions, from a browser session that has passed its second factor.

Versioning

The API is versioned in the path. Fields may be added to responses at any time; removing or renaming a field means a new version. Ids are the game’s own ids wherever they are known.