{"openapi":"3.1.0","info":{"title":"Phoenix Labs API","version":"1.0.0","summary":"Restore old video in the cloud, and turn still images into video clips with sound.","description":"Prices are real US dollars. Motion clips and balance-paid restores spend the Phoenix balance behind the key in `Authorization: Bearer`: the owner's wallet key (pmk_…) or an agent key (pak_…) the owner made with a spending limit. Restores can instead be paid per job through a Lemon Squeezy checkout link, with no key. Files go straight to storage: create the job, PUT the file to `upload_url` with `upload_headers`, then start it. AI agents can use the same operations as MCP tools at https://api.phoenixlabs.space/mcp. Guide: https://phoenixlabs.space/developers","contact":{"email":"support@phoenixlabs.space","url":"https://phoenixlabs.space/developers"},"termsOfService":"https://phoenixlabs.space/terms"},"servers":[{"url":"https://api.phoenixlabs.space"}],"externalDocs":{"url":"https://phoenixlabs.space/developers"},"components":{"securitySchemes":{"walletKey":{"type":"http","scheme":"bearer","description":"A wallet key (pmk_…) or an agent key (pak_…)."}},"schemas":{"Error":{"type":"object","properties":{"detail":{"type":"string"},"needs_topup":{"type":"boolean"},"over_limit":{"type":"boolean"},"refused":{"type":"string","description":"Content-rule refusal code."}}},"Wallet":{"type":"object","properties":{"wallet_id":{"type":"string"},"balance_cents":{"type":"integer"},"spendable_cents":{"type":"integer"},"paused":{"type":"boolean"},"email":{"type":"string","description":"Only shown to the wallet key."},"agent_key":{"$ref":"#/components/schemas/AgentKey"}}},"AgentKey":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"limit_cents":{"type":"integer"},"spent_cents":{"type":"integer"},"remaining_cents":{"type":"integer"},"created_at":{"type":"string"},"last_used_at":{"type":["string","null"]},"revoked":{"type":"boolean"}}},"RestoreJob":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["pending_payment","awaiting_upload","queued","processing","done","failed","refunded","cancelled","expired"]},"quality":{"type":"string","enum":["faithful","enhanced","enhanced_max"]},"amount_cents":{"type":"integer"},"duration_sec":{"type":"number"},"download_url":{"type":["string","null"]},"error":{"type":["string","null"]}}},"Clip":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["awaiting_upload","queued","processing","done","failed","refused","expired","cancelled"]},"size":{"type":"string"},"seconds":{"type":"integer"},"prompt":{"type":"string"},"loop":{"type":"boolean"},"amount_cents":{"type":"integer"},"seed":{"type":["integer","null"]},"video_url":{"type":"string"},"download_url":{"type":"string"},"error":{"type":["string","null"]}}},"RestoreRequest":{"type":"object","required":["duration_sec"],"properties":{"quality":{"type":"string","enum":["faithful","enhanced","enhanced_max"],"default":"enhanced","description":"faithful is shown as Standard, enhanced_max as Studio Max."},"duration_sec":{"type":"number"},"width":{"type":"integer"},"height":{"type":"integer"}}}}},"paths":{"/cloud/quote":{"post":{"operationId":"quoteRestore","summary":"Price a cloud restore","tags":["Cloud Restore"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RestoreRequest"}}}},"responses":{"200":{"description":"The price.","content":{"application/json":{"schema":{"type":"object","properties":{"amount_cents":{"type":"integer"},"display":{"type":"string"}}}}}},"422":{"description":"Over a limit."}}}},"/cloud/jobs":{"post":{"operationId":"createRestore","summary":"Create a restore job and get an upload URL","tags":["Cloud Restore"],"description":"With a key (and pay_with not 'checkout') the price is taken from the balance now and the job waits for its upload; then call /start. Without a key, give an email, upload, then call /checkout.","security":[{},{"walletKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/RestoreRequest"},{"type":"object","properties":{"filename":{"type":"string"},"content_type":{"type":"string"},"size":{"type":"integer","description":"Bytes, up to 2 GB."},"email":{"type":"string","description":"Required without a key."},"pay_with":{"type":"string","enum":["balance","checkout"]}}}]}}}},"responses":{"200":{"description":"The job, where to upload, and what to do next.","content":{"application/json":{"schema":{"type":"object","properties":{"job":{"$ref":"#/components/schemas/RestoreJob"},"upload_url":{"type":"string"},"upload_headers":{"type":"object"},"display_price":{"type":"string"},"paid_from_balance":{"type":"boolean"},"next":{"type":"string"},"wallet":{"$ref":"#/components/schemas/Wallet"}}}}}},"400":{"description":"Bad input. `detail` says what to fix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unrecognised key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The balance, or the agent key's limit, doesn't cover the price. `needs_topup` or `over_limit` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cloud/jobs/{id}":{"get":{"operationId":"getRestore","summary":"Restore status and download link","tags":["Cloud Restore"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The job; download_url is set once status is done.","content":{"application/json":{"schema":{"type":"object","properties":{"job":{"$ref":"#/components/schemas/RestoreJob"}}}}}}}}},"/cloud/jobs/{id}/checkout":{"post":{"operationId":"checkoutRestore","summary":"Checkout link for a restore paid per job","tags":["Cloud Restore"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"checkout_url for the customer; the job starts when they have paid.","content":{"application/json":{"schema":{"type":"object","properties":{"checkout_url":{"type":"string"},"job":{"$ref":"#/components/schemas/RestoreJob"}}}}}}}}},"/cloud/jobs/{id}/start":{"post":{"operationId":"startRestore","summary":"Start a balance-paid restore after its upload","tags":["Cloud Restore"],"security":[{"walletKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The job, now processing (or failed and refunded).","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Bad input. `detail` says what to fix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unrecognised key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The balance, or the agent key's limit, doesn't cover the price. `needs_topup` or `over_limit` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cloud/jobs/{id}/cancel":{"post":{"operationId":"cancelRestore","summary":"Cancel a balance-paid restore that was never uploaded","tags":["Cloud Restore"],"security":[{"walletKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The job, cancelled, with the price back on the balance.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Bad input. `detail` says what to fix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unrecognised key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The balance, or the agent key's limit, doesn't cover the price. `needs_topup` or `over_limit` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/motion/config":{"get":{"operationId":"motionConfig","summary":"Clip sizes, prices and limits","tags":["Motion"],"responses":{"200":{"description":"Sizes with prices, top-up range, limits.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/motion/quote":{"get":{"operationId":"quoteClip","summary":"Price one clip","tags":["Motion"],"parameters":[{"name":"size","in":"query","schema":{"type":"string","enum":["480p","720p"]}}],"responses":{"200":{"description":"The price.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/motion/jobs":{"post":{"operationId":"makeClip","summary":"Make a clip from an image and a prompt","tags":["Motion"],"security":[{"walletKey":[]}],"description":"Pass image_url to start at once. Otherwise give content_type, PUT the image to upload_url, then call /start. The price is held from the balance and refunded if the clip fails or is refused.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["prompt"],"properties":{"prompt":{"type":"string","maxLength":800},"image_url":{"type":"string"},"content_type":{"type":"string","enum":["image/jpeg","image/png","image/webp"]},"image_bytes":{"type":"integer"},"size":{"type":"string","enum":["480p","720p"],"default":"480p"},"seconds":{"type":"integer","enum":[5]},"loop":{"type":"boolean"},"seed":{"type":"integer"}}}}}},"responses":{"200":{"description":"The clip (and upload_url when no image_url was given).","content":{"application/json":{"schema":{"type":"object","properties":{"job":{"$ref":"#/components/schemas/Clip"},"upload_url":{"type":"string"},"wallet":{"$ref":"#/components/schemas/Wallet"}}}}}},"400":{"description":"Bad input. `detail` says what to fix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unrecognised key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The balance, or the agent key's limit, doesn't cover the price. `needs_topup` or `over_limit` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The prompt breaks the content rule. Nothing was charged."}}},"get":{"operationId":"listClips","summary":"Recent clips (an agent key sees its own)","tags":["Motion"],"security":[{"walletKey":[]}],"responses":{"200":{"description":"Up to 40 clips, newest first.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Bad input. `detail` says what to fix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unrecognised key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The balance, or the agent key's limit, doesn't cover the price. `needs_topup` or `over_limit` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/motion/jobs/{id}":{"get":{"operationId":"getClip","summary":"Clip status and links","tags":["Motion"],"security":[{"walletKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The clip; video_url and download_url once done.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Bad input. `detail` says what to fix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unrecognised key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The balance, or the agent key's limit, doesn't cover the price. `needs_topup` or `over_limit` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/motion/jobs/{id}/start":{"post":{"operationId":"startClip","summary":"Start a clip after its image upload","tags":["Motion"],"security":[{"walletKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The clip, queued.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Bad input. `detail` says what to fix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unrecognised key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The balance, or the agent key's limit, doesn't cover the price. `needs_topup` or `over_limit` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/motion/jobs/{id}/cancel":{"post":{"operationId":"cancelClip","summary":"Cancel a clip whose image was never uploaded","tags":["Motion"],"security":[{"walletKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The clip, cancelled and refunded.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Bad input. `detail` says what to fix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unrecognised key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The balance, or the agent key's limit, doesn't cover the price. `needs_topup` or `over_limit` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/wallet":{"post":{"operationId":"createWallet","summary":"Make a new, empty balance and its wallet key","tags":["Balance"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"}}}}}},"responses":{"200":{"description":"key (pmk_…, shown once) and the wallet.","content":{"application/json":{"schema":{"type":"object"}}}}}},"get":{"operationId":"getBalance","summary":"The balance this key can spend","tags":["Balance"],"security":[{"walletKey":[]}],"responses":{"200":{"description":"The wallet.","content":{"application/json":{"schema":{"type":"object","properties":{"wallet":{"$ref":"#/components/schemas/Wallet"}}}}}},"400":{"description":"Bad input. `detail` says what to fix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unrecognised key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The balance, or the agent key's limit, doesn't cover the price. `needs_topup` or `over_limit` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/wallet/topup":{"post":{"operationId":"topUp","summary":"Checkout link to add money to the balance","tags":["Balance"],"security":[{"walletKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["amount_cents"],"properties":{"amount_cents":{"type":"integer","minimum":500,"maximum":20000},"email":{"type":"string"}}}}}},"responses":{"200":{"description":"checkout_url for the person paying.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Bad input. `detail` says what to fix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unrecognised key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The balance, or the agent key's limit, doesn't cover the price. `needs_topup` or `over_limit` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/wallet/topup/crypto":{"post":{"operationId":"topUpWithUsdc","summary":"Add to the balance with USDC on Base (x402 v2)","tags":["Balance"],"security":[{"walletKey":[]}],"description":"x402 v2 over HTTP. Without a PAYMENT-SIGNATURE header the answer is 402 with the payment terms in the PAYMENT-REQUIRED header (base64 JSON; also in the body as payment_required): exact scheme, USDC on Base (eip155:8453), paid to Phoenix Labs. Sign the EIP-3009 transfer and repeat the request with the base64 PaymentPayload in PAYMENT-SIGNATURE. A facilitator settles it (no gas needed from the payer), the transfer is confirmed on-chain, and the balance is credited 1 USDC = $1. The receipt comes back in PAYMENT-RESPONSE. $1 to $200. Refunds of unused USDC balance go back to the paying address in USDC.","parameters":[{"name":"PAYMENT-SIGNATURE","in":"header","required":false,"schema":{"type":"string"},"description":"base64 JSON x402 PaymentPayload"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["amount_cents"],"properties":{"amount_cents":{"type":"integer","minimum":100,"maximum":20000}}}}}},"responses":{"200":{"description":"Paid and credited.","content":{"application/json":{"schema":{"type":"object","properties":{"credited_cents":{"type":"integer"},"transaction":{"type":"string"},"explorer":{"type":"string"},"wallet":{"$ref":"#/components/schemas/Wallet"}}}}}},"202":{"description":"Paid; waiting for the chain to confirm. The balance is credited when it does."},"402":{"description":"Payment required (terms in PAYMENT-REQUIRED), or the payment was refused. Nothing was taken."},"503":{"description":"USDC payments aren't switched on."}}}},"/wallet/keys":{"get":{"operationId":"listAgentKeys","summary":"Agent keys on this balance (wallet key only)","tags":["Balance"],"security":[{"walletKey":[]}],"responses":{"200":{"description":"The keys, without their secrets.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Bad input. `detail` says what to fix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unrecognised key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The balance, or the agent key's limit, doesn't cover the price. `needs_topup` or `over_limit` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createAgentKey","summary":"Make an agent key with a spending limit (wallet key only)","tags":["Balance"],"security":[{"walletKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["limit_cents"],"properties":{"label":{"type":"string"},"limit_cents":{"type":"integer","minimum":1,"maximum":100000}}}}}},"responses":{"200":{"description":"key (pak_…, shown once) and its details.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Bad input. `detail` says what to fix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unrecognised key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The balance, or the agent key's limit, doesn't cover the price. `needs_topup` or `over_limit` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/wallet/keys/{id}/revoke":{"post":{"operationId":"revokeAgentKey","summary":"Revoke an agent key (wallet key only)","tags":["Balance"],"security":[{"walletKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The key, revoked.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Bad input. `detail` says what to fix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unrecognised key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The balance, or the agent key's limit, doesn't cover the price. `needs_topup` or `over_limit` says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/mcp":{"post":{"operationId":"mcp","summary":"MCP server (Streamable HTTP, stateless JSON-RPC)","tags":["Agents"],"description":"The same operations as MCP tools. Authenticate with `Authorization: Bearer pak_…`, or `?key=pak_…` for clients that only take a URL.","responses":{"200":{"description":"JSON-RPC response.","content":{"application/json":{"schema":{"type":"object"}}}}}}}}}