# Keys and environments One API key authenticates your backend. Which environment a call belongs to follows from the key, not the URL — there is no second host to point at. ## What a key looks like ``` pgs_live_8f3a1c9d2e7b4a6f5c0d1e2f3a4b5c6d7e8f9a0b14NXoq pgs_test_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1L0EZk ``` The prefix names the environment; the 46 characters after it are fixed-length and self-checking, so a truncated paste or mistyped character is refused as a `401`. ## The key is a server credential It never belongs in a browser: a key readable from your page lets any of your players mint a session *as any other player of yours*. Keep it in your backend's secret store and call the mint endpoint from there. Your account starts with no key; your first admin claims their invite, enrols a second factor, and mints the first key themselves, shown exactly once. We store only a hash, so a lost key is replaced, never re-displayed. Keys are created, rotated and revoked entirely by your team, from [the portal](/docs/portal.md). ## Test and live Both environments are served by `https://api.peritusgames.com`. Keys are scoped to one environment each; an account may hold more than one live key and more than one test key at once. What they keep apart: | Separated | How | | --- | --- | | Player identity | A test session produces `ptr:test::` — the same `playerId` is two different players. | | The embed path | `/embed/test/` instead of `/embed/`, same games host. | | Origins | Registered per environment; a test-registered origin can't embed the live frame, and the reverse. | | Opponents | A test player is only ever matched with another test player; joining across environments is refused with `403 cross_environment_join`. | | Results | [The results feed](/docs/results.md) returns only rows for the key's environment, cursors included, but every match in it. | | Licensing | Decided per environment; a game enabled for test says nothing about live. | ## Reading your own account One call answers “what have I got, and which environment am I in” — a build script or start-up check can ask it without anyone signing in: ``` GET /partner/me Authorization: Bearer pgs_test_ 200 { "partnerId": "acme", "name": "Acme Arcade", "apiVersion": 1, "environment": "test", "games": ["streakpeaks", "pokersquares"], "origins": ["https://staging.acme.example"], "prices": { "usd_10": 700 }, "webhook": { "url": "https://staging.acme.example/hooks", "enabled": true, "consecutiveFailures": 0, "createdAt": 1785000000, "secretRotatedAt": 1785000000, "disabledAt": null } } ``` `partnerId` is what every uid and later record is built from. `prices` is sparse: an unpriced tier is absent rather than 0, since absent means our floor — read `entryUsdCents` on [the tier ladder](/docs/paid-play.md) for what a player actually pays. `webhook` is absent when unconfigured, and never carries the signing secret, shown once, in the portal. > **It is a read, and only a read.** Changing any of it — minting a key, registering an origin, setting a price or a webhook URL — stays in the portal. This response, like every one on this surface, is answered on your account's pinned version unless the call named a different one — see [Versioning and change](/docs/versioning.md). `apiVersion` on the body is the version this call was actually answered on, always equal to the `X-Peritus-Api-Version` response header on the same reply — read whichever one your code already has in hand. ## How two test players are paired A stake joins the **oldest waiting seat** matching on game and tier whose stake is confirmed — deterministic, unlike live's skill-based pairing, so a two-account rig behaves the same on its fiftieth run as its first. A test key can also decide a match outright — see [Testing your integration](/docs/testing.md). ## Licensing a game Turning a game on **for test** is instant and self-service from the portal. **Live** access is requested from the same place and granted by someone on our side; withdrawable anytime, and a denial isn't final — but a game must be on in test first. The mint endpoint distinguishes a game licensed in the *other* environment (`game_not_licensed_in_this_environment`) from one licensed in neither (`game_not_licensed`) — both in [Errors and limits](/docs/errors.md). ## Rotating a key Rotation is an overlap, not a cutover: it mints the new key immediately — shown once — and schedules the old one for revocation **seven days** out; both mint valid sessions until then, and the portal shows a live countdown plus each key's last use. After revocation, calls with the old key answer `401 invalid_key` — but that affects future mints only, so an already-issued token stays valid until it expires, up to an hour later. To stop sessions already in play, use the emergency stop in [the portal](/docs/portal.md) instead.