Citadel game data API

Items, hulls, ciphers, artifacts, NPCs, stats and art for the live build of Citadel, plus the game's own fit evaluator. Free for the community. No key needed.

Start

The API always describes the build players are on. When the game ships, the API updates itself. Every response carries X-Citadel-Build, the commit of that build, and /v1/status reports it.

RouteReturns
GET /v1/statusThe build, when it went live, and counts.
GET /v1/items, /v1/items/{id}Modules and conduits players can obtain.
GET /v1/hulls, /v1/hulls/{id}Hulls with their fitting grids.
GET /v1/ciphers, /v1/artifacts, /v1/regalia, /v1/regalia-sets, /v1/heretical-specials, /v1/harmonic-sets, /v1/affinityWhat an item roll can reference.
GET /v1/modifier-pools, /v1/qualities, /v1/placement-rulesThe roll and placement rules.
GET /v1/npcsNPC ships, bosses and their weapons.
GET /v1/statsEvery stat type, worded as the game words it.
GET /v1/art/{file}WebP art. File names come from the art objects.
POST /v1/fits/evaluate, /v1/fits/import, /v1/fits/export, /v1/items/factsTools that run the game's simulation on a fit.

Ids are the game's asset GUIDs as decimal strings and stay the same across builds. Fixed-point values are raw decimal strings: divide by 65536. Errors are { "error": { "code", "message" } } with status 400, 404, 405, 413, 415, 429, 500 or 503.

Rules

User-Agent

Send a User-Agent that names your app and a contact, for example MyFitTool/1.0 (me@example.com). A missing or generic one, such as curl/8.4.0 or python-requests/2.31, is still served, with a Warning header telling you to fix it. Blocking such callers stays a later decision.

Rate limits

Each client has a bucket of 60 requests that refills at 10 a second. Art has a bucket of its own (600, refilling at 100 a second), and sde.zip has one too (5, refilling one every 6 seconds). Requests whose body fails validation still count. A 429 carries Retry-After, in seconds. Request bodies are capped at 256 KB.

Caching

Every response has an ETag and a Cache-Control. Send the tag back in If-None-Match and expect a 304 until the data changes. Art files are named by a hash of their bytes and are served immutable for a year. CORS is open to every origin, so browsers can call the API directly.

Examples

curl

curl -H 'User-Agent: MyFitTool/1.0 (me@example.com)' https://api.thecitadel.com/v1/status

# Conditional request: 304 with no body while the data is unchanged
curl -i -H 'User-Agent: MyFitTool/1.0 (me@example.com)' \
     -H 'If-None-Match: "<etag from the last response>"' \
     https://api.thecitadel.com/v1/hulls

curl -H 'User-Agent: MyFitTool/1.0 (me@example.com)' \
     -H 'Content-Type: application/json' \
     -d '{ "fitString": "<a fit string from the game>" }' \
     https://api.thecitadel.com/v1/fits/import

JavaScript

Browsers set their own User-Agent and cannot change it; the API serves them without a warning. Server-side code should set one.

const api = "https://api.thecitadel.com";

const { build, hulls } = await fetch(`${api}/v1/hulls`).then((r) => r.json());
console.log(build, hulls.length);

// Art: every item and hull has an `art` object of API-relative URLs.
const icon = `${api}${hulls[0].art.base}`;

// Evaluate it with the game's own simulation.
const imported = await fetch(`${api}/v1/fits/import`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ fitString }),
}).then((r) => r.json());

const result = await fetch(`${api}/v1/fits/evaluate`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ fit: imported.fit }),
}).then((r) => r.json());
console.log(result.valid, result.rating);

Bulk download

If you need everything, download one file instead of crawling.

Download sde.zip

It holds one JSON file per static route, the art under art/, and a manifest.json with the build and a SHA-256 for every other file. The JSON files are byte for byte what the routes serve. It changes only when the data does, so use If-None-Match here too.

Stability policy

Terms Draft

Short version: community and non-commercial use of the data and art, credit "Data from the Citadel game data API", do not imply we endorse your tool, and the service may change or stop. The full text is a draft that the owner has not yet approved: read the draft terms.

Reference

Generated from /v1/openapi.json, the OpenAPI 3.1 document.