{"components":{"headers":{"ApiVersion":{"description":"API major version served by this response (currently 1).","schema":{"type":"string"}},"RateLimit":{"description":"IETF RateLimit header for the public 60 req/min default policy.","schema":{"type":"string"}}},"schemas":{"AcsHookRequest":{"description":"Stripe Agentic Commerce Suite hook payload. type selects finalize, customize, or price/availability.","properties":{"context":{"type":"string"},"data":{"additionalProperties":true,"type":"object"},"type":{"type":"string"}},"required":["type"],"type":"object"},"AcsHookResponse":{"additionalProperties":true,"description":"Approval, declined details, empty shipping_options, or live sku price/availability.","properties":{"availability":{"properties":{"status":{"type":"string"}},"type":"object"},"manual_approval_details":{"additionalProperties":true,"properties":{"type":{"type":"string"}},"type":"object"},"shipping_options":{"items":{"type":"object"},"type":"array"},"sku_id":{"type":"string"}},"type":"object"},"CaptionRequest":{"properties":{"audience":{"description":"Who will see the meme (e.g. engineering slack).","maxLength":200,"type":"string"},"avoidCaptions":{"description":"On-canvas captions to avoid when mode=assist so Remix returns a new take.","items":{"maxLength":80,"type":"string"},"maxItems":8,"type":"array"},"mode":{"description":"search ranks catalog templates by title match plus embeddings (no caption LLM, no guest quota). assist writes captions for preferredTemplateSlug only. captions (default) generates a multi-template batch. /create uses captions.","enum":["search","captions","assist"],"type":"string"},"preferredTemplateSlug":{"description":"Catalog slug to pin as the first variant when it fits the prompt.","maxLength":200,"type":"string"},"prompt":{"description":"Situation or joke seed.","maxLength":2000,"minLength":3,"type":"string"},"slotImages":{"description":"Optional data-URL photos the user placed in image slots. Used with mode=assist so remix can see them. The server also attaches the catalog template image.","items":{"maxLength":2000000,"type":"string"},"maxItems":4,"type":"array"},"tone":{"maxLength":100,"type":"string"},"uncensored":{"default":false,"description":"Use the uncensored caption model when true.","type":"boolean"}},"required":["prompt"],"type":"object"},"CaptionResponse":{"properties":{"creationId":{"format":"uuid","type":"string"},"guestQuota":{"description":"Present for anonymous sessions; null for signed-in users.","properties":{"limit":{"type":"integer"},"remaining":{"type":"integer"},"used":{"type":"integer"}},"required":["limit","remaining","used"],"type":["object","null"]},"model":{"type":"string"},"requiresAccountToSave":{"type":"boolean"},"returnedCount":{"type":"integer"},"uncensored":{"type":"boolean"},"variants":{"description":"Four to ten unique templates. Each item includes artifactId, templateSlug, and captions[].","items":{"description":"One template + caption set from a batch. Typical batches return 4 variants.","properties":{"artifactId":{"description":"Persisted artifact UUID when the row was saved. Omitted if persistence failed.","format":"uuid","type":"string"},"captions":{"description":"Caption strings in template box order. Length matches the template's textBoxes.","items":{"maxLength":80,"type":"string"},"type":"array"},"templateSlug":{"description":"Catalog JSON slug (e.g. drake-hotline-bling), not the URL path.","type":"string"}},"required":["captions","templateSlug"],"type":"object"},"minItems":4,"type":"array"}},"required":["creationId","model","requiresAccountToSave","returnedCount","uncensored","variants"],"type":"object"},"CaptionVariant":{"description":"One template + caption set from a batch. Typical batches return 4 variants.","properties":{"artifactId":{"description":"Persisted artifact UUID when the row was saved. Omitted if persistence failed.","format":"uuid","type":"string"},"captions":{"description":"Caption strings in template box order. Length matches the template's textBoxes.","items":{"maxLength":80,"type":"string"},"type":"array"},"templateSlug":{"description":"Catalog JSON slug (e.g. drake-hotline-bling), not the URL path.","type":"string"}},"required":["captions","templateSlug"],"type":"object"},"Error":{"properties":{"code":{"description":"Machine-readable error class.","enum":["BAD_REQUEST","FORBIDDEN","INTERNAL_ERROR","NOT_FOUND","PAYMENT_REQUIRED","RATE_LIMITED","SERVICE_UNAVAILABLE","UNAUTHORIZED"],"type":"string"},"error":{"description":"Legacy compatibility copy of message.","type":"string"},"hint":{"default":"See https://magicmeme.com/openapi.json","description":"Where to read the HTTP API contract.","type":"string"},"message":{"description":"Human-readable explanation.","type":"string"}},"required":["code","error","message"],"type":"object"},"FaceSwapRequest":{"properties":{"consentAttested":{"description":"Must be true. User attests rights to the reference face.","enum":[true],"type":"boolean"},"idempotencyKey":{"description":"Client-generated key (8–255 chars) for credit idempotency.","maxLength":255,"minLength":8,"type":"string"},"prompt":{"description":"Optional edit instruction. Production default applies when omitted.","maxLength":500,"type":"string"},"referenceImageBase64":{"contentEncoding":"base64","description":"Base64-encoded face photo (JPEG, PNG, or WebP).","type":"string"},"referenceMediaType":{"enum":["image/jpeg","image/png","image/webp"],"type":"string"},"templateSlug":{"description":"Catalog JSON slug for the meme template canvas.","maxLength":200,"minLength":1,"type":"string"}},"required":["consentAttested","idempotencyKey","referenceImageBase64","referenceMediaType","templateSlug"],"type":"object"},"FaceSwapResponse":{"properties":{"artifactId":{"description":"Private library artifact for the signed-in workspace.","format":"uuid","type":"string"},"fallbackUsed":{"description":"True when the primary model failed and the configured fallback model succeeded.","type":"boolean"},"imageBase64":{"contentEncoding":"base64","description":"Face-swapped meme as base64 PNG/JPEG/WebP.","type":"string"},"imageUrl":{"description":"Authenticated URL that streams the saved library image for this user.","type":"string"},"mediaType":{"type":"string"},"model":{"description":"OpenRouter model that produced the image (primary or fallback).","type":"string"},"templateSlug":{"type":"string"}},"required":["imageBase64","mediaType","model","templateSlug"],"type":"object"},"GuestQuota":{"properties":{"limit":{"type":"integer"},"remaining":{"type":"integer"},"used":{"type":"integer"}},"required":["limit","remaining","used"],"type":"object"},"HealthResponse":{"properties":{"ok":{"enum":[true],"type":"boolean"}},"required":["ok"],"type":"object"},"JsonRpcRequest":{"description":"JSON-RPC 2.0 request (MCP Streamable HTTP).","properties":{"id":{"type":["string","number","null"]},"jsonrpc":{"enum":["2.0"],"type":"string"},"method":{"type":"string"},"params":{"additionalProperties":true,"type":"object"}},"required":["jsonrpc","method"],"type":"object"},"JsonRpcResponse":{"description":"JSON-RPC 2.0 success or error envelope.","properties":{"error":{"properties":{"code":{"type":"integer"},"data":{"additionalProperties":true,"type":"object"},"message":{"type":"string"}},"required":["code","message"],"type":"object"},"id":{"type":["string","number","null"]},"jsonrpc":{"enum":["2.0"],"type":"string"},"result":{"additionalProperties":true,"type":"object"}},"required":["jsonrpc"],"type":"object"},"MemeTemplate":{"description":"Catalog record. Blank template art lives at imageUrl. Finished stills can be rendered via POST /api/render with format=jpeg (default still), webp, or png, or MCP render_meme_still.","properties":{"about":{"type":"string"},"category":{"type":"string"},"description":{"type":"string"},"id":{"type":"integer"},"imageUrl":{"description":"CDN URL of the blank template image.","format":"uri","type":"string"},"keywords":{"items":{"type":"string"},"type":"array"},"name":{"type":"string"},"slug":{"type":"string"},"templateSpecifics":{"properties":{"generation":{"properties":{"exampleCaptions":{"items":{"items":{"type":"string"},"type":"array"},"type":"array"},"format":{"type":"string"},"formula":{"type":"string"}},"type":"object"},"textBoxes":{"items":{"description":"Canvas-percent bounding box for a caption or image slot.","properties":{"default":{"type":"string"},"height":{"type":"number"},"id":{"type":["string","number"]},"label":{"type":"string"},"maxChars":{"type":"integer"},"origin":{"enum":["center","rect"],"type":"string"},"role":{"enum":["caption","label","dialogue","watermark"],"type":"string"},"type":{"enum":["text","image"],"type":"string"},"width":{"type":"number"},"x":{"type":"number"},"y":{"type":"number"}},"required":["width","x","y"],"type":"object"},"type":"array"}},"type":"object"},"textBoxes":{"items":{"description":"Canvas-percent bounding box for a caption or image slot.","properties":{"default":{"type":"string"},"height":{"type":"number"},"id":{"type":["string","number"]},"label":{"type":"string"},"maxChars":{"type":"integer"},"origin":{"enum":["center","rect"],"type":"string"},"role":{"enum":["caption","label","dialogue","watermark"],"type":"string"},"type":{"enum":["text","image"],"type":"string"},"width":{"type":"number"},"x":{"type":"number"},"y":{"type":"number"}},"required":["width","x","y"],"type":"object"},"type":"array"},"title":{"type":"string"},"useCases":{"items":{"type":"string"},"type":"array"}},"required":["id","imageUrl","name","slug","textBoxes"],"type":"object"},"RenderRequest":{"properties":{"captions":{"description":"Caption strings in textBoxes order.","items":{"type":"string"},"type":"array"},"format":{"default":"mp4","description":"mp4 or gif for animation. jpeg (recommended still), webp, or png for a composed still. jpg is accepted as jpeg.","enum":["mp4","gif","jpeg","jpg","webp","png"],"type":"string"},"height":{"maximum":1920,"minimum":64,"type":"integer"},"imageUrl":{"description":"Allowlisted CDN template URL (cdn.magicmeme.com). Arbitrary remote URLs are rejected.","type":"string"},"textBoxes":{"description":"Template bounding boxes used by the Remotion composition.","items":{"description":"Canvas-percent bounding box for a caption or image slot.","properties":{"default":{"type":"string"},"height":{"type":"number"},"id":{"type":["string","number"]},"label":{"type":"string"},"maxChars":{"type":"integer"},"origin":{"enum":["center","rect"],"type":"string"},"role":{"enum":["caption","label","dialogue","watermark"],"type":"string"},"type":{"enum":["text","image"],"type":"string"},"width":{"type":"number"},"x":{"type":"number"},"y":{"type":"number"}},"required":["width","x","y"],"type":"object"},"type":"array"},"width":{"maximum":1920,"minimum":64,"type":"integer"}},"required":["imageUrl"],"type":"object"},"ShareRequest":{"properties":{"artifactId":{"description":"Workspace artifact to publish as an unlisted share.","format":"uuid","type":"string"}},"required":["artifactId"],"type":"object"},"ShareResponse":{"properties":{"share":{"properties":{"artifactId":{"format":"uuid","type":"string"},"createdAt":{"format":"date-time","type":"string"},"id":{"format":"uuid","type":"string"},"noindex":{"type":"boolean"},"slug":{"type":"string"}},"required":["artifactId","id","noindex","slug"],"type":"object"},"url":{"description":"Site-relative share path, e.g. /s/a1b2c3d4e5f6.","type":"string"}},"required":["share","url"],"type":"object"},"TextBox":{"description":"Canvas-percent bounding box for a caption or image slot.","properties":{"default":{"type":"string"},"height":{"type":"number"},"id":{"type":["string","number"]},"label":{"type":"string"},"maxChars":{"type":"integer"},"origin":{"enum":["center","rect"],"type":"string"},"role":{"enum":["caption","label","dialogue","watermark"],"type":"string"},"type":{"enum":["text","image"],"type":"string"},"width":{"type":"number"},"x":{"type":"number"},"y":{"type":"number"}},"required":["width","x","y"],"type":"object"}}},"info":{"description":"Caption-on-template meme studio. One situation in, four editable memes out.\n\nVersioning: unversioned /api/* is v1 (this document, info.version 1.0.0). /api/v1/:path* is an alias rewritten to /api/:path*. Path keys below are origin-relative (/api/...). The https://magicmeme.com/api/v1 server URL is the rewrite destination; call the same suffix as /api/{path} (for example GET https://magicmeme.com/api/v1/health). Deprecation: when a version is retired, responses include Deprecation and Sunset headers (RFC 8594). No version is retired today.\n\nRate limits: documented public default is 60 requests per minute (RateLimit and RateLimit-Policy, plus RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset). HTTP 429 includes Retry-After. Guest caption batches are separately one per UTC day.\n\nPOST /api/ai/captions returns caption variants (not pixels). POST /api/render returns a composed still when format is jpeg, webp, or png, or animation when format is mp4 (HTTP default) or gif. MCP render_meme_still defaults to jpeg. Humans download PNG from the browser editor.\n\nThere is no CORS: browser JavaScript on other origins cannot call these APIs. Server-side clients (curl, backends, MCP) can.\n\nPOST /api/ai/captions creates an anonymous session when none exists; persist Set-Cookie on follow-up calls. Guests get one template batch per UTC day (HTTP 429 when exhausted). POST /api/ai/face-swap requires a verified, non-anonymous session and credits (gpt-image-2.5-sunburst with grok fallback). GET/POST /api/agentic/face-swap is the agent rail: HTTP 402 (MPP, $0.50), then a paid POST with the face image generates the swap. Browsers redirect to /pricing (credits). Hosted MCP is POST /api/mcp (Streamable HTTP JSON, public catalog, no OAuth; send Accept: application/json, text/event-stream). Stripe ACS order approval is POST /api/agentic/hooks. POST /api/shares requires a signed-in, non-anonymous user. imageUrl on /api/render must be an allowlisted cdn.magicmeme.com template.\n\nHuman sign-in is passwordless email OTP at /login (Better Auth under /api/auth/*). Production requires RESEND_API_KEY and EMAIL_FROM. Face swap, shares, uploads, and premium jobs require a verified, non-anonymous session.\n\nUnknown /api/* paths return JSON 404 with code NOT_FOUND (not HTML). Spec: https://magicmeme.com/openapi.json.\n\nHuman-readable agent setup: https://magicmeme.com/agents (MCP connect, tools, face-swap MPP, skill).","title":"MagicMeme API","version":"1.0.0"},"jsonSchemaDialect":"https://json-schema.org/draft/2020-12/schema","openapi":"3.1.0","paths":{"/api/agentic/face-swap":{"get":{"description":"MPP payment URL for one face-swap. Agents GET/POST and receive HTTP 402 until a Shared Payment Token is attached. Browsers (Accept: text/html) redirect to /pricing so humans buy credits instead.\n\nDo not put the face image on the query string. On the paid POST, send consentAttested and referenceImageBase64 in the JSON body.","operationId":"agenticFaceSwapOffer","responses":{"302":{"description":"Browser fallback to credit checkout."},"402":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"MPP payment challenge (agent). Credit authorize URL included."}},"summary":"Agentic face-swap payment (MPP 402 + credit fallback)","tags":["face-swap"]},"post":{"description":"402 until an MPP Shared Payment Token is attached. After payment, the same POST with consentAttested and the face image generates the swap (our OpenRouter key). Browsers redirect to /pricing.","operationId":"agenticFaceSwapCharge","responses":{"302":{"description":"Browser fallback to credit checkout."},"402":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"MPP payment challenge."}},"summary":"Agentic face-swap charge handshake","tags":["face-swap"]}},"/api/agentic/hooks":{"post":{"description":"Stripe Agentic Commerce Suite hook. Handles finalize_checkout (approve known SKUs), customize_checkout (no shipping), and product_price_availability. Verify Stripe-Signature when a hook secret is configured.","operationId":"agenticCommerceHooks","requestBody":{"content":{"application/json":{"schema":{"description":"Stripe Agentic Commerce Suite hook payload. type selects finalize, customize, or price/availability.","properties":{"context":{"type":"string"},"data":{"additionalProperties":true,"type":"object"},"type":{"type":"string"}},"required":["type"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":true,"description":"Approval, declined details, empty shipping_options, or live sku price/availability.","properties":{"availability":{"properties":{"status":{"type":"string"}},"type":"object"},"manual_approval_details":{"additionalProperties":true,"properties":{"type":{"type":"string"}},"type":"object"},"shipping_options":{"items":{"type":"object"},"type":"array"},"sku_id":{"type":"string"}},"type":"object"}}},"description":"Approval, empty shipping, or live price/availability."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid payload or signature."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Webhook verifier missing."}},"summary":"Stripe ACS order-approval and catalog hooks","tags":["face-swap"]}},"/api/ai/captions":{"post":{"description":"Generate a caption batch for a prompt or situation, rank templates with mode=search, or write one caption set with mode=assist. Only prompt is required (3–2000 characters). Optional preferredTemplateSlug, audience, tone, and uncensored pin format and voice. mode=search ranks by title match plus in-memory embeddings (no caption LLM, no guest quota). /create studio uses the caption batch so the first response is already captioned.\n\nCreates an anonymous Better Auth session when the request has none. Caption-mode response variants[] (typically 4, up to 10) each include templateSlug, captions[], and artifactId. This does not return pixels. Follow up with POST /api/render (format=jpeg) or MCP render_meme_still, or send the human to /create?prompt=... to edit and download a PNG.","operationId":"generateCaptions","requestBody":{"content":{"application/json":{"schema":{"properties":{"audience":{"description":"Who will see the meme (e.g. engineering slack).","maxLength":200,"type":"string"},"avoidCaptions":{"description":"On-canvas captions to avoid when mode=assist so Remix returns a new take.","items":{"maxLength":80,"type":"string"},"maxItems":8,"type":"array"},"mode":{"description":"search ranks catalog templates by title match plus embeddings (no caption LLM, no guest quota). assist writes captions for preferredTemplateSlug only. captions (default) generates a multi-template batch. /create uses captions.","enum":["search","captions","assist"],"type":"string"},"preferredTemplateSlug":{"description":"Catalog slug to pin as the first variant when it fits the prompt.","maxLength":200,"type":"string"},"prompt":{"description":"Situation or joke seed.","maxLength":2000,"minLength":3,"type":"string"},"slotImages":{"description":"Optional data-URL photos the user placed in image slots. Used with mode=assist so remix can see them. The server also attaches the catalog template image.","items":{"maxLength":2000000,"type":"string"},"maxItems":4,"type":"array"},"tone":{"maxLength":100,"type":"string"},"uncensored":{"default":false,"description":"Use the uncensored caption model when true.","type":"boolean"}},"required":["prompt"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"creationId":{"format":"uuid","type":"string"},"guestQuota":{"description":"Present for anonymous sessions; null for signed-in users.","properties":{"limit":{"type":"integer"},"remaining":{"type":"integer"},"used":{"type":"integer"}},"required":["limit","remaining","used"],"type":["object","null"]},"model":{"type":"string"},"requiresAccountToSave":{"type":"boolean"},"returnedCount":{"type":"integer"},"uncensored":{"type":"boolean"},"variants":{"description":"Four to ten unique templates. Each item includes artifactId, templateSlug, and captions[].","items":{"description":"One template + caption set from a batch. Typical batches return 4 variants.","properties":{"artifactId":{"description":"Persisted artifact UUID when the row was saved. Omitted if persistence failed.","format":"uuid","type":"string"},"captions":{"description":"Caption strings in template box order. Length matches the template's textBoxes.","items":{"maxLength":80,"type":"string"},"type":"array"},"templateSlug":{"description":"Catalog JSON slug (e.g. drake-hotline-bling), not the URL path.","type":"string"}},"required":["captions","templateSlug"],"type":"object"},"minItems":4,"type":"array"}},"required":["creationId","model","requiresAccountToSave","returnedCount","uncensored","variants"],"type":"object"}}},"description":"Caption batch with persisted artifact ids.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"RateLimit":{"$ref":"#/components/headers/RateLimit"}}},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid JSON body or prompt."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unable to create a session."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Guest daily template allowance exhausted. Sign in to continue.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"Retry-After":{"description":"Seconds until the guest UTC-day allowance resets.","schema":{"type":"integer"}}}},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Generation failed."}},"summary":"Generate AI meme captions","tags":["captions"]}},"/api/ai/face-swap":{"post":{"description":"Swap a reference face onto a catalog meme template. Production tries `openai/gpt-image-2.5-sunburst`, then falls back to `x-ai/grok-imagine-image-quality` on provider failure (safety, moderation, etc.).\n\nRequires a signed-in, non-anonymous user with verified email and sufficient credits (10 by default). consentAttested must be true.\n\nReturns imageBase64, imageUrl, artifactId, mediaType, model, and fallbackUsed synchronously. The image is saved to the user's private library. Face/reference outputs cannot be published via POST /api/shares.","operationId":"faceSwapMeme","requestBody":{"content":{"application/json":{"schema":{"properties":{"consentAttested":{"description":"Must be true. User attests rights to the reference face.","enum":[true],"type":"boolean"},"idempotencyKey":{"description":"Client-generated key (8–255 chars) for credit idempotency.","maxLength":255,"minLength":8,"type":"string"},"prompt":{"description":"Optional edit instruction. Production default applies when omitted.","maxLength":500,"type":"string"},"referenceImageBase64":{"contentEncoding":"base64","description":"Base64-encoded face photo (JPEG, PNG, or WebP).","type":"string"},"referenceMediaType":{"enum":["image/jpeg","image/png","image/webp"],"type":"string"},"templateSlug":{"description":"Catalog JSON slug for the meme template canvas.","maxLength":200,"minLength":1,"type":"string"}},"required":["consentAttested","idempotencyKey","referenceImageBase64","referenceMediaType","templateSlug"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"artifactId":{"description":"Private library artifact for the signed-in workspace.","format":"uuid","type":"string"},"fallbackUsed":{"description":"True when the primary model failed and the configured fallback model succeeded.","type":"boolean"},"imageBase64":{"contentEncoding":"base64","description":"Face-swapped meme as base64 PNG/JPEG/WebP.","type":"string"},"imageUrl":{"description":"Authenticated URL that streams the saved library image for this user.","type":"string"},"mediaType":{"type":"string"},"model":{"description":"OpenRouter model that produced the image (primary or fallback).","type":"string"},"templateSlug":{"type":"string"}},"required":["imageBase64","mediaType","model","templateSlug"],"type":"object"}}},"description":"Face-swapped meme image."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Sign in required for face swap."},"402":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Insufficient credits."},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Verified email required, or consent missing."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Template not found."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Face swap generation failed."},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Face swap temporarily disabled."}},"summary":"Face swap meme (signed-in, credits)","tags":["face-swap"]}},"/api/health":{"get":{"description":"Liveness probe. Does not touch Postgres or Remotion. Returns { ok: true }.","operationId":"getHealth","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"ok":{"enum":[true],"type":"boolean"}},"required":["ok"],"type":"object"}}},"description":"Process is up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"RateLimit":{"$ref":"#/components/headers/RateLimit"}}}},"summary":"Health check","tags":["meta"]}},"/api/mcp":{"post":{"description":"Hosted MagicMeme MCP (Streamable HTTP, JSON). POST with Accept: application/json, text/event-stream. No OAuth or Authorization header. Catalog tools are public. Tools: search_templates, get_template, generate_meme_captions, render_meme_still, face_swap_meme, get_create_url. A browser GET without that Accept header is HTTP 406.","operationId":"hostedMcp","requestBody":{"content":{"application/json":{"schema":{"description":"JSON-RPC 2.0 request (MCP Streamable HTTP).","properties":{"id":{"type":["string","number","null"]},"jsonrpc":{"enum":["2.0"],"type":"string"},"method":{"type":"string"},"params":{"additionalProperties":true,"type":"object"}},"required":["jsonrpc","method"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"description":"JSON-RPC 2.0 success or error envelope.","properties":{"error":{"properties":{"code":{"type":"integer"},"data":{"additionalProperties":true,"type":"object"},"message":{"type":"string"}},"required":["code","message"],"type":"object"},"id":{"type":["string","number","null"]},"jsonrpc":{"enum":["2.0"],"type":"string"},"result":{"additionalProperties":true,"type":"object"}},"required":["jsonrpc"],"type":"object"}}},"description":"JSON-RPC MCP response.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"RateLimit":{"$ref":"#/components/headers/RateLimit"}}},"406":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing Accept: application/json, text/event-stream."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"MCP handler failed."}},"summary":"Hosted MCP server","tags":["face-swap"]}},"/api/memes":{"get":{"description":"Meme catalog including text-box geometry and imageUrl. Omit query params for the full catalog (hundreds of records). Pass slugs to fetch only those records. Do not paste the catalog into chat.","operationId":"listMemes","parameters":[{"description":"Comma-separated catalog slugs. When set, the response is only those records, in request order.","in":"query","name":"slugs","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"description":"Catalog record. Blank template art lives at imageUrl. Finished stills can be rendered via POST /api/render with format=jpeg (default still), webp, or png, or MCP render_meme_still.","properties":{"about":{"type":"string"},"category":{"type":"string"},"description":{"type":"string"},"id":{"type":"integer"},"imageUrl":{"description":"CDN URL of the blank template image.","format":"uri","type":"string"},"keywords":{"items":{"type":"string"},"type":"array"},"name":{"type":"string"},"slug":{"type":"string"},"templateSpecifics":{"properties":{"generation":{"properties":{"exampleCaptions":{"items":{"items":{"type":"string"},"type":"array"},"type":"array"},"format":{"type":"string"},"formula":{"type":"string"}},"type":"object"},"textBoxes":{"items":{"description":"Canvas-percent bounding box for a caption or image slot.","properties":{"default":{"type":"string"},"height":{"type":"number"},"id":{"type":["string","number"]},"label":{"type":"string"},"maxChars":{"type":"integer"},"origin":{"enum":["center","rect"],"type":"string"},"role":{"enum":["caption","label","dialogue","watermark"],"type":"string"},"type":{"enum":["text","image"],"type":"string"},"width":{"type":"number"},"x":{"type":"number"},"y":{"type":"number"}},"required":["width","x","y"],"type":"object"},"type":"array"}},"type":"object"},"textBoxes":{"items":{"description":"Canvas-percent bounding box for a caption or image slot.","properties":{"default":{"type":"string"},"height":{"type":"number"},"id":{"type":["string","number"]},"label":{"type":"string"},"maxChars":{"type":"integer"},"origin":{"enum":["center","rect"],"type":"string"},"role":{"enum":["caption","label","dialogue","watermark"],"type":"string"},"type":{"enum":["text","image"],"type":"string"},"width":{"type":"number"},"x":{"type":"number"},"y":{"type":"number"}},"required":["width","x","y"],"type":"object"},"type":"array"},"title":{"type":"string"},"useCases":{"items":{"type":"string"},"type":"array"}},"required":["id","imageUrl","name","slug","textBoxes"],"type":"object"},"type":"array"}}},"description":"Catalog of meme templates.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"RateLimit":{"$ref":"#/components/headers/RateLimit"}}},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Failed to fetch memes."}},"summary":"List meme templates","tags":["catalog"]}},"/api/render":{"post":{"description":"Remotion renderer for template + captions. Accepts imageUrl (allowlisted CDN only), captions, textBoxes, optional width/height (64–1920), and format.\n\nAgents who want a still MUST pass format=jpeg (or webp/png). Omitting format renders mp4. gif is animation only.\n\nUntrusted image URLs return HTTP 400.","operationId":"renderMeme","requestBody":{"content":{"application/json":{"schema":{"properties":{"captions":{"description":"Caption strings in textBoxes order.","items":{"type":"string"},"type":"array"},"format":{"default":"mp4","description":"mp4 or gif for animation. jpeg (recommended still), webp, or png for a composed still. jpg is accepted as jpeg.","enum":["mp4","gif","jpeg","jpg","webp","png"],"type":"string"},"height":{"maximum":1920,"minimum":64,"type":"integer"},"imageUrl":{"description":"Allowlisted CDN template URL (cdn.magicmeme.com). Arbitrary remote URLs are rejected.","type":"string"},"textBoxes":{"description":"Template bounding boxes used by the Remotion composition.","items":{"description":"Canvas-percent bounding box for a caption or image slot.","properties":{"default":{"type":"string"},"height":{"type":"number"},"id":{"type":["string","number"]},"label":{"type":"string"},"maxChars":{"type":"integer"},"origin":{"enum":["center","rect"],"type":"string"},"role":{"enum":["caption","label","dialogue","watermark"],"type":"string"},"type":{"enum":["text","image"],"type":"string"},"width":{"type":"number"},"x":{"type":"number"},"y":{"type":"number"}},"required":["width","x","y"],"type":"object"},"type":"array"},"width":{"maximum":1920,"minimum":64,"type":"integer"}},"required":["imageUrl"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"image/gif":{"schema":{"description":"GIF when format=gif.","format":"binary","type":"string"}},"image/jpeg":{"schema":{"description":"JPEG still when format=jpeg.","format":"binary","type":"string"}},"image/png":{"schema":{"description":"PNG still when format=png.","format":"binary","type":"string"}},"image/webp":{"schema":{"description":"WebP still when format=webp.","format":"binary","type":"string"}},"video/mp4":{"schema":{"description":"H.264 MP4 when format=mp4 (default).","format":"binary","type":"string"}}},"description":"Rendered still or animation as an attachment."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing imageUrl, invalid dimensions, or untrusted image URL."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Composition or render failure."}},"summary":"Render meme (still JPEG/WebP/PNG or MP4/GIF)","tags":["render"]}},"/api/shares":{"post":{"description":"Create a public unlisted share link for an artifact the signed-in user owns. Anonymous sessions receive 401. Face/reference artifacts cannot be published (403). The artifact is marked visibility=unlisted. Returned url is a site-relative path under /s/{slug}.","operationId":"createShare","requestBody":{"content":{"application/json":{"schema":{"properties":{"artifactId":{"description":"Workspace artifact to publish as an unlisted share.","format":"uuid","type":"string"}},"required":["artifactId"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"share":{"properties":{"artifactId":{"format":"uuid","type":"string"},"createdAt":{"format":"date-time","type":"string"},"id":{"format":"uuid","type":"string"},"noindex":{"type":"boolean"},"slug":{"type":"string"}},"required":["artifactId","id","noindex","slug"],"type":"object"},"url":{"description":"Site-relative share path, e.g. /s/a1b2c3d4e5f6.","type":"string"}},"required":["share","url"],"type":"object"}}},"description":"Share record and relative URL."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Sign in required to publish a share link (anonymous users cannot share)."},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Face/reference outputs cannot be published at launch."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Artifact not found in the user workspace."}},"summary":"Create an unlisted share link","tags":["shares"]}}},"servers":[{"description":"Production origin. Unversioned /api/* is v1; path keys in this document are origin-relative.","url":"https://magicmeme.com"},{"description":"v1 alias. Next.js rewrites /api/v1/:path* to /api/:path*. Use path suffixes without a second /api prefix (GET /health → /api/v1/health).","url":"https://magicmeme.com/api/v1"}],"tags":[{"description":"AI caption batches for the create studio.","name":"captions"},{"description":"Face swap onto catalog meme templates.","name":"face-swap"},{"description":"Meme template catalog.","name":"catalog"},{"description":"Liveness and discovery.","name":"meta"},{"description":"Still and animated template renderer.","name":"render"},{"description":"Unlisted public share links.","name":"shares"}]}