Soulbound: Online — public game data

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

Quick start

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"

Worked example — every legendary chest piece with crit damage

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 ranges are public. 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.

Conventions

Response shapes

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.

Pagination

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.

Methods

GET and HEAD only. OPTIONS answers CORS preflight with 204. Anything else is 405. There is no write path in this API at all.

Caching

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.

CORS

access-control-allow-origin: * on everything. Browser JavaScript can read etag, x-dataset-version and the ratelimit-* headers.

API keys

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.

Rate limits

TierLimitKeyed on
anonymous60 / 60sclient IP
community600 / 60skey id
images1200 / 60sclient 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.

Responses are publicly cacheable, so a ratelimit-remaining you read may have been counted for whoever warmed the cache. The 429 and its retry-after are always fresh.

Reference

GET/

Redirect to the documentation 302 to /docs.

curl -sI "https://api.soulbound.tools/"

GET/docs

Human-readable API reference A self-contained HTML page, rendered from this same spec.

curl -s "https://api.soulbound.tools/docs"

GET/v1/openapi.json

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"

GET/v1/items

List items Every equippable and cosmetic item. Filters combine with AND.

ParameterTypeMeaning
slotstringEquipment slot, exact match, case-insensitive.
raritystringRarity, either numeric (4) or by name (legendary).
subtypestringItem subtype, exact match, case-insensitive.
cosmeticbooleantrue or false.
qstringCase-insensitive substring match on name.
limitintegerPage size. Default 50, maximum 200 (larger values are clamped).
cursorstringOpaque cursor from a previous response's meta.cursor.
curl -s "https://api.soulbound.tools/v1/items?slot=chest&rarity=legendary"

GET/v1/items/{id}

Fetch one item

curl -s "https://api.soulbound.tools/v1/items/accessory_t2_belt_001"

GET/v1/abilities

List abilities

ParameterTypeMeaning
categorystringAbility category, exact match, case-insensitive.
qstringCase-insensitive substring match on name.
limitintegerPage size. Default 50, maximum 200 (larger values are clamped).
cursorstringOpaque cursor from a previous response's meta.cursor.
curl -s "https://api.soulbound.tools/v1/abilities"

GET/v1/abilities/{id}

Fetch one ability

curl -s "https://api.soulbound.tools/v1/abilities/ability_aggro_reset"

GET/v1/relics

List relics

ParameterTypeMeaning
raritystringRarity, either numeric (4) or by name (legendary).
qstringCase-insensitive substring match on name.
limitintegerPage size. Default 50, maximum 200 (larger values are clamped).
cursorstringOpaque cursor from a previous response's meta.cursor.
curl -s "https://api.soulbound.tools/v1/relics?rarity=legendary"

GET/v1/relics/{id}

Fetch one relic

curl -s "https://api.soulbound.tools/v1/relics/dc_relic_blackhole_bomb_heat_death_level1"

GET/v1/followers

List followers

ParameterTypeMeaning
raritystringRarity, either numeric (4) or by name (legendary).
qstringCase-insensitive substring match on name.
limitintegerPage size. Default 50, maximum 200 (larger values are clamped).
cursorstringOpaque cursor from a previous response's meta.cursor.
curl -s "https://api.soulbound.tools/v1/followers?rarity=legendary"

GET/v1/followers/{id}

Fetch one follower

curl -s "https://api.soulbound.tools/v1/followers/pet_berryPatisserie"

GET/v1/stats

The public stat vocabulary Every stat key an item may carry. Small and unpaginated.

curl -s "https://api.soulbound.tools/v1/stats"

GET/v1/meta

Dataset version and counts

curl -s "https://api.soulbound.tools/v1/meta"

GET/v1/img/{spriteId}.png

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"