DarkerDB

A community database and API for Dark and Darker.

The DarkerDB API exposes Dark and Darker game data — items, classes, perks, spells, skills, monsters, dungeons, maps, loot tables, quests, quest chapters, rewards, religions, emotes, merchants, shops, merchant stock, market activity, server population, leaderboards, and trade chat — through a versioned REST API at https://api.darkerdb.com.

The static game-data side is patch-versioned. Every endpoint in that group is patch-aware — see Patches for the time-travel parameters.

Versioned-catalog endpoints (quests, perks, classes, spells, skills, ...) also accept two enrichment query parameters:

  • ?embed=field1,field2 — inline-expand id-ref fields into full entity rows (e.g. ?embed=quest_reward,quest_contents on a quest).
  • ?locale=en|ko|ja|... — resolve Text_* localization keys to their translated strings. Default is the raw key.

Quick start

curl https://api.darkerdb.com/v2/items?limit=5

Or try any endpoint inline — every doc page below has a Try it widget that calls the API live with the parameters you choose.

Versions

The current version is v2. The legacy v1 API runs in parallel during the migration window; new integrations should target v2.

VersionBase URLStatus
v2https://api.darkerdb.com/v2active
v1https://api.darkerdb.com/v1legacy — frozen, accepted until DNS cutover

See Versioning & migration for the v1 → v2 mapping.

Authentication

Most endpoints accept anonymous requests with a low rate limit. Sign in with Discord at darkerdb.com to issue an API key for higher quotas.

API key

Pass the key in the X-Api-Key header:

curl https://api.darkerdb.com/v2/items \
   -H "X-Api-Key: dd_xxxx_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"

Keys are scoped to your DarkerDB account and to the permissions you delegate at issuance time. They cannot exceed your own permissions.

Rate limits

Every response includes the standard rate-limit headers:

X-RateLimit-Limit: 600
X-RateLimit-Remaining: 597
X-RateLimit-Reset: 1735689600

A 429 Too Many Requests response includes a Retry-After: <seconds> header.

See Rate limits for the per-tier quotas.

Patches

Game patches are the time axis every static game-data endpoint hangs off. Each game patch the API has imported is one row here; every item, monster, map, loot table, spawner, etc. carries the patch id it was captured under, so the same id (e.g. id.item.longsword_5001) resolves to whatever stat block was live on the patch you ask for.

Endpoints

GET /v2/patches                                  list all imported patches
GET /v2/patches/current                          shorthand for the latest patch on the default branch
GET /v2/patches/{id}                             single patch by id, version, or "current"

List

curl 'https://api.darkerdb.com/v2/patches?branch=main'

Query parameters

NameTypeDefaultDescription
branchstringmaingit-style branch label. Most consumers want main.
versionstringexact-match filter on version (list endpoint only)
parent_patch_idintlist patches whose parent is this id
pageint11-indexed
limitint25max 250

Response

{
   "pagination": { "count": 4, "limit": 25, "page": 1, "num_pages": 1, "total": 4, "next": null },
   "body": [
      {
         "id":              9,
         "branch":          "main",
         "version":         "0.16.138.8712-2711",
         "version_raw":     "0.16.138.8712-2711",
         "display_name":    "Hotfix #115",
         "released_at":     null,
         "imported_at":     "2026-05-15T22:51:34+00:00",
         "parent_patch_id": 8,
         "changelog_url":   "/v2/patches/0.16.138.8712-2711/changelog",
         "notes":           null,
         "is_current":      true
      }
   ]
}

is_current is computed against the most-recently-imported row on the same branch — at most one row per branch has is_current: true.

Get

# by numeric id
curl 'https://api.darkerdb.com/v2/patches/9'

# by version string
curl 'https://api.darkerdb.com/v2/patches/0.16.138.8712-2711'

# latest on the default branch
curl 'https://api.darkerdb.com/v2/patches/current'

{id} accepts three forms:

  • numeric — the surrogate patch id (e.g. 9)
  • version — the canonical version string or version_raw (e.g. 0.16.138.8712-2711)
  • the literal current — resolves to the most-recently-imported row on the requested branch

Pass ?branch=<name> to scope the version-string and current lookups; numeric ids are global. 404 on unknown id.

Response

{
   "body": {
      "id":              9,
      "branch":          "main",
      "version":         "0.16.138.8712-2711",
      "version_raw":     "0.16.138.8712-2711",
      "display_name":    "Hotfix #115",
      "released_at":     null,
      "imported_at":     "2026-05-15T22:51:34+00:00",
      "parent_patch_id": 8,
      "changelog_url":   "/v2/patches/0.16.138.8712-2711/changelog",
      "notes":           null,
      "is_current":      true
   }
}

changelog_url is null for the very first imported patch on a branch (no parent to diff against). Null fields are stripped by default — pass ?condense=false to keep them.

Time-traveling other endpoints

Patch awareness is implicit across the static game-data surface. Two flavors:

Flat catalogs (items, maps, monsters, loot-tables, spawners)

Accept a ?patch= query parameter:

# items as they were on patch 7 (Hotfix #114)
curl 'https://api.darkerdb.com/v2/items?patch=7'

# same, by version string
curl 'https://api.darkerdb.com/v2/items?patch=0.16.135.8645-2663'

# explicit "current" (same as no parameter)
curl 'https://api.darkerdb.com/v2/items?patch=current'

?patch= accepts the numeric id, the version string, the version_raw string, or current. Omitting the parameter resolves to the current patch on main.

Versioned catalogs (classes, perks, spells, quests, skills, leaderboard ranks, ...)

Accept ?at_patch=<version> plus ?branch=<branch>:

# spell defs at the named version
curl 'https://api.darkerdb.com/v2/spells?at_patch=0.16.135.8645-2663'

# branch override (rare)
curl 'https://api.darkerdb.com/v2/spells?branch=staging'

Omitting ?at_patch= returns the latest resolved state on the chosen branch.

The two parameter names are intentional: ?patch= is a flat-table filter against a patch_id column; ?at_patch= is a time-travel against the versioned history. They aren't interchangeable on a given endpoint — see each endpoint's reference page for which it accepts.

Discovery flow

  1. Hit /v2/patches to enumerate what's imported.
  2. Pick a version (or id) from the response.
  3. Replay any game-data call with the matching query parameter.

Items

GET /v2/items
GET /v2/items/{id}
GET /v2/items/{id}/tooltip

Item definitions — over 2,400 rows from the game's design data. Patch-aware (see Patches).

List

curl 'https://api.darkerdb.com/v2/items?limit=25&rarity=rare&archetype=helmetbarbuta'

Query parameters

Identity / categorization:

NameModeDescription
idexactid.item.<slug> or raw Id_Item_<Name>
archetypeexacte.g. helmetbarbuta, swordoldlong
namesubstringmatches anywhere in the display name
rarityexactpoor, common, uncommon, rare, epic, legendary, unique, artifact
typeexacttop-level item category
armor_typeexactfor armor pieces
hand_typeexactfor weapons
misc_typeexactfor utility / misc
slot_typeexactequipment slot
utility_typeexactfor utility items
required_classexactclass gate

Range filters (integer):

NameDescription
min_required_knowledge, max_required_knowledgeknowledge gate
min_gear_score, max_gear_scoregear score
min_vendor_price, max_vendor_pricebase merchant price
min_adventure_points, max_adventure_pointsAP yield
min_experience, max_experienceXP yield
min_max_stack_size, max_max_stack_sizestack cap
min_primary_attributes, max_primary_attributesprimary attribute slot count
min_secondary_attributes, max_secondary_attributessecondary attribute slot count

Behavior toggles:

NameTypeDescription
has_effectboolitem has an on-use effect (true/false)
patchid|version|currentreplay against a different patch

Pagination: page (default 1), limit (default 25, max 250).

Response

{
   "pagination": { "count": 25, "limit": 25, "page": 1, "num_pages": 14, "total": 342, "next": "..." },
   "body": [
      {
         "id":        "id.item.helmet_barbutashade",
         "cursor":    1,
         "archetype": "helmetbarbuta",
         "name":      "Barbuta Shade Helmet",
         "rarity":    "rare",
         "patch_id":  9
      }
   ]
}

Null fields are stripped from each row — the underlying table has 350+ columns, most are null for any given item.

Get

curl 'https://api.darkerdb.com/v2/items/id.item.helmet_barbutashade'

Same per-item shape, returned at body. Accepts the canonical envelope or the raw upstream key (Id_Item_Helmet_BarbutaShade). 404 on unknown id.

Tooltip

curl 'https://api.darkerdb.com/v2/items/id.item.helmet_barbutashade/tooltip?secondary[strength]=3:Ruby&found_by=Skullee' \
   -H 'X-Api-Key: kf_xxxx_xxxxxxxxxxxxxxxxxxxx'

Display-shaped payload for the DDB tooltip library. Same id resolution as GET /v2/items/{id}, but the response is narrowed to just the columns the renderer needs — and the primary / secondary attribute ranges are pre-formatted as display strings ("Armor Rating 75", "+3 Strength").

Requires an API key (X-Api-Key header) with the tooltips scope. Origin-restricted keys check the caller's Origin against the allow-list registered on the dashboard.

Query parameters — instance overrides

Every parameter is optional. Use them to render a specific instance of an item rather than the base template.

NameDescription
primary[<attr>]Override the displayed value for a primary attribute. <attr> is the snake-case attribute field (strength, armor_rating, move_speed, cooldown_reduction_bonus, …).
secondary[<attr>]Override the secondary roll. Append :GemName to attach a gem socket (e.g. secondary[strength]=3:Ruby).
quantityStack size; rendered as ×N next to the name when greater than 1.
found_byCharacter who looted this instance.
loot_stateLooted, Identified, Handled, …
classClass currently equipping the item.
is_tradeabletrue / false — toggles Tradeable / Bound footer.
amountGold value for coin-pouch items.
at_patchPatch version to read the item template at.
branchCodex branch (default main).

Response

{
   "id": "id.item.helmet_barbutashade",
   "name": "Barbuta Shade Helmet",
   "rarity": "rare",
   "archetype": "helmetbarbuta",
   "slot_type": "Chest",
   "armor_type": "Plate",
   "gear_score": 224,
   "description": "...",
   "required_class": [ "Fighter", "Cleric" ],
   "primary": [ "Armor Rating 75", "Magic Resistance 20", "Will 6" ],
   "secondary": [
      [ "+3 Strength", "Ruby" ],
      [ "+2 Resourcefulness" ]
   ],
   "found_by": "Skullee",
   "is_tradeable": true
}

401 / 403 if the API key is missing, invalid, or its origins allow-list doesn't include the caller's Origin. 404 on unknown id.

Monsters

Monster stat blocks, abilities, drop tables, portraits, and 3D models. Patch-aware (see Patches).

Endpoints

GET /v2/monsters                          list with filters
GET /v2/monsters/{id}                     full stat block
GET /v2/monsters/{id}/abilities           ability list (cooldowns + conditions)
GET /v2/monsters/{id}/loot                loot table this monster drops (weights + probabilities)
GET /v2/monsters/{id}/portrait            portrait PNG (Content-Type: image/png)
GET /v2/monsters/{id}/model               binary glTF (Content-Type: model/gltf-binary)

Query parameters

NameModeDescription
idexactid.monster.<slug> or raw upstream key
namesubstringmatches the display name
familyexacte.g. beast, undead, demon
tierexactstandard, elite, boss, ...
loot_table_idexactid.loot_table.<slug>
min_hp, max_hprangehp
min_damage_min, max_damage_minrangeminimum damage
min_damage_max, max_damage_maxrangemaximum damage
has_modelboolonly monsters with (or without) a registered model
has_portraitboolonly monsters with (or without) a portrait
patchid|version|currentreplay against a different patch
page, limitpaginationdefault 25, max 250

Example: stat block

curl 'https://api.darkerdb.com/v2/monsters/id.monster.flameboar'

{
   "body": {
      "id":            "id.monster.flameboar",
      "name":          "Flame Boar",
      "family":        "beast",
      "tier":          "standard",
      "hp":            280,
      "damage_min":    22,
      "damage_max":    38,
      "resistances":   { "fire": 90, "physical": 0 },
      "behaviors":     [ "charge", "fire_aura" ],
      "loot_table_id": "id.loot_table.flameboar",
      "captured_at":   "2026-05-16T01:03:43+00:00",
      "patch_id":      9
   }
}

Loot

curl 'https://api.darkerdb.com/v2/monsters/id.monster.flameboar/loot'
{
   "loot_table": {
      "id":   "id.loot_table.flameboar",
      "name": "Flame Boar drops"
   },
   "entries": [
      {
         "item_id":      "id.item.hide_boarflame",
         "weight":       3,
         "probability":  1.0,
         "min_quantity": 1,
         "max_quantity": 2,
         "tier":         "uncommon"
      }
   ]
}

probability is the row's weight / sum(all weights). Empty loot_table: null when the monster has no registered table on the requested patch.

Abilities

curl 'https://api.darkerdb.com/v2/monsters/id.monster.flameboar/abilities'
{
   "abilities": [
      {
         "monster_id": "flameboar",
         "ability_id": "fire_charge",
         "cooldown":   8.0,
         "conditions": { "min_distance": 4, "max_distance": 12 }
      }
   ]
}

Rendering the 3D model

/model serves a binary glTF (.glb) file. Drop it into any glTF viewer or render inline with <model-viewer>:

<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
<model-viewer
   src="https://api.darkerdb.com/v2/monsters/id.monster.flameboar/model"
   alt="Flame Boar 3D model"
   auto-rotate
   camera-controls>
</model-viewer>

For more control, load with three.js's GLTFLoader. Animations live in the entity's animation_paths field (currently emits the static T-pose; animations land in a follow-up).

Classes

Playable character classes (Fighter, Barbarian, Wizard, ...). Time-travel with ?at_patch= + ?branch= (see Patches).

Endpoints

GET /v2/classes                            list
GET /v2/classes/{id}                       single class
GET /v2/classes/{id}/icon                  → 302 redirect to the asset endpoint

Query parameters

NameModeDescription
idexactid.player_character.<slug> or raw Id_PlayerCharacter_<Name>
namesubstringmatches the display name
can_useboolonly currently-playable classes
is_defaultboolonly default-roster classes
at_patchversionrender this entity at the named patch's version
branchstringbranch (default main)
page, limitpaginationdefault 25, max 250

Example

curl 'https://api.darkerdb.com/v2/classes/id.player_character.barbarian'

{
   "body": {
      "id":              "id.player_character.barbarian",
      "name":            "Barbarian",
      "patch_version":   "build-22811648-hotfix-113",
      "source_key":      "Id_PlayerCharacter_Barbarian",
      "art_data":        "Barbarian",
      "class_info":      "Barbarian_ClassInfo",
      "sound_data":      "Barbarian_Sound",
      "flavor_text":     "Text_DesignData_PlayerCharacter_PlayerCharacter_FlavorText_Barbarian",
      "can_use":         true,
      "is_default":      true,
      "character_types": [ "Type.Character.Human.Barbarian" ],
      "abilities":       [ "id.player_character_ability.crouch", "..." ],
      "perks":           [ "id.perk.robust", "id.perk.crush", "id.perk.berserker", "id.perk.iron_will" ],
      "emotes":          [ "id.emote.surrender", "id.emote.pointing", "id.emote.cut_throat" ],
      "default_hands":   [ "EDCEquipmentSlotIndex::Weapon_L1" ]
   }
}

abilities, perks, and emotes are envelope-id arrays referencing other catalog rows. effects and class_info/sound_data/art_data are upstream string keys — no canonical type is registered for them yet, so they're passed through unwrapped.

Perks

Class perks — passive bonuses a character can slot. Time-travel with ?at_patch= + ?branch= (see Patches).

Endpoints

GET /v2/perks                              list
GET /v2/perks/{id}                         single perk
GET /v2/perks/{id}/icon                    → 302 redirect to the asset endpoint

Query parameters

NameModeDescription
idexactid.perk.<slug> or raw Id_Perk_<Name>
namesubstringmatches the display name
can_useboolonly currently-equippable perks
classtagfilter by owning class tag (e.g. Fighter, Wizard)
at_patchversionrender this entity at the named patch's version
branchstringbranch (default main)
page, limitpaginationdefault 25, max 250

Example

curl 'https://api.darkerdb.com/v2/perks/id.perk.adrenaline_spike'

{
   "body": {
      "id":            "id.perk.adrenaline_spike",
      "name":          "Adrenaline Spike",
      "patch_version": "build-22811648-hotfix-113",
      "icon_url":      "/v1/codex/assets/118c6986aafd976d46d030c798646766c0c643fc8dd0c70750e4c5a4eb6a2590",
      "source_key":    "Id_Perk_AdrenalineSpike",
      "desc_data":     "AdrenalineSpike_Desc",
      "art_data":      "AdrenalineSpike",
      "can_use":       true,
      "classes":       [ "id.player_character.fighter", "id.player_character.grand_master_fighter" ],
      "abilities":     [ "id.perk_ability.adrenaline_spike" ]
   }
}

icon_url is a content-addressed pointer to the icon asset — safe to cache aggressively.

Spells

Castable spells — range, area, casting/cost type, source element. Time-travel with ?at_patch= + ?branch= (see Patches).

Endpoints

GET /v2/spells                             list
GET /v2/spells/{id}                        single spell
GET /v2/spells/{id}/icon                   → 302 redirect to the asset endpoint

Query parameters

NameModeDescription
idexactid.spell.<slug> or raw Id_Spell_<Name>
namesubstringmatches the display name
source_typeexactelement / source (Type.Spell.Source.Ice, ...)
casting_typeexactType.Spell.Cast.Casting, Type.Spell.Cast.Instant, ...
cost_typeexactType.Spell.Cost.Cooldown, Type.Spell.Cost.SpellPoints, ...
classtagfilter by owning class tag
min_range, max_rangerangerange in cm
at_patchversionrender this entity at the named patch's version
branchstringbranch (default main)
page, limitpaginationdefault 25, max 250

Example

curl 'https://api.darkerdb.com/v2/spells/id.spell.aqua_prison'

{
   "body": {
      "id":            "id.spell.aqua_prison",
      "name":          "Aqua Prison",
      "patch_version": "build-22811648-hotfix-113",
      "icon_url":      "/v1/codex/assets/62864450ebacec9b215eaf4f85e9b06373ceee6b24470519a7f0b6d1cefdaeaa",
      "source_key":    "Id_Spell_AquaPrison",
      "desc":          "AquaPrison_Desc",
      "range":         700,
      "area_radius":   20,
      "art_data":      "AquaPrison",
      "casting_type":  "Type.Spell.Cast.Casting",
      "cost_type":     "Type.Spell.Cost.Cooldown",
      "source_type":   "Type.Spell.Source.Ice",
      "spell_tag":     "Ability.Spell.AquaPrison",
      "sound_data":    "AquaPrison_Sound",
      "abilities":     [ "id.spell_ability.aqua_prison" ]
   }
}

range and area_radius are in centimeters (game-engine units). Multiply by 0.01 for meters.

Skills

Stamina-based class abilities — distinct from /spells, which are magical / cooldown-based. Patch-aware (see Patches).

Endpoints

GET /v2/skills                             list
GET /v2/skills/{id}                        single skill
GET /v2/skills/{id}/icon                   → 302 redirect to the asset endpoint

Query parameters

NameModeDescription
idexactid.skill.<slug> or raw Id_Skill_<Name>
namesubstringmatches the display name
skill_typeexactType.Skill.Instant, Type.Skill.Channel, ...
can_useboolonly currently-equippable skills
classtagfilter by owning class tag (Fighter, Barbarian, ...)
at_patchversiontime-travel
branchstringdefault main
page, limitpaginationdefault 25, max 250

?embed= expansion

Pass a comma-separated list to expand id-ref fields into full entity rows:

  • classesplayer_character rows
  • abilitiesskill_ability rows

Example

Emotes

Character emotes (Wave, Sit, Point, ...). Patch-aware (see Patches).

Endpoints

GET /v2/emotes                             list
GET /v2/emotes/{id}                        single emote
GET /v2/emotes/{id}/icon                   → 302 redirect to the asset endpoint

Query parameters

NameModeDescription
idexactid.emote.<slug>
namesubstringmatches the display name
at_patchversiontime-travel
branchstringdefault main
page, limitpaginationdefault 25, max 250

Example

Religions

Deity affinity tracks (Blythar, the Beggar, the Iron Lord, ...) — offerings cost gold, leveling up grants passive bonuses. Patch-aware (see Patches).

Endpoints

GET /v2/religions                          list
GET /v2/religions/{id}                     single religion
GET /v2/religions/{id}/icon                → 302 redirect to the asset endpoint

Query parameters

NameModeDescription
idexactid.religion.<slug>
namesubstringmatches the display name
min_order, max_orderrangefilter by order index
at_patchversiontime-travel
branchstringdefault main
page, limitpaginationdefault 25, max 250

Example

offering_lv_count is an int[] indexed by level — [1000, 1500, 2500, ...] means lv 1 costs 1000, lv 2 costs 1500, etc.

Quests

GET /v2/quests
GET /v2/quests/{id}

Quest definitions — title, description, prerequisite, rewards, objectives. The single-row endpoint inlines all referenced objectives and rewards into one response, so consumers can render a full quest panel in one fetch.

List

curl 'https://api.darkerdb.com/v2/quests?is_daily=true&limit=10'

Filters

NameModeDescription
id_likesubstringid substring; _ is literal
archetypeexactfamily slug, e.g. id.quest.alchemist_01
chapter_idexactid.quest.<prereq> (the chain pointer)
is_repeatablebooltrue / false
is_dailybool
rank_min, rank_maxrangeadventure rank gate
sortenumasc (default) / desc
limitintdefault 50, max 200
cursorstringfrom prior response's next
localestringen (default), de, es, fr, ja, ko, pt-BR, ru, zh-Hans, zh-Hant

LIST response

{
   "count":  643,
   "limit":  10,
   "sort":   "asc",
   "next":   "id.quest.alchemist_10",
   "rows": [
      {
         "id":                "id.quest.alchemist_01",
         "archetype":         "id.quest.alchemist_01",
         "title":             "Marks of Malice",
         "description":       "Recently, the number of patients has increased sharply…",
         "chapter_id":        "id.quest.tavern_master_01",
         "objectives": {
            "order":       105,
            "content_ids": [ "id.quest_content.fetch_bandages_01" ]
         },
         "reward_ids":        [ "id.reward.quest_alchemist_01" ],
         "is_repeatable":     false,
         "is_daily":          false
      }
   ]
}

LIST stays thin — objectives and reward_ids are just refs to keep paged browsing small. Click into a single quest for the inlined version.

Get

curl 'https://api.darkerdb.com/v2/quests/id.quest.alchemist_05'

The single-quest GET inlines:

  • objectives[] — fully resolved quest_content rows with content_type, content_count, target_kind, target_archetype
  • rewards[] — fully resolved reward rows with their entries[] array (one entry per reward grant)
{
   "id":                "id.quest.alchemist_05",
   "archetype":         "id.quest.alchemist_05",
   "title":             "Beasts of the Dark Forest",
   "description":       "Creatures able to survive in a near pitch black forest…",
   "chapter_id":        "id.quest.alchemist_04",
   "order":             109,
   "objectives": [
      {
         "id":               "id.quest_content.fetch_blue_eyeball_03",
         "content_type":     "fetch",
         "content_count":    1,
         "target_kind":      "item",
         "target_archetype": "id.item.blue_eyeballs",
         "target_tag":       "Id.Item.BlueEyeballs",
         "dungeon_tags":     []
      }
   ],
   "rewards": [
      {
         "id": "id.reward.quest_alchemist_05",
         "entries": [
            { "type": "item",       "count": 30, "item_id": "id.item.gold_coins" },
            { "type": "random",     "count": 1,  "random_reward_id": "id.random_reward.quest_armor_uncommon_01" },
            { "type": "experience", "count": 25 }
         ]
      }
   ],
   "min_adventure_rank": null,
   "is_repeatable":      false,
   "is_daily":           false
}

Objective shape

Eight content_type values: fetch, kill, explore, props, escape, hold, use_item, damage. The target_archetype field is the same shape as the archetype column on /items, /monsters, etc., so you can pivot directly:

# What items satisfy "fetch Bandage" objectives?
curl 'https://api.darkerdb.com/v2/items?archetype=id.item.bandage'

See Quest contents for the standalone endpoint.

Reward entries

Each entry has a type and a count, plus a type-specific reference field where applicable:

TypeReference fieldExample
itemitem_id{type:"item", count:30, item_id:"id.item.gold_coins"}
experience(none){type:"experience", count:25}
adventure_points(none){type:"adventure_points", count:5}
randomrandom_reward_id{type:"random", count:1, random_reward_id:"id.random_reward.x"}
affinitymerchant_id (optional){type:"affinity", count:50, merchant_id:"id.merchant.x"}
item_skinitem_skin_id
character_skincharacter_skin_id
armor_skinarmor_skin_id
nameplate_skinnameplate_skin_id
actionaction_skin_id
emoteemote_id
stash / none(none)placeholder rows

Achievements

GET /v2/achievements
GET /v2/achievements/{id}

Wiki-surfaced achievements — Arena, Class, Coreplay, HighRoller, Event, Feat, Community, Item categories.

List

curl 'https://api.darkerdb.com/v2/achievements?category=arena&limit=5'

Filters

NameOpDescription
categoryexactarena, class, community, coreplay, event, feat, highroller, item
id_likesubstringid substring search
archetypeexactfamily slug
sortenumasc (default) or desc
limitintdefault 50, max 200
cursorstringfrom prior response's next

Response

Cursor-paginated envelope: { count, limit, sort, next, rows }. Each row carries id, name, description, category, icon (when an asset is indexed), plus a payload blob for the raw upstream shape.

Get

curl 'https://api.darkerdb.com/v2/achievements/id.achievement.arena_01'

404 on unknown id.

Triumphs

GET /v2/triumphs
GET /v2/triumphs/{id}

Adventure rank progression — per-tier XP requirements.

List

curl 'https://api.darkerdb.com/v2/triumphs?tier_min=1&tier_max=10'

Filters

NameOpDescription
tier_min, tier_maxrangeinclusive tier integer
id_likesubstring
sortenumasc / desc
limit, cursorstandard

Each row: tier, xp_requirement, plus the full upstream payload.

Dungeons

Dungeon definitions — the playable level metadata behind a /v2/maps entry. Patch-aware (see Patches).

Endpoints

GET /v2/dungeons                           list
GET /v2/dungeons/{id}                      single dungeon

Query parameters

NameModeDescription
idexactid.dungeon.<slug> or raw Id_Dungeon_<Name>
namesubstringmatches the display name
min_floor, max_floorrangefilter by floor number
at_patchversiontime-travel
branchstringdefault main
page, limitpaginationdefault 25, max 250

Example

{
   "body": {
      "id":                    "id.dungeon.arena_abyss_deep_flow",
      "name":                  "Ice Abyss Arena",
      "source_key":            "Id_Dungeon_ArenaAbyss_DeepFlow",
      "id_tag":                "Id.Arena",
      "floor":                 1,
      "triumph_exp":           1,
      "layout_size":           "1",
      "level_asset":           "Arena_Abyss_DeepFlow_P",
      "game_types":            [ "EGameType::ArenaTrio" ],
      "floor_rule":            "id.floor_rule.arena_1x1",
      "default_dungeon_grade": "id.dungeon_grade.2012"
   }
}

Dungeon grades

GET /v2/dungeon_grades
GET /v2/dungeon_grades/{id}

Difficulty tiers for dungeons (~60 rows).

Filters

NameOpDescription
tierexactdifficulty tier integer
id_likesubstring
sort, limit, cursorstandard

Each row carries name, description, tier.

Dungeon cards

GET /v2/dungeon_cards
GET /v2/dungeon_cards/{id}

Game-mode variants applied to dungeons (~10 rows).

Filters

NameOpDescription
modeexactgame mode slug
party_size_min, party_size_maxrange
id_likesubstring

Each row carries mode, party_size_min, party_size_max, plus full payload.

Dungeon layouts

GET /v2/dungeon_layouts
GET /v2/dungeon_layouts/{id}

Physical map layouts — the game's per-floor layout definitions (~270 rows). What used to be called "maps."

Filters

NameOpDescription
id_likesubstring
archetypeexactfamily slug

Each row carries floor (1-based for multi-floor dungeons) and the full layout payload.

Spawners

GET /v2/spawners
GET /v2/spawners/{id}

Where monsters, treasures, and props are placed on dungeon maps. 516 spawners split evenly across the three roles.

List

curl 'https://api.darkerdb.com/v2/spawners?spawner_type=monster&limit=10'

Filters

NameOpDescription
spawner_typeexactmonster / treasure / props / empty
monster_idexactid.monster.* — find all spawners that spawn a given monster
loot_drop_group_idexactid.loot_drop_group.* — reverse lookup for loot tables
id_likesubstring
sort, limit, cursorstandard

Row shape

{
   "id":                  "id.spawner.monster_demon_executioner",
   "archetype":           "id.spawner.monster_demon_executioner",
   "spawner_type":        "monster",
   "monster_id":          "id.monster.demon_executioner",
   "loot_drop_group_id":  null,
   "location":            {},
   "payload":             { /* full SpawnerItemArray entry list */ }
}

Get

curl 'https://api.darkerdb.com/v2/spawners/id.spawner.monster_demon_executioner'

Loot

Static loot-table and spawner definitions. Patch-aware (see Patches).

Endpoints

GET /v2/loot-tables                                list, filter by type
GET /v2/loot-tables/{id}                           single table + entries (weights + computed probabilities)
GET /v2/spawners                                   spawn points (loot or monster) by map/type
GET /v2/spawners/{id}                              single spawner

Loot tables

Query parameters:

NameModeDescription
idexactid.loot_table.<slug> or raw LT_<Name>
namesubstringmatches the display name
typeexactmonster, chest, merchant, ...
patchid|version|currentreplay against a different patch
page, limitpaginationdefault 25, max 250

Get

curl 'https://api.darkerdb.com/v2/loot-tables/id.loot_table.flameboar'

{
   "body": {
      "loot_table": {
         "id":          "id.loot_table.flameboar",
         "type":        "monster",
         "name":        "Flame Boar drops",
         "captured_at": "2026-05-16T01:03:43+00:00",
         "patch_id":    9
      },
      "entries": [
         {
            "loot_table_id": "id.loot_table.flameboar",
            "item_id":       "id.item.hide_boarflame",
            "weight":        3,
            "probability":   1.0,
            "min_quantity":  1,
            "max_quantity":  2,
            "tier":          "uncommon",
            "conditions":    null
         }
      ]
   }
}

probability is weight / sum(all weights in table). Quantity ranges and tier conditions reflect the static game definition; the actual on-server roll may include additional rolls (item rarity, attribute variance) handled by the game server.

Spawners

Query parameters:

NameModeDescription
idexactid.spawner.<slug>
map_idexactid.map.<slug>
spawner_typeexactloot, monster, ...
loot_table_idexactid.loot_table.<slug>
monster_idexactid.monster.<slug>
patchid|version|currentreplay against a different patch
page, limitpaginationdefault 25, max 250

Example

curl 'https://api.darkerdb.com/v2/spawners?map_id=id.map.cr'

Each spawner row carries its location (JSON {x,y,z,rotation}), spawner_type, and either a loot_table_id or monster_id link depending on type.

Workshops

GET /v2/workshops
GET /v2/workshops/{id}
GET /v2/workshop_levels
GET /v2/workshop_upgrades
GET /v2/workshop_services

The DarkerDB crafting system. Four related tables:

TableWhat
workshopsThe buildings (2 rows).
workshop_levelsPer-workshop tier definitions with tier + xp_requirement.
workshop_upgradesUpgrade tracks (~50 rows).
workshop_servicesThe recipes — craft, rebuild, enhance, scrap unified via service_type.

Workshops

The buildings themselves. Filterable by id_like, archetype, sort, limit, cursor.

Workshop levels

Per-workshop tier progression. Each row carries tier and xp_requirement.

Workshop upgrades

Upgrade requirements per workshop level. Carries name, description, full payload.

Workshop services

The actual crafting recipes (combined across the four service types).

Filters

NameOpDescription
service_typeexactcraft / rebuild / enhance / scrap
id_likesubstring
sort, limit, cursorstandard

Row shape

{
   "id":              "id.workshop_service.armor_1",
   "archetype":       "id.workshop_service.armor_1",
   "name":            "Tier 1 Armor",
   "description":     "...",
   "service_type":    "craft",
   "payload":         { /* recipe materials, output, requirements */ }
}

Workshop services

See Workshops.

Faustian bargains

GET /v2/faustian_bargains
GET /v2/faustian_bargains/{id}

The Faustian Bargain system — temporary risk/reward modifiers a player can take during a dungeon run.

List

curl 'https://api.darkerdb.com/v2/faustian_bargains?tier=2&limit=10'

Filters

NameOpDescription
tierexactbargain tier integer
id_likesubstring
archetypeexactfamily slug
sort, limit, cursorstandard

Row shape

{
   "id":              "id.faustian_bargain.centaur_demon",
   "archetype":       "id.faustian_bargain.centaur_demon",
   "name":            "Centaur Demon's Bargain",
   "description":     "...",
   "icon":            { "hash": "...", "url": "...", "media_type": "image/png" },
   "tier":            2,
   "payload":         { /* abilities, effects, skills, overrides denormalized */ }
}

The payload field carries the full bargain definition. Companion docs (FaustianBargainAbility, FaustianBargainEffect, FaustianBargainSkill, FaustianBargainEffectOverride) are resolved into the payload at projection time so consumers don't need to chase references.

Merchants

NPC merchant definitions — the static "who they are" surface, distinct from /merchant/stock (which is bot-fed live inventory). Time-travel with ?at_patch= + ?branch= (see Patches).

Endpoints

GET /v2/merchants                          list
GET /v2/merchants/{id}                     single merchant
GET /v2/merchants/{id}/icon                → 302 redirect to the asset endpoint

Query parameters

NameModeDescription
idexactid.merchant.<slug> or raw Id_Merchant_<Name>
namesubstringmatches the display name
at_patchversionrender this entity at the named patch's version
branchstringbranch (default main)
page, limitpaginationdefault 25, max 250

Example

curl 'https://api.darkerdb.com/v2/merchants/id.merchant.alchemist'

{
   "body": {
      "id":             "id.merchant.alchemist",
      "name":           "Alchemist",
      "patch_version":  "build-22811648-hotfix-113",
      "icon_url":       "/v1/codex/assets/...",
      "source_key":     "Id_Merchant_Alchemist",
      "art_data":       "Alchemist",
      "order":          1,
      "service_types":  [ "EDCMerchantServiceType::Buy", "EDCMerchantServiceType::Sell" ],
      "greeting_texts": [ { "key": "Text_…", "namespace": "DC" } ]
   }
}

service_types is the enum-array of capabilities the merchant exposes (Buy, Sell, Craft, Quest). greeting_texts are localization keys — resolve via the /v2/quests-style ?locale= parameter once localization comes online for merchants.

Shops

Cash-shop / cosmetic listings — the storefront the game UI renders. Patch-aware (see Patches).

Endpoints

GET /v2/shops                              list
GET /v2/shops/{id}                         single shop entry
GET /v2/shops/{id}/icon                    → 302 redirect to the asset endpoint

Query parameters

NameModeDescription
idexactid.shop.<slug>
namesubstringmatches the display name
sales_channelexactEDCShopSalesChannelType::Shop, ::Exclusive, ...
is_on_saleboolonly entries with an active sale flag
min_cash_price, max_cash_pricerangefilter by listed cash price
at_patchversiontime-travel
branchstringdefault main
page, limitpaginationdefault 25, max 250

Example

Merchant

Live NPC merchant inventory snapshots plus quest availability. Bot-fed: the Ghost bot observes MERCHANT_STOCK_BUY_ITEM_LIST_RES and MERCHANT_QUEST_LIST_INFO_RES broadcasts on a ~10 minute cadence. Not patch-aware.

Endpoints

GET /v2/merchant/stock                         most recent stock snapshot, all merchants
GET /v2/merchant/stock/{merchant}              single merchant inventory
GET /v2/merchant/stock/history?item_id=...     availability history for a specific item
GET /v2/merchant/quests                        currently available quests + reset windows

Stock

curl 'https://api.darkerdb.com/v2/merchant/stock/surgeon'

{
   "body": {
      "merchant": "id.merchant.surgeon",
      "stock": [
         {
            "merchant_id": "id.merchant.surgeon",
            "item_id":     "id.item.potion_health",
            "price":       50,
            "currency":    "Gold",
            "stock":       20,
            "rank_id":     "gold",
            "captured_at": "2026-05-16T01:03:43+00:00"
         }
      ]
   }
}

{merchant} accepts the canonical envelope (id.merchant.surgeon) or the raw slug (surgeon). Stock rotates on the game's internal schedule; captured_at is when the bot observed the snapshot, not when the rotation occurred on the game server — expect up to a 10-minute lag.

item_id and merchant_id are canonical envelopes — plug item_id straight into /v2/items/{id} to resolve the item row.

Stock history

curl 'https://api.darkerdb.com/v2/merchant/stock/history?item_id=id.item.potion_health'

item_id is required and accepts the canonical envelope or the raw slug. Returns every captured snapshot that included the item, newest first.

Quests

curl 'https://api.darkerdb.com/v2/merchant/quests'

{
   "body": {
      "quests": [
         {
            "merchant_id":      "id.merchant.surgeon",
            "quest_id":         "id.quest.dailyhealer",
            "reset_at":         "2026-05-16T21:03:43+00:00",
            "is_repeatable":    true,
            "rank_requirement": "iron",
            "captured_at":      "2026-05-16T01:03:43+00:00"
         }
      ]
   }
}

reset_at is when the daily quest rolls over. is_repeatable distinguishes daily/weekly quests from one-time story quests. Plug quest_id straight into /v2/quests/{id} for the static definition.

Population

GET /v2/population
GET /v2/population/history

Live and historical Dark and Darker server population samples.

Current

curl https://api.darkerdb.com/v2/population

{
   "sample": {
      "timestamp": "2026-05-07T14:30:00+00:00",
      "num_online": 12450,
      "num_lobby": 3120,
      "num_dungeon": 9330
   }
}

History

curl "https://api.darkerdb.com/v2/population/history?from=2026-05-01T00:00:00Z&to=2026-05-07T00:00:00Z"

from and to are required ISO 8601 timestamps. Returns up to 1000 samples ordered ascending.

Market

Live trade-market listings observed by the Ghost bot, plus pre-computed price history aggregates. Bot-fed, not patch-aware — listings carry the patch they were observed under in captured_patch_id but aren't time-travelable.

Endpoints

GET /v2/market                                                    paginated listing feed
GET /v2/market/{id}                                               single listing by surrogate id
GET /v2/market/analytics/{item_id}/prices/history                 price history for one item

List

curl 'https://api.darkerdb.com/v2/market?item_id=id.item.potion_health&has_sold=true&limit=50'

Query parameters

Identity / categorization:

NameModeDescription
idexactsurrogate listing id (integer)
item_idexactid.item.<slug>
itemexactraw upstream item key
archetypeexactitem archetype slug
rarityexactlisting rarity
loot_stateexactgame-side loot state at observation time
sellerexactseller display name

Price / quantity ranges:

NameDescription
min_price, max_pricetotal listing price
min_price_per_unit, max_price_per_unitnormalized per-unit price
min_quantity, max_quantitystack size

Time window / status:

NameDescription
from, toISO 8601 — restrict by created_at
has_sold, has_expired, has_cancelledbool — listing terminal state

Pagination: page (default 1), limit (default 25, max 250). Default order is created_at DESC.

Response

{
   "body": [
      {
         "id":             1005,
         "cursor":         5,
         "item_id":        "id.item.potion_health",
         "item":           "Health Potion",
         "archetype":      "potionhealth",
         "rarity":         "common",
         "price":          50,
         "price_per_unit": "25.00",
         "quantity":       2,
         "created_at":     "2026-05-18T04:41:02+00:00",
         "expires_at":     "2026-05-23T04:56:02+00:00",
         "has_sold":       false,
         "has_cancelled":  false,
         "seller":         "Trader2"
      }
   ]
}

Price history

curl 'https://api.darkerdb.com/v2/market/analytics/id.item.potion_health/prices/history?interval=15m&from=2026-05-01T00:00:00Z&to=2026-05-07T00:00:00Z'

Query parameters:

NameTypeDefaultDescription
intervalenum15mbucket size — see below
from, toISO 8601last 7dwindow bounds

Pre-computed buckets exist for: 1m, 3m, 5m, 10m, 15m, 30m, 45m, 1h, 2h, 4h, 1d, 1w, 2w, 4w. Other intervals fall through to a live time_bucket aggregate (TimescaleDB extension required).

Response:

{
   "body": {
      "item_id":  "id.item.potion_health",
      "interval": "15m",
      "from":     "2026-05-01T00:00:00Z",
      "to":       "2026-05-07T00:00:00Z",
      "series": [
         { "timestamp": "2026-05-01T00:00:00+00:00", "avg": 24.50, "min": 22, "max": 27, "volume": 41 }
      ]
   }
}

Capped at 250 buckets per response.

Trades

Global trade-channel chat feed observed by the Ghost bot. Bot-fed, not patch-aware. Polled — no websocket / SSE today.

Endpoints

GET /v2/trades                             feed
GET /v2/trades/chat                        legacy alias — same shape, same params

Query parameters

NameModeDescription
senderexactaccount display name
sender_classexactcharacter class at message time
sender_rankexactrank tier at message time
containssubstringfull-text-ish search on the message body
from, toISO 8601restrict by message timestamp
page, limitpaginationdefault 25, max 250 — newest first

Example

curl 'https://api.darkerdb.com/v2/trades?contains=potion&limit=5'

{
   "body": [
      {
         "id":           5,
         "timestamp":    "2026-05-18T04:55:02+00:00",
         "sender":       "BetaPlayer",
         "sender_id":    2,
         "sender_class": "Rogue",
         "sender_rank":  "Bronze",
         "message":      "Bumping the potions",
         "items":        []
      }
   ]
}

items is a JSON array of { item_id, quantity, ... } references the bot parsed out of the message (e.g. [Health Potion x3]-style item tokens). Empty when the message body had no recognizable item references.

Leaderboards

The leaderboard surface mirrors the game's true model: season → period → sheet (ladder) → content_type → class → entries. The bot discovers all of these from the live game — there is no static catalog to maintain.

Endpoints

GET /v2/leaderboards/seasons                                                      list active and historical seasons
GET /v2/leaderboards/seasons/{season}                                             season metadata (state, begin/end/wipe/reward, periods, sheets)
GET /v2/leaderboards/seasons/{season}/sheets                                      sheets (ladders) in a season
GET /v2/leaderboards/seasons/{season}/sheets/{sheet}                              sheet metadata
GET /v2/leaderboards/seasons/{season}/sheets/{sheet}/periods                      periods in a sheet
GET /v2/leaderboards/seasons/{season}/sheets/{sheet}/entries                      paginated ranked entries
GET /v2/leaderboards/seasons/{season}/sheets/{sheet}/characters/{name}/history    rank trajectory for a character

Query parameters

  • class — filter entries by character class (e.g. Fighter, Rogue)
  • period — period index, integer; defaults to the current period
  • page — 1-indexed, default 1
  • page_size — default 100, max 1000

Example

curl 'https://api.darkerdb.com/v2/leaderboards/seasons/EA7_HOF/sheets/TreasureCollector_Solo/entries?class=Fighter&period=0&page_size=5'

{
   "entries": [
      {
         "season_id":       "EA7_HOF",
         "sheet_id":        "TreasureCollector_Solo",
         "period_index":    0,
         "character_class": "Fighter",
         "character":       "ExamplePlayer",
         "rank":            1,
         "previous_rank":   2,
         "score":           "1450000",
         "karma":           42,
         "rank_tier":       "Diamond",
         "rank_tier_level": "I"
      }
   ],
   "page":      1,
   "page_size": 5,
   "total":     127
}

Legacy ID shim

Pre-v2 leaderboards used flat IDs like EA7_HOF_SHR_B. Those endpoints still respond:

GET /v2/leaderboards                  legacy summary, returns pointer + season list
GET /v2/leaderboards/{legacy_id}      legacy lookup, best-effort resolved to (season, sheet)

The legacy shim translates the prefix (e.g. EA7_HOF) to the new season key and returns the best-matching sheet's entries. It exists only to keep existing frontends rendering during their own migration — new integrations should target the structured /seasons/... paths above.

Leaderboard ranks

Tier metadata for leaderboard scoring (Apprentice, Adept, Mythic, ...). Time-travel with ?at_patch= + ?branch= (see Patches).

Sits under /leaderboards/ranks rather than /ranks — it's leaderboard-scoped metadata, not a separate top-level resource. See Leaderboards for the entries surface that references these ranks.

Endpoints

GET /v2/leaderboards/ranks                 list
GET /v2/leaderboards/ranks/{id}            single rank
GET /v2/leaderboards/ranks/{id}/icon       → 302 redirect to the asset endpoint

Query parameters

NameModeDescription
idexactid.leaderboard_rank.<slug> (e.g. apprentice_i)
namesubstringmatches the display name
groupexactid.leaderboard_rank_group.<slug> (e.g. apprentice)
min_sub_grade, max_sub_graderangesub-grade index within the group (1..N)
at_patchversionrender this entity at the named patch's version
branchstringbranch (default main)
page, limitpaginationdefault 25, max 250

Example

curl 'https://api.darkerdb.com/v2/leaderboards/ranks/id.leaderboard_rank.apprentice_i'

{
   "body": {
      "id":                      "id.leaderboard_rank.apprentice_i",
      "patch_version":           "0.16.135.8645-2663",
      "source_key":              "Id_LeaderboardRank_Apprentice_I",
      "art_data":                "LeaderboardRank_Apprentice_I",
      "rank_name":               "Text_LeaderboardRank_Apprentice_I",
      "rank_sub_grade":          3,
      "leaderboard_rank_group":  "id.leaderboard_rank_group.apprentice",
      "leaderboard_rank_a_ps":   [ "Id_LeaderboardRankAP_SoloHighRoller_Apprentice_I", "..." ],
      "leaderboard_rank_e_fs":   [ "Id_LeaderboardRankEF_GoblinNormal_Apprentice_I", "..." ]
   }
}

rank_name is a localization key. leaderboard_rank_a_ps (entry-point bindings) and leaderboard_rank_e_fs (eligibility filters) are raw upstream key arrays — no canonical type is registered for them, so they pass through unwrapped.

Facets

GET /v2/facets[?locale=en]

A single payload mapping every enum-typed column across all DarkerDB tables to its localized display labels. Designed to be fetched once per session and cached client-side.

Why a separate endpoint

Item/monster/etc. responses carry raw slugs only ("rarity": "rare"). The display label ("Rare") is shared by hundreds of items, so inlining it on every row is waste. Instead, render-time UIs fetch /facets once at boot and look up labels from a tiny dictionary.

Response shape

{
   "rarity":     { "common": "Common", "rare": "Rare", "epic": "Epic", "legend": "Legendary" },
   "armor_type": { "cloth": "Cloth", "leather": "Leather", "plate": "Plate" },
   "slot_type":  { "chest": "Chest", "head": "Head", "hands": "Hands", ... },
   "class_type": { "common": "Common", "sub_boss": "Sub-Boss", "boss": "Boss" },
   "grade_type": { "common": "Common", "elite": "Elite", "nightmare": "Nightmare" },
   ...
}

Locale override

curl 'https://api.darkerdb.com/v2/facets?locale=fr'

Falls back to English when a tag has no French translation. Default is en.

Discovering valid values for a filter

SELECT DISTINCT rarity FROM darkerdb.items

— but you don't need to query the DB. The set of valid rarities is always exactly the keys in body.rarity from /facets. The dictionary is rebuilt from the live tables on every refresh, so new values in future patches appear automatically.

Rate limits

TierPer-minutePer-dayNotes
anonymous6010 000shared per IP
free6010 000per API key
pro6001 000 000per API key
unlimitedinternal/system keys

When a key is created, its quota defaults to the tier's standard. Custom per-key quotas can be set on a request basis.

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and (when the bucket has been touched) X-RateLimit-Reset. A 429 response carries Retry-After.

Versioning & migration

The DarkerDB API uses URL-prefix versioning. The current version is v2.

v1 → v2 changes

v1v2Notes
/v1/items/v2/itemsricher data, full filter set
/v1/items/{id}/v2/items/{id}canonical id format (id.item.<slug> or upstream Id_Item_<Name>)
/v1/population/v2/populationunchanged shape
/v1/population/history/v2/population/historyrequires from/to ISO 8601
/v1/market/v2/marketnew pagination cursor
/v1/leaderboards/v2/leaderboardsunchanged
/v1/trades/chat/v2/trades/chatunchanged
/v1/characters/v2/charactersunchanged
/v1/users/@me/keys/v2/users/@me/keysnow reports tier and quota usage

v1 sunset

The v1 API runs in parallel until DNS cutover, then is retired out-of-band. New integrations should target v2.

API keys do not survive cutover — legacy plaintext keys are unrecoverable. After cutover, regenerate via the dashboard.