Minecraft Modpack Item Image & Metadata API
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.
Retrieve an item's PNG image directly. The variant is optional and defaults to 0.
| Parameter | Required | Description |
|---|---|---|
modpack | Yes | Modpack / cluster ID, e.g. atm9 |
namespace | Yes | Item namespace, e.g. minecraft |
itemName | Yes | Item name, e.g. diamond_sword |
variant | No | Variant number. Defaults to 0 |
Example
GET /img/atm9/minecraft/diamond_sword/0
Response
| Header | Value |
|---|---|
Content-Type | image/png |
X-Display-Name | URL-encoded display name, e.g. Diamond%20Sword |
X-Recipe-Count | Recipes that produce this item. Always present, 0 when there are none |
X-Recipes-Url | Path to the full recipe document. Always present |
X-Recipes | URL-encoded compact recipe JSON, when small enough to inline |
X-Recipes-Truncated | true when the recipes were too large to inline — follow X-Recipes-Url |
ETag | Entity tag for caching |
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 across all modpacks for an item and redirect to the first match's image.
| Parameter | Required | Description |
|---|---|---|
namespace | Yes | Item namespace, e.g. minecraft |
itemName | Yes | Item name, e.g. diamond_pickaxe |
Example
GET /search/first/minecraft/diamond_pickaxe
# → 302 redirect to /img/atm9/minecraft/diamond_pickaxe/0
X-Display-Name header.Search by human-readable display name and redirect to the first match's image. The query is URL-decoded automatically.
| Parameter | Required | Description |
|---|---|---|
query | Yes | Display 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.
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
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
Retrieve the complete item manifest for a modpack. Returns a JSON array of every item.
| Parameter | Required | Description |
|---|---|---|
modpack | Yes | Modpack 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 }
]
Retrieve JSON metadata for a specific item.
| Parameter | Required | Description |
|---|---|---|
modpack | Yes | Modpack ID |
namespace | Yes | Item namespace |
itemName | Yes | Item name |
variant | No | Variant number (defaults to 0) |
Example
GET /atm9/meta/minecraft/diamond_sword/0
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.
| Parameter | Required | Description |
|---|---|---|
modpack | Yes | Modpack ID |
namespace | Yes | Item namespace |
itemName | Yes | Item name |
Example
GET /atm9/recipes/minecraft/diamond_sword
Response body
{
"item": "minecraft:diamond_sword",
"count": 2,
"recipes": [ { "type": "minecraft:crafting_shaped", ... } ]
}
404 when the item has no recipes on file.Upload an item image and metadata to a modpack. Requires Bearer token authentication.
| Field | Type | Required | Description |
|---|---|---|---|
itemName | string | Yes | Item ID, e.g. minecraft:diamond_sword |
displayName | string | Yes | Human-readable name, e.g. Diamond Sword |
variant | string | Yes | Variant number, e.g. 0 |
image | file | Yes | PNG image file |
meta | file | Yes | JSON metadata file |
namespace | string | No | Namespace (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" }
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"] }
| Field | Required | Description |
|---|---|---|
item | Yes | Item identifier, namespace:name |
variant | No | Variant number. Defaults to 0 |
sha256 | No | Lowercase hex SHA-256 of the image content. An item with no usable hash always needs upload |
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.
| Part | Required | Description |
|---|---|---|
index | Yes | JSON document describing every item in the batch |
img0, img1, … | No | The 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: ..." } }
| Field | Meaning |
|---|---|
accepted | Stored or updated |
skipped | Image already stored at the same content hash, so its bytes were not rewritten. Row, metadata and recipes were still updated |
failures | Per-item reasons, keyed {item}#{variant}. An entry with no usable item id is keyed index[N] |
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": { } }
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./{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.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." }
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" }
All /api/* endpoints require a Bearer token in the Authorization header.
Authorization: Bearer YOUR_API_KEY
/img, /search, /random, manifests, metadata) require no authentication.All errors return JSON in a consistent format:
{
"error": "Short error description",
"message": "Detailed info (optional)"
}
| Code | Meaning | Common Cause |
|---|---|---|
200 | OK | Request succeeded |
302 | Redirect | Search endpoints redirect to image URL |
400 | Bad Request | Invalid modpack name or missing fields |
401 | Unauthorized | Missing or invalid API key |
404 | Not Found | Image, manifest, or modpack doesn't exist |
429 | Too Many Requests | Rate limit exceeded |
500 | Server Error | Internal processing failure |
All endpoints are fully CORS-enabled:
| Header | Value |
|---|---|
| Allowed Origins | * (all origins) |
| Exposed Headers | X-Display-Name, X-Recipe-Count, X-Recipes-Url, X-Recipes, X-Recipes-Truncated |
| Header | Present On | Description |
|---|---|---|
| X-Display-Name | Every image response | URL-encoded human-readable item name, e.g. Diamond%20Sword |
| X-Recipe-Count | Every image response | Number of recipes that produce the item; 0 when there are none |
| X-Recipes-Url | Every image response | Path to the full recipe document, e.g. /atm9/recipes/minecraft/diamond_sword |
| X-Recipes | When small enough to inline | URL-encoded compact JSON array of the item's recipes |
| X-Recipes-Truncated | When X-Recipes is absent | true: the recipes exist but were too large to inline. Follow X-Recipes-Url |
| ETag | Images, manifests, metadata, recipes | Entity tag for HTTP caching / conditional requests |
| Content-Type | All responses | image/png for images, application/json for data |
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"
Access-Control-Expose-Headers), so it's readable from browser fetch() and XMLHttpRequest calls on any origin.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;
}
# 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
// 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" />
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
<!-- 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" />
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"
Swagger UI — interactive browser for all endpoints.
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.