API overview
Base URL, authentication, organization routes, errors, idempotency, concurrency, pagination, rate limits and caching for the Depot REST API.
Everything a studio does on Depot goes through one public REST API: creating games, pushing releases, moving channels, publishing store pages, issuing keys and granting access. The depot CLI is a client of this API, so anything the CLI does, your own tools and agents can do too.
The pages in this section document every operation, generated from the API's OpenAPI document.
Base URL
https://api.staging.depot.gamesDepot runs on staging today. All routes are under /v1.
The OpenAPI 3.1 document is at https://api.staging.depot.games/v1/openapi.json. Each operation lists the permission it needs.
Authentication
Send a bearer token in the Authorization header:
curl https://api.staging.depot.games/v1/whoami \
-H "Authorization: Bearer dpk_your_key"There are two kinds of credential for studio work:
| Credential | Use it for | Looks like |
|---|---|---|
| Organization API key | CI, integrations and agents. Acts for one organization, with the permissions you gave it. | dpk_… |
| Signed-in session | A person, through the CLI or launcher. Acts with the person's current role in each organization. | an access token from Depot's sign-in |
Create API keys with depot api-key create. See CI and agents for permissions.
Tokens go only in the Authorization header. The API never accepts them in URLs.
GET /v1/whoami tells you what a credential is. For an API key, it returns the key id, its organization and its permissions:
{
"kind": "api_key",
"key_id": "…",
"organization": { "id": "…", "name": "My Studio", "slug": "my-studio", "created_at": "…" },
"permissions": { "game": ["read"], "release": ["publish"], "channel": ["write"] }
}Some routes need no credential at all, such as published store pages under /v1/public/… and the launcher catalog.
Organization routes
Studio operations always name the organization in the path:
/v1/organizations/{org}/games
/v1/organizations/{org}/games/{id}/releases
/v1/organizations/{org}/games/{id}/channels/{platform}/{name}{org} is the organization's id. Get it from GET /v1/whoami (API keys) or GET /v1/organizations (people). Game ids look like game_ followed by 32 hex characters.
On every request, Depot checks that the credential belongs to that organization right now and holds the permission the route needs. A removed member loses access on their next request. A game, release or key that belongs to another organization answers 404, so ids can't be probed across studios.
Only people manage people and keys. Routes for members, invitations and API keys refuse API keys with 403.
Errors
Errors use RFC 9457 problem details, with the content type application/problem+json:
{
"type": "https://depot.games/problems/forbidden",
"title": "forbidden",
"status": 403,
"detail": "this API key lacks channel:write",
"request_id": "req_0123456789abcdef0123456789abcdef"
}titleis a stable code. Branch on it, not ondetail, which is for people and may change.request_idmatches thex-request-idresponse header. Include it when you report a problem.- Some errors carry extra fields. A channel move that lost a race says where the channel is now:
{
"type": "https://depot.games/problems/revision_mismatch",
"title": "revision_mismatch",
"status": 409,
"detail": "channel changed since you read it",
"request_id": "req_0123456789abcdef0123456789abcdef",
"current_revision": 4,
"current_release_id": "rel_0123456789abcdef0123456789abcdef"
}Common codes:
| Status | Codes | Meaning |
|---|---|---|
400 | bad_request | A field is missing or invalid. detail names it. |
401 | unauthorized, unauthenticated, token_expired | No credential, or it's invalid, expired or revoked. |
403 | forbidden | The credential is valid but lacks the permission. |
404 | not_found | No such resource in this organization, or no such route. |
409 | conflict, revision_mismatch, game_archived | The request conflicts with the current state. Read again and decide. |
413 | payload_too_large, too_large | The body or upload is too big. |
422 | idempotency_mismatch, bad_signature, hash_mismatch, untrusted_key, invalid_image | The request is well formed but can't be accepted. |
429 | rate_limited | Too many requests of this kind. See Rate limits. |
5xx | internal, storage_error, … | A problem on Depot's side. Retry with backoff. |
Idempotency keys
Send an Idempotency-Key header on a POST to make retries safe:
curl -X POST "https://api.staging.depot.games/v1/organizations/$ORG/games" \
-H "Authorization: Bearer $DEPOT_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: create-my-game-1" \
-d '{"slug": "my-game", "name": "My Game"}'- The same credential, route, key and body returns the stored response instead of acting again. The replay carries the header
idempotent-replay: true. - The same key with a different body is
422 idempotency_mismatch. - Keys are 8 to 128 characters: letters, digits,
.,_,:and-. They're scoped to the credential and organization. - Only successful responses are stored. If a request failed, a retry with the same key runs it again.
- Granting an entitlement and issuing a batch of game keys require a key. A retried game key batch returns the batch without its codes, because codes are delivered once.
Optimistic concurrency
Resources that people and tools edit at the same time have a revision that goes up on every change. Send it back as expected_revision and Depot refuses the write with 409 if someone changed the resource since you read it:
- Channels (
PUT …/channels/{platform}/{name}):expected_revisionis required. Usenullto create a channel. A stale value gets409 revision_mismatchwith the current revision and release. A retried move can never move the channel twice. - Games, store pages and storefront profiles (
PATCH, and publishing a page):expected_revisionis optional. Leave it out to overwrite. A stale value gets409 conflictwith the currentrevision.
Pagination
List routes take limit (1 to 200) and cursor, and return a next_cursor:
{ "releases": [ … ], "next_cursor": "…" }Pass next_cursor as cursor to get the next page. null means there are no more pages.
Rate limits
Some operations are limited per organization, player or credential over a fixed time window. Examples are creating organizations and API keys, sending invitations, issuing game key batches, granting entitlements, redeeming keys and claiming games. Over the limit, you get 429 rate_limited. Wait and retry later, with backoff. Don't retry in a tight loop.
Caching
- Responses are
cache-control: no-storeunless a route says otherwise. Anything tied to a credential is never cached. - Published store pages and studio pages under
/v1/public/…send anETagand are cached for about a minute. SendIf-None-Matchto get304 Not Modifiedwhen nothing changed. After you publish a page, it can take that long to show everywhere. - The OpenAPI document is cached for a few minutes.
Uploads never pass through the API
Game builds don't travel through the API, and neither do store page videos. When you push, the API hands out short-lived presigned URLs, and the client uploads each object straight to storage. Large objects use multipart uploads, and the API completes them from storage's own list of parts. Players download the same way, with presigned URLs that expire after 15 minutes.
The API checks every uploaded object's size and hash, and the release's signature, before it publishes anything. See Push builds.
The depot push command does all of this for you. Use the upload routes directly only if you're writing your own publishing client.
Try it in the browser
Each operation page in this section has a playground that sends real requests to staging from your browser. Paste an organization API key to authenticate.
Use a staging key with only the permissions you need for the test, and revoke it when you're done. Never paste production secrets into the playground. Requests are real: a playground POST creates real resources in your organization.