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"
}
}| Code | Status | When |
|---|---|---|
bad_request | 400 | The request is not one the endpoint can act on. |
unauthorized | 401 | No token, or one that is not valid. |
forbidden | 403 | Signed in, but not allowed: somebody else’s comment, or a moderator’s endpoint. |
upgrade_required | 403 | The desktop uploader is older than its newest release. |
not_found | 404 | No such entry, or one you may not see. |
conflict | 409 | It clashes with what is already there: a name that is taken, say. |
payload_too_large | 413 | The body is over the endpoint’s limit. |
validation_failed | 422 | A parameter or field is wrong; details.issues says which. |
rate_limited | 429 | Over the rate limit; Retry-After says when to try again. |
internal | 500 | Something broke on this side. |
unavailable | 503 | Something the request needs is paused or down for now. |
Caching, limits and CORS
- Public reads send
Cache-Controland a weakETag; send it back inIf-None-Matchto get a304when 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-RemainingandX-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.
| Endpoint | Filters |
|---|---|
GET /itemsGET /items/{id} | As on Items: quality, class, spec, slot, minlevel, maxlevel, stat, minvalue, source, binding, usable, reputation, standing, set, missing |
GET /spellsGET /spells/{id} | As on Spells: class, school, category, minlevel, maxlevel |
GET /questsGET /quests/{id} | As on Quests: zone, side, type, minlevel, maxlevel, reputation, missing |
GET /npcsGET /npcs/{id} | As on NPCs: zone, type, classification, faction, reputation, service, family, tameable, minlevel, maxlevel, missing |
GET /zonesGET /zones/{id} | As on Zones: territory, type, continent |
GET /objectsGET /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.
| Endpoint | Returns |
|---|---|
GET /professionsGET /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 /classesGET /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 /racesGET /races/{key} | Every playable race; one with its classes and racial traits. night-elf or 4. |
GET /item-setsGET /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 /factionsGET /factions/{key} | Every reputation faction; one with what raises it and what it rewards. 529 or argent-dawn. |
GET /dungeons | Every 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-points | Every flight point, by continent and zone. side and zone narrow
it. |
Search and the rest
| Endpoint | Returns |
|---|---|
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 /latest | Everything in the order it was first recorded, newest first. Pass meta.older back as before for the next page. |
GET /wanted | What nobody has recorded yet, counted per listing filter and zone. |
GET /different | What 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 /stats | Database totals. |
GET /uploader | The 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.
| Endpoint | Auth | Does |
|---|---|---|
GET /comments?entity=item&id=19019 | — | An entry’s thread. sort is top, new or old. |
POST /comments | token | Posts { "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} | token | Deletes your comment. |
POST /comments/{id}/vote | token | { "value": 1 | -1 | 0 }; 0 takes your vote back. |
POST /comments/{id}/reports | token | Reports 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 /screenshots | token | Sends 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} | token | Deletes 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.
| Endpoint | Auth | Does |
|---|---|---|
POST /auth/devicePOST /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/revoke | token | Revokes the token the request is made with: signing a program out. |
POST /auth/logout | session | Ends the browser session. |
GET /me | token | Who you are. |
PATCH /me | session | Changes the name shown beside your comments. |
GET /me/tokens | token | Your tokens. |
POST /me/tokensDELETE /me/tokens/{id} | session | Makes a token, whose secret is shown this once; revokes one. |
GET /me/comments | token | Your comments. |
GET /me/repliesPOST /me/replies/read | token | Replies in the conversations you are in; marks them read. |
GET /me/screenshots | token | Every screenshot you sent, whatever became of it. |
GET /me/contributions | token | Your uploads, and what each applied. |
GET /me/uploads | token | What 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.