Partner API
Create and iterate Fifth Door games from a hosted agent over REST. Muse signs the player in with OAuth; scripts can use a personal API key. POST a prompt, poll for replies, answer questions, and open the play URL when available.
Public guide for hosted agents (Muse first). This file is the source for /docs/partners and the raw markdown at /docs/partners.md — read this URL; you do not need to call the API first.
The Partner API lets a hosted agent that cannot run MCP create and iterate Fifth Door games from a natural-language prompt. It is REST over HTTPS, generated from Fifth Door's oRPC contract. Builds run asynchronously on Fifth Door's existing Studio turn pipeline. You get a play URL when the game is ready, and images when the builder asks about a look before the first publish.
If you are a person building in your own editor, use the MCP server instead.
Base URL
https://api.beta.fifthdoor.com/api/partners/v1
Machine-readable spec (no auth): /openapi.json. This guide is also raw markdown at /docs/partners.md.
Authenticate
Hosted agents (Muse) should use OAuth 2.1 authorization code + PKCE so the player can sign in or create a Fifth Door account in a browser window. Personal API keys (fd_) still work for scripts.
OAuth (Muse)
- Discover the authorization server at
/.well-known/oauth-authorization-server(no auth). Protected-resource metadata is at/.well-known/oauth-protected-resource. - Register a public client (
token_endpoint_auth_method: "none") againstPOST /api/oauth/registerwith yourredirect_uris. HTTPS is required excepthttp://localhost/http://127.0.0.1. - Open the
authorization_endpoint(/oauth/authorizeon the Fifth Door web origin) withresponse_type=code,client_id,redirect_uri,state,code_challenge, andcode_challenge_method=S256. The player signs in or signs up, then Allow. - Exchange the code at
POST /api/oauth/token(application/x-www-form-urlencodedor JSON) withgrant_type=authorization_code,code,redirect_uri,client_id, andcode_verifier. - Send the returned
access_tokenasAuthorization: Beareron Partner API requests.
The access token carries the partner scope and is valid for up to one hour (expires_in reports the remaining seconds). It reaches this API only: it cannot publish bundles directly or change artifact visibility under /api/platform. OAuth apps can list and change only games created by their own client_id; personal API keys retain account-wide access to partner-created games. Sessions without an app association remain accessible in Studio or with a personal API key.
Token responses include a refresh_token. Exchange it at POST /api/oauth/token with grant_type=refresh_token, refresh_token, and client_id. Save both replacement tokens atomically before another request: refresh tokens are single-use, rotation invalidates the previous access token, and replay revokes the connection. Serialize refresh requests. The connection lasts at most 30 days from authorization, after which the player must authorize again. A lost refresh response also requires reauthorization.
Re-authorizing the same client_id supersedes its previous grant. Disconnect with POST /api/oauth/revoke, passing token (access or refresh token) and client_id; it returns 200 for unknown or already revoked tokens too. The player can also disconnect under Connected apps on their account page. Disconnect blocks future API access; it does not delete games or cancel builds already accepted.
Authorization and token requests may include resource=https://api.beta.fifthdoor.com/api/partners/v1. Other resources and scopes are rejected. Omitting resource selects this API. Self-registered clients are visibly marked Unverified app until Fifth Door verifies their identity.
A 401 from the Partner API includes WWW-Authenticate pointing at the protected-resource metadata.
API keys
Create a personal API key on the Fifth Door account page. Keys start with fd_. Send it as a Bearer token on every request except / and /openapi.json:
Authorization: Bearer fd_your_key
X-API-Key: fd_your_key is also accepted.
The token belongs to a full Fifth Door account. Guest or anonymous users get 403. If the account has not accepted the current terms, the API returns 403 with instructions to sign in on the web and accept, then retry.
GET /me confirms the token:
curl -sS \
-H "Authorization: Bearer fd_your_key" \
https://api.beta.fifthdoor.com/api/partners/v1/me
{
"userId": "…",
"username": "creator",
"email": "creator@example.com",
"name": "Creator"
}
Create a game
POST /games with a prompt and an Idempotency-Key header. The prompt is 1–8000 characters after trim.
curl -sS -X POST \
-H "Authorization: Bearer fd_your_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: create-memory-match-1" \
-d '{"prompt":"Make a memory-match game with animal cards"}' \
https://api.beta.fifthdoor.com/api/partners/v1/games
201 and a game object. Status is building. playUrl is null until the first successful publish.
{
"id": "museGame01",
"status": "building",
"title": null,
"playUrl": null,
"message": null,
"messageId": null,
"images": [],
"studioPath": "/sessions/museGame01/chat",
"source": "muse"
}
Poll until the turn finishes
Poll GET /games/{id} while status is building. Turns can take several minutes. Do not send another prompt while a turn is building.
Once the turn finishes, deliver any new message to the player, remembering messageId to avoid repeating it on subsequent polls or reconnects. Show images when present — a look-approval turn may have no playUrl yet, and the picture is the thing to react to. Offer playUrl whenever available: a ready game can still ask a question about an edit. A look question with neither images nor playUrl is incomplete. Send the player's answer to POST /games/{id}/messages with a fresh idempotency key, then resume polling. Handle interrupted explicitly; the player can retry with another prompt or inspect Studio.
curl -sS \
-H "Authorization: Bearer fd_your_key" \
https://api.beta.fifthdoor.com/api/partners/v1/games/museGame01
{
"id": "museGame01",
"status": "ready",
"title": "Animal Match",
"playUrl": "https://play.fifthdoor.com/play/animal-match",
"message": "Animal Match is ready to play!",
"messageId": "assistant-1:respond:reply-1",
"images": [],
"studioPath": "/sessions/museGame01/chat",
"source": "muse"
}
A look question before publishing can return:
{
"id": "museGame01",
"status": "idle",
"title": "A Jar of Dusk",
"playUrl": null,
"message": "Here's the soft, painted look. Does this feel right? [options: A) Looks good, build it (recommended) B) Make dusk darker]",
"messageId": "assistant-2:respond:reply-1",
"images": [
{ "url": "https://api.beta.fifthdoor.com/api/platform/style-refs/museGame01/style-reference.png" }
],
"studioPath": "/sessions/museGame01/chat",
"source": "muse"
}
message is the latest player-facing reply from the most recent player turn, including labeled question options and suggested next steps. It excludes internal tool activity. messageId is an opaque, stable reply identifier. Both fields are null while building or when that turn has no visible reply; an older answered question is never substituted. An idle response with no message has no reply to deliver; inspect Studio or send another prompt rather than polling indefinitely. A message can also be a completion reply, so its presence alone does not mean the builder is waiting for an answer.
images are absolute URLs for that same reply: the style reference on a look question, or playtest frames from that turn when the reply did not attach its own. Empty while building, on list/create/iterate, and when the turn has no durable picture. Fetch them as images; they do not require a Partner token. Do not approve a look from text alone.
studioPath is a path on the Fifth Door web origin. The account that owns the API key can open it in Studio.
Status
| Status | Meaning |
|---|---|
building |
A turn is in progress. Keep polling. |
ready |
The game is published. playUrl is an absolute play URL. |
idle |
The last turn finished without a published play URL. Send another prompt or inspect the session in Studio. |
interrupted |
The last turn stopped. You can send another prompt. |
Iterate
POST /games/{id}/messages sends another builder turn (answer a question, change difficulty, add a mechanic, fix a bug). Same prompt limits as create.
curl -sS -X POST \
-H "Authorization: Bearer fd_your_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: iterate-memory-match-1" \
-d '{"prompt":"Make it harder and add a timer"}' \
https://api.beta.fifthdoor.com/api/partners/v1/games/museGame01/messages
200 and the game with status: "building". If a turn is already running, the API returns 409:
{
"defined": true,
"code": "CONFLICT",
"status": 409,
"message": "Wait until the current build finishes, then send your change"
}
Poll GET /games/{id} until status is not building, then retry.
List games
GET /games lists games this OAuth app created for the account. Personal API keys list all games this account created through the Partner API. Studio-created sessions are not included. List and create/iterate responses keep message, messageId, and images empty; fetch a game by ID to read its latest reply.
{ "games": [{ "id": "museGame01", "status": "ready", "title": "Animal Match", "playUrl": "https://play.fifthdoor.com/play/animal-match", "message": null, "messageId": null, "images": [], "studioPath": "/sessions/museGame01/chat", "source": "muse" }] }
Safe retries
Every create or iterate request requires Idempotency-Key (1–128 letters, numbers, dots, underscores, colons or hyphens). Use a fresh key for each intentional action and the same key and body for retries. Reusing a key for a different game or prompt returns 409. Completed requests retain their original response for 30 days; poll the game for its current status. Retrying does not consume another build allowance.
The turn and retry record are saved before dispatch. If dispatch fails, retry with the same key; the background worker also retries pending dispatches. Concurrent changes to one game return 409 rather than superseding an active build.
Visibility and data
Creating and iterating are write actions that run an AI builder and may publish a playable bundle. Share links may be accessible to anyone who has the link; unlisted does not mean private. Studio edits after a public release may be staged for review rather than immediately changing the released game. The API cannot change visibility or promote a draft; use Studio for those actions.
GET /me shares the account's name, email, username and ID with the connected app. Prompts become part of the stored Studio conversation and are processed by the configured AI providers and observability systems. Do not include secrets or unrelated personal data in prompts. See the privacy policy and terms for governing policies; account deletion is available on the web.
Rate limits and errors
Create and iterate each count as one turn. The limit is 10 turns per hour per account. Over the limit: 429 with code: "TOO_MANY_REQUESTS".
OAuth endpoints accept bodies up to 16 KiB, with 10 registrations or 120 other requests per minute per IP and an additional global registration cap. OAuth 429 responses include Retry-After: 60. Partner request bodies are limited to 40 KiB.
Partner procedure errors use the oRPC body: { defined, code, status, message }.
| Status | When |
|---|---|
400 |
Invalid JSON, empty / overlong prompt, or missing / invalid idempotency key. |
401 |
Missing or invalid token. WWW-Authenticate names the OAuth authorization server. |
403 |
Guest account, or terms not accepted on the web. |
404 |
Unknown id, or the game is not owned by this account, or it was not created through this API. |
409 |
Iterate while status is building, or reuse an idempotency key with different input. |
413 |
Request body too large. |
429 |
Turn rate limit. |
500 |
Dispatch pending. Retry with the same idempotency key. |
What you should not do
- Do not call Studio oRPC or the MCP server from a hosted connector. This REST surface is the one for that.
- Do not create a second builder. A create or iterate is one
enqueueTurnon the existing pipeline. - Do not assume a game is playable because
POSTreturned201. Wait forreadyand a non-nullplayUrl. - Do not answer a look question from text alone. Show
imageswhen present.