Use the API
Conventions
The rules every AlloyMC endpoint follows: JSON, errors, rate limits, pages of results, caching, handles and versions.
Requests and responses
- Base URL:
https://api.alloymc.dev/v1. HTTPS only. - Bodies are JSON (
Content-Type: application/json), UTF-8. Unknown fields in a request body are rejected withbad_json, so typos don't fail silently. - Field names are
camelCase. Times are RFC 3339 in UTC, like2026-10-31T12:00:00Z. - Endpoints that succeed without anything to say answer
204 No Content.
Errors
Every error has the same shape, with a fitting HTTP status:
{
"error": {
"code": "handle_taken",
"message": "That handle is taken."
}
}
Match on code: codes are stable. message is written for people, so show it to your users if you like, but don't parse it; it can change.
| Status | Codes you'll see | Meaning |
|---|---|---|
| 400 | bad_json, handle_invalid, link_invalid, too_long, name_required |
The request can't work as sent |
| 401 | unauthorized |
A token is needed, or the one sent isn't valid |
| 403 | private |
The profile is private |
| 404 | not_found |
No such handle, player or endpoint |
| 409 | handle_taken |
Someone else has it |
| 410 | link_expired |
A one-time code was already used or ran out |
| 429 | slow_down, handle_cooldown |
Too many requests; see below |
Rate limits
Each IP address can make 300 requests a minute. Past that you get 429 with a Retry-After header in seconds; wait that long and carry on. Some actions have tighter limits of their own, like trading sign-in codes (20 a minute).
If you need more, cache (see below) or tell us what you're building.
Pages of results
Lists such as followers come 50 at a time, newest first, with a next cursor:
{ "users": [ ... ], "next": 1793451234 }
Pass it back as before to get the next page. When next is 0, you've seen everything.
curl "https://api.alloymc.dev/v1/users/elchi/followers?before=1793451234"
Caching
Responses say how long they may be kept, in Cache-Control:
| What | Cache |
|---|---|
| Public profiles, player lookups | 60 seconds |
| Avatars and skin renders | 1 hour |
| Status, anything with a token | not cached |
Avatar URLs in responses carry a v= value that changes whenever the picture changes, so you can cache those URLs for as long as you like.
Handles
Handles are 3 to 20 characters (a to z, 0 to 9, _) and case-insensitive: /v1/users/Elchi and /v1/users/elchi are the same person. When someone changes their handle, the old one answers 301 Moved Permanently to the new one for 30 days. Follow redirects (curl: -L), and store handles as people change them, or better, keep the profileUrl.
Private and removed profiles
A private profile only shows handle, displayName, avatarUrl, profileUrl and "private": true. Its lists answer 403 private. A deleted or suspended account answers 404, as if it never existed. If you keep copies of profile data, refresh them, and drop what's gone within 30 days.
CORS
Every origin may call the API from a browser (Access-Control-Allow-Origin: *). Credentials travel only in the Authorization header, never in cookies.
Versions
Everything is under /v1. Within a version we may add endpoints and fields, so ignore fields you don't know. We don't rename or remove anything within a version. If we ever have to, it will be /v2, announced in the changelog with time to move.