Atlas CDN

Minecraft Modpack Item Image & Metadata API

https://atlas.playcdu.co

📌 Every Image Response Includes X-Display-Name

Every image served by Atlas CDN attaches an X-Display-Name response header containing the URL-encoded human-readable name of the item (e.g. Diamond%20Sword). This applies to direct /img/ requests and any request that redirects to an image (/search/, /random).

Decode it with decodeURIComponent(header) in JS or urllib.parse.unquote(header) in Python. The header is also listed in CORS Access-Control-Expose-Headers so it's accessible from browser fetch() calls.

Get Item Image

GET /img/{modpack}/{namespace}/{itemName}/{variant?}

Retrieve an item's PNG image directly. The variant is optional and defaults to 0.

ParameterRequiredDescription
modpackYesModpack / cluster ID, e.g. atm9
namespaceYesItem namespace, e.g. minecraft
itemNameYesItem name, e.g. diamond_sword
variantNoVariant number. Defaults to 0

Example

GET /img/atm9/minecraft/diamond_sword/0

Response

HeaderValue
Content-Typeimage/png
X-Display-NameURL-encoded display name, e.g. Diamond%20Sword
X-Recipe-CountRecipes that produce this item. Always present, 0 when there are none
X-Recipes-UrlPath to the full recipe document. Always present
X-RecipesURL-encoded compact recipe JSON, when small enough to inline
X-Recipes-Truncatedtrue when the recipes were too large to inline — follow X-Recipes-Url
ETagEntity tag for caching
The X-Display-Name header is present on every image response. Use it to show the item's human-readable name without an extra API call. The recipe headers come from metadata already attached to the image object, so they cost no extra request and no extra latency.

Search by Item ID

GET /search/first/{namespace}/{itemName}

Search across all modpacks for an item and redirect to the first match's image.

ParameterRequiredDescription
namespaceYesItem namespace, e.g. minecraft
itemNameYesItem name, e.g. diamond_pickaxe

Example

GET /search/first/minecraft/diamond_pickaxe
# → 302 redirect to /img/atm9/minecraft/diamond_pickaxe/0
Great for when you don't know which modpack contains an item. The browser / HTTP client follows the redirect automatically. The final image response includes the X-Display-Name header.

Search by Display Name

GET /search/display/{query}

Search by human-readable display name and redirect to the first match's image. The query is URL-decoded automatically.

ParameterRequiredDescription
queryYesDisplay name to search for, e.g. Diamond%20Sword

Example

GET /search/display/Diamond%20Sword
# → 302 redirect to the first matching item image

Following the redirect returns the image with the X-Display-Name header.

Random Item

GET /random

Get a random item image from any modpack. Redirects to the image URL. The final response includes the X-Display-Name header.

Example

GET /random
# → 302 redirect to a random item image

Random Minecraft Item

GET /random/minecraft

Get a random vanilla Minecraft item image. Redirects to the image URL. The final response includes the X-Display-Name header.

Example

GET /random/minecraft
# → 302 redirect to a random minecraft item image

Get Modpack Manifest

GET /{modpack}/manifest

Retrieve the complete item manifest for a modpack. Returns a JSON array of every item.

ParameterRequiredDescription
modpackYesModpack ID, e.g. atm9

Example

GET /atm9/manifest

Response body

[
  { "name": "minecraft:diamond_sword", "display_name": "Diamond Sword", "variant": 0 },
  { "name": "minecraft:diamond_pickaxe", "display_name": "Diamond Pickaxe", "variant": 0 }
]

Get Item Metadata

GET /{modpack}/meta/{namespace}/{itemName}/{variant?}

Retrieve JSON metadata for a specific item.

ParameterRequiredDescription
modpackYesModpack ID
namespaceYesItem namespace
itemNameYesItem name
variantNoVariant number (defaults to 0)

Example

GET /atm9/meta/minecraft/diamond_sword/0

Get Item Recipes

GET /{modpack}/recipes/{namespace}/{itemName}

Every recipe that produces an item, in full. This is what X-Recipes-Url points at, and it never truncates. Recipes belong to the item rather than a variant, so there is no variant segment.

ParameterRequiredDescription
modpackYesModpack ID
namespaceYesItem namespace
itemNameYesItem name

Example

GET /atm9/recipes/minecraft/diamond_sword

Response body

{
  "item": "minecraft:diamond_sword",
  "count": 2,
  "recipes": [ { "type": "minecraft:crafting_shaped", ... } ]
}
Recipe documents are stored and returned verbatim. Atlas does not interpret them. 404 when the item has no recipes on file.

Upload Item AUTH

POST /api/{modpack}/upload

Upload an item image and metadata to a modpack. Requires Bearer token authentication.

FieldTypeRequiredDescription
itemNamestringYesItem ID, e.g. minecraft:diamond_sword
displayNamestringYesHuman-readable name, e.g. Diamond Sword
variantstringYesVariant number, e.g. 0
imagefileYesPNG image file
metafileYesJSON metadata file
namespacestringNoNamespace (if not in itemName)

cURL example

curl -X POST https://atlas.playcdu.co/api/atm9/upload \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "itemName=minecraft:diamond_sword" \
  -F "displayName=Diamond Sword" \
  -F "variant=0" \
  -F "image=@diamond_sword.png" \
  -F "meta=@meta.json"

Success response

{ "success": true, "message": "Successfully uploaded minecraft:diamond_sword v0" }

Check What Changed AUTH

POST /api/{modpack}/upload/check

Ask which items Atlas does not already hold at the same content hash. Uploading a modpack that has not changed then costs almost nothing — everything the answer leaves out can be sent without its image.

Request body

{
  "items": [
    { "item": "minecraft:diamond_sword", "variant": 0, "sha256": "8e4ae8c5...4150" }
  ]
}

Response

{ "needsUpload": ["minecraft:diamond_pickaxe#0"] }
FieldRequiredDescription
itemYesItem identifier, namespace:name
variantNoVariant number. Defaults to 0
sha256NoLowercase hex SHA-256 of the image content. An item with no usable hash always needs upload
Up to 10,000 items per request, answered in a single database query. A gzipped request body is accepted and decompressed automatically. The endpoint fails safe: if the lookup cannot run, every key comes back as needing upload.

Batch Upload AUTH

POST /api/{modpack}/upload/batch

Upload many item variants, their metadata and their recipes in one request. Writes the database rows in a single batched transaction and writes each item's meta.json once rather than once per variant.

PartRequiredDescription
indexYesJSON document describing every item in the batch
img0, img1, …NoThe PNG bodies, named by each item's image field

index

{
  "items": [
    { "item": "minecraft:diamond_pickaxe", "variant": 0,
      "displayName": "Diamond Pickaxe", "meta": { }, "sha256": "886b188c...ba04",
      "recipeCount": 1, "recipes": [ ], "image": "img0" },
    // no "image": a metadata-only update of an image Atlas already holds
    { "item": "minecraft:diamond_sword", "variant": 0,
      "displayName": "Diamond Sword", "meta": { }, "sha256": "8e4ae8c5...4150" }
  ]
}

cURL example

curl -X POST https://atlas.playcdu.co/api/atm9/upload/batch \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "index=@index.json;type=application/json" \
  -F "img0=@diamond_pickaxe.png;type=image/png"

Response

{ "accepted": 61, "skipped": 2, "failures": { "mod:ghost#0": "failed to store image: ..." } }
FieldMeaning
acceptedStored or updated
skippedImage already stored at the same content hash, so its bytes were not rewritten. Row, metadata and recipes were still updated
failuresPer-item reasons, keyed {item}#{variant}. An entry with no usable item id is keyed index[N]
One bad item never fails the batch — everything else in the request still commits. Every write is an upsert, so retrying a failed item is safe. Up to 256 items per request.

Update Recipes AUTH

POST /api/{modpack}/recipes

Replace the stored recipes for items without re-sending their images. Use this when recipes changed but the rendered items did not.

cURL example

curl -X POST https://atlas.playcdu.co/api/atm9/recipes \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"item":"minecraft:diamond_pickaxe","recipes":[ ... ]}]}'

Response

{ "updated": 1, "cleared": 0, "failures": { } }
An empty recipes array deletes the item's stored recipes; omitting the field never deletes anything. Entries naming the same item are merged and deduplicated. Up to 256 items per request.
This updates /{modpack}/recipes/… immediately. It does not rewrite image objects, so X-Recipe-Count and X-Recipes keep reporting the recipes supplied when each image was last stored and refresh on the next image upload. X-Recipes-Url is always current.

Publish Modpack AUTH

POST /api/{modpack}/publish

Generate and publish the manifest for a modpack. Run this after uploading items to make them discoverable.

cURL example

curl -X POST https://atlas.playcdu.co/api/atm9/publish \
  -H "Authorization: Bearer YOUR_API_KEY"

Success response

{ "success": true, "message": "Successfully published manifest for atm9 with 15234 items." }

Modpack Status AUTH

GET /api/{modpack}/status

Check a modpack's status including last update timestamp.

cURL example

curl https://atlas.playcdu.co/api/atm9/status \
  -H "Authorization: Bearer YOUR_API_KEY"

Response

{ "name": "atm9", "last_updated": "2025-01-15 10:30:45" }

Authentication

All /api/* endpoints require a Bearer token in the Authorization header.

Authorization: Bearer YOUR_API_KEY
Public endpoints (/img, /search, /random, manifests, metadata) require no authentication.

Error Handling

All errors return JSON in a consistent format:

{
  "error": "Short error description",
  "message": "Detailed info (optional)"
}

Status Codes

CodeMeaningCommon Cause
200OKRequest succeeded
302RedirectSearch endpoints redirect to image URL
400Bad RequestInvalid modpack name or missing fields
401UnauthorizedMissing or invalid API key
404Not FoundImage, manifest, or modpack doesn't exist
429Too Many RequestsRate limit exceeded
500Server ErrorInternal processing failure

CORS

All endpoints are fully CORS-enabled:

HeaderValue
Allowed Origins* (all origins)
Exposed HeadersX-Display-Name, X-Recipe-Count, X-Recipes-Url, X-Recipes, X-Recipes-Truncated

Response Headers

HeaderPresent OnDescription
X-Display-NameEvery image responseURL-encoded human-readable item name, e.g. Diamond%20Sword
X-Recipe-CountEvery image responseNumber of recipes that produce the item; 0 when there are none
X-Recipes-UrlEvery image responsePath to the full recipe document, e.g. /atm9/recipes/minecraft/diamond_sword
X-RecipesWhen small enough to inlineURL-encoded compact JSON array of the item's recipes
X-Recipes-TruncatedWhen X-Recipes is absenttrue: the recipes exist but were too large to inline. Follow X-Recipes-Url
ETagImages, manifests, metadata, recipesEntity tag for HTTP caching / conditional requests
Content-TypeAll responsesimage/png for images, application/json for data

Using X-Display-Name

Every image served by Atlas includes X-Display-Name — this is the easiest way to get an item's readable name alongside its image in a single request.

JavaScript

const r = await fetch(imageUrl);
const raw = r.headers.get("X-Display-Name");
const name = decodeURIComponent(raw || "");
// "Diamond Sword"

Python

from urllib.parse import unquote
r = requests.get(image_url)
name = unquote(r.headers["X-Display-Name"])
# "Diamond Sword"
This header is exposed via CORS (Access-Control-Expose-Headers), so it's readable from browser fetch() and XMLHttpRequest calls on any origin.

Using the Recipe Headers

Most items carry their recipes in the image response, so a tooltip needs one request. Items with a lot of recipes fall back to the pointer.

const res = await fetch(imageUrl);
const count = Number(res.headers.get("X-Recipe-Count") || "0");

let recipes = [];
if (count > 0) {
    const inline = res.headers.get("X-Recipes");
    recipes = inline
        ? JSON.parse(decodeURIComponent(inline))
        : (await (await fetch(res.headers.get("X-Recipes-Url"))).json()).recipes;
}

Quick Examples

cURL

# Fetch an image
curl https://atlas.playcdu.co/img/atm9/minecraft/diamond_sword/0 -o sword.png

# Search for an item across all modpacks
curl -L https://atlas.playcdu.co/search/first/minecraft/diamond_sword -o sword.png

# Search by display name
curl -L https://atlas.playcdu.co/search/display/Diamond%20Sword -o sword.png

# Get a random item
curl -L https://atlas.playcdu.co/random -o random.png

# Download a modpack manifest
curl https://atlas.playcdu.co/atm9/manifest -o manifest.json

JavaScript

// Direct image fetch
const res = await fetch("https://atlas.playcdu.co/img/atm9/minecraft/diamond_sword/0");
const blob = await res.blob();
const name = decodeURIComponent(res.headers.get("X-Display-Name") || "");

// Search (follows redirect automatically)
const img = await fetch("https://atlas.playcdu.co/search/first/minecraft/diamond_sword");

// Use in an <img> tag
// <img src="https://atlas.playcdu.co/img/atm9/minecraft/diamond_sword/0" />

Python

import requests
from urllib.parse import unquote

# Direct fetch
r = requests.get("https://atlas.playcdu.co/img/atm9/minecraft/diamond_sword/0")
display_name = unquote(r.headers.get("X-Display-Name", ""))

with open("sword.png", "wb") as f:
    f.write(r.content)

# Search across all modpacks
r = requests.get("https://atlas.playcdu.co/search/first/minecraft/diamond_sword")
# requests follows the 302 redirect automatically

HTML Embed

<!-- Direct image (fastest) -->
<img src="https://atlas.playcdu.co/img/atm9/minecraft/diamond_sword/0" alt="Diamond Sword" />

<!-- Search-based (auto-redirects, use when modpack is unknown) -->
<img src="https://atlas.playcdu.co/search/first/minecraft/diamond_sword" alt="Diamond Sword" />

Complete Upload Workflow

For a whole modpack, ask what changed first, then send batches:

# 1. Ask which items actually changed (up to 10,000 per call)
curl -X POST https://atlas.playcdu.co/api/atm9/upload/check \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"item":"minecraft:diamond_sword","variant":0,"sha256":"8e4ae8c5...4150"}]}'
# → {"needsUpload":[]}   nothing to send

# 2. Send batches (up to 256 items each). Items the check cleared go without an "image" field.
curl -X POST https://atlas.playcdu.co/api/atm9/upload/batch \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "index=@index.json;type=application/json" \
  -F "img0=@diamond_sword.png;type=image/png"

# 3. Publish, then verify
curl -X POST https://atlas.playcdu.co/api/atm9/publish -H "Authorization: Bearer YOUR_API_KEY"
curl -sI https://atlas.playcdu.co/img/atm9/minecraft/diamond_sword/0 | grep -i x-recipe

For a handful of items, the single-item endpoint is simpler:

# 1. Upload items (repeat for each item)
curl -X POST https://atlas.playcdu.co/api/atm9/upload \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "itemName=minecraft:diamond_sword" \
  -F "displayName=Diamond Sword" \
  -F "variant=0" \
  -F "image=@diamond_sword.png" \
  -F "meta=@meta.json"

# 2. Publish the modpack manifest (makes items discoverable)
curl -X POST https://atlas.playcdu.co/api/atm9/publish \
  -H "Authorization: Bearer YOUR_API_KEY"

# 3. Verify
curl https://atlas.playcdu.co/api/atm9/status \
  -H "Authorization: Bearer YOUR_API_KEY"

Interactive Docs & OpenAPI Spec

GET /docs

Swagger UI — interactive browser for all endpoints.

GET /docs/openapi.json

Raw OpenAPI 3.0 specification in JSON format. Useful for code generation or importing into tools like Postman.

Open /docs in your browser to explore endpoints interactively, or fetch /docs/openapi.json for the machine-readable spec.