Public, read-only game data for Soulbound: Online. No signup, no cost, no write path.
dataset 2026-08-06.1 https://api.soulbound.tools items 1467 · abilities 39 · relics 693 · followers 54 · stats 58
Everything is a GET. Nothing needs a key.
curl -s "https://api.soulbound.tools/v1/items?slot=chest&rarity=legendary&limit=5"
Search by name — q is a case-insensitive substring, and every collection
takes it:
curl -s "https://api.soulbound.tools/v1/items?q=iron"
One resource by id:
curl -s "https://api.soulbound.tools/v1/items/accessory_t2_belt_001"
The artwork for whatever an object's icon field names:
curl -s -o icon.png "https://api.soulbound.tools/v1/img/belt_dark_2.png"
Stat keys are not guessable, so start from the vocabulary:
curl -s "https://api.soulbound.tools/v1/stats" | jq '.data[] | select(.name | test("crit"; "i"))'
That names the key you want — critical_strike_damage at the time this page was
rendered. Now filter the items by slot and rarity, and select on the stat
client-side: the API filters on the fields listed in the reference below, and
stats are an array on each item.
curl -s "https://api.soulbound.tools/v1/items?slot=chest&rarity=legendary&limit=200" \
| jq '[ .data[]
| select(any(.stats[]?; .stat == "critical_strike_damage"))
| { id, name,
crit: (.stats[] | select(.stat == "critical_strike_damage") | .value),
range: .roll.critical_strike_damage } ]'
If meta.cursor comes back, there is another page. Follow it until it does not:
cursor=""; while :; do
page=$(curl -s "https://api.soulbound.tools/v1/items?slot=chest&rarity=legendary&limit=200${cursor:+&cursor=$cursor}")
echo "$page" | jq -c '.data[] | select(any(.stats[]?; .stat == "critical_strike_damage")) | {id, name}'
cursor=$(echo "$page" | jq -r '.meta.cursor // empty')
[ -z "$cursor" ] && break
done
roll.critical_strike_damage is the
[min, max] the stat can roll on that item. Drop rates, loot-table weights and
loot-table composition are not published. effects[].chance is the chance an
effect fires in combat — it is not a drop rate.A collection:
{ "data": [ … ], "meta": { "total": 1468, "count": 50, "cursor": "eyJvIjo1MH0" } }
A single resource:
{ "data": { … } }
An error, always this shape:
{ "error": { "code": "not_found", "message": "…" } }
Codes: not_found, bad_request, rate_limited,
method_not_allowed, internal.
limit defaults to 50 and is clamped to 200. meta.cursor is
opaque — pass it back as ?cursor= and change nothing else. It is
absent once the results are exhausted.
GET and HEAD only. OPTIONS answers CORS preflight
with 204. Anything else is 405. There is no write path in this
API at all.
JSON is public, max-age=300. Artwork comes straight off static
assets and carries its own content etag; sprite ids are stable, so
cache it hard on your side. Every JSON response carries an etag and
the dataset version in x-dataset-version. Send the etag back to skip
the body:
curl -s -o /dev/null -w '%{http_code}\n' \
-H 'If-None-Match: "2026-08-06.1-…"' "https://api.soulbound.tools/v1/stats"
Unknown query parameters are ignored, never an error.
access-control-allow-origin: * on everything. Browser JavaScript can read
etag, x-dataset-version and the ratelimit-* headers.
Optional. A key raises your rate limit and lets us tell one consumer from another. It unlocks no additional data — there is no gated tier.
curl -s -H "Authorization: Bearer sbt_live_7f3a2b_…" "https://api.soulbound.tools/v1/items"
?key= is also accepted, and discouraged: a key in a URL ends up in browser
history, in Referer headers, and in any log that records request lines.
Use the header wherever you can.
An unknown or revoked key is treated as anonymous rather than rejected. The data is public; a bad key should cost you your extra headroom, not your access.
Want one? Say what you are building and how to reach you — see the studio contact on soulbound.tools.
| Tier | Limit | Keyed on |
|---|---|---|
anonymous | 60 / 60s | client IP |
community | 600 / 60s | key id |
images | 1200 / 60s | client IP |
Every response carries ratelimit-limit, ratelimit-remaining and
ratelimit-reset (seconds until the window rolls). A 429 adds
retry-after. ratelimit-remaining is a best-effort figure: the
limit is enforced across the edge, the header is what the responding instance has
counted, so treat it as a hint and back off on 429.
ratelimit-remaining
you read may have been counted for whoever warmed the cache. The 429 and its
retry-after are always fresh.Redirect to the documentation 302 to /docs.
curl -sI "https://api.soulbound.tools/"
Human-readable API reference A self-contained HTML page, rendered from this same spec.
curl -s "https://api.soulbound.tools/docs"
This OpenAPI document Generated from the Worker's own route table, so it cannot drift from what is served.
curl -s "https://api.soulbound.tools/v1/openapi.json"
List items Every equippable and cosmetic item. Filters combine with AND.
| Parameter | Type | Meaning |
|---|---|---|
slot | string | Equipment slot, exact match, case-insensitive. |
rarity | string | Rarity, either numeric (4) or by name (legendary). |
subtype | string | Item subtype, exact match, case-insensitive. |
cosmetic | boolean | true or false. |
q | string | Case-insensitive substring match on name. |
limit | integer | Page size. Default 50, maximum 200 (larger values are clamped). |
cursor | string | Opaque cursor from a previous response's meta.cursor. |
curl -s "https://api.soulbound.tools/v1/items?slot=chest&rarity=legendary"
Fetch one item
curl -s "https://api.soulbound.tools/v1/items/accessory_t2_belt_001"
List abilities
| Parameter | Type | Meaning |
|---|---|---|
category | string | Ability category, exact match, case-insensitive. |
q | string | Case-insensitive substring match on name. |
limit | integer | Page size. Default 50, maximum 200 (larger values are clamped). |
cursor | string | Opaque cursor from a previous response's meta.cursor. |
curl -s "https://api.soulbound.tools/v1/abilities"
Fetch one ability
curl -s "https://api.soulbound.tools/v1/abilities/ability_aggro_reset"
List relics
| Parameter | Type | Meaning |
|---|---|---|
rarity | string | Rarity, either numeric (4) or by name (legendary). |
q | string | Case-insensitive substring match on name. |
limit | integer | Page size. Default 50, maximum 200 (larger values are clamped). |
cursor | string | Opaque cursor from a previous response's meta.cursor. |
curl -s "https://api.soulbound.tools/v1/relics?rarity=legendary"
Fetch one relic
curl -s "https://api.soulbound.tools/v1/relics/dc_relic_blackhole_bomb_heat_death_level1"
List followers
| Parameter | Type | Meaning |
|---|---|---|
rarity | string | Rarity, either numeric (4) or by name (legendary). |
q | string | Case-insensitive substring match on name. |
limit | integer | Page size. Default 50, maximum 200 (larger values are clamped). |
cursor | string | Opaque cursor from a previous response's meta.cursor. |
curl -s "https://api.soulbound.tools/v1/followers?rarity=legendary"
Fetch one follower
curl -s "https://api.soulbound.tools/v1/followers/pet_berryPatisserie"
The public stat vocabulary Every stat key an item may carry. Small and unpaginated.
curl -s "https://api.soulbound.tools/v1/stats"
Dataset version and counts
curl -s "https://api.soulbound.tools/v1/meta"
Item, ability, relic and follower artwork PNG for the sprite id carried by an object's `icon` field. Served straight from static assets, so it is fast and cheap to fetch in bulk. Sprite ids are stable — cache them hard.
curl -s -o icon.png "https://api.soulbound.tools/v1/img/belt_dark_2.png"