Docs/Reference/DarkerDB

DarkerDB

Last reviewed May 22, 2026

Read-only, public, unauthenticated. Two equivalent base URLs:

text
https://api.katforge.com/v1/realms/darkerdb     (canonical, KATforge-versioned)
https://api.darkerdb.com/v2                     (bare-host shorthand, identical bytes)

All paths below are written under /v1/realms/darkerdb. The /v2 shadow accepts the same routes with the prefix stripped.

The DarkerDB API is the projection layer over the codex: per-game typed tables that get refreshed after every codex import. Most endpoints follow a single uniform shape; a handful add per-type filters or attribute hydration.

Conventions

  • IDs are id.<type>.<slug> everywhere (e.g. id.item.jester_outfit_4001, id.monster.abomination_common). Slugged from raw upstream ids at projection time.
  • Enum values in responses are slugs ("rare", "cloth", "chest"). Their localized display labels are served via /facets.
  • Per-document text (name, description, flavor) is resolved inline against codex.locales at response time. Override locale with ?locale=fr|de|ja|… (default en).
  • Icons are resolved against the asset index at response time and emitted as { hash, url, media_type, bytes } objects.
  • Pagination is cursor-based on id — pass back the next value via ?cursor=…. Default limit=50, max 200.
  • Sort is asc (default) or desc via ?sort=….
  • Historical reads are single-id only via ?at_patch=<version>. LIST endpoints reject at_patch with 400.

Quick start

shell
# Browse rare cloth chest armor at gear score >= 20
curl 'https://api.katforge.com/v1/realms/darkerdb/items?rarity=rare&armor_type=cloth&slot_type=chest&gear_score_min=20&limit=10'

# Get one item with attributes hydrated
curl https://api.katforge.com/v1/realms/darkerdb/items/id.item.jester_outfit_4001

# Fetch the slug→label dictionary for all enum columns
curl https://api.katforge.com/v1/realms/darkerdb/facets

# Time-travel a single item to an older patch
curl 'https://api.katforge.com/v1/realms/darkerdb/item/id.item.jester_outfit_4001?at_patch=0.16.135.8645-2663'

Response envelope

Every LIST endpoint returns the same envelope:

JSON
{
   "count": 481,
   "limit": 50,
   "sort":  "asc",
   "next":  "id.item.adventurer_cloak_4001",
   "rows":  [ { /* row */ }, ... ]
}

Single-row GET returns the row directly at body.

Facets

GET /facets[?locale=en] returns a slug → label map for every enum-typed column across all projection tables, in one payload. Cache it client-side for the session; refetch on locale change.

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

Single-row reads (generic)

text
GET /{type}/{id}                              current state
GET /{type}/{id}?at_patch=<version>           historical (on-demand reprojection)

Works for every projected type. The type segment of the URL must match the type segment of the id — e.g. /item/id.item.foo, not /item/id.monster.foo.

Resource tables

EndpointRows (current patch)Notes
/items~2,400Plus item attribute pivot
/monsters~400Plus monster_abilities junction
/classes2010 playable + 10 NPC arena variants
/skills~70Active class abilities
/spells~80Magic
/perks~130Passive class traits
/emotes~70Cosmetic emotes
/religions~40Deity definitions
/quests~640Everything about a quest — both list and single GET inline objectives + rewards
/merchants26NPC vendors
/shops~300Merchant inventories
/dungeons~80Game-mode definitions
/dungeon_grades~60Difficulty tiers
/dungeon_cards~10Game-mode variants
/dungeon_layouts~270Physical map layouts
/spawners~520Monster/loot/props placement
/achievements~240Wiki-surfaced achievements
/triumphs~10Adventure rank progression
/faustian_bargains~150Faustian system
/workshops2Crafting buildings
/workshop_levels~6Upgrade tiers
/workshop_upgrades~50Upgrade tracks
/workshop_services~30Craft / rebuild / enhance / scrap
/loot_drop_groups~390Loot tier 1 (monster → drop set)
/loot_drops~390Loot tier 2 (drop list)
/loot_drop_rates~2,300Loot tier 3 (rate weights)

Items

text
GET /items                        list with typed filters
GET /items/{id}                   single with full attribute hydration

Filters

NameOpDescription
rarityeqpoor / common / uncommon / rare / epic / legend / unique / artifact
item_typeeqarmor / weapon / consume / utility / misc
slot_typeeqchest / head / hands / feet / legs / ring / …
armor_typeeqcloth / leather / plate (null for non-armor)
hand_typeeqone_handed / two_handed / off_hand
gear_score_min, gear_score_maxrangeinclusive
is_droppableeqtrue / false
is_tradableeqtrue / false
archetypeeqfamily slug (e.g. id.item.jester_outfit)
id_likesubstringwildcards optional; underscores are literal

Single-item response

GET /items/{id} includes primary_attributes and secondary_attributes arrays. Each attribute entry:

JSON
{ "attribute": "effect_armor_rating", "min": 58, "max": 58, "enchanted_min": 0, "enchanted_max": 0 }

The attribute field is the snake-cased game property type (mechanically derived from Id_ItemPropertyType_*; new types in future patches arrive without code changes).

Monsters

Filters

NameOpDescription
class_typeeqnormal_mobs / sub_boss / boss / …
grade_typeeqcommon / elite / nightmare / …
hp_min, hp_maxrangefilter on max_health
archetypeeqfamily slug
id_likesubstring

Single-monster response

Includes attributes (str/dex/agi/will/knw/rsc/vig base values), damage_reductions per element, and an abilities array of id.monster_ability.* refs.

Classes

NameOpDescription
is_playableeqtrue for the 10 player-selectable classes; false for the 10 Master Arena NPC variants (grand_master_*).

Each class lists perk_ids and emote_ids (text arrays).

Skills · Spells · Perks · Emotes

Same shape per type. Each row carries name, icon, refs to abilities/effects (text[]), and (where applicable) allowed_classes. Filterable via id_like and archetype only.

Religions

Includes description, subtitle, display_order, offering_cost, offering_lv_xp (the per-level XP requirement array).

Quests

The quest endpoint is consolidated: both list and single-GET inline the resolved objectives and rewards. Quest contents, rewards, and chapters are no longer separate public endpoints — everything you need to render a quest is on the quest response.

NameOpDescription
namesubstringmatches the localized title (case-insensitive)
is_repeatableeq
is_dailyeq
chapter_ideqid.quest.* (prerequisite quest)
rank_min, rank_maxrangeadventure rank gate
id_likesubstringmatches the canonical id

Response shape (list + single GET are identical per row)

JSON
{
   "id":          "id.quest.alchemist_05",
   "title":       "Beasts of the Dark Forest",
   "description": "...",
   "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 }
         ]
      }
   ],
   "is_repeatable": false,
   "is_daily":      false
}

Reward entry types

The rewards[*].entries[] array contains one entry per grant. Each entry has type + count, plus a type-specific reference field where applicable:

TypeReference fieldExample
itemitem_idgrants N copies of a specific item
experience(none)XP grant
adventure_points(none)AP grant
randomrandom_reward_idrolls from a random pool
affinitymerchant_id (optional)merchant affinity
item_skinitem_skin_idcosmetic
character_skincharacter_skin_idcosmetic
armor_skinarmor_skin_idcosmetic
nameplate_skinnameplate_skin_idcosmetic
actionaction_skin_idcosmetic action
emoteemote_idunlocks emote
stash / none(none)placeholder rows

Type slugs are mechanically derived from EDCRewardType::* (with Expexperience and Advadventure_points for readability). New types in future patches appear automatically.

Objective shape

The objectives[] array contains one entry per Id_QuestContent_* ref the quest carries. Each entry:

JSON
{
   "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":     []
}

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

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

Merchants

NameOpDescription
religion_ideqfilter by deity affinity

Carries shop_id, religion_id, quest_chapter_ids[].

Shops

Carries stock (jsonb array). Shop documents have many sub-shapes upstream (ItemSkinShop, EmoteShop, etc.); all are unified into this table.

Dungeons

Carries grade_ids[], layout_ids[], card_ids[], adventure_rank_min.

Dungeon grades

NameOpDescription
tiereqdifficulty tier integer

Dungeon cards

NameOpDescription
modeeqgame mode slug
party_size_min, party_size_maxrange

Dungeon layouts

Physical map layouts (what used to be called "maps"). Carries floor (1-based for multi-floor dungeons).

Spawners

NameOpDescription
spawner_typeeqmonster / treasure / props / empty
monster_ideqfilter to spawners of a specific monster
loot_drop_group_ideqfilter to spawners pointing at a loot group

The 516 spawner rows are evenly split between monster (~155), treasure (~225), and props (~135). Each row references at most one of monster_id, loot_drop_group_id, or implicit props via the full payload.

Achievements

NameOpDescription
categoryeqderived from id prefix — arena, class, community, coreplay, event, feat, highroller, item, …

Carries the full game props at payload.

Triumphs

Adventure rank progression. tier + xp_requirement per row.

NameOpDescription
tier_min, tier_maxrange

Faustian bargains

NameOpDescription
tiereq

Each row carries the full bargain definition at payload. Companion docs (FaustianBargainAbility, Effect, Skill, Override) are resolved into the payload at projection time.

Workshops

The buildings themselves. 2 rows.

Workshop levels

Per-workshop tier definitions with tier and xp_requirement.

Workshop upgrades

Upgrade requirements. 54 rows.

Workshop services

The crafting recipes — four sub-types unified.

NameOpDescription
service_typeeqcraft / rebuild / enhance / scrap

Loot drop groups

Header table that ties monsters/treasures to drop sets. entries[] is a jsonb array of (loot_drop_id, loot_drop_rate_id, count, dungeon_grade) tuples.

Loot drops

The actual drop list. items[] is a jsonb array of {item_id, count, luck_grade} entries.

Loot drop rates

The weight tables. params[] is the (drop_rate, luck_grade) array used when drawing from a loot drop.

Refresh model

After a codex:import run (which hearth codex update dark-and-darker invokes), the projection auto-refreshes:

  1. codex.assets walked → codex.asset_index rebuilt
  2. All registered projectors run in dependency order
  3. Tables become consistent before the import command returns

The pipeline is idempotent — re-running the same patch is a no-op. Re-projection alone (without re-importing codex) is bin/console darkerdb:project inside the api container.

Historical reads

text
GET /{type}/{id}?at_patch=<version>

Triggers an on-demand reprojection of the single document at the given patch — reads from codex.documents_at(<version>) and runs the projector kernel against that raw payload without touching the live projection tables.

Aggregate historical (?at_patch=<X> on LIST endpoints) is not supported by design — returns 400. The codex retains the raw history for ad-hoc queries via /v1/codex/{game}/documents/{id}?at_patch=….

Conventions on booleans, nulls, and missing data

  • All boolean columns use the is_* prefix (is_playable, is_droppable, is_tradable, is_repeatable, is_daily).
  • Defaults match the common case (true for the permissive flags; explicit false only for the exception rows).
  • Missing/unknown enum values come back as null. The wire never carries empty strings for enum-typed columns.
  • Asset references resolve to null when no entry exists in codex.asset_index for that family.
  • Locale keys resolve to null when the key isn't in the locales table for the requested locale and English fallback also misses.