{"openapi":"3.0.3","info":{"title":"memeworld API","version":"1.0.0","description":"Search memeworld’s meme and GIF templates and render captioned images from them. Only memeworld’s own templates can be rendered — there is no arbitrary image URL input."},"servers":[{"url":"https://memeworld.afterlume.workers.dev/api"}],"tags":[{"name":"templates","description":"Browse and search the template catalog"},{"name":"render","description":"Produce a captioned PNG or GIF"},{"name":"suggest","description":"Turn an idea into captioned meme variants"},{"name":"face swap","description":"Put a face into a template. Costs credits"},{"name":"credits","description":"The balance metered actions are paid from"}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key"},"bearer":{"type":"http","scheme":"bearer","description":"The same API key as a bearer token"}},"schemas":{"Template":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"slug":{"type":"string"},"kind":{"type":"string","enum":["still","gif"]},"name":{"type":"string"},"width":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"height":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"thumbhash":{"nullable":true,"type":"string"},"posterUrl":{"type":"string"},"imageUrl":{"type":"string"},"path":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"durationMs":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"faceSwap":{"type":"boolean","description":"True when a hand-written face swap instruction exists for it"}},"required":["id","slug","kind","name","width","height","thumbhash","posterUrl","imageUrl","path","tags","durationMs","faceSwap"]},"TemplateList":{"type":"object","properties":{"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"slug":{"type":"string"},"kind":{"type":"string","enum":["still","gif"]},"name":{"type":"string"},"width":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"height":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"thumbhash":{"nullable":true,"type":"string"},"posterUrl":{"type":"string"},"imageUrl":{"type":"string"},"path":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"durationMs":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"faceSwap":{"type":"boolean","description":"True when a hand-written face swap instruction exists for it"}},"required":["id","slug","kind","name","width","height","thumbhash","posterUrl","imageUrl","path","tags","durationMs","faceSwap"]}},"nextCursor":{"nullable":true,"type":"string"}},"required":["total","items","nextCursor"]},"TagList":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"kind":{"type":"string","enum":["category","topic"]},"templateCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"isFeatured":{"type":"boolean"}},"required":["slug","name","kind","templateCount","isFeatured"]}}},"required":["items"]},"SuggestRequest":{"type":"object","properties":{"prompt":{"type":"string","minLength":3,"maxLength":500,"description":"The joke, situation or feeling the meme should land — plain language"},"tone":{"description":"`safe` is work-safe; `unhinged` allows profanity and dark humour. Default safe","type":"string","enum":["safe","unhinged"]},"count":{"description":"How many variants. Default 4","type":"integer","minimum":1,"maximum":6},"kind":{"description":"Restrict the templates considered. Default: both","type":"string","enum":["still","gif"]},"exclude":{"description":"Template ids to skip, so asking again returns a different set","maxItems":60,"type":"array","items":{"type":"integer","minimum":0,"exclusiveMinimum":true,"maximum":9007199254740991}}},"required":["prompt"],"description":"An idea to turn into captioned memes"},"SuggestResult":{"type":"object","properties":{"id":{"type":"string","description":"Hash of the request; the same request always returns the same id"},"prompt":{"type":"string"},"tone":{"type":"string","enum":["safe","unhinged"]},"model":{"type":"string"},"cached":{"type":"boolean","description":"True when the answer came from the cache and cost nothing"},"variants":{"type":"array","items":{"type":"object","properties":{"template":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"slug":{"type":"string"},"kind":{"type":"string","enum":["still","gif"]},"name":{"type":"string"},"width":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"height":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"thumbhash":{"nullable":true,"type":"string"},"posterUrl":{"type":"string"},"imageUrl":{"type":"string"},"path":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"durationMs":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"faceSwap":{"type":"boolean","description":"True when a hand-written face swap instruction exists for it"}},"required":["id","slug","kind","name","width","height","thumbhash","posterUrl","imageUrl","path","tags","durationMs","faceSwap"]},"textBoxes":{"type":"array","items":{},"description":"Text box geometry, in order"},"captions":{"type":"array","items":{"type":"string"},"description":"Captions in text-box order"},"why":{"nullable":true,"type":"string"},"editorPath":{"type":"string","description":"Editor path with the captions prefilled"}},"required":["template","textBoxes","captions","why","editorPath"]}}},"required":["id","prompt","tone","model","cached","variants"]},"RenderRequest":{"type":"object","properties":{"templateId":{"anyOf":[{"type":"integer","minimum":0,"exclusiveMinimum":true,"maximum":9007199254740991},{"type":"string","minLength":1}],"description":"Template id, slug, or the `<slug>-<id>` form used in editor URLs"},"captions":{"anyOf":[{"maxItems":16,"type":"array","items":{"type":"string","maxLength":300}},{"type":"object","additionalProperties":{"type":"string","maxLength":300}}],"description":"Either an array applied to the template’s text boxes in order, or an object keyed by box id"},"format":{"description":"Defaults to the template’s own kind: png for stills, gif for GIFs","type":"string","enum":["png","gif"]},"preset":{"description":"GIF size preset. One of high, discord, emoji, compact. Ignored for stills","type":"string","enum":["high","discord","emoji","compact"]},"watermark":{"description":"Defaults to true","type":"boolean"}},"required":["templateId","captions"],"description":"A caption job against one of memeworld’s own templates"},"RenderResult":{"type":"object","properties":{"url":{"type":"string","description":"Permanent, content-addressed URL for the rendered file"},"format":{"type":"string","enum":["png","gif"]},"width":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"height":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"bytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"frames":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"maxBytes":{"description":"Size ceiling the chosen GIF preset targets. Absent for stills and `high`","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"overBudget":{"description":"True when the GIF came out over `maxBytes`. The file is still usable, but a stricter preset (`compact`, `emoji`) or a shorter source is needed where the limit is enforced","type":"boolean"},"cached":{"type":"boolean"}},"required":["url","format","width","height","bytes","cached"]},"FaceSwapRequest":{"type":"object","properties":{"templateId":{"anyOf":[{"type":"integer","minimum":0,"exclusiveMinimum":true,"maximum":9007199254740991},{"type":"string","minLength":1}],"description":"Template id, slug, or the `<slug>-<id>` form used in editor URLs"},"faceImage":{"type":"string","minLength":64,"description":"The face photo as base64, with or without a `data:image/jpeg;base64,` prefix. JPEG, PNG or WebP, up to 10 MB decoded. It is sent to the image model and never stored."},"direction":{"description":"Extra instructions, e.g. “keep the sunglasses”, “swap both faces”","type":"string","maxLength":300}},"required":["templateId","faceImage"],"description":"A face to put into one of memeworld’s still templates"},"FaceSwapResult":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["ready","failed"]},"url":{"nullable":true,"description":"Permanent URL for the swapped image","type":"string"},"width":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"height":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"bytes":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"direction":{"nullable":true,"type":"string"},"error":{"nullable":true,"type":"string"},"creditsSpent":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"createdAt":{"type":"string"},"template":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"name":{"type":"string"},"slug":{"type":"string"},"path":{"type":"string"},"posterUrl":{"type":"string"}},"required":["id","name","slug","path","posterUrl"]},"editorPath":{"nullable":true,"description":"Editor path that opens the result with the template’s caption boxes over it","type":"string"},"credits":{"type":"object","properties":{"balance":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Credits available"},"granted":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"spent":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"faceSwapsLeft":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Face swaps the balance pays for"}},"required":["balance","granted","spent","faceSwapsLeft"],"description":"The caller’s balance after this swap"}},"required":["id","status","url","width","height","bytes","direction","error","creditsSpent","createdAt","template","editorPath","credits"]},"FaceSwapList":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["ready","failed"]},"url":{"nullable":true,"description":"Permanent URL for the swapped image","type":"string"},"width":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"height":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"bytes":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"direction":{"nullable":true,"type":"string"},"error":{"nullable":true,"type":"string"},"creditsSpent":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"createdAt":{"type":"string"},"template":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"name":{"type":"string"},"slug":{"type":"string"},"path":{"type":"string"},"posterUrl":{"type":"string"}},"required":["id","name","slug","path","posterUrl"]},"editorPath":{"nullable":true,"description":"Editor path that opens the result with the template’s caption boxes over it","type":"string"}},"required":["id","status","url","width","height","bytes","direction","error","creditsSpent","createdAt","template","editorPath"]}},"nextOffset":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["items","nextOffset"]},"Credits":{"type":"object","properties":{"balance":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Credits available"},"granted":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"spent":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"faceSwapsLeft":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Face swaps the balance pays for"},"costs":{"type":"object","properties":{"faceSwap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["faceSwap"],"description":"What each metered action costs"},"recent":{"type":"array","items":{"type":"object","properties":{"delta":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"reason":{"type":"string","enum":["welcome","grant","face_swap","refund"]},"refId":{"nullable":true,"type":"string"},"balanceAfter":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"createdAt":{"type":"string"}},"required":["delta","reason","refId","balanceAfter","createdAt"]},"description":"Recent movements, newest first"}},"required":["balance","granted","spent","faceSwapsLeft","costs","recent"]},"Error":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}},"paths":{"/v1/templates":{"get":{"tags":["templates"],"operationId":"searchTemplates","summary":"Search or list templates","parameters":[{"name":"q","in":"query","description":"Free-text search over name, keywords and tags","required":false,"schema":{"type":"string"}},{"name":"kind","in":"query","description":"Restrict to still images or GIFs","required":false,"schema":{"type":"string","enum":["still","gif"]}},{"name":"tag","in":"query","description":"Restrict to one tag slug","required":false,"schema":{"type":"string"}},{"name":"faceSwap","in":"query","description":"Set to 1 for only templates tuned for face swaps","required":false,"schema":{"type":"string","enum":["1"]}},{"name":"seed","in":"query","description":"Integer seed for a stable shuffled order","required":false,"schema":{"type":"integer"}},{"name":"cursor","in":"query","description":"Opaque cursor from a previous response’s nextCursor","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Page size, 1–100 (default 60)","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Matching templates, most popular first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateList"}}}},"400":{"description":"Invalid query parameter","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}}}}},"/v1/templates/{idOrSlug}":{"get":{"tags":["templates"],"operationId":"getTemplate","summary":"One template, with its text boxes and captioning guide","description":"Includes the text box ids `POST /v1/render` expects, example captions, and notes on how the format’s joke works.","parameters":[{"name":"idOrSlug","in":"path","required":true,"description":"Numeric id, slug, or the `<slug>-<id>` form used in editor URLs","schema":{"type":"string"}}],"responses":{"200":{"description":"The template"},"404":{"description":"No such template","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}}}}},"/v1/tags":{"get":{"tags":["templates"],"operationId":"listTags","summary":"Every tag with at least one published template","responses":{"200":{"description":"Tags, featured first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagList"}}}}}}},"/v1/suggest":{"post":{"tags":["suggest"],"operationId":"suggest","summary":"Pick templates for an idea and write their captions","description":"Describe the joke; a model picks templates from the catalog and captions them. Nothing is rendered — each variant comes back with its text boxes, the captions in box order, and an editor link with them prefilled. Pass those captions to POST /v1/render to get a file. Identical requests are answered from cache.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestRequest"}}}},"responses":{"200":{"description":"The chosen templates, captioned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestResult"}}}},"400":{"description":"Invalid request body, or the model found nothing that fits","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"503":{"description":"No model is configured on this deployment","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}}}}},"/v1/render":{"post":{"tags":["render"],"operationId":"render","summary":"Caption a template and store the result","description":"Renders server-side and returns a permanent URL. Renders are content-addressed, so repeating a request is free and returns the same URL.","security":[{"apiKey":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderRequest"}}}},"responses":{"200":{"description":"The rendered file","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderResult"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"404":{"description":"No such template","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"413":{"description":"The GIF is too large to render in one request","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"415":{"description":"The template file is in a format the renderer can’t decode","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}}}}},"/v1/face-swap":{"post":{"tags":["face swap"],"operationId":"faceSwap","summary":"Put a face into a still template","description":"Costs 2 credits, and takes several seconds — the request stays open until the image model answers. The uploaded photo is sent to the model and never stored; the swapped meme is stored and returned at a permanent URL. A failed swap refunds its credits and returns the balance alongside the error. Also accepts `multipart/form-data` with `templateId`, a `face` file and an optional `direction` field.","security":[{"apiKey":[]},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FaceSwapRequest"}},"multipart/form-data":{"schema":{"type":"object","required":["templateId","face"],"properties":{"templateId":{"type":"string"},"face":{"type":"string","format":"binary"},"direction":{"type":"string"}}}}}},"responses":{"200":{"description":"The swapped image","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FaceSwapResult"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"402":{"description":"Not enough credits","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"404":{"description":"No such template","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"413":{"description":"The face photo is too large","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"415":{"description":"A GIF template, or a face photo that isn’t JPEG, PNG or WebP","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"422":{"description":"The model declined to make that swap. Credits were refunded","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"502":{"description":"The image model failed. Credits were refunded","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"503":{"description":"Face swapping is not configured on this deployment","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"get":{"tags":["face swap"],"operationId":"listFaceSwaps","summary":"The caller’s own face swaps, newest first","security":[{"apiKey":[]},{"bearer":[]}],"parameters":[{"name":"limit","in":"query","description":"How many to return, 1–100 (default 12)","required":false,"schema":{"type":"integer"}},{"name":"offset","in":"query","description":"Number of newer swaps to skip","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Recent swaps, including failures","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FaceSwapList"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}}}}},"/v1/face-swap/{id}":{"get":{"tags":["face swap"],"operationId":"getFaceSwap","summary":"One face swap","security":[{"apiKey":[]},{"bearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The swap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FaceSwapResult"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}},"404":{"description":"No such swap, or it belongs to somebody else","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}}}}},"/v1/credits":{"get":{"tags":["credits"],"operationId":"getCredits","summary":"Credit balance and recent movements","description":"Reading this opens the account’s credit row and pays in its welcome grant, so a new account can be told what it has before it spends anything.","security":[{"apiKey":[]},{"bearer":[]}],"responses":{"200":{"description":"The balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Credits"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code"},"message":{"type":"string"}},"required":["code","message"]}}}}}}}}}