Skip to content
xivdyetools.app ↗

Responses ​

All /v1 JSON responses use a consistent envelope regardless of endpoint. The one /v1 route that is not JSON is GET /v1/chara/icon/:iconId, which returns a PNG; /health (outside /v1) returns a bare { status, timestamp } object.

Success ​

json
{
  "success": true,
  "data": { ... },
  "meta": {
    "requestId": "550e8400-e29b-41d4-a716-446655440000",
    "apiVersion": "v1",
    "locale": "ja"
  }
}
FieldTypeDescription
successtrueAlways true on 2xx responses
dataobject | arrayThe response payload
meta.requestIdstringUUID — echo this when reporting issues
meta.apiVersionstring"v1"
meta.localestring?Effective locale — present only when a non-English locale was requested (omitted for en)

Errors ​

json
{
  "success": false,
  "error": "INVALID_HEX",
  "message": "Invalid hex color format. Expected #RRGGBB or RRGGBB.",
  "details": {
    "parameter": "hex",
    "received": "not-a-color",
    "expected": "Hex color string matching /^#?[0-9A-Fa-f]{6}$/"
  },
  "meta": {
    "requestId": "550e8400-e29b-41d4-a716-446655440000",
    "apiVersion": "v1",
    "locale": "ja"
  }
}
FieldTypeDescription
successfalseAlways false on 4xx/5xx responses
errorstringMachine-readable error code — safe to switch on
messagestringHuman-readable description
detailsobject?Additional context (which param, what was received)
meta.localestring?Same rule as a success response's meta.locale — present only for a non-English ?locale= on a /v1/* route, omitted otherwise (including en, and every route outside /v1/*)

See the Error Reference for the full code catalog.

Paginated Lists ​

Endpoints returning arrays include a pagination field alongside data:

json
{
  "success": true,
  "data": [ ... ],
  "pagination": {
    "page": 2,
    "perPage": 50,
    "total": 125,
    "totalPages": 3,
    "hasNext": true,
    "hasPrev": true
  },
  "meta": { ... }
}

Control pagination with ?page= and ?perPage= (max 200). Non-paginated endpoints (/search, /categories, /match/*) return all results in data without a pagination field.

Response Headers ​

Every /v1 response includes these headers:

HeaderExampleDescription
X-Request-ID550e8400-…Unique ID — matches meta.requestId in body
X-API-Versionv1API version
X-RateLimit-Limit65Requests allowed per window (60 + 5 burst)
X-RateLimit-Remaining64Headroom flag (64 while allowed, 0 when refused) — see Rate Limits
X-RateLimit-Reset1702684860Unix timestamp when the window resets
Cache-Controlpublic, max-age=3600, s-maxage=86400Caching directives
Access-Control-Allow-Origin*Open CORS — callable from any origin

Caching ​

Dye data is deterministic and changes only with FFXIV patches, so aggressive caching is safe:

Endpoint groupCache-Control
/v1/dyes/*public, max-age=3600, s-maxage=86400
/v1/match/*public, max-age=3600, s-maxage=86400
POST /v1/chara/resolveno-store on the envelope — each (slot, key) is cached ~7 days behind it; X-Cache: HIT / MISS
/v1/chara/icon/:iconIdpublic, max-age=2592000, immutable + X-Cache

The Age header (set by Cloudflare) tells you how old the cached response is. A fresh cache hit means sub-millisecond response time at the nearest PoP. Note that X-Cache and Age are not in the CORS Access-Control-Expose-Headers list, so browser code cannot read them — they are for curl, servers, plugins and bots; the headers a browser can read are the ones in the table above.

Compression ​

Cloudflare automatically negotiates Brotli or gzip based on your Accept-Encoding header. No configuration needed.

FINAL FANTASY XIV © 2010-2026 SQUARE ENIX CO., LTD. All Rights Reserved.
XIV Dye Tools is a fan-made application and is not affiliated with or endorsed by Square Enix. · Discord ↗