Integrations
First-party services reading the hub with a service key.
An integration connects a platform to Elixir. It has no personal player, clan identity, or human admin powers. Elixir Drop is the first consumer: it reads game context and automatically enrolls supplied player tags for recording. Drop keeps its accounts, scores, XP and badges in Drop.
People and clan agents use MCP. Integrations use REST at
https://elixir.poapkings.com/api/v1. These routes share the recorder and its
collector job ledger; they do not call MCP tools over HTTP. The
OpenAPI contract describes the wire format.
Provisioning and administration
Admin → Integrations creates the platform identity, issues a key, sets API and refresh budgets, and grants addition rights to existing player collections. Admins can change permissions, revoke or rotate keys, and suspend or resume an integration. The screen shows last key use, daily usage, recording depth, member counts and enrollment limits. Collection depth and ownership remain controlled by the collection's administrator. Removing a grant does not remove members.
The raw svt_… key appears once; only its SHA-256 digest is stored. Keep it in
the consuming platform's server configuration. Never ship it to a browser.
Send Authorization: Bearer <key> on every request. Cookie sessions, personal
MCP tokens, agent tokens and OAuth tokens do not authenticate here. A REST key
cannot authenticate to MCP. Legacy MCP integrations remain compatible during
migration; no new integration should use that transport.
Rotation immediately revokes the previous key. Suspension refuses all keys; resumption restores an unrevoked key. Quotas and grants belong to the integration, so rotation neither resets usage nor multiplies capacity. The human sponsor is accountable for the integration but contributes no admin authority or quota.
Resources and permissions
| Method and path | Permission | Result |
|---|---|---|
GET /game/clock |
game:read |
Game calendar with absolute season and day boundaries |
GET /players/{tag} |
players:read |
Recorded name, clan, account age and source timestamp |
POST /profile-refreshes |
profiles:refresh |
Accepted asynchronous profile refresh |
GET /profile-refreshes/{id} |
profiles:refresh |
Pending, complete or failed refresh |
PUT /collections/{id}/members/{tag} |
collections:members:add plus collection grant |
Idempotent addition and recording enrollment |
POST /collections/{id}/members |
Same | Bounded add-only batch |
Tags must be URL-encoded in paths: #2PYQ0 becomes %232PYQ0. Collection IDs
are decimal identifiers; a collection's slug is also accepted. A grant is for a
specific existing collection, not every collection owned by the sponsoring human.
Successful responses contain data and request_id. Failures use
application/problem+json, with type, title, status, code, detail and
request_id. The X-Request-ID response header ties either response to the
operational audit. Treat unknown response fields as additions compatible with v1.
The game clock is policy
GET /game/clock reports source: "policy", as_of, season_id,
season_started_at, season_ends_at, week, section_index,
period_index, day_kind (training or war), war_day (integer or
null), day_started_at, day_ends_at and notes (strings). Its calendar is the
same calculation as MCP's game_clock: days and seasons roll at 10:00 UTC;
seasons span first Monday to first Monday. It does not borrow a clan's observed
river-race opening. as_of dates the calculation, not a collector observation.
Consumers should use the supplied boundaries. Drop retains its FIFO rule: finalize the previous season successfully before caching the new clock. Existing results keep their assigned seasons. During an outage a cached policy clock must not carry the old season beyond its explicit end.
Profiles and asynchronous refreshes
GET /players/{tag} performs no live fetch. observed_at is the recorder's
source timestamp, not the time the response was delivered. A known tag without a
profile observation returns 404 not_recorded. Names, clan and account age can
be incomplete; null means unknown, not zero. Recording enrollment is not itself
an observation.
When a profile is missing or too old for your product, send:
POST /api/v1/profile-refreshes
Authorization: Bearer <key>
Content-Type: application/json
Idempotency-Key: <stable-operation-key>
{"player_tag":"#2PYQ0"}
The 202 response includes an opaque refresh id, status, created_at and
expires_at, plus a Location status URL and Retry-After: 5. Retry the same
operation with the same key. Reusing a key for a different tag is a conflict.
Collectors execute the request through the existing live lane and global CR
budget. An active live job for the same tag can be shared.
Poll the status URL at or after Retry-After. A refresh is readable only by its
integration; rotating the credential preserves access. complete includes a
recorded profile and requires an admitted collector result and projected
profile data. A job being marked done is insufficient. Rejection, dead work or a
15-minute timeout yields failed with refresh_unavailable. Use a new operation
key to retry a failed refresh. Requests expire after 24 hours, after which their
status returns 404. A one-time refresh does not create a recording subscription.
Automatic collection membership
A platform may automatically add a supplied tag to its granted collection. Drop asserts membership on login and when a player saves an optional CR tag, through its durable refresh queue. Failed enrollment retries there; queue submission or hub outages must not fail login or profile save. The add-only reconciliation script repairs older or missed additions.
A single addition uses PUT with an empty JSON object. A batch uses POST with
{"tags":["#2PYQ0"]} and accepts 1–500 tags. The response includes added,
already_present, total, recordings_started and enrollment_established.
The latter confirms the recording reason exists, not that capture has finished.
Membership and recording enrollment commit together. Repeated additions preserve manual members and are safe to retry. The member limit is checked under the collection lock, so an oversized batch fails atomically. Recording is shared with any other accounts or collections already following the subject.
V1 is add-only. Removing a tag from Drop or changing it does not remove its old collection membership. Integrations cannot replace membership, delete members, change recording depth, create collections, or upload game facts. A supplied tag is unverified and does not prove identity or participation in the platform. Only normal collector admission establishes canonical game observations.
Limits and errors
Admins size each integration independently: API calls per UTC day (default
10,000), API calls per hour (default 2,000), profile-refresh requests per UTC
day (default 1,000), and collection member capacity per grant (default
10,000). Retry-After is the seconds to the top of the hour, to UTC midnight,
or 3600 for a refresh refusal.
Refresh retries with the same idempotency key do not spend another refresh unit.
These allowances do not increase the collector fleet's shared upstream budget.
| Status | Meaning |
|---|---|
| 400 | invalid_json (checked before authentication; send {} on GET), invalid_tag, invalid_members, idempotency_key_required, or bad_request for a badly percent-encoded path |
| 401 | Missing, wrong-purpose, revoked or suspended credential |
| 403 | Missing permission |
| 404 | Unknown or inaccessible resource; not_recorded for missing profile data |
| 409 | enrollment_limit or idempotency_conflict |
| 429 | rate_limited, daily_quota_exceeded or refresh_quota_exceeded |
| 503 | temporarily_unavailable |
Every request with a resolved key is logged with surface: rest, the
operation name, duration, size, HTTP status and error code, never the
arguments; usage rows are kept 90 days.
Honor Retry-After for throttling and temporary failures. Preserve useful cached
data and retry through background work. Do not turn authentication failures or
quota refusals into extra live fetches.