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_contentson a quest).?locale=en|ko|ja|...— resolveText_*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.
| Version | Base URL | Status |
|---|---|---|
| v2 | https://api.darkerdb.com/v2 | active |
| v1 | https://api.darkerdb.com/v1 | legacy — 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
| Name | Type | Default | Description |
|---|---|---|---|
branch | string | main | git-style branch label. Most consumers want main. |
version | string | — | exact-match filter on version (list endpoint only) |
parent_patch_id | int | — | list patches whose parent is this id |
page | int | 1 | 1-indexed |
limit | int | 25 | max 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
versionstring orversion_raw(e.g.0.16.138.8712-2711) - the literal
current— resolves to the most-recently-imported row on the requestedbranch
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
- Hit
/v2/patchesto enumerate what's imported. - Pick a
version(orid) from the response. - 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:
| Name | Mode | Description |
|---|---|---|
id | exact | id.item.<slug> or raw Id_Item_<Name> |
archetype | exact | e.g. helmetbarbuta, swordoldlong |
name | substring | matches anywhere in the display name |
rarity | exact | poor, common, uncommon, rare, epic, legendary, unique, artifact |
type | exact | top-level item category |
armor_type | exact | for armor pieces |
hand_type | exact | for weapons |
misc_type | exact | for utility / misc |
slot_type | exact | equipment slot |
utility_type | exact | for utility items |
required_class | exact | class gate |
Range filters (integer):
| Name | Description |
|---|---|
min_required_knowledge, max_required_knowledge | knowledge gate |
min_gear_score, max_gear_score | gear score |
min_vendor_price, max_vendor_price | base merchant price |
min_adventure_points, max_adventure_points | AP yield |
min_experience, max_experience | XP yield |
min_max_stack_size, max_max_stack_size | stack cap |
min_primary_attributes, max_primary_attributes | primary attribute slot count |
min_secondary_attributes, max_secondary_attributes | secondary attribute slot count |
Behavior toggles:
| Name | Type | Description |
|---|---|---|
has_effect | bool | item has an on-use effect (true/false) |
patch | id|version|current | replay 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.
| Name | Description |
|---|---|
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). |
quantity | Stack size; rendered as ×N next to the name when greater than 1. |
found_by | Character who looted this instance. |
loot_state | Looted, Identified, Handled, … |
class | Class currently equipping the item. |
is_tradeable | true / false — toggles Tradeable / Bound footer. |
amount | Gold value for coin-pouch items. |
at_patch | Patch version to read the item template at. |
branch | Codex 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
| Name | Mode | Description |
|---|---|---|
id | exact | id.monster.<slug> or raw upstream key |
name | substring | matches the display name |
family | exact | e.g. beast, undead, demon |
tier | exact | standard, elite, boss, ... |
loot_table_id | exact | id.loot_table.<slug> |
min_hp, max_hp | range | hp |
min_damage_min, max_damage_min | range | minimum damage |
min_damage_max, max_damage_max | range | maximum damage |
has_model | bool | only monsters with (or without) a registered model |
has_portrait | bool | only monsters with (or without) a portrait |
patch | id|version|current | replay against a different patch |
page, limit | pagination | default 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
| Name | Mode | Description |
|---|---|---|
id | exact | id.player_character.<slug> or raw Id_PlayerCharacter_<Name> |
name | substring | matches the display name |
can_use | bool | only currently-playable classes |
is_default | bool | only default-roster classes |
at_patch | version | render this entity at the named patch's version |
branch | string | branch (default main) |
page, limit | pagination | default 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
| Name | Mode | Description |
|---|---|---|
id | exact | id.perk.<slug> or raw Id_Perk_<Name> |
name | substring | matches the display name |
can_use | bool | only currently-equippable perks |
class | tag | filter by owning class tag (e.g. Fighter, Wizard) |
at_patch | version | render this entity at the named patch's version |
branch | string | branch (default main) |
page, limit | pagination | default 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
| Name | Mode | Description |
|---|---|---|
id | exact | id.spell.<slug> or raw Id_Spell_<Name> |
name | substring | matches the display name |
source_type | exact | element / source (Type.Spell.Source.Ice, ...) |
casting_type | exact | Type.Spell.Cast.Casting, Type.Spell.Cast.Instant, ... |
cost_type | exact | Type.Spell.Cost.Cooldown, Type.Spell.Cost.SpellPoints, ... |
class | tag | filter by owning class tag |
min_range, max_range | range | range in cm |
at_patch | version | render this entity at the named patch's version |
branch | string | branch (default main) |
page, limit | pagination | default 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
| Name | Mode | Description |
|---|---|---|
id | exact | id.skill.<slug> or raw Id_Skill_<Name> |
name | substring | matches the display name |
skill_type | exact | Type.Skill.Instant, Type.Skill.Channel, ... |
can_use | bool | only currently-equippable skills |
class | tag | filter by owning class tag (Fighter, Barbarian, ...) |
at_patch | version | time-travel |
branch | string | default main |
page, limit | pagination | default 25, max 250 |
?embed= expansion
Pass a comma-separated list to expand id-ref fields into full entity rows:
classes→player_characterrowsabilities→skill_abilityrows
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
| Name | Mode | Description |
|---|---|---|
id | exact | id.emote.<slug> |
name | substring | matches the display name |
at_patch | version | time-travel |
branch | string | default main |
page, limit | pagination | default 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
| Name | Mode | Description |
|---|---|---|
id | exact | id.religion.<slug> |
name | substring | matches the display name |
min_order, max_order | range | filter by order index |
at_patch | version | time-travel |
branch | string | default main |
page, limit | pagination | default 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
| Name | Mode | Description |
|---|---|---|
id_like | substring | id substring; _ is literal |
archetype | exact | family slug, e.g. id.quest.alchemist_01 |
chapter_id | exact | id.quest.<prereq> (the chain pointer) |
is_repeatable | bool | true / false |
is_daily | bool | |
rank_min, rank_max | range | adventure rank gate |
sort | enum | asc (default) / desc |
limit | int | default 50, max 200 |
cursor | string | from prior response's next |
locale | string | en (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 resolvedquest_contentrows withcontent_type,content_count,target_kind,target_archetyperewards[]— fully resolvedrewardrows with theirentries[]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:
| Type | Reference field | Example |
|---|---|---|
item | item_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} |
random | random_reward_id | {type:"random", count:1, random_reward_id:"id.random_reward.x"} |
affinity | merchant_id (optional) | {type:"affinity", count:50, merchant_id:"id.merchant.x"} |
item_skin | item_skin_id | |
character_skin | character_skin_id | |
armor_skin | armor_skin_id | |
nameplate_skin | nameplate_skin_id | |
action | action_skin_id | |
emote | emote_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
| Name | Op | Description |
|---|---|---|
category | exact | arena, class, community, coreplay, event, feat, highroller, item |
id_like | substring | id substring search |
archetype | exact | family slug |
sort | enum | asc (default) or desc |
limit | int | default 50, max 200 |
cursor | string | from 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
| Name | Op | Description |
|---|---|---|
tier_min, tier_max | range | inclusive tier integer |
id_like | substring | |
sort | enum | asc / desc |
limit, cursor | standard |
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
| Name | Mode | Description |
|---|---|---|
id | exact | id.dungeon.<slug> or raw Id_Dungeon_<Name> |
name | substring | matches the display name |
min_floor, max_floor | range | filter by floor number |
at_patch | version | time-travel |
branch | string | default main |
page, limit | pagination | default 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
| Name | Op | Description |
|---|---|---|
tier | exact | difficulty tier integer |
id_like | substring | |
sort, limit, cursor | standard |
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
| Name | Op | Description |
|---|---|---|
mode | exact | game mode slug |
party_size_min, party_size_max | range | |
id_like | substring |
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
| Name | Op | Description |
|---|---|---|
id_like | substring | |
archetype | exact | family 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
| Name | Op | Description |
|---|---|---|
spawner_type | exact | monster / treasure / props / empty |
monster_id | exact | id.monster.* — find all spawners that spawn a given monster |
loot_drop_group_id | exact | id.loot_drop_group.* — reverse lookup for loot tables |
id_like | substring | |
sort, limit, cursor | standard |
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:
| Name | Mode | Description |
|---|---|---|
id | exact | id.loot_table.<slug> or raw LT_<Name> |
name | substring | matches the display name |
type | exact | monster, chest, merchant, ... |
patch | id|version|current | replay against a different patch |
page, limit | pagination | default 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:
| Name | Mode | Description |
|---|---|---|
id | exact | id.spawner.<slug> |
map_id | exact | id.map.<slug> |
spawner_type | exact | loot, monster, ... |
loot_table_id | exact | id.loot_table.<slug> |
monster_id | exact | id.monster.<slug> |
patch | id|version|current | replay against a different patch |
page, limit | pagination | default 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:
| Table | What |
|---|---|
workshops | The buildings (2 rows). |
workshop_levels | Per-workshop tier definitions with tier + xp_requirement. |
workshop_upgrades | Upgrade tracks (~50 rows). |
workshop_services | The 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
| Name | Op | Description |
|---|---|---|
service_type | exact | craft / rebuild / enhance / scrap |
id_like | substring | |
sort, limit, cursor | standard |
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
| Name | Op | Description |
|---|---|---|
tier | exact | bargain tier integer |
id_like | substring | |
archetype | exact | family slug |
sort, limit, cursor | standard |
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
| Name | Mode | Description |
|---|---|---|
id | exact | id.merchant.<slug> or raw Id_Merchant_<Name> |
name | substring | matches the display name |
at_patch | version | render this entity at the named patch's version |
branch | string | branch (default main) |
page, limit | pagination | default 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
| Name | Mode | Description |
|---|---|---|
id | exact | id.shop.<slug> |
name | substring | matches the display name |
sales_channel | exact | EDCShopSalesChannelType::Shop, ::Exclusive, ... |
is_on_sale | bool | only entries with an active sale flag |
min_cash_price, max_cash_price | range | filter by listed cash price |
at_patch | version | time-travel |
branch | string | default main |
page, limit | pagination | default 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:
| Name | Mode | Description |
|---|---|---|
id | exact | surrogate listing id (integer) |
item_id | exact | id.item.<slug> |
item | exact | raw upstream item key |
archetype | exact | item archetype slug |
rarity | exact | listing rarity |
loot_state | exact | game-side loot state at observation time |
seller | exact | seller display name |
Price / quantity ranges:
| Name | Description |
|---|---|
min_price, max_price | total listing price |
min_price_per_unit, max_price_per_unit | normalized per-unit price |
min_quantity, max_quantity | stack size |
Time window / status:
| Name | Description |
|---|---|
from, to | ISO 8601 — restrict by created_at |
has_sold, has_expired, has_cancelled | bool — 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:
| Name | Type | Default | Description |
|---|---|---|---|
interval | enum | 15m | bucket size — see below |
from, to | ISO 8601 | last 7d | window 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
| Name | Mode | Description |
|---|---|---|
sender | exact | account display name |
sender_class | exact | character class at message time |
sender_rank | exact | rank tier at message time |
contains | substring | full-text-ish search on the message body |
from, to | ISO 8601 | restrict by message timestamp |
page, limit | pagination | default 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 periodpage— 1-indexed, default1page_size— default100, max1000
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
| Name | Mode | Description |
|---|---|---|
id | exact | id.leaderboard_rank.<slug> (e.g. apprentice_i) |
name | substring | matches the display name |
group | exact | id.leaderboard_rank_group.<slug> (e.g. apprentice) |
min_sub_grade, max_sub_grade | range | sub-grade index within the group (1..N) |
at_patch | version | render this entity at the named patch's version |
branch | string | branch (default main) |
page, limit | pagination | default 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
| Tier | Per-minute | Per-day | Notes |
|---|---|---|---|
| anonymous | 60 | 10 000 | shared per IP |
| free | 60 | 10 000 | per API key |
| pro | 600 | 1 000 000 | per API key |
| unlimited | — | — | internal/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
| v1 | v2 | Notes |
|---|---|---|
/v1/items | /v2/items | richer data, full filter set |
/v1/items/{id} | /v2/items/{id} | canonical id format (id.item.<slug> or upstream Id_Item_<Name>) |
/v1/population | /v2/population | unchanged shape |
/v1/population/history | /v2/population/history | requires from/to ISO 8601 |
/v1/market | /v2/market | new pagination cursor |
/v1/leaderboards | /v2/leaderboards | unchanged |
/v1/trades/chat | /v2/trades/chat | unchanged |
/v1/characters | /v2/characters | unchanged |
/v1/users/@me/keys | /v2/users/@me/keys | now 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.