Getting started
- Make a key. Open API keys, name it and copy it. It starts with
phk_and is shown once. - Connect. Add the MCP server to your agent, or call the REST API from a script.
- Make something. You get an id at once. Check it until it is
done, then use itsurl.
Keys
Send your key in every request as Authorization: Bearer phk_…. A key acts as you: what it makes is in your account, and it uses your daily moons. You can have up to 5 keys; revoke one on the API keys page and it stops working at once. Keep keys out of shared code and screenshots.
MCP server
For AI agents such as Claude, ChatGPT, Claude Code, Cursor or any client that speaks MCP (Streamable HTTP). Apps that support sign-in (claude.ai, ChatGPT) connect with just the address: they open a Phantasia page where you sign in and press Allow, and get their own key, which you can revoke on API keys. Others take a key in a header. Step-by-step for each app: Integrations.
Server address
https://phantasia.pics/api/mcp
Claude Code
claude mcp add --transport http phantasia https://phantasia.pics/api/mcp --header "Authorization: Bearer phk_…"
Cursor and other clients (mcp.json)
{
"mcpServers": {
"phantasia": {
"url": "https://phantasia.pics/api/mcp",
"headers": {
"Authorization": "Bearer phk_…"
}
}
}
}Tools
| Tool | Takes |
|---|---|
make_picture | prompt, shape (square · portrait · landscape) |
make_song | prompt, seconds (10–60), lyrics (leave out for an instrumental) |
make_voice | text, voice, speed (0.8–1.3) |
make_clip | prompt, shape, length (short 2 s · long 4 s), sound (none · music · narration), sound_text |
check_creation | id → status and file url when done |
list_creations | kind, limit (1–50) → your recent creations |
list_models | kind → the open models, and which are on |
todays_moons | what you have left today |
share_creation | id, shared → put it in (or take it out of) the public gallery |
Then just ask your agent, for example: “make a picture of a lighthouse at dawn and tell me when it’s ready”.
REST API
Base address https://phantasia.pics/api/v1. JSON in, JSON out. The whole API as an OpenAPI file, for n8n, Make, Postman, GPT Actions or code generators: openapi.json. A map for AI tools: llms.txt.
| Request | Does |
|---|---|
POST /creations | Start a creation: {kind, prompt, options} → 202 with its id |
GET /creations/{id} | One creation: status, and its file url when done |
GET /creations?kind=&limit= | Your recent creations, newest first (limit 1–50, default 20) |
POST /creations/{id}/share | Put it in the public gallery; {"shared": false} takes it out |
GET /usage | What you have used and have left today, per kind |
GET /models | The open models and which are on (no key needed) |
GET /webhooks/secret | Your webhook signing secret |
Start a song
curl -X POST https://phantasia.pics/api/v1/creations \
-H "Authorization: Bearer phk_…" -H "Content-Type: application/json" \
-d '{"kind": "music", "prompt": "lo-fi hip hop, soft piano, rainy evening", "options": {"seconds": 30}}'{ "id": "6f1c…", "kind": "music", "status": "queued", "wait": "about a minute" }Check it
curl https://phantasia.pics/api/v1/creations/6f1c… -H "Authorization: Bearer phk_…"
{ "id": "6f1c…", "kind": "music", "status": "done", "prompt": "lo-fi hip hop…",
"url": "https://…/song.mp3", "error": null, "shared": false,
"created_at": "2026-11-14T10:02:11Z", "finished_at": "2026-11-14T10:03:05Z" }Status goes queued → running → done (or failed with an error). Check every few seconds; pictures and voices take seconds, songs about a minute, clips 1–3 minutes.
Webhooks
Instead of checking, add "webhook": "https://…" to POST /creations (or send the header X-Webhook-URL). When the creation finishes we POST this to it:
{ "type": "creation.done",
"creation": { "id": "6f1c…", "kind": "music", "status": "done", "prompt": "lo-fi hip hop…",
"url": "https://…/song.mp3", "error": null, "created_at": "…", "finished_at": "…" } }Failed ones come as creation.failed with an error. The address must be https on a public host name. We try once with a 5 second timeout, so keep checking as a backup for anything important.
Check it came from us
Each message has the headers webhook-id, webhook-timestamp and webhook-signature (the Standard Webhooks scheme). Take your secret from API keys (or GET /webhooks/secret), drop the whsec_ start and base64-decode the rest. HMAC-SHA256 the text id.timestamp.body (the raw body) with it, base64 the result, and compare with the part after v1,:
import base64, hashlib, hmac
def verify(secret, headers, raw_body):
key = base64.b64decode(secret.removeprefix("whsec_"))
signed = f"{headers['webhook-id']}.{headers['webhook-timestamp']}.{raw_body}".encode()
expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
return any(hmac.compare_digest(expected, s.split(",", 1)[1]) for s in headers["webhook-signature"].split())Also reject messages whose timestamp is more than 5 minutes old.
Options per kind
All options are optional. In the REST API they go in options; MCP tools take them as named above.
| Kind | Options | Values |
|---|---|---|
image | aspect | square · portrait · landscape |
music | seconds · lyrics · instrumental | 10–60 (default 30) · text up to 1000 · true / false |
voice | voice · speed | af_heart · af_bella · am_michael · am_fenrir · bf_emma · bm_george · tk_mms · 0.8–1.3 |
video | aspect · length · sound · sound_prompt / narration | landscape · portrait · square · short · long · none · music · narration · text up to 200 |
Prompts are 3–500 characters. See every model on the Models page.
Limits and errors
Your daily moons are shared between the studio, agents and scripts, and refill every day: each day’s first of every kind is free. After that a creation uses credits (every account starts with 100; prices on Pricing), and Pro models always do. GET /usage includes your credits. A creation that fails gives its credits back. Your account needs to be approved before it can make things.
| Code | Means |
|---|---|
401 | Missing, wrong or revoked key |
400 | Something in the request is off; error says what |
404 | No creation with that id in your account |
429 | Today’s free one is used and there aren’t enough credits, or the account is not approved yet |
Every error is JSON: {"error": "…"}, written for people.