# API overview (/docs/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 [#base-url] ```text https://api.staging.depot.games ``` Depot runs on staging today. All routes are under `/v1`. The OpenAPI 3.1 document is at [`https://api.staging.depot.games/v1/openapi.json`](https://api.staging.depot.games/v1/openapi.json). Each operation lists the permission it needs. ## Authentication [#authentication] Send a bearer token in the `Authorization` header: ```bash 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](/docs/publishing/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: ```json { "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 [#organization-routes] Studio operations always name the organization in the path: ```text /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] Errors use [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem details, with the content type `application/problem+json`: ```json { "type": "https://depot.games/problems/forbidden", "title": "forbidden", "status": 403, "detail": "this API key lacks channel:write", "request_id": "req_0123456789abcdef0123456789abcdef" } ``` - `title` is a stable code. Branch on it, not on `detail`, which is for people and may change. - `request_id` matches the `x-request-id` response 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: ```json { "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](#rate-limits). | | `5xx` | `internal`, `storage_error`, … | A problem on Depot's side. Retry with backoff. | ## Idempotency keys [#idempotency-keys] Send an `Idempotency-Key` header on a `POST` to make retries safe: ```bash 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 [#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_revision` is required. Use `null` to create a channel. A stale value gets `409 revision_mismatch` with 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_revision` is optional. Leave it out to overwrite. A stale value gets `409 conflict` with the current `revision`. ## Pagination [#pagination] List routes take `limit` (1 to 200) and `cursor`, and return a `next_cursor`: ```json { "releases": [ … ], "next_cursor": "…" } ``` Pass `next_cursor` as `cursor` to get the next page. `null` means there are no more pages. ## Rate limits [#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 [#caching] - Responses are `cache-control: no-store` unless a route says otherwise. Anything tied to a credential is never cached. - Published store pages and studio pages under `/v1/public/…` send an `ETag` and are cached for about a minute. Send `If-None-Match` to get `304 Not Modified` when 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 [#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](/docs/publishing/push#what-depot-checks-before-it-publishes). 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 [#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. # CLI reference (/docs/cli) This page lists the `depot` commands studios and players use, grouped by task. Each command also prints its own help: ```bash depot --help depot push --help depot game page --help ``` ## Common options [#common-options] These work on every command that talks to Depot: | Option | What it does | | --- | --- | | `--api ` | The API URL. Default: `$DEPOT_API`, then the default saved by `depot auth login`. Use `https://api.staging.depot.games`. | | `--json` | Machine-readable output, on commands that list it. Most other commands print JSON already. | | `-v`, `-vv`, `-vvv` | More logging (info, debug, trace). `RUST_LOG` overrides it. | | `-h`, `--help` | Help for the command. | | `-V`, `--version` | The CLI version. | Credentials: commands that act for an organization use `DEPOT_TOKEN` or a key stored with `depot auth login` first, then the session from `depot login`. See [Install the CLI](/docs/getting-started/install-cli). `--game` takes a game's slug or id. `--org` takes an organization's id or slug, and defaults to the API key's organization or the only one you belong to. Where `--platform` is optional, it defaults to the machine you run the command on, such as `linux-x64` or `macos-arm64`. --- ## Account [#account] ### depot login [#depot-login] Sign in to Depot in the browser. The session is kept in the OS keychain. | Option | Description | | --- | --- | | `--no-browser` | Print the sign-in URL instead of opening the browser. | | `--timeout-secs ` | Give up after this many seconds. Default `300`. | | `--json` | JSON output. | ### depot whoami [#depot-whoami] Show the signed-in Depot identity and your @handle. Options: `--json`. ### depot logout [#depot-logout] Sign out: revoke the session and remove it from the OS keychain. ### depot auth [#depot-auth] Store an organization API key for an API, or show the current credential. For people, `depot login` is the usual way in. | Subcommand | Description | Options | | --- | --- | --- | | `login` | Store an organization API key (`dpk_…`). It's read from standard input or `$DEPOT_TOKEN`, never from a flag. Also saves the API as the default. | `--api ` (required) | | `status` | Show the current credential. API key: its organization and permissions. Person: identity and organizations. | | | `logout` | Forget the stored API key for an API. | | ```bash depot auth login --api https://api.staging.depot.games < ci.key ``` --- ## Studio [#studio] ### depot org [#depot-org] Organizations (studios): create, list, members, invitations and the audit log. | Subcommand | Description | Options | | --- | --- | --- | | `create` | Create an organization. You become its owner. | `--slug ` (3-48 lowercase letters, digits and `-`), `--name `, `--json` | | `list` | Organizations you belong to. | `--json` | | `show` | An organization, and your role and permissions in it. | `--org` | | `members` | Members of an organization. | `--org` | | `invite` | Invite someone by email. They accept on the invitation page Depot emails them. | `--org`, `--email `, `--role `: `owner`, `admin`, `publisher` or `member` (default `member`) | | `set-role` | Change a member's role. | `--org`, `--user ` (see `org members`), `--role ` | | `remove` | Remove a member, or yourself to leave. Their access ends on their next request. | `--org`, `--user ` | | `audit` | The organization's audit log, newest first. | `--org`, `--limit ` (default `50`) | ### depot api-key [#depot-api-key] Organization API keys for CI, integrations and agents. Managing keys needs a signed-in person. See [CI and agents](/docs/publishing/ci-and-agents). | Subcommand | Description | Options | | --- | --- | --- | | `create` | Create a key. Permissions must be a subset of your role. Keys can't manage members or keys. The secret is written to `--out` (owner-only file) or printed with `--print`, once. | `--org`, `--name `, `--permissions ` (comma-separated `resource:action`, for example `release:publish,channel:write,game:read`), `--expires-in-days <1-365>` (default `90`), `--out `, `--print` | | `list` | The organization's keys (never the secrets). | `--org` | | `rotate` | Replace a key with the same name and permissions. The old key stops now, or after the grace period. | `--org`, `--id `, `--grace-seconds ` (default `0`), `--out `, `--print` | | `revoke` | Revoke a key immediately. | `--org`, `--id ` | --- ## Games and builds [#games-and-builds] ### depot game [#depot-game] Create, show and edit games and their store pages. | Subcommand | Description | Options | | --- | --- | --- | | `create` | Create a game in an organization. | `--org`, `--slug `, `--name `, `--json` | | `show ` | Show a game (public fields). | | | `page` | Write the game's store page and, optionally, publish it. Prints the page URL. | see below | | `art` | Upload store art. | `--game`, `--kind `: `icon`, `cover`, `hero` or `logo` (the game's wordmark, a transparent PNG or WebP), `--file ` | | `screenshot` | Upload a screenshot and add it to the end of the page's gallery (draft). | `--game`, `--file `, `--caption ` | | `video` | Upload an MP4 for the page (draft). | `--game`, `--file `, `--purpose `, `--poster `, `--caption ` | | `update` | List the game in the launcher catalog (or unlist it), set its description. | `--game`, `--listed `, `--description ` | `depot game page` options: | Option | Description | | --- | --- | | `--game` | The game. | | `--tagline ` | The line under the title. | | `--short-description ` | Shown in link previews. | | `--description ` | Plain text: paragraphs, `- ` lists, `**bold**` and https links. | | `--access ` | `free` (anyone signed in can claim it), `key` (redeem a key) or `invite` (grants only). | | `--status ` | `alpha`, `beta`, `early-access` or `released`. | | `--channel ` | The channel players install from. Default `stable`. | | `--publish ` | Publish after writing: `public` (listed) or `unlisted` (link only). | | `--unpublish` | Take the page down instead. Players who own the game keep it. | | `--listed ` | Also list the game in the Depot launcher's catalog. It shows there while the page is public and free or key only. | | `--accent ` | The page's colour, for example `"#e0a030"` (Depot adjusts it for contrast), or `none`. | | `--accessibility ` | Accessibility notes, comma-separated, for example `"Full controller support, Subtitles"`. | `depot game video --purpose`: `loop` plays muted over the hero (up to 30 seconds and 40 MB), `trailer` plays on request, `clip` goes in the gallery (default). `--poster` is the still frame shown before it plays (PNG, JPEG or WebP). Images for `art` and `screenshot` must be `.png`, `.jpg` or `.webp`. More in [Store pages](/docs/store/pages). ### depot keys [#depot-keys] Ed25519 signing keys for your releases. Not game keys: see [`depot game-keys`](#depot-game-keys). Guide: [Signing keys](/docs/publishing/signing-keys). | Subcommand | Description | Options | | --- | --- | --- | | `generate` | Generate a signing key. The private key stays local, in an owner-only file. | `--out ` (default `~/.config/depot/keys/default.json`), `--force` (replace an existing file; the old key is lost) | | `enroll` | Enroll the key's public half for a game. | `--game`, `--key ` | | `revoke` | Revoke a key for a game. | `--game`, `--key-id ` | | `list` | A game's keys and their states. | `--game` | ### depot sign [#depot-sign] Sign a release descriptor offline. | Option | Description | | --- | --- | | `--descriptor ` | The descriptor JSON to sign. Required. | | `--key-id ` | The enrolled key id to sign as. Required. | | `--key ` | The signing key file. Default `~/.config/depot/keys/default.json`. | ### depot push [#depot-push] Build a release from a folder and publish it. Resumable. Guide: [Push builds](/docs/publishing/push). | Option | Description | | --- | --- | | `--game ` | The game. Required. | | `--release ` | The release name, for example `1.2.0`. Required. | | `--input ` | The build folder. Required. | | `--platform ` | The platform, for example `windows-x64`. Default: this machine. | | `--package ` | The package. Default `base`. | | `--key ` | The signing key file. | | `--content-key ` | The content key backup file. Default `/content-keys/.json`. | | `--encrypt` | Encrypt even though the game is public. Only matters for older public games. | | `--channel ` | Move this channel to the new release after publishing. | | `--note ` | What's in this release, for players (the store page's "What's new"). Needs `--channel`. | | `--launch-file ` | JSON array of launch targets: `[{"platform": "linux-x64", "path": "bin/game", "args": [], "working_dir": ".", "env": {}}]`. | | `--rebuild` | Ignore a staged build from an earlier, interrupted push. | | `--min-chunk-kib ` | Smallest chunk. Default `512`. | | `--avg-chunk-kib ` | Average chunk. Default `2048`. | | `--max-chunk-kib ` | Largest chunk. Default `8192`. | | `--pack-mib ` | Target pack size. Default `128`. | | `--zstd-level ` | zstd compression level. Default `3`. | | `--parallel ` | Parallel uploads. Default `4`. | | `--json` | Print the push report as JSON. | Keep the chunk settings the same from release to release. Changing them moves chunk boundaries, so fewer chunks match earlier releases and more is uploaded and downloaded. ### depot release [#depot-release] List or show releases. Guide: [Channels and releases](/docs/publishing/channels-and-releases). | Subcommand | Description | Options | | --- | --- | --- | | `list` | A game's releases, newest first. | `--game`, `--platform` | | `show` | One release. | `--game`, `--release-id ` | ### depot channel [#depot-channel] Move or show channels. | Subcommand | Description | Options | | --- | --- | --- | | `set` | Point a channel at a published release (compare-and-swap). | `--game`, `--platform`, `--channel `, `--release-id `, `--allow-rollback`, `--note ` (what changed, for players: the store page's "What's new") | | `show` | The release a channel points at, and its revision. | `--game`, `--platform`, `--channel ` | --- ## Players and access [#players-and-access] ### depot package [#depot-package] Packages of a game, the things players can own: `base`, `dlc`, `bundle` or `test`. | Subcommand | Description | Options | | --- | --- | --- | | `create` | Define a package. Content packages (`base`, `dlc`, `test`) hold the releases pushed with `depot push --package `. A bundle includes other packages. | `--game`, `--name ` (matches `depot push --package`; default `base`), `--kind ` (default `base`), `--title `, `--includes <ids>` (bundle members, package ids, comma-separated), `--free-claim` (any signed-in player may claim it for free), `--json` | | `list` | A game's packages. | `--game` | | `update` | Enable or disable a package, or change its title. A disabled package grants nothing new. | `--game`, `--id <package id>`, `--enabled <true\|false>`, `--title <title>` | ### depot entitlement [#depot-entitlement] Grant, list or revoke players' entitlements to your packages. | Subcommand | Description | Options | | --- | --- | --- | | `grant` | Grant a package to a Depot user, by user id or @handle. If they already own it, nothing changes. | `--game`, `--package <name or id>` (default `base`), `--user <user id>`, `--handle <@handle>`, `--ends-at <time>` (time-limited access, ISO 8601), `--reference <text>` (an external reference, such as an order id), `--json` | | `list` | Entitlements to your packages. | `--game`, `--package`, `--user` | | `revoke` | Revoke an entitlement. New downloads and key delivery stop now. Installed copies keep launching. | `--game`, `--id <entitlement id>`, `--reason <text>` | ### depot game-keys [#depot-game-keys] Game keys for a package: issue a batch, list, revoke. Not signing keys: see [`depot keys`](#depot-keys). | Subcommand | Description | Options | | --- | --- | --- | | `create` | Issue a batch of single-use keys. The codes are delivered once. | `--game`, `--package <name or id>` (default `base`), `--quantity <1-1000>`, `--label <text>`, `--expires-at <time>` (ISO 8601; unredeemed keys stop working after this), `--out <file>` (codes one per line, owner-only file), `--print`, `--idempotency-key <key>` (reuse it to retry safely after a timeout; the same key never issues a second batch) | | `list` | Batches with available, redeemed and revoked counts. Never codes. | `--game` | | `revoke` | Revoke a batch's still-available keys. Redeemed keys keep their entitlements. | `--game`, `--batch <batch id>`, `--reason <text>` | ### depot redeem [#depot-redeem] Redeem a game key into your library. Needs `depot login`. ```bash depot redeem # paste the key; read from standard input, so it stays out of shell history depot redeem <code> ``` Options: `--json`. ### depot library [#depot-library] The games you own. Needs `depot login`. Options: `--json`. ### depot license [#depot-license] Offline licenses for games that use the [Depot SDK](/docs/sdk). The license is stored where the game can read it. | Subcommand | Description | Options | | --- | --- | --- | | `refresh` | Fetch your account's offline license for a game and store it. | `--game <slug or id>`, `--json` | | `show` | The cached license for a game: state, expiry, entitlements. No network. | `--game`, `--json` | ### depot retail [#depot-retail] Retailer delegation: a publisher lets a retailer organization issue keys, and the retailer issues them per order. | Subcommand | Who | Description | Options | | --- | --- | --- | --- | | `delegate` | Publisher | Let a retailer organization issue keys for a package, up to a quota. | `--game`, `--package` (default `base`), `--retailer <org id>`, `--quota <n>`, `--expires-at <time>` | | `delegations` | Publisher | Delegations you granted, for the game's organization. | `--game` | | `revoke` | Publisher | Stop a delegation now. Issued keys stay valid unless you pass `--revoke-issued-keys`. | `--game`, `--id <delegation id>`, `--revoke-issued-keys` | | `holdings` | Retailer | Delegations you hold and what's left of them. | `--org` (default: the API key's) | | `issue` | Retailer | Issue keys for an order. Unique per order id. Codes are delivered once. | `--org`, `--delegation <id>`, `--order <order id>`, `--quantity <n>` (default `1`), `--out <file>`, `--print` | ### depot access [#depot-access] Hand-issued download grants for restricted games. Players normally get access from their entitlements instead. | Subcommand | Description | Options | | --- | --- | --- | | `grant` | Issue a download grant and write it to an owner-only file, or print it. | `--game`, `--package` (default `base`), `--ttl-days <n>` (default `7`), `--max-sessions <n>` (default `20`), `--out <file>`, `--print` | | `revoke` | Revoke a grant. Its sessions stop working. | `--game`, `--grant-id <dpg_…>` | --- ## Tools [#tools] ### depot doctor [#depot-doctor] Check configuration, API reachability, credentials and, optionally, an installation. | Option | Description | | --- | --- | | `--destination <dir>` | Also check this installed game: its record, trusted keys and files. | | `--json` | JSON output. | It exits with `0` when every check passes and `5` when one fails. --- ## Low-level tools [#low-level-tools] The CLI also includes the engine underneath Depot. Most studios never need these. **Installing from Depot.** `depot install --game <game> --destination <dir>` installs a game you own, and `depot update --destination <dir>` follows its channel. `depot verify` checks an installation against its release, and `depot recover` finishes or undoes an interrupted install or update. The launcher does all of this for players. Useful options include `--platform`, `--channel`, `--allow-rollback` (accept a channel that moved to an older release), `--trust-key <key>` (trust exactly this publisher key on first install) and `--trust-new-keys` (also trust keys enrolled after the first install). **Offline repositories and analysis.** These work on local folders and don't talk to Depot: | Command | Purpose | | --- | --- | | `build` | Chunk a release folder and publish it into a local repository. | | `delta` | Add zstd patch (delta) frames for an already-published local release. | | `inspect` | Report file, chunk and pack statistics for a release. | | `serve` | Serve a repository's packs over HTTP with range support. | | `plan` | Show the download requests for a release without downloading (dry run). | | `bench`, `bench-chain` | Benchmark the pipeline across scenarios and network profiles. | | `fixture` | Generate deterministic test trees. | | `compare` | Compare two folders: paths, bytes and executable bits. | Run any of them with `--help` for their options. # Concepts (/docs/getting-started/concepts) This page explains the words the rest of the docs use. Read it once and the commands will make sense. ## The shape of it [#the-shape-of-it] <Files> <Folder name="Studio (organization)" defaultOpen> <File name="Members and API keys" /> <File name="Studio page" /> <Folder name="Game" defaultOpen> <File name="Signing keys" /> <File name="Releases (one per build, per platform)" /> <File name="Channels (stable, beta, ...)" /> <File name="Packages (base, dlc, bundle, test)" /> <File name="Game keys" /> <File name="Store page (who can get it)" /> </Folder> </Folder> </Files> Players sit outside your studio. They have a Depot account, and they own packages through **entitlements**. ## Studio [#studio] A studio is an **organization** on Depot. Everything you publish belongs to it: games, builds, signing keys, store pages and game keys. People join with a role (owner, admin, publisher or member), and their permissions come from that role. CI and agents use **organization API keys** (`dpk_…`) with a chosen set of permissions. ```bash depot org create --slug my-studio --name "My Studio" depot org invite --email teammate@example.com --role publisher ``` The studio slug appears in your store URLs and never changes. ## Game [#game] A game has a permanent slug, an id (`game_…`), and everything below. Create one with `depot game create`. ## Release [#release] A release is one signed build of your game for one platform, with a name you choose, such as `1.0.0`. Releases are immutable. Pushing again with new files makes a new release. Depot stores builds as content-defined chunks, so a new release uploads only the chunks that changed, and players download only those chunks when they update. ## Platform [#platform] Each release targets one platform, named like `macos-arm64`, `windows-x64` or `linux-x64`. Push once per platform. The launch file you push with the release tells the launcher which file to start on each platform. ## Channel [#channel] A channel is a named pointer to a release, per platform. Players install from a channel and follow it. When you move `stable` to a newer release, players get the update. Your store page installs from `stable` unless you choose another channel, and you can keep others, such as `beta`. ```bash depot channel set --game my-game --platform macos-arm64 --channel stable --release-id <release id> ``` Moving a channel to an older release is a rollback and needs `--allow-rollback`. ## Signing key [#signing-key] You sign every release with your own Ed25519 key. The private key stays on your machine. You enroll the public half for the game, and Depot checks every release against it. The launcher and CLI check it again before they install anything, and they remember the keys a game was first installed with. Depot never re-signs your builds. ```bash depot keys generate depot keys enroll --game my-game ``` ## Protection and encryption [#protection-and-encryption] **Every game is restricted.** Its builds are encrypted chunk by chunk, and only a player who owns the game can download them and receive the key to decrypt them. A store page never authorizes a download on its own. Only an entitlement does. The first push to a game creates its content key and saves a backup on your machine. You need that backup for every later push, and Depot never returns it. Once a game is installed, it plays offline. Depot doesn't add runtime DRM to your game. If you want the game itself to check ownership, use the [Depot SDK](/docs/sdk). ## Package [#package] A package is something a player can own. Every game starts with a `base` package. You can add more: | Kind | What it is | | --- | --- | | `base` | The game itself. Holds the builds you push (the default for `depot push`). | | `dlc` | Extra content. Push builds to it with `--package <name>`, or leave it empty and check it with the SDK. | | `bundle` | A set of other packages from the same game. Owning a bundle means owning everything in it. | | `test` | Test builds you want to give to some players only. | Owning a DLC never gives a player the base game. Ownership only flows from a bundle to its contents. ```bash depot package create --game my-game --name expansion-1 --kind dlc --title "Expansion 1" ``` ## Entitlement [#entitlement] An entitlement says one player owns one package. It records where it came from: a free claim, a redeemed key, or a grant from you. It can end at a set time, and you can revoke it. ```bash depot entitlement grant --game my-game --handle @player ``` Revoking stops new downloads and updates at once. Copies already installed keep launching. ## Store page and who can get it [#store-page-and-who-can-get-it] Each game can have a public store page at `/p/<studio>/<game>`. The page's **access** setting decides who can get the game: | Access | What players do | | --- | --- | | Free | Anyone signed in with a Depot account claims it from the page. | | Key only | Players redeem a game key from you, on the page or in the launcher. | | Invite only | The page describes the game. Players get it only from keys or grants you give them. | You set access with `depot game page --access free|key|invite`. It applies when you publish and you can change it any time. Players who already own the game keep it. A page can be **public** (listed on your studio page and indexed by search engines) or **unlisted** (served by link only). Unlisted means "not listed". It isn't a secret, and it doesn't control access. Unpublishing takes the page down and stops new free claims. Nobody loses a game they already own. ## Game keys [#game-keys] Game keys are single-use codes for a package, like `DPT1-XXXX-…`. You issue them in batches, and the codes are shown once: ```bash depot game-keys create --game my-game --quantity 100 --label press --out keys.txt ``` A player who redeems a key gets an entitlement. A player who already owns the package keeps the key unused, so they can pass it on. Game keys are different from signing keys. See [Keys and access](/docs/store/keys-and-access). ## Launcher catalog [#launcher-catalog] The Depot launcher has an **Available to get** list. You can choose to list your game there with `depot game page --listed true`. A listed game shows only while its page is public and its access is Free or Key only. Invite-only and unpublished games never show. Depot has no global store, ranking or search. Players find your game through the links you share. # How players get your game (/docs/getting-started/how-players-get-your-game) Players meet Depot on your store page, from a link you shared. This page walks through what they see, step by step, so you can explain it to them and know what to expect. <Steps> <Step> ### They open your link [#they-open-your-link] Your page is at `https://staging.depot.games/p/<studio>/<game>` on staging. Anyone can read it without an account. It shows your art, description, platforms (from the builds on the page's channel), status, requirements and a button that depends on the page's access: | Access | Button | | --- | --- | | Free | **Sign in to get it free**, then **Get it free** | | Key only | **Sign in to redeem a key**, then a key field and **Redeem key** | | Invite only | **Invite only**, with a note that you give access directly | </Step> <Step> ### They sign in [#they-sign-in] The button takes them to the Depot sign-in page, where they sign in or create an account. Depot then sends them back to your page. For a free game, the claim happens as soon as they're back. </Step> <Step> ### They claim the game or redeem a key [#they-claim-the-game-or-redeem-a-key] - **Free:** the game goes into their Depot library. Claiming twice does nothing new. - **Key only:** they paste the key you gave them. If the key belongs to a different game, that game goes into their library instead and the page says so. - **Invite only:** if you've granted them access or they've redeemed a key elsewhere, the page shows the game as theirs once they sign in. The page then says **You own this game**. </Step> <Step> ### They press Install with Depot [#they-press-install-with-depot] The page hands off to the Depot launcher with a link like this: ```text depot://game/game_0123456789abcdef0123456789abcdef?v=1&channel=stable ``` The launcher accepts only this shape: a Depot game id, an optional channel name, and nothing else. It never runs a path or command from a link. It opens your game (adding it to the library if needed) and installs from the page's channel. If the launcher doesn't open, the page offers the launcher download and asks the player to sign in there with the same account. The game stays in their account, so it's waiting in the launcher's library. <Callout type="info"> On staging, the signed launcher installer is available for macOS only. Windows and Linux installers aren't published yet. </Callout> </Step> <Step> ### The launcher installs it [#the-launcher-installs-it] If the player isn't signed in to the launcher, it asks them to sign in through the browser first. Then it shows the version, channel, download size and space needed, and that the build is signed by your studio. Pressing **Install**: 1. Checks the release signature against your enrolled signing key. 2. Uses the player's entitlement to get access to the encrypted build. 3. Downloads the chunks, decrypts them and checks every file against the signed release. 4. Stores an offline license for the game, for games that use the [Depot SDK](/docs/sdk). An interrupted install can be finished later without starting over. </Step> <Step> ### They play [#they-play] **Play** starts the game using the launch file you pushed. When the game quits, the launcher shows **Play** again. Once installed, the game plays offline. </Step> </Steps> ## Updates [#updates] When you move the channel to a newer release, the launcher shows **Update ready**. It checks on its own while the launcher runs (players can turn that off in Settings), and players can press **Check for updates** at any time. An update downloads only the chunks that changed. If an update fails or is interrupted, the previous version stays in place and playable. ## Verify, repair and uninstall [#verify-repair-and-uninstall] - **Verify files** checks every installed file against what you signed. If something is missing or changed, **Repair** downloads only what's needed. - **Uninstall** removes only the files Depot installed. Saves, mods and other files the player or the game created in the folder stay. The game stays in the library to install again. ## Offline and revoked access [#offline-and-revoked-access] - A game that's installed keeps launching offline. Depot doesn't put runtime DRM in your game. - If you revoke a player's entitlement, new downloads and updates stop at once. The copy already installed keeps launching. - Games that use the Depot SDK can read the player's offline license to check the base game and DLC. See [Offline licenses](/docs/sdk/offline-licenses). ## From the launcher or the CLI [#from-the-launcher-or-the-cli] Players can also get your game without visiting the page: - In the launcher, a game you list in the catalog shows under **Available to get**. Free games have **Get it free**. Key-only games ask for a key. - In the launcher, **Redeem a game key** adds any game a key belongs to. - With the CLI, a player runs `depot login`, then `depot redeem` (it reads the key from standard input), then installs: ```bash depot install --game my-game --destination ~/Games/my-game depot update --destination ~/Games/my-game depot verify --destination ~/Games/my-game ``` # Install the CLI (/docs/getting-started/install-cli) The `depot` CLI does everything a studio does on Depot: create games, sign and push builds, publish store pages and manage keys. Players can also use it to install games, though most use the launcher. ## Get the binary [#get-the-binary] There's no prebuilt download of the CLI yet. You build it from the Depot source with Rust. <Steps> <Step> ### Install Rust [#install-rust] Install Rust with [rustup](https://rustup.rs). The repository pins its toolchain in `rust-toolchain.toml` (1.95.0), and rustup installs that version for you the first time you build. </Step> <Step> ### Build [#build] From the root of the repository: ```bash cargo build --release -p depot-cli ``` The binary is `target/release/depot`. Copy it somewhere on your `PATH`, or install it into Cargo's bin directory instead: ```bash cargo install --path crates/depot-cli ``` </Step> <Step> ### Check it [#check-it] ```bash depot --version depot --help ``` </Step> </Steps> The CLI is tested on macOS (Apple silicon) and Linux. <Callout type="info"> Another tool is also called `depot`: the CLI for depot.dev, a CI service that has nothing to do with this project. If `depot --help` doesn't list commands like `push` and `game`, the wrong binary is first on your `PATH`. </Callout> ## Choose the API [#choose-the-api] Every command that talks to Depot takes `--api`. The CLI picks the URL in this order: 1. The `--api` flag. 2. The `DEPOT_API` environment variable. 3. The default saved by `depot auth login`. Depot runs on staging today, so set: ```bash export DEPOT_API=https://api.staging.depot.games ``` Put that line in your shell profile so every session uses it. `depot login` doesn't save a default, so without `DEPOT_API` you'd pass `--api` each time. ## Sign in [#sign-in] There are two kinds of credential. Use `depot login` on your own machine, and an organization API key for CI and agents. <Tabs items={['You (depot login)', 'CI and agents (API key)']}> <Tab value="You (depot login)"> ```bash depot login depot whoami ``` `depot login` opens the Depot sign-in page in your browser. The CLI keeps only a session in the OS keychain, never your password. On a machine without a browser, add `--no-browser` and open the printed URL yourself. Your permissions come from your role in each organization. Sign out with `depot logout`, which also revokes the session. </Tab> <Tab value="CI and agents (API key)"> Create a key while signed in as yourself. Give it only the permissions the job needs: ```bash depot api-key create --name ci \ --permissions release:publish,channel:write,game:read \ --expires-in-days 90 --out ci.key ``` The secret (`dpk_…`) is written to `ci.key` with owner-only permissions, once. Depot can't show it again. Store it in your CI's secret store and delete the local file. In CI, set the key as `DEPOT_TOKEN`: ```bash export DEPOT_API=https://api.staging.depot.games export DEPOT_TOKEN=dpk_your_key depot push --game my-game --platform linux-x64 --release 1.0.1 --input build/ --channel stable ``` On a machine you control, you can store the key instead. `depot auth login` reads it from standard input (or `DEPOT_TOKEN`), never from a flag, so it stays out of your shell history: ```bash depot auth login --api https://api.staging.depot.games < ci.key depot auth status ``` `depot auth login` also saves that API as the default. `depot auth logout` forgets the stored key. An API key's permissions can't go beyond its creator's role, and a key can never manage members or other keys. Rotate a key with `depot api-key rotate --id <key id>` and revoke it with `depot api-key revoke --id <key id>`. </Tab> </Tabs> When both exist, an API key wins: the CLI uses `DEPOT_TOKEN` or a stored key first, and falls back to your `depot login` session. ## Check your setup [#check-your-setup] ```bash depot doctor ``` `depot doctor` checks your credentials file, whether the API answers (and which environment it is), the API key in use if there is one, and the CLI's state directory. Add `--destination ~/Games/my-game` to also check an installed game. Add `--json` for machine-readable output. ## Where the CLI keeps things [#where-the-cli-keeps-things] | What | Where | | --- | --- | | Config, stored API keys | `~/.config/depot/` (or `$XDG_CONFIG_HOME/depot`, or `$DEPOT_CONFIG_DIR`) | | Signing keys | `~/.config/depot/keys/` | | Content key backups | `~/.config/depot/content-keys/` | | Your `depot login` session | the OS keychain | Back up the signing keys and content key backups. See [Concepts](/docs/getting-started/concepts) for what each one does. ## Logging [#logging] Every command accepts `-v`, `-vv` or `-vvv` for more detail. `RUST_LOG` overrides those flags. Most commands also take `--json`. Full command reference: [CLI](/docs/cli). # Quickstart (/docs/getting-started/quickstart) This guide takes you from nothing to a playable link. You sign in, create a game, sign and push a build, publish a free store page and share the link. It takes about fifteen minutes, plus the upload time for your build. <Callout type="info"> Depot runs on staging today. Production (`depot.games`) isn't launched yet, so every example uses `https://api.staging.depot.games` and store pages live on `https://staging.depot.games`. </Callout> ## Before you start [#before-you-start] You need: - The `depot` CLI. See [Install the CLI](/docs/getting-started/install-cli). - A finished build of your game in a folder, for example `build/`. - A web browser to sign in. Point the CLI at staging for this shell session: ```bash export DEPOT_API=https://api.staging.depot.games ``` <Steps> <Step> ### Sign in [#sign-in] ```bash depot login ``` Your browser opens the Depot sign-in page. Sign in, or choose **Create an account** if you're new. The CLI keeps your session in the OS keychain. Check who you are with: ```bash depot whoami ``` On a machine without a browser, run `depot login --no-browser` and open the printed URL somewhere else. </Step> <Step> ### Create your studio [#create-your-studio] A studio is an organization on Depot. Games, builds, keys and store pages belong to it, and you become its owner. ```bash depot org create --slug my-studio --name "My Studio" ``` The slug is part of your store URLs and can't change later. It takes 3 to 48 lowercase letters, digits and `-`. </Step> <Step> ### Create a game [#create-a-game] ```bash depot game create --slug my-game --name "My Game" ``` Every new game is restricted: its builds are encrypted, and a player needs to own the game to download it. You decide who can get it later, on the store page. The game also gets a `base` package automatically, which is what players own. See [Concepts](/docs/getting-started/concepts). The game's slug is permanent too. Run `depot game show my-game` to see its id (`game_…`). </Step> <Step> ### Create and enroll a signing key [#create-and-enroll-a-signing-key] You sign every build with your own key. Depot and the launcher check that signature before anything is installed. ```bash depot keys generate depot keys enroll --game my-game ``` `depot keys generate` writes an Ed25519 key to your Depot config directory (`~/.config/depot/keys/default.json` by default) with owner-only permissions. The private key never leaves your machine. `depot keys enroll` uploads only the public half. <Callout type="warn"> Back up the key file. Pushes from CI or another machine need the same key, and players' launchers trust the key you enrolled first. </Callout> </Step> <Step> ### Push a build [#push-a-build] Tell the launcher how to start the game with a launch file. This one is for a macOS build: ```json title="launch.json" [ { "platform": "macos-arm64", "path": "MyGame.app/Contents/MacOS/MyGame", "args": [], "working_dir": "." } ] ``` Then push the folder, move the `stable` channel to it, and add a release note for players: ```bash depot push --game my-game --platform macos-arm64 --release 1.0.0 \ --input build/ --launch-file launch.json \ --channel stable --note "First public build." ``` `depot push` splits the build into chunks, encrypts them, uploads only what Depot doesn't already have, and signs the release with your key. If the upload is interrupted, run the same command again and it picks up where it stopped. Platform names look like `macos-arm64`, `windows-x64` or `linux-x64`. Push once per platform. Without `--platform`, the CLI uses the platform of the machine you run it on. <Callout type="warn"> The first push creates the game's content key and writes a backup to `~/.config/depot/content-keys/`. Back that file up. Every later push needs it, and Depot never gives it back to you. </Callout> More on builds, channels and release notes: [Push builds](/docs/publishing/push). </Step> <Step> ### Write and publish a free store page [#write-and-publish-a-free-store-page] ```bash depot game page --game my-game \ --tagline "A short line about your game." \ --short-description "Shown in link previews." \ --description "What the game is about. Paragraphs, - lists, **bold** and https links work." \ --status early-access \ --access free \ --publish public ``` `--access free` lets anyone with a Depot account claim the game. Use `key` if players need a key from you, or `invite` if you give access directly. `--publish unlisted` serves the page by link only. The command prints the page URL. Add art and screenshots whenever you like: ```bash depot game art --game my-game --kind cover --file cover.png depot game art --game my-game --kind hero --file hero.png depot game screenshot --game my-game --file shot-1.png --caption "The first level" ``` Screenshots go into the page draft. Run `depot game page --game my-game --publish public` again to publish them. See [Store pages](/docs/store/pages). </Step> <Step> ### Share the link [#share-the-link] Your page lives at: ```text https://staging.depot.games/p/my-studio/my-game ``` The URL uses your studio and game slugs, so it never changes. Post it wherever your players are. </Step> </Steps> ## What a player sees [#what-a-player-sees] 1. They open your link and see your page: art, description, platforms and a **Sign in to get it free** button. 2. They sign in or create a Depot account and come straight back to the page. The game is added to their library. 3. They press **Install with Depot**. The Depot launcher opens on your game. If they don't have the launcher yet, the page offers it for download. 4. The launcher downloads, decrypts and verifies the build, then shows **Play**. From then on the game plays offline and updates when you move the `stable` channel. The full journey is in [How players get your game](/docs/getting-started/how-players-get-your-game). ## Next steps [#next-steps] <Cards> <Card title="Concepts" href="/docs/getting-started/concepts" description="Studios, games, channels, packages and entitlements in plain words." /> <Card title="Keys and access" href="/docs/store/keys-and-access" description="Give out game keys, grant access to testers, or make a game invite only." /> <Card title="Depot SDK" href="/docs/sdk" description="Check ownership and DLC from inside your game, offline." /> <Card title="CLI reference" href="/docs/cli" description="Every command and flag." /> </Cards> # Introduction (/docs) Depot is game distribution for small studios. You push a signed build, write a store page, and share the link. Players claim your game or redeem a key, install it with the Depot launcher, and get fast, verified updates. Your game can check that a player owns it, even offline, with the Depot SDK. There's no marketplace in between. The link is yours, the players are yours, and so are the keys. <Callout type="info"> Depot is in staging. Every example here uses the staging service at `https://api.staging.depot.games`. Production isn't launched yet. </Callout> ## Start here [#start-here] <Cards> <Card title="Quickstart" href="/docs/getting-started/quickstart" description="From a build folder to a playable link in a few commands." /> <Card title="Concepts" href="/docs/getting-started/concepts" description="Games, releases, channels, packages and who can get your game." /> <Card title="How players get your game" href="/docs/getting-started/how-players-get-your-game" description="What happens between your link and their Play button." /> </Cards> ## Everything works from the CLI and the API [#everything-works-from-the-cli-and-the-api] The dashboard is one client of a public API. Anything you do there, you can do with the `depot` CLI or a plain HTTP request, so your CI pipeline or an AI agent can publish a game end to end. ```bash depot game create --slug lantern-keep --name "Lantern Keep" depot push --game lantern-keep --platform macos-arm64 --release 1.0.0 \ --input ./build --channel stable --note "The first release." depot game page --game lantern-keep --tagline "Keep the lantern lit." \ --access free --publish public ``` <Cards> <Card title="Publishing builds" href="/docs/publishing/push" description="Push, channels, release notes, rollbacks and signing keys." /> <Card title="Store pages" href="/docs/store/pages" description="Write your page, brand it with your art, and choose who can get the game." /> <Card title="Depot SDK" href="/docs/sdk" description="Check ownership and DLC in your game, online or off." /> <Card title="CLI reference" href="/docs/cli" description="Every command and flag." /> <Card title="API reference" href="/docs/api" description="Every endpoint, with a playground against staging." /> </Cards> ## For agents [#for-agents] This site publishes [`/llms.txt`](/llms.txt) and [`/llms-full.txt`](/llms-full.txt), and every page has a Markdown version. The API's own OpenAPI document is at `https://api.staging.depot.games/v1/openapi.json`. # Channels and releases (/docs/publishing/channels-and-releases) A **release** is one build you pushed: a name like `1.2.0`, for one platform, signed and immutable. A **channel** is a named pointer, like `stable` or `beta`, that points at one release of one platform. Players install and update from a channel, never from a release directly. You push releases whenever you like. Players only get one when you move a channel to it. ## Releases [#releases] Each push creates a release. Depot gives it an id (`rel_…`) and a **sequence number**. Sequence numbers count up per game and platform, starting at 1, and never go down. The launcher uses them to tell newer from older. A release has a status: | Status | Meaning | | --- | --- | | `reserved`, `uploading` | A push started and hasn't finished. Run the same push again to resume it. | | `verifying` | Depot is checking the uploaded objects and the signature. | | `published` | Every check passed. Only published releases can go on a channel. | | `failed`, `aborted` | The push didn't complete. Players never see it. | List and inspect releases: ```bash depot release list --game my-game depot release list --game my-game --platform windows-x64 depot release show --game my-game --release-id rel_0123456789abcdef0123456789abcdef ``` `release list` shows each release's name, platform, sequence, status, package, size, signing key and who pushed it. `release show` prints one published release with its signed envelope. Both print JSON. ## Channels [#channels] A channel belongs to one platform. `stable` on `windows-x64` and `stable` on `macos-arm64` are two separate channels, and you move each one on its own. That lets you ship a fix to one platform without touching the others. Your store page installs from one channel, `stable` unless you set another with `depot game page --channel`. You can make more channels for testers, such as `beta`. ### Look at a channel [#look-at-a-channel] ```bash depot channel show --game my-game --platform windows-x64 --channel stable ``` This prints the release the channel points at, its name and sequence, and the channel's **revision**. The revision goes up by one on every move. Without `--platform`, `channel show` and `channel set` use the platform of the machine you run them on. ### Move a channel (promote) [#move-a-channel-promote] The usual way is to move the channel as part of the push: ```bash depot push --game my-game --platform windows-x64 --release 1.3.0 \ --input build/windows/ --channel stable --note "The winter update." ``` To promote a release you already pushed, for example one you tested on `beta`, use `depot channel set` with its release id: ```bash depot channel set --game my-game --platform windows-x64 --channel stable \ --release-id rel_0123456789abcdef0123456789abcdef \ --note "The winter update, now for everyone." ``` Moving a channel that doesn't exist yet creates it. The note is what players see under **What's new** on your store page. The page shows the latest releases on its channel, newest first, each with its note. Rollbacks aren't listed there. ### Moves are safe to retry [#moves-are-safe-to-retry] Every move is a compare-and-swap on the channel's revision. The CLI reads the current revision, then asks Depot to move the channel only if it hasn't changed. If someone else moved it in the meantime, you get `409 revision_mismatch` with the channel's current release, and nothing moves. Read the channel again and decide. This also means a retried request can never move a channel twice. ## Roll back [#roll-back] Moving a channel to a release with a lower sequence number is a rollback. Depot refuses it unless you ask for it: ```bash depot channel set --game my-game --platform windows-x64 --channel stable \ --release-id rel_0123456789abcdef0123456789abcdef \ --allow-rollback ``` Without `--allow-rollback` you get a `409` that names both sequence numbers. What a rollback does for players: - **New installs** get the release the channel points at now. - **Players who already installed the newer release** keep it. The launcher refuses to install a release older than the one on the machine, because an attacker could otherwise replay an old, vulnerable build. A player using the CLI can accept it on purpose with `depot update --allow-rollback`. To move everyone off a bad build, push the good build again under a new name, for example `1.3.1`, and move the channel to it. It gets a higher sequence number, so every player's next update installs it. Because the chunks are already on Depot, the push uploads almost nothing, and players' updates download only the chunks that differ from what they have. ```bash depot push --game my-game --platform windows-x64 --release 1.3.1 \ --input build/windows-1.2.0/ --channel stable \ --note "Back to 1.2.0 while we fix a crash in 1.3.0." ``` ## History [#history] Every move is recorded with who made it, the release it moved from and to, whether it was a rollback, and the note. You can read a channel's history through the API at `GET /v1/organizations/{org}/games/{id}/channels/{platform}/{name}/history`. Moves also appear in your organization's audit log, which owners and admins can read: ```bash depot org audit ``` ## Permissions [#permissions] | Action | Permission | | --- | --- | | Push a release | `release:publish` | | Move a channel (`--channel` on push, `channel set`) | `channel:write` | | List releases and channels, read history | `game:read` | Owners, admins and publishers have all three. Members can read. API keys have only the permissions you give them. See [CI and agents](/docs/publishing/ci-and-agents). # CI and agents (/docs/publishing/ci-and-agents) Everything a studio does on Depot goes through the public API, and the `depot` CLI is a client of that API. A CI job or an AI agent can create a game, push builds, move channels and publish a store page with the same commands you run by hand. It authenticates with an organization API key instead of a person's sign-in. ## Organization API keys [#organization-api-keys] An API key (`dpk_…`) belongs to your organization, not to a person. It keeps working if the person who made it leaves, and it can only do what its permissions allow. Create one while signed in as yourself: ```bash depot api-key create --name ci-push \ --permissions release:publish,channel:write,game:read \ --expires-in-days 90 --out ci-push.key ``` <TypeTable type={{ '--name': { type: 'string', description: 'A label for the key. Required.' }, '--permissions': { type: 'list', description: 'Comma-separated resource:action pairs. Required.' }, '--expires-in-days': { type: '1-365', description: 'When the key stops working.', default: '90' }, '--out': { type: 'path', description: 'Write the secret to this file, readable only by you.' }, '--print': { type: 'flag', description: 'Print the secret instead.' }, '--org': { type: 'id or slug', description: 'The organization.', default: 'your only one' }, }} /> The secret is shown once. Depot stores only a hash of it and can't show it again. Put it in your CI's secret store and delete the local file. Only people manage keys. Creating a key needs a signed-in member with the `apiKey:create` permission (owners and admins), and an API key can never create, rotate or revoke keys or change members. ### Permissions [#permissions] Give each key only what its job needs. A key's permissions must be a subset of your own role's permissions when you create it. | Permission | Lets the key | | --- | --- | | `game:create` | Create games. | | `game:read` | Read games, releases, channels, packages and store pages. | | `game:update` | Edit games and store pages, upload art and media, publish pages, define packages. | | `release:publish` | Push releases. | | `channel:write` | Move channels, including rollbacks. | | `signingKey:manage` | Enroll and revoke signing keys, and register a package's content key on its first push. | | `keys:issue`, `keys:read`, `keys:revoke` | Issue, list and revoke game key batches. | | `entitlement:grant`, `entitlement:read`, `entitlement:revoke` | Grant, list and revoke players' entitlements. | | `retailer:delegate` | Let a retailer organization issue keys for your packages. | | `retail:issue` | Issue keys under a delegation your organization holds. | A key can't hold `organization`, `member`, `invitation` or `apiKey` permissions. Common sets: | Job | Permissions | | --- | --- | | Push builds and move channels | `release:publish,channel:write,game:read` | | Push builds for testers, promote by hand | `release:publish,game:read` | | Publish the store page too | add `game:update` | | An agent that sets up a new game end to end | `game:create,game:read,game:update,release:publish,channel:write,signingKey:manage` | ### Rotate and revoke [#rotate-and-revoke] ```bash depot api-key list depot api-key rotate --id <key id> --grace-seconds 3600 --out ci-push.key depot api-key revoke --id <key id> ``` `rotate` creates a replacement with the same name and permissions. The old key stops at once, or after `--grace-seconds` so you have time to update your secret store. `revoke` stops a key immediately. Every create, rotate and revoke is in the audit log. ## Environment variables [#environment-variables] | Variable | What it does | | --- | --- | | `DEPOT_API` | The API base URL. Use `https://api.staging.depot.games`. | | `DEPOT_TOKEN` | The organization API key. The CLI uses it before any stored key or `depot login` session. | | `DEPOT_CONFIG_DIR` | Where the CLI looks for config, signing keys and content key backups. Default `~/.config/depot`. | | `DEPOT_STATE_DIR` | Where the CLI keeps staged builds and caches. Default `~/.local/state/depot`. | Check what a job is running as: ```bash depot auth status ``` With an API key, this prints the key's organization and permissions. ## What a push job needs [#what-a-push-job-needs] A push from CI needs three secrets, not one: 1. **The API key**, as `DEPOT_TOKEN`. 2. **The signing key file**, after you've enrolled it with `depot keys enroll`. The file records which key id it has for each game, so copy it after enrolling. Pass it with `--key`. 3. **The content key backup** for each package you push, from `~/.config/depot/content-keys/`. Pass it with `--content-key`. Do the first push of each package from your own machine. The first push generates the package's content key and registers it, which needs `signingKey:manage`. After that, a key with `release:publish` and `channel:write` is enough. <Callout type="warn"> The signing key and content key files are as sensitive as the API key. Anyone with all three can publish builds of your game. Keep them in your CI's secret store, never in the repository. </Callout> ## GitHub Actions example [#github-actions-example] This workflow builds a Linux build on every tag and pushes it to `stable`. Store `DEPOT_TOKEN`, `DEPOT_SIGNING_KEY` (the signing key file's contents) and `DEPOT_CONTENT_KEY` (the content key backup's contents) as repository secrets. Replace the build step with your own. ```yaml title=".github/workflows/release.yml" name: Release on: push: tags: ["v*"] jobs: push: runs-on: ubuntu-latest env: DEPOT_API: https://api.staging.depot.games DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} steps: - uses: actions/checkout@v4 - name: Build the game run: ./scripts/build-linux.sh out/linux - name: Install the depot CLI # There's no prebuilt download yet: build it from the Depot source # (see "Install the CLI") or restore a binary from your cache. run: depot --version - name: Write the signing and content keys env: DEPOT_SIGNING_KEY: ${{ secrets.DEPOT_SIGNING_KEY }} DEPOT_CONTENT_KEY: ${{ secrets.DEPOT_CONTENT_KEY }} run: | umask 077 printf '%s' "$DEPOT_SIGNING_KEY" > "$RUNNER_TEMP/signing-key.json" printf '%s' "$DEPOT_CONTENT_KEY" > "$RUNNER_TEMP/content-key.json" - name: Push to Depot run: | depot push --game my-game --platform linux-x64 \ --release "${GITHUB_REF_NAME#v}" \ --input out/linux --launch-file launch-linux.json \ --key "$RUNNER_TEMP/signing-key.json" \ --content-key "$RUNNER_TEMP/content-key.json" \ --channel stable --note "Release ${GITHUB_REF_NAME}" \ --json ``` Notes: - Re-running a failed job is safe. An unfinished push of the same name resumes. A published release name is refused with a `409`, so a re-run never publishes twice. If the release was published but the channel didn't move, move it with `depot channel set`. - To ship several platforms, run one push per platform, each with its own `--platform`, input folder and launch file. Each platform's `stable` channel moves on its own. - To promote by hand instead, drop `--channel` and `--note` and move the channel later with `depot channel set`. ## Driving Depot from an AI agent [#driving-depot-from-an-ai-agent] An agent can publish a game end to end with the CLI, the same way a person does. Give it an API key with the permissions from the table above and these two documents: - **The docs as text:** [`https://docs.staging.depot.games/llms.txt`](https://docs.staging.depot.games/llms.txt) is an index of these docs for language models. [`llms-full.txt`](https://docs.staging.depot.games/llms-full.txt) has every page in one file. - **The API description:** [`https://api.staging.depot.games/v1/openapi.json`](https://api.staging.depot.games/v1/openapi.json) is the OpenAPI 3.1 document for every route, with the permission each one needs. Tips for agents: - Pass `--json` where a command has it, and read the JSON. Most commands without `--json` print JSON anyway. - Run `depot auth status` first to see which organization and permissions the key has. - Never put a key on the command line. `DEPOT_TOKEN` and `depot auth login` (which reads from standard input) keep it out of shell history and logs. - Errors from the API are problem+json objects with a stable `title` code, such as `revision_mismatch` or `forbidden`. Branch on the code, not on the message. See the [API overview](/docs/api#errors). A full setup an agent can run looks like this. Everything except the first sign-in and key creation works with an API key: ```bash depot game create --slug my-game --name "My Game" depot keys generate && depot keys enroll --game my-game depot push --game my-game --platform linux-x64 --release 1.0.0 \ --input build/ --launch-file launch.json --channel stable --note "First build." depot game art --game my-game --kind cover --file art/cover.png depot game page --game my-game --tagline "A short line about the game." \ --short-description "Shown in link previews." --access free --publish public ``` The last command prints the page URL. ## Idempotency keys [#idempotency-keys] Networks fail. When a request times out, you don't know if it happened. Idempotency keys make a retry safe. Send an `Idempotency-Key` header on a `POST`. If Depot has already handled a request with the same key from the same credential on the same route, it returns the stored response instead of acting again, with the header `idempotent-replay: true`. If you reuse the key with a different body, you get `422 idempotency_mismatch`. - Keys are 8 to 128 characters of letters, digits, `.`, `_`, `:` and `-`. - Use a new key for each distinct action, and the same key for retries of that action. - Some requests require a key: granting an entitlement and issuing a batch of game keys. For game keys, a retry returns the batch without the codes, because codes are delivered once. The CLI uses idempotency keys for you where it matters. `depot game-keys create` takes `--idempotency-key` so you can retry a timed-out batch without issuing a second one: ```bash depot game-keys create --game my-game --quantity 100 --out keys.txt \ --idempotency-key launch-batch-2026-12 ``` Channel moves don't need an idempotency key: they're compare-and-swap on the channel's revision. See [Channels and releases](/docs/publishing/channels-and-releases#moves-are-safe-to-retry). # Push builds (/docs/publishing/push) `depot push` takes a folder with your game build, turns it into a signed, encrypted release and uploads it to Depot. Run it once per platform for each version you ship. ```bash depot push --game my-game --platform windows-x64 --release 1.2.0 \ --input build/windows/ --launch-file launch-windows.json \ --channel stable --note "New boss, faster loading, two crash fixes." ``` Before your first push you need a game (`depot game create`) and an enrolled signing key (`depot keys generate` and `depot keys enroll`). The [Quickstart](/docs/getting-started/quickstart) walks through both. Signing keys are covered in [Signing keys](/docs/publishing/signing-keys). ## What you pass [#what-you-pass] <TypeTable type={{ '--game': { type: 'slug or id', description: 'The game to publish to. Required.' }, '--release': { type: 'string', description: 'The release name, for example 1.2.0. Letters, digits, ".", "_" and "-", up to 64 characters. Required.' }, '--input': { type: 'path', description: 'The folder that holds the build. Required.' }, '--platform': { type: 'string', description: 'The platform this build is for, for example windows-x64, macos-arm64 or linux-x64.', default: 'this machine' }, '--channel': { type: 'string', description: 'Move this channel to the new release after it is published, for example stable.' }, '--note': { type: 'string', description: 'What is in this release, for players. Shown under "What\'s new" on the store page. Needs --channel.' }, '--launch-file': { type: 'path', description: 'JSON file that tells the launcher how to start this build.' }, '--package': { type: 'string', description: 'The package the release belongs to. Use it for DLC and test builds.', default: 'base' }, '--key': { type: 'path', description: 'Your signing key file.', default: '~/.config/depot/keys/default.json' }, '--content-key': { type: 'path', description: 'The content key backup file for the package.', default: '~/.config/depot/content-keys/<domain>.json' }, '--rebuild': { type: 'flag', description: 'Ignore a staged build left by an earlier, interrupted push and build again.' }, '--json': { type: 'flag', description: 'Print the push report as JSON.' }, }} /> The full list, including chunking and upload tuning, is in the [CLI reference](/docs/cli#depot-push). ### The input folder [#the-input-folder] Point `--input` at the folder you would zip up for players. Every regular file under it becomes part of the release, with its relative path and its executable bit. Symlinks, hardlinks and special files are refused, so resolve them in your build step. Anything you leave in the folder ships. Remove debug symbols, logs and local config first if players shouldn't get them. ### Platforms [#platforms] A release is for one platform. Platform names are lowercase letters, digits and `-`, up to 32 characters. The CLI names machines like this: | Platform | Name | | --- | --- | | Windows, x64 | `windows-x64` | | macOS, Apple silicon | `macos-arm64` | | macOS, Intel | `macos-x64` | | Linux, x64 | `linux-x64` | Without `--platform`, the CLI uses the platform of the machine it runs on. Set it explicitly when you push a Windows build from a Mac or from CI. Release names are per platform, so you push `1.2.0` once for `windows-x64` and once for `macos-arm64`. Each platform has its own channels too. See [Channels and releases](/docs/publishing/channels-and-releases). ### Release names are permanent [#release-names-are-permanent] Once a release is published, its name can't be reused on that platform. Pushing a name that's already published fails with a `409 conflict`. Pick a new name, such as `1.2.1`. ## Launch targets [#launch-targets] A launch file tells the launcher which file to start, with which arguments and from which folder. It's a JSON array of launch targets, one per platform. Every target is checked against the build you push, so give each platform's build its own launch file. <Tabs items={['Windows', 'macOS', 'Linux']}> <Tab value="Windows"> ```json title="launch-windows.json" [ { "platform": "windows-x64", "path": "MyGame.exe", "args": ["--skip-intro"], "working_dir": "." } ] ``` </Tab> <Tab value="macOS"> ```json title="launch-macos.json" [ { "platform": "macos-arm64", "path": "MyGame.app", "args": [] } ] ``` </Tab> <Tab value="Linux"> ```json title="launch-linux.json" [ { "platform": "linux-x64", "path": "bin/mygame", "args": [], "working_dir": ".", "env": { "MYGAME_RENDERER": "vulkan" } } ] ``` </Tab> </Tabs> <TypeTable type={{ platform: { type: 'string', description: 'The platform this entry is for.' }, path: { type: 'string', description: 'The executable, relative to the input folder. On macOS this can be the .app bundle folder.' }, args: { type: 'string[]', description: 'Arguments passed to the game. Up to 64.', default: '[]' }, working_dir: { type: 'string', description: 'The folder the game starts in, relative to the input folder.', default: 'the folder that holds path' }, env: { type: 'object', description: 'Extra environment variables, up to 32. Names are uppercase letters, digits and _.', default: '{}' }, }} /> `depot push` checks the launch file against the build before it uploads anything: - Each target's `path` must be a file in this build (or a `.app` bundle folder on macOS). - There's at most one target per platform. - On macOS and Linux, the file must have its executable bit set. - `env` can't set variables that change how programs load or which shell runs, such as `PATH`, `HOME`, `LD_*`, `DYLD_*`, `TMP` or `SHELL`. If a check fails, nothing is uploaded. Fix the file and push again. ## Release notes [#release-notes] `--note` is the text players see under **What's new** on your store page. It's attached to the channel move, so it needs `--channel`: ```bash depot push --game my-game --platform linux-x64 --release 1.2.1 \ --input build/linux/ --channel stable \ --note "Fixes the save bug on the second island." ``` Notes are plain text. Depot keeps the first 500 characters. You can also add a note when you move a channel later with `depot channel set --note`. ## Encryption [#encryption] Every game on Depot is restricted. Each push encrypts the build chunk by chunk with AES-256-GCM, and only players who own the game can get the key to decrypt it. The content key is made on your machine. The first push to a package generates it, registers it with Depot and writes a backup file: ```text ~/.config/depot/content-keys/<domain>.json ``` Depot stores its copy wrapped under its own key. It releases the key only to a download session for your game, which players get from an active entitlement, and it never returns the key to you. <Callout type="warn"> Back up the content key file. Every later push to the same package needs it, from any machine. If you push from CI, store it as a secret and pass it with `--content-key`. See [CI and agents](/docs/publishing/ci-and-agents). </Callout> You don't need `--encrypt`. It only matters for older public games, and every new game is encrypted anyway. ## Only changed bytes are uploaded [#only-changed-bytes-are-uploaded] Depot splits files into content-defined chunks. A chunk's id is the hash of its bytes, so an unchanged part of a file has the same chunks in every release, even when bytes were inserted before it. When you push, the CLI fetches the list of chunks already published for the package and only compresses, encrypts and uploads chunks that are new. The report at the end shows the release's sequence number and id, how many chunks were reused and how many were new, how much was uploaded, and the channel's new revision if you passed `--channel`. Add `--json` to get the same report as an object. Players benefit the same way. An update downloads only the chunks their installed copy doesn't have, and reuses the rest from disk after checking them. Chunks are reused within one package. A DLC package has its own content key, so it never shares chunks with the base game. ## Interrupted pushes resume [#interrupted-pushes-resume] A push builds into a staging folder in the CLI's state directory (`~/.local/state/depot/push/` by default), then uploads. If the upload stops because of a network error, a closed laptop or a cancelled CI job, run the same command again: - If the input folder and launch file haven't changed, the CLI reuses the staged build instead of building again. - The release reservation is reused, so the release keeps its sequence number. - Objects Depot already has are skipped. Large packs are uploaded in parts, and parts already uploaded are skipped too. Pass `--rebuild` to throw away the staged build and start the build again. ## Uploads go straight to storage [#uploads-go-straight-to-storage] The CLI never sends your build through the API. Depot gives it short-lived presigned URLs, and it uploads each object directly to storage. Objects over 64 MiB use multipart uploads. See [Uploads never pass through the API](/docs/api#uploads-never-pass-through-the-api). ## What Depot checks before it publishes [#what-depot-checks-before-it-publishes] A release becomes visible only after Depot has checked it: 1. Every object the release needs exists in storage at its exact size. 2. The signed descriptor matches the reserved release: game, platform, name, sequence and package. 3. The descriptor names the package's content key by its check value. 4. The signature verifies with an active signing key enrolled for the game. 5. The manifest and index match their SHA-256 hashes, and the index lists exactly the packs the descriptor lists. The release then has the status `published` and can go on a channel. A release that fails a check is never shown to players. The launcher checks again on every install and update. It verifies the signature with the keys it trusted when the player first installed, refuses releases older than what's installed, checks every chunk's hash after decryption, and checks every file before it switches to the new version. A broken or tampered release can't be installed, and a failed update leaves the old version in place. ## DLC and other packages [#dlc-and-other-packages] A package is something players can own: the base game, a DLC, a bundle or a test build. Releases go to the `base` package unless you say otherwise. To publish content for another package, create it once and push with `--package`: ```bash depot package create --game my-game --name expansion-1 --kind dlc --title "The Frozen Coast" depot push --game my-game --platform windows-x64 --release 1.0.0 \ --input build/expansion-1/ --package expansion-1 ``` The first push to the new package generates its own content key and backup file. The launcher and `depot install` install the `base` package. You can also ship DLC content inside the base build and unlock it in the game with an ownership check from the [Depot SDK](/docs/sdk). How players get packages is covered in [Keys and access](/docs/store/keys-and-access). ## After a push [#after-a-push] ```bash depot release list --game my-game --platform windows-x64 depot channel show --game my-game --platform windows-x64 --channel stable ``` If you pushed without `--channel`, the release is published but players don't get it until you move a channel to it. See [Channels and releases](/docs/publishing/channels-and-releases). # Signing keys (/docs/publishing/signing-keys) Every release on Depot is signed by the studio that made it. You hold an Ed25519 private key, Depot holds only the public half, and the launcher refuses any build whose signature doesn't check out against a key it trusts. This means Depot itself can't publish a build in your name. If Depot's servers or storage were ever compromised, an attacker could stop downloads, but couldn't make a player's launcher install different files. ## Generate a key [#generate-a-key] ```bash depot keys generate ``` This creates a key file at `~/.config/depot/keys/default.json`, readable only by you, and prints its path and public key. Use `--out` to write it somewhere else: ```bash depot keys generate --out ~/.config/depot/keys/my-studio.json ``` `depot keys generate` refuses to overwrite an existing file. `--force` replaces it, and the old key is gone for good. ### Where the private key lives [#where-the-private-key-lives] The private key stays in that file on your machine. No command sends it to Depot: `depot keys enroll` uploads only the public key, and `depot push` signs on your machine and uploads only the signature. <Callout type="warn"> Back up the key file somewhere safe, such as a password manager. If you lose it, you can't sign new releases with it, and players who trust only that key won't accept releases signed by a new one. See [Rotate a key](#rotate-a-key). </Callout> ## Enroll it for a game [#enroll-it-for-a-game] ```bash depot keys enroll --game my-game depot keys enroll --game my-game --key ~/.config/depot/keys/my-studio.json ``` Enrolling uploads the public key, and Depot gives it a key id (`key_…`). The CLI writes that id into the key file, so later pushes know which key id to sign with. One key file can be enrolled for many games. Enrolling needs the `signingKey:manage` permission, which only owners and admins have. Whoever can enroll a key can publish builds, so keep this with the people who run the studio. List a game's keys and their states: ```bash depot keys list --game my-game ``` This list is public: it's what launchers read to decide which keys to trust. ## How players' launchers trust keys [#how-players-launchers-trust-keys] - **First install.** When a player installs your game for the first time on a machine, the launcher trusts the game's keys that are active at that moment. - **Later keys.** Keys you enroll after that aren't trusted by that installation. A release signed only with a newer key is refused there. A player using the CLI can accept new keys on purpose with `--trust-new-keys`. - **Revoked keys.** A revoked key is dropped on the launcher's next check, and releases it signed are refused. This is deliberate. If Depot's control plane were compromised, it couldn't enroll its own key and push builds to your existing players. ## Rotate a key [#rotate-a-key] There's no single rotate command. Rotation is: enroll a new key, push with it, then revoke the old one. ```bash depot keys generate --out ~/.config/depot/keys/2027.json depot keys enroll --game my-game --key ~/.config/depot/keys/2027.json depot push --game my-game --platform windows-x64 --release 1.4.0 \ --input build/windows/ --key ~/.config/depot/keys/2027.json --channel stable depot keys revoke --game my-game --key-id key_0123456789abcdef0123456789abcdef ``` Players who installed before you enrolled the new key don't trust it. To keep rotation possible for them, enroll a second key **before** you launch and keep its private half offline. Players who install while both keys are active trust both, so you can switch to the backup key later without breaking their updates. ## Revoke a key [#revoke-a-key] ```bash depot keys revoke --game my-game --key-id key_0123456789abcdef0123456789abcdef ``` Revoke a key as soon as you think its private half has leaked. Revoking takes effect right away: - Depot refuses new releases signed with it. - Launchers drop it on their next check and refuse every release it signed, including releases already on your channels. Installed games keep working, because revocation stops downloads, not play. But players can't install, update or repair from a release signed only by the revoked key. Push the current build again, signed with a key they still trust, and move your channels to it. Revoking needs `signingKey:manage`. ## Sign offline [#sign-offline] `depot push` signs for you with the local key file. If you drive the publishing API yourself, you can sign a release descriptor on a separate machine that holds the key, for example one that's never online: ```bash depot sign --descriptor descriptor.json --key-id key_0123456789abcdef0123456789abcdef depot sign --descriptor descriptor.json --key-id key_0123456789abcdef0123456789abcdef \ --key /path/to/key.json ``` It prints the descriptor and its signature as JSON: ```json { "descriptor": { "schema": "depot.release/v1", "...": "..." }, "signature": { "key_id": "key_0123456789abcdef0123456789abcdef", "algorithm": "ed25519", "value": "…" } } ``` Send both to the upload session's finalize step (`POST /v1/organizations/{org}/games/{id}/uploads/{sid}/finalize`). ### What gets signed [#what-gets-signed] The descriptor is a small JSON object that pins everything about a release: ```json { "schema": "depot.release/v1", "game_id": "game_…", "platform": "linux-x64", "release_name": "1.2.0", "sequence": 7, "package": "base", "protection": { "mode": "aes-256-gcm", "domain_id": "pd_…", "key_version": 1, "key_check": "c353…" }, "manifest": { "hash": "<sha256>", "size": 12345 }, "index": { "hash": "<sha256>", "size": 67890 }, "packs": [{ "hash": "<pack id>", "size": 134217728 }], "created_at": "2026-11-01T12:00:00Z" } ``` The signature is Ed25519 over the bytes `DEPOT-RELEASE-v1` followed by a zero byte, then the descriptor as RFC 8785 canonical JSON. Because the descriptor pins the hashes of the manifest, the index and every pack, the signature covers every byte of the build. It also pins the sequence number, so an old release can't be passed off as a new one, and the content key's check value, so a player's launcher can tell that the key it was given is the one you signed for. # Depot SDK (/docs/sdk) The Depot SDK lets your game ask one question: does this player own this package? It answers from the signed offline license that the Depot launcher (or the CLI) stores on the player's computer. The SDK works offline, doesn't need the launcher to be running, and never opens a browser or asks the player to sign in. You don't need the SDK to sell or ship a game on Depot. Builds are already encrypted, and only players who own the game can download them. Use the SDK when you want the game itself to react to ownership, for example: - unlock DLC the player owns, or show a "get the expansion" screen when they don't, - show a message if the game is started without a license on this computer, - check that the installed files still match the release you signed. ## How it works [#how-it-works] 1. A player gets your game and installs it with the launcher. 2. The launcher fetches a license for that player and game from Depot and stores it in a per-user folder. The license lists the packages the player owns, and Depot signs it. 3. Your game creates the SDK with its game id and calls `HasEntitlement("base")` or `HasEntitlement("expansion-1")`. The SDK checks Depot's signature and the license's validity, then answers. The SDK makes no network requests. Details: [Offline licenses](/docs/sdk/offline-licenses). ## What it answers [#what-it-answers] | Status | Meaning | | --- | --- | | `Granted` | The license is valid and includes this package. | | `Denied` | The license is valid and doesn't include this package. | | `Unknown` | There's no license on this computer. That doesn't mean the player doesn't own the game. | | `Expired` | The license is too old. Opening the launcher renews it. | | `RefreshNeeded` | Your game requires a fresher license than the one stored. | | `Error` | The license file is invalid, for example its signature doesn't verify. | The SDK never blocks the game or shows UI. Your code decides what each result means for the player. ## Engines [#engines] | Engine | Package | Status | | --- | --- | --- | | Unity 6 (6000.0 LTS and later) | `com.depot.games.sdk` | Available. The native library in the package is for macOS today. | | .NET (MonoGame, FNA, Stride, Godot C# and others) | `Depot.Games.Sdk` (netstandard2.1) | Built and tested, same API shape as Unity. Not published to a package registry yet. | Both wrap the same native library, so they verify licenses the same way. Windows and Linux builds of the native library, and signed SDK downloads, aren't available yet. ```csharp using Depot.Games.Sdk; using var depot = DepotSdk.Create(new DepotSdkOptions { GameId = "game_…", Environment = DepotEnvironment.Staging }); if (depot.HasEntitlement("expansion-1").Status == EntitlementStatus.Granted) UnlockExpansion(); ``` That's the .NET API. For Unity, see [Unity](/docs/sdk/unity). <Cards> <Card title="Unity" href="/docs/sdk/unity" description="Add the package, configure your game id, check the base game and DLC." /> <Card title="Offline licenses" href="/docs/sdk/offline-licenses" description="What the launcher stores, how long it lasts, and what the SDK checks." /> </Cards> # Offline licenses (/docs/sdk/offline-licenses) An offline license is a small file Depot signs for one player and one game. It lists the packages that player owns. The launcher stores it on the player's computer, and the Depot SDK in your game reads it without going online. ## What's in a license [#whats-in-a-license] - **The game** it's for. A license for one game never works for another. - **The player**, as an opaque Depot account id. No email address or handle. - **The packages they own**, by package name (such as `base` or `expansion-1`), with each one's kind and end time if it has one. Bundles are already expanded into their contents. A DLC never implies the base game. - **Your game's signing keys**, so the SDK can check installed files against your signed release. - **When it was issued and when it expires.** - **Depot's signature.** Licenses aren't tied to a device, and there's no device limit. ## Who stores it [#who-stores-it] Licenses live in one per-user folder that all Depot tools share: | OS | Folder | | --- | --- | | macOS | `~/Library/Application Support/games.depot/licenses/` | | Windows | `%APPDATA%\games.depot\licenses\` | | Linux | `$XDG_DATA_HOME/games.depot/licenses/` (usually `~/.local/share/games.depot/licenses/`) | Each game has one file, named after its game id. A file is trusted only after its signatures verify, and a newer license is never replaced by an older one. The **launcher** writes licenses for the games a signed-in player owns. It refreshes them at startup, after an install, update or key redemption, and every 12 hours, renewing a license once it's a day old. The **CLI** writes them too, on `depot install` and `depot update` when signed in, and on `depot license refresh --game <game>`. Use `depot license show --game <game>` to read the cached license. Signing out of the launcher or the CLI removes the licenses that tool wrote. ## How long it lasts [#how-long-it-lasts] A license is valid for 30 days from when it was issued, or until the earliest end time of the entitlements in it, whichever comes first. The SDK allows 5 minutes of clock difference. Players who open the launcher now and then never see a license expire, because the launcher renews it long before then. ## What your game checks [#what-your-game-checks] When your game asks `HasEntitlement("expansion-1")`, the SDK: 1. Reads the license file for your game id. 2. Checks that Depot signed it. The SDK has Depot's root key built in for each environment, and the license carries the certificate of the key that signed it, so this works offline. 3. Checks that it's for your game and that it's within its validity window. 4. Looks up the package and its end time. It answers with one of these: | Situation | Status | | --- | --- | | Valid license, package included | `Granted` | | Valid license, package not included (or its entitlement has ended) | `Denied` | | No license on this computer | `Unknown` | | License past its expiry | `Expired` | | Online Required policy and the license is older than your freshness window | `RefreshNeeded` | | File changed, signature doesn't verify, or wrong game | `Error`, with the reason in the snapshot's `ErrorKind` | The SDK only reads files. It never contacts Depot, opens a browser or asks the player to sign in. To get a newer license, the player opens the launcher (or runs `depot license refresh`), and your game calls `ReloadAsync`. ## Offline, expired and revoked [#offline-expired-and-revoked] - **Offline:** a valid license works with no network at all, for as long as it's valid. - **Expired:** the player opens the launcher while online and it fetches a new license. Decide in your game what an expired license means, for example a message asking the player to open Depot. - **Revoked:** if you revoke a player's entitlement, Depot stops new downloads at once, and the next license the player gets won't include that package. A license they already have stays valid until it expires. - **Clock set back:** the SDK sets `ClockAnomaly` if the computer's clock looks earlier than a time it has already trusted. It's a warning only, and the SDK doesn't change its answer because of it. ## What licenses don't protect against [#what-licenses-dont-protect-against] Offline licenses are a fair, low-friction ownership check, not DRM. They don't stop someone who patches your game's code or copies the decrypted files out of an installation. They can't enforce revocation while an offline license is still valid, and they can't fully guard against a wrong clock. For multiplayer or anything valuable, check on your own server too. # Unity (/docs/sdk/unity) The Unity package `com.depot.games.sdk` checks whether the player owns your game and its DLC. It reads the signed license the Depot launcher stores, with no network requests and no sign-in inside the game. ## Requirements [#requirements] - Unity 6 (6000.0 LTS or later). - A Depot game and its id (`game_…`). Run `depot game show my-game` to see it. - The package currently ships its native library for macOS only. Windows and Linux builds aren't available yet. ## Install the package [#install-the-package] The package isn't on a package registry yet. It's a folder, `com.depot.games.sdk`, that you add from disk. <Tabs items={['Package Manager', 'manifest.json']}> <Tab value="Package Manager"> 1. Copy the `com.depot.games.sdk` folder somewhere stable, next to your project. 2. In Unity, open **Window → Package Manager**. 3. Press **+**, choose **Install package from disk**, and pick the folder's `package.json`. </Tab> <Tab value="manifest.json"> Add a `file:` entry to `Packages/manifest.json`, with a path relative to the `Packages` folder: ```json title="Packages/manifest.json" { "dependencies": { "com.depot.games.sdk": "file:../../com.depot.games.sdk" } } ``` </Tab> </Tabs> ## Configure it [#configure-it] Create a settings asset with **Assets → Create → Depot → SDK Settings**. It holds nothing secret. | Field | What to set | | --- | --- | | Game Id | Your game's id, `game_…`. | | Environment | **Staging**. Production isn't launched yet, and the SDK refuses Production until it is. The default is Production, so change it. | | Policy | **Offline Lease** (default), **Online Required** or **Optional**. See below. | | Freshness Hours | Online Required only: how old a license may be, in hours (default 24). | | Critical Files | Paths inside the installation that a `Critical` integrity check hashes, for example `MyGame.app/Contents/MacOS/MyGame`. | | Integrity Excluded | Path prefixes a `Full` integrity check skips, such as folders the game rewrites. | ### Policies [#policies] - **Offline Lease:** a valid license decides. An expired license gives `Expired`, and no license gives `Unknown`. - **Online Required:** like Offline Lease, but the license must be newer than the freshness window, otherwise `RefreshNeeded`. - **Optional:** results are advisory (`Advisory` is `true`). Use this to show information without enforcing anything. ## Check the base game and DLC [#check-the-base-game-and-dlc] Create the SDK once, keep it for the life of the game, and dispose of it when you're done. `HasEntitlement` takes a package name, such as `base` or the name you gave a DLC package. It does no I/O, so you can call it whenever you need it. ```csharp using Depot.Games.Sdk; using UnityEngine; public sealed class Ownership : MonoBehaviour { public DepotSdkSettings settings; DepotSdk sdk; void Start() { try { sdk = DepotSdk.Initialize(settings); } catch (DepotSdkException e) { Debug.LogWarning($"Depot SDK didn't start: {e.Kind}: {e.Message}"); return; } var baseGame = sdk.HasEntitlement("base"); var expansion = sdk.HasEntitlement("expansion-1"); if (expansion.Status == EntitlementStatus.Granted) UnlockExpansion(); if (baseGame.Status == EntitlementStatus.Unknown) ShowNoLicenseMessage(); } void OnDestroy() => sdk?.Dispose(); void UnlockExpansion() { /* your code */ } void ShowNoLicenseMessage() { /* your code */ } } ``` `HasEntitlement` returns an `EntitlementResult` with: - `Status`: `Granted`, `Denied`, `Unknown`, `Expired`, `RefreshNeeded` or `Error`. - `Kind`: the package kind, such as `base` or `dlc`. - `ValidUntil`: when this entitlement ends, in Unix seconds, or 0 for no end. - `LicenseExpiresAt`: when the stored license expires, in Unix seconds. - `Advisory`: `true` under the Optional policy. - `ClockAnomaly`: `true` if the computer's clock looks earlier than it should be. It's a warning only. `Unknown` means there's no license on this computer, not that the player doesn't own the game. Tell the player to open the Depot launcher rather than treating them as a non-owner. Owning a DLC never implies owning the base game. Check each package you care about. ## React when the license changes [#react-when-the-license-changes] If a player redeems a DLC key in the launcher while your game is running, the launcher refreshes the stored license. Your game picks it up when it calls `ReloadAsync`. The SDK never polls on its own, so call it when it makes sense, for example when the player opens the DLC menu or presses a "Reload" button. ```csharp sdk.EntitlementsChanged += snapshot => Debug.Log($"License changed: {snapshot.State}"); async void OnReloadPressed() { var snapshot = await sdk.ReloadAsync(); if (sdk.HasEntitlement("expansion-1").Status == EntitlementStatus.Granted) UnlockExpansion(); } ``` `ReloadAsync` completes on Unity's main thread and raises `EntitlementsChanged` only when something changed. `GetSnapshot()` returns the current state without I/O: `State` (`Valid`, `Expired`, `Missing`, `Invalid` or `RefreshNeeded`), `ExpiresAt`, the `Entitlements` list and, for an invalid license, `ErrorKind`. ## Verify the installed files [#verify-the-installed-files] You can check the installed files against the release you signed. It's never automatic. It runs off the main thread and reports progress from 0 to 1. ```csharp var report = await sdk.VerifyInstallationAsync(IntegrityMode.Full, new System.Progress<float>(p => progress = p)); if (!report.Ok) Debug.Log($"{report.Modified.Count} modified, {report.Missing.Count} missing"); ``` | Mode | What it checks | | --- | --- | | `Manifest` | The release signature and metadata only. | | `Critical` | The files listed in **Critical Files**. | | `Full` | Every file Depot installed, except **Integrity Excluded** prefixes. | Files that aren't part of the release, such as saves and mods, never count as failures. ## Test it [#test-it] You need a license on your own computer for your game. The simplest way is to own the game on staging and let the CLI store your license. <Steps> <Step> ### Own the game [#own-the-game] Claim it on your store page, or grant it to yourself: ```bash depot entitlement grant --game my-game --handle @your-handle ``` </Step> <Step> ### Store your license [#store-your-license] Sign in as yourself and fetch the license. Installing with the launcher or `depot install` also stores it. ```bash depot login depot license refresh --game my-game depot license show --game my-game ``` `depot license show` prints the cached license's state, expiry and entitlements, without the network. </Step> <Step> ### Run the game [#run-the-game] Press Play in the Editor. `HasEntitlement("base")` should return `Granted`. </Step> <Step> ### Test DLC [#test-dlc] Create a DLC package, then grant it to yourself (or issue a key and redeem it with `depot redeem`): ```bash depot package create --game my-game --name expansion-1 --kind dlc --title "Expansion 1" depot entitlement grant --game my-game --package expansion-1 --handle @your-handle depot license refresh --game my-game ``` Call `ReloadAsync` in the running game. `HasEntitlement("expansion-1")` changes from `Denied` to `Granted`. </Step> </Steps> To test with no license, run `depot logout`. Signing out removes the licenses the CLI wrote, and the SDK then reports `Unknown`. ### Development overrides [#development-overrides] Development builds and the Editor can pass `DevelopmentOverrides` to `DepotSdk.Initialize`, for example `licenseDirectory` to read licenses from a test folder, or `installRoot` for integrity checks. Release builds refuse overrides. ```csharp sdk = DepotSdk.Initialize(settings, new DevelopmentOverrides { licenseDirectory = "/tmp/depot-test-licenses" }); ``` # Branding and media (/docs/store/branding-and-media) The top of your store page is yours: your hero art, your logo, your colour and, if you have one, a short muted loop. Depot keeps its own look out of the way. Depot checks every image from its bytes. It accepts PNG, JPEG and WebP. It refuses SVG and anything else. Videos must be MP4 (H.264). Depot does not transcode or resize anything, so upload files at the size you want shown. ## Store art [#store-art] Store art belongs to the game. It appears on the store page, on your studio page and in the Depot launcher. | Art | Where it shows | Recommended | Accepted | | --- | --- | --- | --- | | **Hero** | The top of the page, behind the title, and your game's card on your studio page. | 1920×620 | About 3:1 (2.5:1 to 3.6:1), 1280 to 3840 px wide, up to 8 MB. | | **Cover** | Portrait art. The page shows it when a player gets the game. | 600×900 | 2:3 portrait, 300 to 2400 px wide, up to 4 MB. | | **Icon** | A small square image. | 512×512 | Square, 128 to 2048 px wide, up to 1 MB. | | **Logo** | Your game's wordmark, shown instead of its name over the hero. | 1200×400 | Transparent PNG or WebP (not JPEG), 1:2 to 8:1, 200 to 2400 px wide, up to 1 MB. | If you have no hero, the page uses the cover behind the title. Link previews in chat apps and social posts use the cover or the hero. <Tabs items={["Dashboard","CLI"]}> <Tab value="Dashboard"> Open the game's **Settings** tab and find **Store art**. Upload, replace or remove each image there. Art changes show on the live page without publishing again. </Tab> <Tab value="CLI"> ```bash depot game art --game my-game --kind hero --file hero.png depot game art --game my-game --kind cover --file cover.png depot game art --game my-game --kind icon --file icon.png depot game art --game my-game --kind logo --file logo.png ``` `--kind` is `icon`, `cover`, `hero` or `logo`. </Tab> </Tabs> ## Page colour [#page-colour] You can give the page your own colour. Depot keeps its hue and adjusts its lightness so buttons and links stay readable in both light and dark mode. With no colour, the page uses Depot's neutral colours. <Tabs items={["Dashboard","CLI"]}> <Tab value="Dashboard"> On the **Store page** tab, find **How the page looks** and **Page colour**. If you have uploaded a hero, Depot suggests colours picked from it. You can also choose your own, or **No colour**. A sample shows how the colour looks in light and dark. Save the draft and publish to apply it. </Tab> <Tab value="CLI"> Pass a hex colour, or `none` to remove it: ```bash depot game page --game my-game --accent "#e0a030" depot game page --game my-game --accent none ``` The colour is part of the draft. Publish to apply it. </Tab> </Tabs> ## Accessibility notes [#accessibility-notes] Accessibility notes show in their own section on the page. Add up to 10 short notes of up to 60 characters each. <Tabs items={["Dashboard","CLI"]}> <Tab value="Dashboard"> On the **Store page** tab, fill in **Accessibility**, comma separated. </Tab> <Tab value="CLI"> ```bash depot game page --game my-game \ --accessibility "Full controller support, Subtitles, No time limits, No flashing lights" ``` </Tab> </Tabs> ## Hero loop [#hero-loop] The hero loop is a few seconds of gameplay that plays muted, on repeat, behind your title. - MP4, up to 30 seconds and 40 MB. - At least 640×360 is recommended. Depot refuses videos smaller than 320×180. - It plays only while it is on screen, and players can pause it. - Players who prefer reduced motion, or who have data saver turned on, see the hero art instead. - Until it plays, the page shows the video's poster frame, or the hero if there is no poster. <Tabs items={["Dashboard","CLI"]}> <Tab value="Dashboard"> On the **Store page** tab, under **How the page looks**, select **Upload a loop**. The dashboard checks the file, uploads it and captures a poster frame from it. Save the draft and publish to show it. </Tab> <Tab value="CLI"> ```bash depot game video --game my-game --file loop.mp4 --purpose loop --poster poster.jpg ``` `--poster` is optional: a PNG, JPEG or WebP still frame. The loop goes into the draft. Publish to show it. </Tab> </Tabs> ## Trailer [#trailer] The trailer shows first in the page's media viewer, before your screenshots and clips. It plays only when a player presses play. You have two options: - **Upload an MP4**, up to 10 minutes and 500 MB. - **Link a YouTube or Vimeo video.** The page loads the player only when someone presses play, so nothing loads from YouTube or Vimeo before that. The embed comes from `youtube-nocookie.com` or `player.vimeo.com`. <Tabs items={["Dashboard","CLI"]}> <Tab value="Dashboard"> On the **Store page** tab, find **Trailer and gallery**. Paste a **YouTube or Vimeo link**, or select **Or upload a video**. Save the draft and publish. </Tab> <Tab value="CLI"> Upload an MP4: ```bash depot game video --game my-game --file trailer.mp4 --purpose trailer --poster trailer-poster.jpg ``` The CLI has no flag for a YouTube or Vimeo link. Use the dashboard, or set `trailer` with the API (`PATCH /v1/organizations/{org}/games/{id}/page`). </Tab> </Tabs> ## Gallery [#gallery] The gallery holds screenshots and video clips in the order you choose. Each item can have a caption of up to 140 characters. Captions are optional, but they help players and screen readers. | Item | Recommended | Accepted | | --- | --- | --- | | **Screenshot** | 1920×1080 | PNG, JPEG or WebP, landscape (1.2:1 to 2.4:1), 960 to 3840 px wide, up to 6 MB. | | **Clip** | At least 640×360 | MP4, up to 10 minutes and 500 MB. | A page can show up to 12 screenshots and up to 20 gallery items in total. A game can have up to 12 uploaded videos, counting the loop, the trailer and clips. <Tabs items={["Dashboard","CLI"]}> <Tab value="Dashboard"> On the **Store page** tab, under **Trailer and gallery**, select **Add screenshot** or **Add a clip**. Type a caption next to each item, and use the arrows to reorder or remove items. Save the draft and publish. </Tab> <Tab value="CLI"> Each upload goes to the end of the gallery in the draft: ```bash depot game screenshot --game my-game --file shot1.png --caption "Dusk: topping up the lamp oil" depot game screenshot --game my-game --file shot2.png --caption "The storm, and the beam you keep turning" depot game video --game my-game --file clip.mp4 --purpose clip --caption "The first storm" --poster clip-poster.jpg ``` `--purpose` defaults to `clip`. Then publish: ```bash depot game page --game my-game --publish public ``` </Tab> </Tabs> ## Posters [#posters] A poster is the still frame shown before a video plays. The dashboard captures one from the video when you upload it. With the CLI, pass `--poster` with an image. A poster is a PNG, JPEG or WebP, 480 to 3840 px wide, up to 4 MB. Match the video's shape. ## Drafts and the live page [#drafts-and-the-live-page] Screenshots, videos, captions, the colour and the loop are part of the draft. The live page changes only when you publish. If you remove an item from the draft, the live page keeps showing it until you publish again. See [Store pages](/docs/store/pages). # Keys and access (/docs/store/keys-and-access) A player can download a game only if they own it. Depot records ownership as an **entitlement**: one player owns one package, from a recorded source, until it ends or you revoke it. This page covers the ways you create and remove entitlements yourself. For free claims from your store page, see [Who can get it](/docs/store/who-can-get-it). In the dashboard, all of this is under **Keys & access**. Choose the game at the top. ## Packages [#packages] A **package** is something a player can own. | Kind | What it is | | --- | --- | | `base` | The base game. Depot creates one, named `base`, with every game. | | `dlc` | Extra content, such as an expansion. | | `test` | A test build, for example for a closed beta. | | `bundle` | A set of other packages from the same game. It has no content of its own. Owning a bundle gives the player every package in it. | Content packages (`base`, `dlc`, `test`) hold the releases you push to them with `depot push --package <name>`. The default is `base`. See [Push a build](/docs/publishing/push). Owning a DLC never gives the player the base game. Ownership only flows from a bundle to its members. Your game can check which packages a player owns with the [Depot SDK](/docs/sdk), using the package name, for example `HasEntitlement("expansion-1")`. A package's name can't be changed later. <Tabs items={["Dashboard","CLI"]}> <Tab value="Dashboard"> Under **Packages**, select **New package**. Choose the kind (Base game, DLC or Test build), a name and an optional title. Bundles are created with the CLI or the API. </Tab> <Tab value="CLI"> ```bash # A DLC depot package create --game my-game --name expansion-1 --kind dlc --title "The Frozen Reach" # A bundle of packages (package ids, comma separated) depot package create --game my-game --name complete --kind bundle --title "Complete Edition" \ --includes pkg_base_id,pkg_dlc_id # List packages and their ids depot package list --game my-game ``` </Tab> </Tabs> ### Turn a package off [#turn-a-package-off] A disabled package grants nothing new: no claims, no new keys and no grants. Existing entitlements stay on record and apply again when you turn the package back on. If the base package is off, nobody can claim, redeem or be invited to the game, and the dashboard says so on the **Store page** tab. ```bash depot package update --game my-game --id pkg_id --enabled false depot package update --game my-game --id pkg_id --enabled true ``` You can also change a package's title with `--title`. ## Game keys [#game-keys] A game key is a single-use code for one package. A player redeems it to own that package. Use keys for testers, press, backers, or stores that sell your game. You issue keys in **batches** of 1 to 1000. A batch can have a label, such as "Press, launch week", and an optional expiry date. After that date, unredeemed keys stop working. <Callout type="warn"> Depot shows the codes **once**, right after you issue them. It keeps only fingerprints of the codes, so it can't show them again. Save them before you close the dialog or the terminal. If you lose them, revoke the batch and issue a new one. </Callout> <Tabs items={["Dashboard","CLI"]}> <Tab value="Dashboard"> Under **Game keys**, select **Issue keys**. Choose the package, how many, an optional expiry and an optional label, then select **Issue keys**. The dialog then shows the codes. Use **Copy all**, or **Download** to save them as a text file with one code per line. Hand them to your store, press or players. The **Game keys** table lists each batch with its available and redeemed counts. It never shows codes. </Tab> <Tab value="CLI"> ```bash depot game-keys create --game my-game --package base --quantity 100 \ --label "Press, launch week" --expires-at 2027-01-31T23:59:59Z \ --out press-keys.txt ``` The codes are written to `--out`, one per line, readable only by you. Use `--print` instead to print them. You must pass one of the two. `--package` takes a package name or id and defaults to `base`. `--expires-at` is an ISO 8601 time. If the request times out, run it again with the same `--idempotency-key`. The CLI prints the key to use. Depot never issues a second batch for the same key, but it also won't show the codes again. List batches with their counts: ```bash depot game-keys list --game my-game ``` </Tab> </Tabs> You can't issue keys for a disabled package, or for an archived game. ### Revoke a batch [#revoke-a-batch] Revoking a batch stops its unused keys at once. Players who already redeemed a key from it keep the game. To take the game away from one of them, revoke their entitlement (see below). <Tabs items={["Dashboard","CLI"]}> <Tab value="Dashboard"> In the **Game keys** table, select **Revoke** on the batch, give a reason and confirm. The reason is recorded in the audit log. </Tab> <Tab value="CLI"> ```bash depot game-keys revoke --game my-game --batch batch_id --reason "Leaked on a forum" ``` </Tab> </Tabs> ### How players redeem keys [#how-players-redeem-keys] Keys look like this: ```text DPT1-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX ``` Players can redeem a key on your store page with **Redeem a key**, in the Depot launcher, or with the CLI: ```bash depot redeem ``` `depot redeem` reads the key from standard input, which keeps it out of shell history. Players don't need to type a key exactly. Lowercase is fine, and spaces, dashes and underscores are ignored. Letters that look like digits are read as the digit: `O` as `0`, and `I` or `L` as `1`. When a key is redeemed, the player owns its package at once and the game appears in their library. Some cases are refused: - **Already redeemed**: someone else used the key. - **Expired**: the batch's expiry date has passed. - **Already owned**: the player already owns the package. The key is not used up, so they can give it to someone else. - **Invalid**: the key doesn't exist, is mistyped, or was revoked. Repeated failed attempts are rate-limited. ## Give the game to a player [#give-the-game-to-a-player] You can give a package directly to a Depot player by their `@handle`. This is how invite-only games get to players, and it works for every game. The player gets the package in their library right away. <Tabs items={["Dashboard","CLI"]}> <Tab value="Dashboard"> Under **Player access**, select **Grant access**. Enter the player's **@handle** and choose: - **Package**: what they get. - **Kind**: **Gift / comp**, or **Test access**. - **Until** (optional): a date when the access ends. - **Note** (optional): a reference such as a support ticket. Select **Grant access**. The grant is recorded in the audit log. </Tab> <Tab value="CLI"> ```bash depot entitlement grant --game my-game --package base --handle @ada ``` Optional flags: - `--user`: a Depot user id instead of `--handle`. - `--ends-at`: an ISO 8601 time when the access ends. - `--reference`: an outside reference, such as a store order id. </Tab> </Tabs> If the player already owns the package, nothing changes. ## See who owns a package [#see-who-owns-a-package] <Tabs items={["Dashboard","CLI"]}> <Tab value="Dashboard"> Under **Player access**, **Keys & access** lists the owners of the selected package and how each got it: **Granted**, **Key**, **Free claim** or **Test access**. Search by **@handle** to find a player. </Tab> <Tab value="CLI"> ```bash depot entitlement list --game my-game depot entitlement list --game my-game --package base depot entitlement list --game my-game --user user_id ``` </Tab> </Tabs> ## Revoke a player's access [#revoke-a-players-access] Revoking an entitlement stops new downloads and new licenses for that player now. Copies already installed keep launching. If the game checks its license with the [Depot SDK](/docs/sdk), an installed copy keeps launching until its offline license expires. Only the entitlement you revoke ends. If the player owns the package another way, for example from a key and a grant, the other one stays. A revocation can't be undone. To give access back, grant it again. <Tabs items={["Dashboard","CLI"]}> <Tab value="Dashboard"> Under **Player access**, select **Revoke** next to the player, give a reason and select **Revoke access**. </Tab> <Tab value="CLI"> ```bash depot entitlement revoke --game my-game --id ent_id --reason "Refunded" ``` </Tab> </Tabs> ## Let a store issue keys [#let-a-store-issue-keys] If a store sells your game, you can let it issue keys itself, per order, up to a quota you set. The store needs its own Depot organization. It sees its delegations, orders and counts, never your players or their entitlements. ```bash # You: let a store's organization issue up to 500 keys for the base game depot retail delegate --game my-game --package base --retailer org_id --quota 500 # You: list and stop delegations depot retail delegations --game my-game depot retail revoke --game my-game --id delegation_id # The store: see what it can issue, and issue keys for an order depot retail holdings depot retail issue --delegation delegation_id --order order-1234 --quantity 1 --out order-1234.txt ``` Each order id can be used once. Repeating an order returns it without codes, so a retry never issues extra keys. Stopping a delegation stops new keys at once. Keys the store already issued stay valid unless you add `--revoke-issued-keys`. ## Permissions [#permissions] What a member or API key can do here depends on its permissions, for example `keys:issue`, `keys:revoke`, `entitlement:grant`, `entitlement:revoke` and `retailer:delegate`. See the [CLI reference](/docs/cli) and [API reference](/docs/api). # Launcher catalog (/docs/store/launcher-catalog) Besides your store page, you can list a game in the Depot launcher's catalog. Players who browse the catalog in the launcher can then get the game there, without opening your page first. Listing is opt-in and off by default. It is a sub-option of [Who can get it](/docs/store/who-can-get-it). ## When a listed game shows [#when-a-listed-game-shows] A listed game shows in the catalog only while all of these are true: - Its store page is published as **public**. Unlisted pages and drafts don't show. - Who can get it is **Free game** or **Key only**. - For a free game, the base package is turned on. - The game is not archived. **Invite-only games can't be listed.** If you switch a listed game to Invite only, or unpublish its page, it leaves the catalog. Its listing setting stays, so it comes back when the page is public again with Free or Key only. Changes reach the catalog within about a minute. ## What players do in the launcher [#what-players-do-in-the-launcher] - **Free game:** players select **Get it free** in the launcher to claim the game with their Depot account, then install it. - **Key only:** the install screen asks the player for a key. Listing never gives anyone the game on its own. Players still need to own it, by claiming it, redeeming a key or being granted it. ## List or unlist a game [#list-or-unlist-a-game] <Tabs items={["Dashboard","CLI"]}> <Tab value="Dashboard"> Open the game's **Store page** tab. Under **Who can get it**, turn on **Also list it in the Depot launcher's catalog**. This switch saves at once. You don't need to publish. It is turned off for invite-only and archived games. </Tab> <Tab value="CLI"> ```bash depot game update --game my-game --listed true depot game update --game my-game --listed false ``` You can also set it while you write or publish the page: ```bash depot game page --game my-game --access free --publish public --listed true ``` </Tab> </Tabs> The catalog uses the game's name, art and description from its **Settings** tab. You can also set the description with `depot game update --game my-game --description "..."`. # Store pages (/docs/store/pages) Every game can have one public store page on Depot. You write it as a draft, preview it, and publish it. When you publish, you get a link you can share: ```text https://staging.depot.games/p/{studio}/{game} ``` `{studio}` is your studio's slug and `{game}` is the game's slug. Neither can change, so the link stays the same for the life of the game. The page describes the game and gives players a button to get it. It never authorizes a download on its own: players still need to own the game, through a free claim, a key or a grant. See [Who can get it](/docs/store/who-can-get-it). ## Write the page [#write-the-page] <Tabs items={["Dashboard","CLI"]}> <Tab value="Dashboard"> <Steps> <Step> Open [the dashboard](https://dashboard.staging.depot.games), pick your game and open the **Store page** tab. </Step> <Step> Under **Who can get it**, choose **Free game**, **Key only** or **Invite only**. Set the **Development status** and the channel players install from. </Step> <Step> Fill in **What players read**: tagline, short description, description, tags, content warnings, accessibility notes, support link and minimum requirements. </Step> <Step> Add art, a trailer and a gallery. See [Branding and media](/docs/store/branding-and-media). </Step> <Step> Select **Save draft**, then **Preview** to see the page exactly as players will. </Step> </Steps> </Tab> <Tab value="CLI"> `depot game page` writes the draft and prints the page's state and URL: ```bash depot game page --game my-game \ --tagline "Keep the lantern lit through one long night." \ --short-description "A quiet game about one long night on a lighthouse rock." \ --description "$(cat description.txt)" \ --access free \ --status early-access \ --channel stable ``` Each flag sets one field. Flags you leave out keep their current value. Run `depot game page --game my-game` with no other flags to see the current state and URL. Tags, content warnings, minimum requirements, the support link and a YouTube or Vimeo trailer have no CLI flags. Set them in the dashboard, or with the API: `PATCH /v1/organizations/{org}/games/{id}/page`. See the [API reference](/docs/api). </Tab> </Tabs> ## Fields [#fields] | Field | What it is | Limits | | --- | --- | --- | | Tagline | One line under the game's name. | Up to 120 characters. | | Short description | Shown in link previews in chat apps, social posts and search results. Also shown as the "About the game" text if you have no description. | Up to 200 characters. | | Description | The main text of the page. | Up to 8000 characters. Plain text with the formatting below. | | Tags | Short labels shown on the page. Display only. | Up to 10. Letters, numbers, spaces and dashes, up to 24 characters each. Stored in lowercase. | | Content warnings | For example, violence or flashing lights. | Up to 10, up to 40 characters each. | | Accessibility | For example, full controller support, subtitles, colour-blind modes. | Up to 10 notes, up to 60 characters each. | | Development status | Alpha, Beta, Early access or Released. | Released by default. | | Minimum requirements | Free text per platform: OS, CPU, memory, GPU, storage. | Up to 400 characters per platform. | | Support link | Where players get help. | An https URL. | | Install channel | The channel players install from. | `stable` by default. | You need a tagline or a short description before you can publish. In the dashboard, the minimum requirements fields appear for each platform the game supports. You set the game's platforms in its **Settings** tab. ### Description formatting [#description-formatting] The description is plain text. Depot never renders HTML from it. It understands four things: - A blank line starts a new paragraph. - Lines that start with `- ` (a dash and a space) make a list. - `**bold**` makes text bold. - An `https://` link becomes a clickable link. The page shows the link's real domain. ```text Lantern Keep is a quiet game about one long night on a lighthouse rock. - About two hours, start to dawn - Plays with a mouse or a controller - **Saves every few minutes** Devlog: https://example.com/devlog ``` ### Install channel [#install-channel] The page installs from one channel, `stable` unless you change it. Players who select **Install** get the build on that channel for their platform. The platforms and download size on the page also come from the builds on that channel. The dashboard warns you if no build is on the channel yet and lists the channels that have builds. See [Channels and releases](/docs/publishing/channels-and-releases). ## Publish [#publish] Publishing copies your saved draft to the live page and applies the access setting. You choose how visible the page is: | Visibility | Who can see it | | --- | --- | | **Public** | Anyone. It is on your [studio page](/docs/store/storefront), in Depot's sitemap and can appear in search engines. | | **Unlisted** | Anyone with the link. It is not on your studio page, not in the sitemap, and asks search engines not to index it. | An unlisted link is not a password. Who can get the game is still decided by the access setting. <Tabs items={["Dashboard","CLI"]}> <Tab value="Dashboard"> On the **Store page** tab, select **Publish** for a public page or **Publish unlisted**. Publish uses the saved draft, so save first. Once the page is live, edits stay in the draft until you select **Publish changes**. The tab shows **Unpublished changes** while the draft and the live page differ. To switch visibility, use **Make public** or **Make unlisted**. </Tab> <Tab value="CLI"> Add `--publish public` or `--publish unlisted`. You can write and publish in one command: ```bash depot game page --game my-game --tagline "Keep the lantern lit." --publish public ``` To publish the current draft without changing it: ```bash depot game page --game my-game --publish public ``` </Tab> </Tabs> Changes reach every visitor within about a minute. ### What "live" means [#what-live-means] A page can be published while no player can install the game yet. The dashboard shows a green **Live** only when a player can actually get and install the game. Otherwise it shows **Live · nobody can install yet** in amber, with what is missing and how to fix it. A game is installable when all of these are true: - The game is not archived. - A build is on the page's install channel. Push one with `depot push --channel stable`, or choose a channel that has a build. See [Push a build](/docs/publishing/push). - The game has a base package, and it is turned on. Depot creates one with every game. See [Keys and access](/docs/store/keys-and-access). - The page is published, and Depot has not taken it down. The API returns the same check on the game as `readiness`, with `installable`, `channel` and a list of `blockers`. <Callout type="info"> You can publish before your first build. This lets you share the page early. Until a build is on the channel, the page lists the platforms set in the game's **Settings**, or "Not out yet" if there are none, and nobody can install. </Callout> ## Unpublish [#unpublish] Unpublishing takes the page down and turns off new free claims. Players who already own the game keep it and keep getting updates. Nothing is revoked. To remove a player's access, use [Keys and access](/docs/store/keys-and-access). <Tabs items={["Dashboard","CLI"]}> <Tab value="Dashboard"> On the **Store page** tab, select **Unpublish** and confirm. </Tab> <Tab value="CLI"> ```bash depot game page --game my-game --unpublish ``` </Tab> </Tabs> The page disappears within about a minute. Your draft stays, so you can publish again later. ## What players see [#what-players-see] The public page shows: - The hero art, your logo or the game's name, the tagline, and your studio's name with a link to your studio page. - A button to get the game: **Get it free**, **Redeem a key** or **Invite only**. Once a player owns it, the button installs it with the Depot launcher. - A row of facts taken from the real build on the install channel: platforms, the download size, that the game plays offline once installed, and the development status. - The trailer, then your screenshots and clips. - The description, under "About the game". - **What's new**: the latest releases on the install channel with their release notes, newest first. Add a note when you move the channel, with `depot push --channel stable --note "..."` or `depot channel set --note "..."`. Rollbacks are left out. See [Channels and releases](/docs/publishing/channels-and-releases). - Accessibility notes, content warnings, tags, minimum requirements, and a details box with the studio, status and support link. Link previews in chat apps and social posts use the cover or hero art and the short description. The public page never shows release, build or key identifiers. ## Reports and takedowns [#reports-and-takedowns] Anyone can report a page with the **Report this page** link at the bottom. Depot can take down a page that breaks the rules. Players who own the game keep it. If Depot takes your page down, the **Store page** tab shows **Taken down by Depot** and the reason. Contact support to restore it. # Studio storefront (/docs/store/storefront) Your studio gets one public page on Depot that lists your games: ```text https://staging.depot.games/p/{studio} ``` `{studio}` is your studio's slug. It can't change, so the link stays the same. The studio page goes live once at least one of your game pages is public. Until then, the link shows nothing. ## Edit your studio page [#edit-your-studio-page] <Tabs items={["Dashboard","API"]}> <Tab value="Dashboard"> <Steps> <Step> Open [the dashboard](https://dashboard.staging.depot.games) and select **Storefront** in the studio menu. </Step> <Step> Under **Branding**, fill in the fields below and select **Save**. </Step> <Step> Under **Logo and banner**, upload your images. Images save as soon as they upload. </Step> </Steps> The **Studio page** card shows your link, with **Copy link** and **Open**. </Tab> <Tab value="API"> The CLI has no storefront commands. Use the API with an organization API key or a signed-in session: - `GET` and `PATCH /v1/organizations/{org}/storefront`: display name, bio, links and support link. - `PUT` and `DELETE /v1/organizations/{org}/storefront/art/{kind}`: `logo` or `banner`, with the image as the request body. See the [API reference](/docs/api). </Tab> </Tabs> Editing the storefront needs permission to update the organization. ## Fields [#fields] | Field | What it is | Limits | | --- | --- | --- | | **Display name** | What players see on your store pages. Your studio's name inside Depot is set separately, in **Settings**. | Required, up to 60 characters. | | **About the studio** | A short bio under your name. | Up to 600 characters. Plain text. | | **Links** | For example your website, newsletter or social accounts. Each has a label and a URL. | Up to 5. Labels up to 30 characters. https URLs only. | | **Support link** | Where players get help. Shown with your links as "Support". | An https URL. | Links show their real domain to players. ## Logo and banner [#logo-and-banner] | Image | Recommended | Accepted | | --- | --- | --- | | **Logo** | 512×512 | Square, 128 to 1024 px wide, up to 1 MB. | | **Banner** | 1920×480 | Wide, 2.5:1 to 6:1, 1200 to 3840 px wide, up to 4 MB. | Both accept PNG, JPEG or WebP. The banner sits behind your name at the top of the page. Without a banner, the page shows a plain background. These are your studio's images. Each game's own art (hero, cover, icon and logo) is set per game. See [Branding and media](/docs/store/branding-and-media). ## How games appear [#how-games-appear] Your studio page lists every game whose store page is **public**, newest published first. Each game shows as a card with: - Its hero art, or its cover if it has no hero. - Its name and tagline. - Its development status, unless it is Released. Each card links to the game's page. Unlisted pages, drafts and archived games are not on your studio page. To add a game, publish its page as public. To take it off, make the page unlisted or unpublish it. See [Store pages](/docs/store/pages). The dashboard's **Storefront** section also lists your games, shows who can get each one, and links to each game's **Store page** tab. ## Your studio's address [#your-studios-address] Verified studios also get their own address: `https://<your-studio>.depot.games`. It shows your studio page, and each game at `https://<your-studio>.depot.games/<game>`. Share whichever link you like; both show the same pages. - Search engines treat the `staging.depot.games/p/...` page as the main one, so your pages don't compete with each other. - Players sign in on the main site. On your address, **Get it free** and **Redeem a key** take them to the same game page there, where they get the game into their library. - A few names are reserved for Depot (for example `www`, `api`, `docs` and `store`). If your studio's name is one of them, your studio has no separate address; your `/p/` link still works. Depot verifies a studio before its address goes live, so a lookalike name never becomes a working link. Until then the dashboard's **Storefront** section shows the address you'll get; once you're verified, it shows the live address with a copy button. <Callout type="info"> While Depot is in staging, your address shows your staging storefront. </Callout> # Who can get it (/docs/store/who-can-get-it) Every build you push is encrypted. A player can download and play it only if they own the game. **Who can get it** decides how a player comes to own it. You set it in one place: the game's store page. ## The three options [#the-three-options] | Option | Button on the page | How players get the game | | --- | --- | --- | | **Free game** | **Get it free** | Anyone with a Depot account can claim it and play. Good for free games and demos. | | **Key only** | **Redeem a key** | Players redeem a key you hand out, for example to alpha testers, press or backers. Free claims are turned off. | | **Invite only** | **Invite only** | The page describes the game. Only players you grant access can download it. Free claims are turned off. | In all three cases you can still give the game to specific players and issue keys. See [Keys and access](/docs/store/keys-and-access). A free claim needs a Depot account, so players sign in before they get the game. Claiming is free and safe to repeat: a player who already owns the game keeps what they have. ## Set it [#set-it] <Tabs items={["Dashboard","CLI"]}> <Tab value="Dashboard"> Open the game's **Store page** tab. Under **Who can get it**, choose **Free game**, **Key only** or **Invite only**. Save the draft, then publish. See [Store pages](/docs/store/pages). </Tab> <Tab value="CLI"> `--access` is `free`, `key` or `invite`: ```bash depot game page --game my-game --access free --publish public ``` </Tab> </Tabs> The setting is part of the page draft. It takes effect when you publish. ## Change it later [#change-it-later] You can change who can get it at any time. Change the setting and publish again. The publish dialog in the dashboard tells you what changes. Players who already have the game keep it, whichever option you switch to. Switching from Free to Key only stops new free claims. It does not take the game away from anyone who claimed it. Unpublishing the page also stops new free claims. Players who own the game keep it and keep getting updates. To take the game away from a specific player, revoke their access. See [Keys and access](/docs/store/keys-and-access). ## The base package [#the-base-package] Each option works through the game's **base package**: the package that holds the base game. Depot creates it with every new game, so you don't have to. - **Free** turns on free claims of the base package when you publish. - **Key only** and **Invite only** turn free claims off when you publish. A free page needs the base package turned on. If it is turned off, the dashboard says so and you can't publish the page as free until you turn it on in **Keys & access**. If a script runs `depot package create --name base`, it takes over the base package Depot already made instead of failing. Scripts written before this change keep working. ## Where the dashboard shows it [#where-the-dashboard-shows-it] The game's header, the **Games** list and the **Storefront** section show who can get each game and the page's state. For example: - **Free · live**: the page is public and players can get and install the game. - **Key only · live, unlisted**: the page is unlisted and players need a key. - **Free · nobody can install yet**: the page is up, but something is missing, such as a build on the install channel. - **Keys and invites · no store page yet**: no page is published. You can still issue keys and grant access. <Callout type="info"> Games created before this model may be older public games. Anyone can install those without a Depot account, whatever you choose on the page. The dashboard marks them as older public games. Depot no longer offers this for new games. </Callout> ## Related [#related] <Cards> <Card title="Store pages" href="/docs/store/pages" description="Write, publish and unpublish the page." /> <Card title="Keys and access" href="/docs/store/keys-and-access" description="Packages, keys, grants and revoking access." /> <Card title="Launcher catalog" href="/docs/store/launcher-catalog" description="Also list a free or key-only game in the Depot launcher." /> </Cards> # Describe the calling credential: an API key (organization, permissions) or a person (identity, organizations) (/docs/api/organization/v1/whoami/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Organizations I belong to, with my role and permissions (/docs/api/organization/v1/organizations/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Create an organization (a publisher); you become its owner (/docs/api/organization/v1/organizations/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # The organization, and the caller's role and permissions in it (member or API key) (/docs/api/organization/v1/organizations/org/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Rename (organization:update; people only) (/docs/api/organization/v1/organizations/org/patch) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Members (people only; emails only for member managers) (/docs/api/organization/v1/organizations/org/members/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Change a member's role (member:update; only owners grant or remove ownership) (/docs/api/organization/v1/organizations/org/members/user/patch) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Remove a member (member:delete), or leave (your own id). The last owner can't leave. (/docs/api/organization/v1/organizations/org/members/user/delete) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Pending invitations (invitation:create) (/docs/api/organization/v1/organizations/org/invitations/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Invite by email (invitation:create); accepted on the identity service's invitation page (/docs/api/organization/v1/organizations/org/invitations/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Cancel an invitation (invitation:cancel) (/docs/api/organization/v1/organizations/org/invitations/id/delete) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Organization API keys (apiKey:read; never the secrets) (/docs/api/organization/v1/organizations/org/api-keys/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Create an API key (apiKey:create; people only). Permissions must be a subset of your role and can't include member or key management. Secret shown once. (/docs/api/organization/v1/organizations/org/api-keys/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Replace a key (same name and permissions); the old one stops now or after grace_seconds (/docs/api/organization/v1/organizations/org/api-keys/id/rotate/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Revoke a key (apiKey:delete); immediate (/docs/api/organization/v1/organizations/org/api-keys/id/delete) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Audit events, newest first (organization:update). Events carry safe metadata only: never codes, secrets or keys. (/docs/api/organization/v1/organizations/org/audit/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Storage and activity, computed live (game:read). stored_bytes counts each pack once per game (deduplicated); logical_bytes counts every published release in full. Only verified releases count. (/docs/api/organization/v1/organizations/org/usage/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # The organization's games (game:read) (/docs/api/games/v1/organizations/org/games/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Create a game (/docs/api/games/v1/organizations/org/games/post) Requires `game:create` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # One game (game:read) (/docs/api/games/v1/organizations/org/games/id/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Edit a game (game:update): name, description, catalog listing (public, active games only), supported platforms, or archive (status). Archived games keep working for players who own them but take no new releases, keys, grants or free claims, and leave the catalog. Pass expected_revision to refuse stale edits (409). (/docs/api/games/v1/organizations/org/games/id/patch) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Remove store art (game:update) (/docs/api/games/v1/organizations/org/games/id/art/kind/delete) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Upload store art (game:update). logo: the game's wordmark shown in place of its name, a transparent PNG or WebP 200–2400 px wide, 1:2 to 8:1, ≤1 MB. The body is the image: PNG, JPEG or WebP (identified from the bytes; SVG refused). icon: square, 128–2048 px, ≤1 MB (512×512 recommended). cover: 2:3 portrait, 300–2400 px wide, ≤4 MB (600×900). hero: about 3:1, 1280–3840 px wide, ≤8 MB (1920×620). Served from the CDN. (/docs/api/games/v1/organizations/org/games/id/art/kind/put) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # The studio's public storefront profile (game:read). Created on first use from the organization's name and slug. (/docs/api/store-pages/v1/organizations/org/storefront/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Edit the storefront profile (organization:update): display name, bio, up to 5 https links, support URL. Optional expected_revision. (/docs/api/store-pages/v1/organizations/org/storefront/patch) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Remove the logo or banner (organization:update) (/docs/api/store-pages/v1/organizations/org/storefront/art/kind/delete) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Upload the studio logo (square, 128–1024 px, ≤1 MB) or banner (1200–3840 px wide, 2.5:1–6:1, ≤4 MB); organization:update. PNG, JPEG or WebP body. (/docs/api/store-pages/v1/organizations/org/storefront/art/kind/put) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # The game's public page: draft, published snapshot, visibility and public URL (game:read) (/docs/api/store-pages/v1/organizations/org/games/id/page/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Edit the page draft (game:update). Plain text only (the description supports paragraphs, '- ' lists, **bold** and https links, all escaped). Trailers are a YouTube or Vimeo id. Links must be https. Publishing is separate. (/docs/api/store-pages/v1/organizations/org/games/id/page/patch) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Publish the draft (game:update) as public (listed, indexed) or unlisted (link only, noindex). Applies the access setting: free turns on free claims of the base package; key and invite turn them off. Visibility never authorizes downloads. (/docs/api/store-pages/v1/organizations/org/games/id/page/publish/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Take the page down (game:update). New free claims stop; players who already own the game keep it, updates included. Nothing is revoked. (/docs/api/store-pages/v1/organizations/org/games/id/page/unpublish/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Upload a screenshot (game:update): landscape, 960–3840 px wide, ≤6 MB, PNG/JPEG/WebP body. Add its id to the draft's screenshots to show it. (/docs/api/store-pages/v1/organizations/org/games/id/page/screenshots/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Remove a screenshot from the draft (game:update); the published page keeps it until the next publish (/docs/api/store-pages/v1/organizations/org/games/id/page/screenshots/shot/delete) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # The game's uploaded videos (game:read) (/docs/api/store-pages/v1/organizations/org/games/id/page/videos/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Start a video upload (game:update). purpose loop: the muted clip over the hero, MP4 ≤30 s and ≤40 MB. trailer or clip: MP4 ≤10 min and ≤500 MB. PUT the file to put_url with exactly the returned headers, then call complete. (/docs/api/store-pages/v1/organizations/org/games/id/page/videos/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Check an uploaded video (game:update): an MP4 within its purpose's length and size. 422 (and the upload is discarded) otherwise. (/docs/api/store-pages/v1/organizations/org/games/id/page/videos/vid/complete/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Set a video's still frame (game:update): PNG, JPEG or WebP 480–3840 px wide, ≤4 MB. Shown before the video plays and instead of it for reduced motion. (/docs/api/store-pages/v1/organizations/org/games/id/page/videos/vid/poster/put) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Remove a video (game:update): out of the draft now; the published page keeps it until the next publish (/docs/api/store-pages/v1/organizations/org/games/id/page/videos/vid/delete) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Packages of a game (game:read) (/docs/api/packages-and-access/v1/organizations/org/games/id/packages/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Define a package (game:update): base, dlc, test (content = releases pushed with --package <name>) or bundle (includes other packages of the game) (/docs/api/packages-and-access/v1/organizations/org/games/id/packages/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Enable/disable, retitle, toggle free claims (game:update). Disabled packages grant nothing new. (/docs/api/packages-and-access/v1/organizations/org/packages/pkg/patch) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Grant a package to a Depot user (entitlement:grant). Already owned: 200 with the existing entitlement. (/docs/api/packages-and-access/v1/organizations/org/entitlements/grants/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Entitlements to the organization's packages, oldest first (entitlement:read). Each names its player by display name and @handle only. (/docs/api/packages-and-access/v1/organizations/org/entitlements/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Revoke an entitlement (entitlement:revoke): new grants, sessions and key delivery stop at once; installed copies keep launching (/docs/api/packages-and-access/v1/organizations/org/entitlements/id/revoke/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Claim a free package (/docs/api/packages-and-access/v1/packages/pkg/claim/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # A short-lived download grant (1 hour, 5 sessions) for a package the player owns now; exchange it at /v1/sessions. Every use re-checks the entitlement. (/docs/api/packages-and-access/v1/games/id/download-grants/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Issue a batch of single-use game keys (keys:issue). The codes are in this response only; a retry with the same Idempotency-Key returns the batch without codes. (/docs/api/packages-and-access/v1/organizations/org/packages/pkg/key-batches/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Delegations this publisher granted (retailer:delegate) (/docs/api/packages-and-access/v1/organizations/org/retailer-delegations/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Let a retailer organization issue keys for one package, up to a quota (retailer:delegate) (/docs/api/packages-and-access/v1/organizations/org/retailer-delegations/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Stop a retailer's issuance now; optionally revoke its still-available keys (default: they stay valid) (/docs/api/packages-and-access/v1/organizations/org/retailer-delegations/id/revoke/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Delegations this retailer holds, with remaining quota (retail:issue) (/docs/api/packages-and-access/v1/organizations/org/retail/delegations/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Retailer just-in-time issuance (retail:issue): unique per order_id; quota reserved atomically; codes shown once (/docs/api/packages-and-access/v1/organizations/org/retail/keys/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Issue a download grant for a package (/docs/api/packages-and-access/v1/organizations/org/games/id/grants/post) Requires `entitlement:grant` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Revoke a grant (its sessions stop working) (/docs/api/packages-and-access/v1/organizations/org/games/id/grants/gid/revoke/post) Requires `entitlement:grant` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Games and packages the signed-in player owns (store-page fields only) (/docs/api/players/v1/me/library/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # The signed-in person: identity (issuer, subject), display name, handle and organization memberships. Requires a Depot (OIDC) access token. (/docs/api/players/v1/me/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Claim a game whose published page is free (signed-in player): grants its base package, provenance free_claim. Idempotent: already owned (from any source) returns 200 and changes nothing. (/docs/api/players/v1/games/id/claim/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Listed games and their channels for the launcher (not download authorization) (/docs/api/players/v1/catalog/get) Games their studios listed: older public games, and restricted games whose store page is public and free (claimable) or key only. `access` is what a player does next. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Look up a game by id or slug (what a store link's launcher shows before sign-in) (/docs/api/players/v1/games/ref/get) Art, studio and `access` come from the game's published store page (public or unlisted); `page_url` only for a public page. A draft or taken-down page shares none of them. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Batches with available/redeemed/revoked counts, never codes (keys:read) (/docs/api/game-keys/v1/organizations/org/key-batches/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Revoke a batch's still-available keys (keys:revoke); redeemed keys keep their entitlements (/docs/api/game-keys/v1/organizations/org/key-batches/id/revoke/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Redeem a game key into an entitlement (signed-in player; atomic, single use). Already owned: the key is not used. (/docs/api/game-keys/v1/keys/redeem/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # A signed, game-scoped offline license (30 days) for the signed-in player's entitlements in this game (ADR 0010). Root-certified licensing key; not device-bound. (/docs/api/licensing/v1/games/game/licenses/offline/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Root-signed list of revoked licensing keys for this environment (/docs/api/licensing/v1/licensing/key-status/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Liveness and database check (/docs/api/public/healthz/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # This document (/docs/api/public/v1/openapi-json/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # A studio's public storefront and its public game pages (404 when it has none). ETag; cached ~60 s. (/docs/api/public/v1/public/studios/slug/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # A published game page (public or unlisted) by game slug or id. Only published metadata: never release, build or key identifiers. ETag; cached ~60 s. (/docs/api/public/v1/public/studios/slug/games/game/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Public game pages (never unlisted or drafts), for sitemaps (/docs/api/public/v1/public/sitemap/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Report a studio or game page (malware, copyright, impersonation, scam, dangerous link, misleading, other). Rate-limited; never public. (/docs/api/public/v1/public/reports/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Enroll an Ed25519 publisher public key (/docs/api/signing-keys/v1/organizations/org/games/id/keys/post) Requires `signingKey:manage` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Revoke a publisher key (/docs/api/signing-keys/v1/organizations/org/games/id/keys/keyid/revoke/post) Requires `signingKey:manage` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Publisher keys and their states (clients pin these) (/docs/api/releases-and-channels/v1/games/id/keys/trusted/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Get or create the protection domain of a package (/docs/api/releases-and-channels/v1/organizations/org/games/id/domains/post) Requires `release:publish` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Register a domain's content key once; wrapped under the Worker KEK and never returned to publishers (/docs/api/releases-and-channels/v1/organizations/org/games/id/domains/pd/content-key/post) Requires `signingKey:manage` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Published releases in a domain (for encrypted record reuse) (/docs/api/releases-and-channels/v1/organizations/org/games/id/domains/pd/published-indexes/get) Requires `release:publish` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Releases, newest first (game:read). status published means the API verified every object's hash and size and the descriptor's signature by an active key of the game; only published releases can go on a channel. (/docs/api/releases-and-channels/v1/organizations/org/games/id/releases/get) Requires `game:read` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Reserve a release name and the next sequence number (/docs/api/releases-and-channels/v1/organizations/org/games/id/releases/post) Requires `release:publish` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Published release: signed envelope, manifest and index URLs (/docs/api/releases-and-channels/v1/games/id/releases/rid/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Download URLs for packs of a release (CDN for public, presigned for restricted) (/docs/api/releases-and-channels/v1/games/id/releases/rid/pack-urls/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Open (or resume) an upload session; existing objects are reported present (/docs/api/releases-and-channels/v1/organizations/org/games/id/releases/rid/uploads/post) Requires `release:publish` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Presigned multipart part URLs and already-uploaded parts (/docs/api/releases-and-channels/v1/organizations/org/games/id/uploads/sid/parts/post) Requires `release:publish` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Complete a multipart upload from the parts R2 holds (/docs/api/releases-and-channels/v1/organizations/org/games/id/uploads/sid/complete/post) Requires `release:publish` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Abort an upload session (/docs/api/releases-and-channels/v1/organizations/org/games/id/uploads/sid/abort/post) Requires `release:publish` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Verify objects, index and signature; publish the release (/docs/api/releases-and-channels/v1/organizations/org/games/id/uploads/sid/finalize/post) Requires `release:publish` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # List channels (game:read) (/docs/api/releases-and-channels/v1/organizations/org/games/id/channels/get) Requires `game:read` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Resolve a channel (/docs/api/releases-and-channels/v1/games/id/channels/platform/name/get) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Promote or roll back a channel (channel:write): point it at a published release of its platform. Compare-and-swap on revision (null creates the channel): a stale or replayed request gets 409 revision_mismatch with the current release, never a double move. Moving to a lower sequence is a rollback and needs allow_rollback. Recorded in the channel's history with the optional note. (/docs/api/releases-and-channels/v1/organizations/org/games/id/channels/platform/name/put) Requires `channel:write` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # A channel's moves, newest first (game:read) (/docs/api/releases-and-channels/v1/organizations/org/games/id/channels/platform/name/history/get) Requires `game:read` in the organization. Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Exchange a download grant for a short-lived session (/docs/api/releases-and-channels/v1/sessions/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json # Release a content key to a download session (/docs/api/releases-and-channels/v1/games/id/content-key/post) Full request and response schemas: https://api.staging.depot.games/v1/openapi.json