REST API
Two ways in#
Browser access
Your page calls laby.net straight from the visitor's browser. No key, no server of yours in between, and every visitor brings their own rate limit.
- For
- Every organization with a verified domain
- Auth
- None - the page's origin is the identity
- Limits
- Each visitor's own, like on laby.net
Organization tokens
Your backend sends a token of your organization. Each scope is unlocked only for the organizations it is meant for:
- Backend access On request
organization.api.readPartners & trusted organizations - Shop cashback
organization.partner.cashbackServer partners - Server achievements
organization.achievements.grantOrganizations with a verified server
Quickstart#
fetch() on your page is all it takes. Responses are JSON, every route is a public GET.// On https://your-domain.com, a page on one of your verified domains
const response = await fetch("https://laby.net/api/v3/user/LabyStudio/uuid");
if (response.status === 429) {
// This visitor asked too often; Retry-After says how many seconds to wait
const retryAfter = Number(response.headers.get("Retry-After"));
}
const { uuid, name } = await response.json();Press Send to call the live API for the test player.
Browser access#
Access-Control-Allow-Origin for your domain, so the browser lets your script read the response.Browser access has to be requested
Set it up
- 1Create an organization in the console, or use the one you already have.
- 2Verify your domain under Domains with a DNS record. Subdomains are included, only https pages count.
- 3Request browser access from the Laby team, with the site and what it shows. We switch it on for your verified domains.
- 4Call the endpoints from your pages. The console shows the requests per domain, so you see what your visitors use.
The rules
- Plain
GETrequests without custom headers or credentials. Anything else needs a preflight, and laby.net does not answer one. - Every visitor spends their own budget, exactly as on laby.net.
- A
429means this visitor asked too often.Retry-Aftersays how many seconds to wait. - Load the data when a page is viewed instead of storing it, and link to laby.net where you show it.
GET /api/v3/user/34e57efa-5783-46c7-a9fc-890296aaba1f/names HTTP/2 Host: laby.net Origin: https://stats.your-domain.com HTTP/2 200 Access-Control-Allow-Origin: https://stats.your-domain.com Content-Type: application/json
Why the browser and not your server
A request from the visitor's browser counts against the visitor's connection, the same way laby.net itself loads a profile. A server that asks on behalf of everyone puts thousands of visitors behind one address, and that address runs into the per-network budget within minutes. With browser access the limits grow with your audience; backend access is the exception.
Per visitor and route
| Route | Limit |
|---|---|
| …/{name}/uuid | 10 per 30 s per visitor |
| …/{uuid}/names | 10 per 30 s per visitor |
| …/{uuid}/textures | 10 per 30 s per visitor |
Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; past the limit the answer is 429 with Retry-After in seconds.
Per network and day
On top, laby.net keeps one budget for everything a network reads anonymously, and one per account for signed-in players. A busy page with browser access never gets near it: each visitor reads from their own network.
A server or script calling without a token shares its network's budget with everything else behind that address. That is the limit backend access, a token scope for partners, lifts.
Errors#
| 400 | Malformed input, for example something that is neither a name nor a UUID. |
| 401 | Unknown, revoked or expired token. |
| 403 | The token lacks the scope, or the organization lacks the feature. |
| 404 | laby.net does not know this player. |
| 409 | Conflict, for example a cashback reference reported twice. |
| 429 | Rate limit reached - wait for Retry-After. |
Player data#
Name to UUID#
Resolves a Minecraft name to the player's UUID and current name. A UUID works as input too, which makes this the quickest way to normalize whatever a visitor typed.
- name
- Current Minecraft name or UUID, with or without dashes.
- Rate limit
- 10 per 30 s per visitor
- Cache
- 60 s
Names change hands, UUIDs never do. Keep the UUID and call the routes below with it.
{
"uuid": "34e57efa-5783-46c7-a9fc-890296aaba1f",
"name": "LabyStudio",
"lastUpdated": "2026-11-07T08:35:02+00:00"
}Press Send to call the live API for the test player.
Name history#
Every name the player has had, oldest first. The first entry carries the account's creation date where it is known.
- uuid
- Minecraft UUID (a name works too, but resolves first).
- Rate limit
- 10 per 30 s per visitor
- Cache
- Not cached
accurate: false means laby.net saw the new name first on that day; the change itself may be older.
last_seen_at is when laby.net last saw the name in use. Names a player hid on laby.net stay hidden here too, and changed_at is null where the date is not known or the player hides it.
[
{ "name": "FirstName", "changed_at": "2013-04-02", "accurate": true },
{
"name": "LabyStudio",
"changed_at": "2016-01-11",
"accurate": false,
"last_seen_at": "2026-11-07T08:35:02+00:00"
}
]Press Send to call the live API for the test player.
Skins & capes#
The skins and capes laby.net has seen on the player, grouped by type: SKIN, CAPE, and the LabyMod textures CLOAK and BANDANA. The one they wear right now carries active: true.
- uuid
- Minecraft UUID, with or without dashes.
- Rate limit
- 10 per 30 s per visitor
- Cache
- 60 s
image_hash is what the Render API takes: /api/v3/render/skin/{image_hash}.webp draws exactly this skin, forever.
{
"SKIN": [
{
"type": "SKIN",
"image_hash": "670f9d318cefb8f7d61e21a5afb767e9",
"file_hash": "…",
"first_seen_at": "2024-02-18T09:12:03+00:00",
"last_seen_at": "2026-11-07T08:35:02+00:00",
"use_count": 1843,
"slim_skin": false,
"active": true
}
],
"CAPE": [ … ]
}Press Send to call the live API for the test player.
Tokens#
Creating one
- 1Open your organization in the console and go to API Tokens.
- 2Name it after the integration and pick only the scopes it needs. A scope shows up once your organization has access to it.
- 3Copy it right away: a token is shown once and cannot be retrieved again. Revoking it stops every integration that uses it immediately.
All endpoints live under https://laby.net/api/v3. Send the token with every request as a Bearer token. Tokens act for the organization, not for a person: they keep working when a team member leaves, and they can only do what their scopes allow.
Authorization: Bearer laby_org_... # Legacy header from the retired next.labymod.net cashback API, still accepted: X-AUTH-TOKEN: <token>
Responses are JSON. Errors carry an HTTP status (400 invalid input, 401 unknown or revoked token, 403 missing scope or no serverpartner, 404 not found, 409 conflict, 429 rate limited) and a message.
Backend access#
organization.api.readPartners & trusted organizationsRead public player data from your servers, outside the anonymous limits.Backend access has to be requested
The same player endpoints a page with browser access reads, called from your backend. A token with this scope is a known caller: it leaves the per-network budgets and gets the rate limit agreed for your organization. The scope appears in the console once the Laby team opened the API for your organization.
GET /api/v3/user/{uuid}/textures
Authorization: Bearer laby_org_...All tokens of an organization share one rate limit window for this scope, so a second token does not double it. The scope only reads, and it stops working the moment API access is taken back, without touching a single token.
Shop cashback#
organization.partner.cashbackServer partnersReport purchases in your server shop so players receive LabyCoins.Report a purchase once it is paid. The player receives 3 percent of the price as LabyCoins (1000 coins per currency unit) and a notification; the coins are claimed on the rewards page. reference is your own order id and doubles as idempotency key, reporting the same reference twice returns 409.
POST /api/v3/cashback
Authorization: Bearer laby_org_...
Content-Type: application/json
{
"uuid": "<player-uuid>",
"price": 4.99,
"reference": "order-2026-000123",
"item_name": "VIP Rank"
}
→ 200 { "reference": 39482, "amount": 150 }price is the amount the player paid, as a number. item_name is optional and shown to the player. amount in the response is the coin value credited; it is 0 for very small prices, then no notification is sent.
GET /api/v3/cashback/39482
GET /api/v3/cashback?reference=order-2026-000123
→ 200 {
"reference": 39482,
"added_at": "2026-09-06T10:15:00Z",
"article_name": "VIP Rank",
"amount": 150,
"redeemed": false,
"redeemed_at": null,
"partner_reference": "order-2026-000123"
}GET /cashback?user={uuid} lists every cashback you reported for one player, newest first, and returns 404 when there is none.
The paths of the retired next.labymod.net API (POST /cashback/create, GET /cashback/get/{id}, GET /cashback/get/{reference}?server_reference=true, GET /cashback/get/user/{uuid}) still work on laby.net with the same payloads, but are deprecated: switch to the routes above when you rotate your token.
Server achievements#
organization.achievements.grantOrganizations with a verified serverHand achievements to players on the servers your organization has claimed.Achievements are defined and reviewed on laby.net; the API only hands them out. Every call addresses an achievement by the key you gave it and a server by its name, the one in the URL of its page on laby.net, so a plugin needs no internal ids. Granting is idempotent: replaying a queue never fails, it reports the repeat as a no-op.
POST /api/v3/achievements/servers/labymod/grant
Authorization: Bearer laby_org_...
Content-Type: application/json
{
"achievement": "first_win",
"uuid": "<player-uuid>"
}
→ 200 { "granted": true, "reactivated": false }granted is false when the player already holds it, so you can call this on every login without checking first. reactivated is true when a previously revoked grant came back. The player is notified only on a real grant, and only while the achievement is approved.
POST /api/v3/achievements/servers/labymod/grant/bulk
Authorization: Bearer laby_org_...
Content-Type: application/json
{
"achievement": "first_win",
"uuids": ["<uuid-a>", "<uuid-b>"]
}
→ 200 {
"results": [
{ "uuid": "<uuid-a>", "result": "granted" },
{ "uuid": "<uuid-b>", "result": "unknown_user" }
],
"granted": 1,
"total": 2
}At most 100 players per request. A player without a Laby.net account does not fail the batch, they come back as unknown_user; a repeat is already_granted. Send the rest of your roster in further requests.
DELETE /api/v3/achievements/servers/labymod/grant
Authorization: Bearer laby_org_...
Content-Type: application/json
{
"achievement": "first_win",
"uuid": "<player-uuid>"
}
→ 200 { "success": true }GET /api/v3/achievements/servers
→ 200 { "servers": [ { "id": 12, "name": "labymod", "nice_name": "LabyMod" } ] }
GET /api/v3/achievements/servers/labymod
→ 200 {
"server": { "id": 12, "name": "labymod", "nice_name": "LabyMod" },
"achievements": [
{ "key": "first_win", "name": "First win", "status": "ACTIVE",
"grantable": true, "use_count": 1428 }
]
}
GET /api/v3/achievements/servers/labymod/first_win/grants?page=1&pageSize=100
→ 200 {
"grants": [ { "uuid": "...", "username": "...",
"granted_at": "2026-09-06T10:15:00Z", "source": "API" } ],
"total": 1428, "page": 1, "page_size": 100
}status is PENDING while a new achievement waits for review, ACTIVE once it is approved, and REJECTED, DISABLED or BLOCKED otherwise. Only grantable ones accept new grants, anything else answers 409, so check the list after changing your configuration. Grants survive a rejection, they are just hidden until the achievement is live again.
Every route allows 120 requests per minute and token. A server your organization has not claimed answers 403, an unknown key 404.
Sign in with Laby#
What an application can do
Read and write on behalf of a signed-in player: their textures, notifications, friends, presence and chat, and a realtime connection to LabyConnect at wss://connect.laby.net, each behind a scope of its own.
Where it lives
Applications belong to an organization and are managed in the console. Each scope your application has been granted comes with its own reference there, endpoints and packets included.
Open the consoleFair use#
Live, not copied
Load player data when a page is viewed. Do not mirror it into a database of your own or crawl it in bulk.
Credit the source
Link to the player's laby.net profile wherever you show what came from it.
Respect privacy
What a player hides on laby.net stays hidden for you too. Do not try to work around it.
Missing an endpoint?
Tell us what you are building and which data you need.