Developers
Build bots, dashboards and integrations on top of Game Central: read communities, chat, forums, events and game servers with the API, and hear about what happens with webhooks.
Getting a token
Make a personal token in Settings → Developer. The API acts as you, with exactly your access: it sees the communities and channels you can see. Tokens can only read unless you let them post. Send it with every request:
curl https://gamecentral.app/api/v1/me \
-H "Authorization: Bearer mx_your_token"Each token can make 120 requests a minute. Over that you get 429 with a Retry-After header. Keep tokens secret: anyone with one can act as you. Delete a token in settings and it stops working at once.
Responses
Everything is JSON. Success is { "data": … }; a problem is { "error": { "code": "…", "message": "…" } } with a matching status: 401 (no or bad token), 403 (not allowed), 404 (not found, or not visible to you), 422 (invalid input) or 429 (too many requests). A message held for moderator review by the community’s automod answers 202 with the code held.
Endpoints
All paths start with https://gamecentral.app/api/v1.
| Method | Path | What it does |
|---|---|---|
| GET | /me | You, and the communities you’re in. |
| GET | /communities/{slug} | A community: name, members, how to join, and your place in it. |
| GET | /communities/{slug}/channels | The channels you can see. |
| GET | /communities/{slug}/threads | Forum threads. ?channel={id} for one forum (with ?sort= and ?page=), otherwise the latest across all of them. |
| GET | /communities/{slug}/events | Upcoming events, each date of a repeating event separately. |
| GET | /communities/{slug}/servers | The community’s game servers and their live status. |
| GET | /channels/{id}/messages | A chat channel’s messages, oldest first. ?before={id} or ?after={id} to page, ?limit= up to 100. |
| POST | /channels/{id}/messages | Post a message as you: {"content": "…"}. Needs a token that can post. |
| GET | /servers/{id} | A listed game server and its live status. |
Posting a message:
curl -X POST https://gamecentral.app/api/v1/channels/CHANNEL_ID/messages \
-H "Authorization: Bearer mx_your_token" \
-H "Content-Type: application/json" \
-d '{"content": "Server restarting in 5 minutes"}'Webhooks
Community managers add webhooks in their community’s settings under Integrations. A Discord webhook URL gets readable messages in that Discord channel; any other https:// address gets a JSON POST for each event it chose. Only content from channels everyone in the community can see is sent. Nobody is pinged by Discord messages.
The events:
member.joinedmember.leftmessage.createdthread.createdpost.createdannouncement.createdevent.createdapplication.submittedserver.downserver.up
A delivery looks like this:
POST /your/endpoint
Content-Type: application/json
X-GameCentral-Event: thread.created
X-GameCentral-Delivery: 0192f1c4-…
X-GameCentral-Timestamp: 1767225600
X-GameCentral-Signature: sha256=5d41402abc4b2a76…
{
"id": "0192f1c4-…",
"event": "thread.created",
"occurredAt": "2026-01-01T00:00:00.000Z",
"community": { "id": "…", "slug": "my-clan", "name": "My Clan", "url": "…" },
"data": {
"channel": { "id": "…", "name": "general" },
"thread": { "id": "…", "title": "Season 4 plans", "excerpt": "…", "url": "…" },
"author": { "id": "…", "name": "Alice", "username": "alice", "url": "…" }
}
}Answer with any 2xx status within 8 seconds. Network errors, timeouts, 408, 429 and 5xx are tried again up to 5 times, waiting longer each time; redirects aren’t followed. After 20 failed deliveries in a row the webhook switches itself off (switch it back on in settings). The Send test button sends a ping event.
Checking signatures
When you add a JSON webhook you get a signing secret (shown once). Each delivery’s x-gamecentral-signature header is sha256= and the hex HMAC-SHA256, with that secret, of the timestamp header, a dot, and the raw body. Check it, and reject old timestamps, before trusting a delivery:
import { createHmac, timingSafeEqual } from 'node:crypto';
function isFromGameCentral(secret, headers, rawBody) {
const timestamp = headers['x-gamecentral-timestamp'];
const expected =
'sha256=' + createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
const given = headers['x-gamecentral-signature'] ?? '';
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
return fresh && given.length === expected.length &&
timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}