POST/bulletmeta.api.v1.ClanService/GetClan
Request
| slug | string |
Response
| clan | Clan |
A typed API over servers that count toward stats, their matches, players, boards and clans — plus your own organization's records, and the two lists your own tooling has to keep in step: the whitelist and the ban list. Available on the Enterprise plan.
Every method is a POST of a JSON body to /package.Service/Method on https://api.bulletmeta.app. That is the Connect protocol; the same endpoints speak gRPC and gRPC-Web.
curl https://api.bulletmeta.app/bulletmeta.api.v1.ServerService/GetLiveStatus \
-H "Authorization: Bearer $BULLETMETA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"serverId": "…"}'Streaming needs a real gRPC client — with buf curl:
buf curl --protocol grpc --http2-prior-knowledge \
-H "Authorization: Bearer $BULLETMETA_API_KEY" \
-d '{"serverId": "…"}' \
https://api.bulletmeta.app/bulletmeta.api.v1.ServerService/WatchLiveStatusGenerated SDKs come from the schema on the Buf Schema Registry:
# Go go get buf.build/gen/go/bulletmeta/api/connectrpc/go # TypeScript npm i @buf/bulletmeta_api.connectrpc_es
A key belongs to one organization and is created by its owner under Settings → API. The token bm_live_<prefix>_<secret> is shown once and only its SHA-256 is stored, so a lost key is replaced rather than recovered. Up to 25 active keys, expiry from 1 to 730 days, and a revocation takes effect within 60 seconds.
Authorization: Bearer bm_live_…
| Scope | What it opens |
|---|---|
| public:read | Everything except OrgService, plus your own organization's servers and their matches. |
| org:read | OrgService reads: sanctions, whitelist, audit log, members, playtime and seeding, seeding standings. |
| lists:write | OrgService writes: whitelist grants, bans, sanction evidence, finalize and lift. |
There is no organization parameter anywhere: a key acts on the organization it belongs to. Anything it may not see answers not_found, which is indistinguishable from something that does not exist.
10 requests a second per key, bursting to 100. Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset; over the limit is HTTP 429 with Retry-After in seconds.
for attempt in 1 2 3 4 5; do sleep $(( 2 ** attempt )) # and honour Retry-After when it is larger done
List methods take pageSize and answer with nextPageToken. Send it back as pageToken for the next page; an empty token is the end. Tokens are opaque keyset cursors — do not parse or build one.
Errors are Connect codes, with the HTTP status a Connect client maps them to:
| unauthenticated (401) | Missing, malformed, expired or revoked key. |
| permission_denied (403) | The key lacks the scope this method needs. |
| not_found (404) | No such record, or one this key may not see. |
| invalid_argument (400) | A field is missing or out of range. |
| failed_precondition (412) | The record is not in a state that allows this, such as a finalized sanction. |
| resource_exhausted (429) | Rate limited; retry after Retry-After. |
| unavailable (503) | A dependency is down. Retry with backoff. |
Every write takes a requestId. Retrying with the same one returns the first response instead of writing again, for 24 hours — so a timeout you did not see the answer to is safe to repeat.
curl https://api.bulletmeta.app/bulletmeta.api.v1.OrgService/CreateWhitelistGrant \
-H "Authorization: Bearer $BULLETMETA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"subject": "76561198000000000", "requestId": "seed-2026-09-16-01"}'A write that was saved but could not be pushed to a game server answers with warnings and no error: the record exists, and retrying would write it twice. Every write is audited as your key, and shows in the console's audit log as "API key ‹name›".
The schema is bulletmeta.api.v1, published on buf.build/bulletmeta/api. Fields are added, never renumbered or removed — every change runs through buf breaking. A breaking change would be a new package, never an edit to v1.
Clans' public pages.
| slug | string |
| clan | Clan |
| slug | string | |
| pageSize | integer | |
| pageToken | string |
| members | ClanMember[] | |
| nextPageToken | string |
The public boards, as bulletmeta.app/stats ranks them.
| category | BoardCategory | |
| pageSize | integer | 1–100; 0 means 50. The board holds the top 1000. |
| pageToken | string |
| entries | BoardEntry[] | |
| nextPageToken | string |
Finished matches on servers visible to the key.
| matchId | string |
| match | Match | |
| results | MatchResult[] |
Oldest first, as a timeline.
| matchId | string | |
| pageSize | integer | 1–500; 0 means 200. |
| pageToken | string |
| kills | Kill[] | |
| nextPageToken | string |
Newest first.
| serverId | string | Only this server's matches, when set. |
| pageSize | integer | |
| pageToken | string |
| matches | Match[] | |
| nextPageToken | string |
The key's own organization. Reads need the `org:read` scope and writes need `lists:write`; there is no organization parameter on either, because a key only ever touches the organization it belongs to. Every write is audited as the key itself, and takes a `request_id` so a retried call returns the first result instead of writing twice.
| sanctionId | string | |
| evidenceUrls | string[] | |
| requestId | string |
| sanction | Sanction |
| steamId | string | |
| serverId | string | Unset bans on every server of the organization. |
| reason | string | |
| notes | string | |
| evidenceUrls | string[] | Without evidence the ban applies but stays open ("needs evidence"). |
| expiresAt | google.protobuf.Timestamp | |
| requestId | string |
| sanction | Sanction | |
| warnings | string[] |
Writes (`lists:write`).
| subject | string | A SteamID64 or a BulletMeta profile id. The profile is created for an unknown SteamID64, exactly as a join would create it. |
| serverId | string | Unset grants on every server of the organization, now and future. |
| note | string | |
| expiresAt | google.protobuf.Timestamp | Unset is open-ended. The game has no expiry, so BulletMeta enforces one by leaving the SteamID out of the next push. |
| requestId | string | Repeat a call safely: the same id returns the first call's grant. |
| grant | WhitelistGrant | |
| warnings | string[] | Servers the push did not reach. The grant is written either way. |
| sanctionId | string | |
| requestId | string |
| sanction | Sanction |
| sanctionId | string | |
| reason | string | |
| requestId | string |
| sanction | Sanction | |
| warnings | string[] |
| pageSize | integer | |
| pageToken | string |
| entries | AuditEntry[] | |
| nextPageToken | string |
| pageSize | integer | |
| pageToken | string |
| members | OrgMember[] | |
| nextPageToken | string |
Playtime and seeding time per player, for the key's own servers.
| pageSize | integer | |
| pageToken | string | |
| serverId | string | One of the key's own servers; unset covers every one of them. |
| from | google.protobuf.Timestamp | |
| to | google.protobuf.Timestamp |
| players | PlayerPresence[] | |
| nextPageToken | string |
| pageSize | integer | |
| pageToken | string |
| sanctions | Sanction[] | |
| nextPageToken | string |
Where players stand under the organization's seeding whitelist rules.
| pageSize | integer | |
| pageToken | string | |
| ruleId | string | One rule; unset covers every rule of the organization. |
| standings | SeedingStanding[] | |
| nextPageToken | string |
| pageSize | integer | |
| pageToken | string | |
| includeRevoked | boolean | Include revoked grants. |
| grants | WhitelistGrant[] | |
| nextPageToken | string |
| grantId | string | |
| requestId | string |
| grant | WhitelistGrant | |
| warnings | string[] |
Player careers, from counted matches on enrolled servers.
| player | PlayerSelector |
| stats | CareerStats |
| player | PlayerSelector |
| player | PlayerRef | |
| createdAt | google.protobuf.Timestamp |
| player | PlayerSelector | |
| serverId | string | Only this server's sessions, when set. |
| organizationId | string | Only sessions on this organization's servers, when set. |
| totalSeconds | integerstring | The sum over `servers`. |
| online | boolean | |
| lastConnectedAt | google.protobuf.Timestamp | |
| servers | ServerPlaytime[] | Most played first. |
| player | PlayerSelector | |
| pageSize | integer | 1–500; 0 means 100. Newest first. |
| pageToken | string |
| points | RatingPoint[] | |
| nextPageToken | string |
| player | PlayerSelector |
| weapons | WeaponStat[] |
| player | PlayerSelector | |
| pageSize | integer | |
| pageToken | string |
| matches | PlayerMatch[] | |
| nextPageToken | string |
Game servers: enrolled (public) servers, plus the key's own organization's.
The live tile: status, map, players, scores. No roster.
| serverId | string |
| liveStatus | LiveStatus |
| serverId | string |
| server | Server |
Servers visible to the key, by name.
| pageSize | integer | 1–100; 0 means 25. |
| pageToken | string |
| servers | Server[] | |
| nextPageToken | string |
Server stream: the server's live tile, then every update as it is published. gRPC, gRPC-Web or Connect streaming — not plain JSON over POST.