[{"data":1,"prerenderedAt":877},["ShallowReactive",2],{"pricing-plans":3,"content-page-\u002Fdocs\u002Fintegrations\u002Faws-s3":4,"api-docs-metadata":370,"i-ph:list":3,"i-ph:caret-right":3,"i-ph:copy":3,"i-ph:caret-down":3,"i-ph:twitter-logo":3,"i-ph:youtube-logo":3},null,{"id":5,"title":6,"body":7,"description":360,"extension":361,"meta":362,"navigation":365,"path":366,"seo":367,"stem":368,"__hash__":369},"all\u002Fdocs\u002Fintegrations\u002Faws-s3.md","AWS S3 integration",{"type":8,"value":9,"toc":352},"minimark",[10,15,23,28,41,45,68,72,93,186,190,200,204,207,320,324,348],[11,12,14],"h1",{"id":13},"aws-s3","AWS S3",[16,17,18,19,22],"p",{},"Use your own ",[20,21,14],"strong",{}," bucket—or any S3-compatible storage such as Cloudflare R2 or MinIO—as the destination for rendered images and template thumbnails. Keep assets in infrastructure you control and serve them from your own domain.",[24,25,27],"h2",{"id":26},"what-you-can-do","What you can do",[29,30,31,35,38],"ul",{},[32,33,34],"li",{},"Upload rendered images and thumbnails to your bucket.",[32,36,37],{},"Serve assets from a custom CDN domain.",[32,39,40],{},"Point at S3-compatible providers (R2, MinIO) via a custom endpoint.",[24,42,44],{"id":43},"prerequisites","Prerequisites",[29,46,47,50,61],{},[32,48,49],{},"An AWS account with an S3 bucket (or an S3-compatible bucket).",[32,51,52,53,56,57,60],{},"IAM credentials (",[20,54,55],{},"Access Key ID"," + ",[20,58,59],{},"Secret Access Key",") with write access to the bucket.",[32,62,63,64,67],{},"The bucket's ",[20,65,66],{},"region",".",[24,69,71],{"id":70},"connect-aws-s3","Connect AWS S3",[73,74,75,81,90],"ol",{},[32,76,77,78,67],{},"In Bannx, open ",[20,79,80],{},"Settings → Space → Integrations",[32,82,83,84,86,87,67],{},"Find ",[20,85,14],{}," and click ",[20,88,89],{},"Connect",[32,91,92],{},"Fill in the fields below and save.",[94,95,96,112],"table",{},[97,98,99],"thead",{},[100,101,102,106,109],"tr",{},[103,104,105],"th",{},"Field",[103,107,108],{},"Required",[103,110,111],{},"Description",[113,114,115,130,139,152,162,176],"tbody",{},[100,116,117,120,123],{},[118,119,55],"td",{},[118,121,122],{},"Yes",[118,124,125,126],{},"IAM access key, e.g. ",[127,128,129],"code",{},"AKIA…",[100,131,132,134,136],{},[118,133,59],{},[118,135,122],{},[118,137,138],{},"Secret for the access key",[100,140,141,144,146],{},[118,142,143],{},"Region",[118,145,122],{},[118,147,148,149],{},"Bucket region, e.g. ",[127,150,151],{},"us-east-1",[100,153,154,157,159],{},[118,155,156],{},"Bucket",[118,158,122],{},[118,160,161],{},"Target bucket name",[100,163,164,167,170],{},[118,165,166],{},"CDN URL",[118,168,169],{},"No",[118,171,172,173],{},"Serve files from a custom domain, e.g. ",[127,174,175],{},"https:\u002F\u002Fcdn.example.com",[100,177,178,181,183],{},[118,179,180],{},"Endpoint",[118,182,169],{},[118,184,185],{},"Custom endpoint for S3-compatible providers (R2, MinIO)",[24,187,189],{"id":188},"s3-compatible-providers","S3-compatible providers",[16,191,192,193,195,196,199],{},"For Cloudflare R2, MinIO, or similar, set the ",[20,194,180],{}," to the provider's S3 API URL (for example ",[127,197,198],{},"https:\u002F\u002F\u003Caccount>.r2.cloudflarestorage.com",") and keep the bucket and credentials for that provider.",[24,201,203],{"id":202},"recommended-iam-policy","Recommended IAM policy",[16,205,206],{},"Grant the minimum permissions needed to write and read objects in the target bucket:",[208,209,214],"pre",{"className":210,"code":211,"language":212,"meta":213,"style":213},"language-json shiki shiki-themes github-light github-dark","{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\"s3:PutObject\", \"s3:GetObject\"],\n      \"Resource\": \"arn:aws:s3:::YOUR_BUCKET\u002F*\"\n    }\n  ]\n}\n","json","",[127,215,216,225,242,251,257,270,291,302,308,314],{"__ignoreMap":213},[217,218,221],"span",{"class":219,"line":220},"line",1,[217,222,224],{"class":223},"sVt8B","{\n",[217,226,228,232,235,239],{"class":219,"line":227},2,[217,229,231],{"class":230},"sj4cs","  \"Version\"",[217,233,234],{"class":223},": ",[217,236,238],{"class":237},"sZZnC","\"2012-10-17\"",[217,240,241],{"class":223},",\n",[217,243,245,248],{"class":219,"line":244},3,[217,246,247],{"class":230},"  \"Statement\"",[217,249,250],{"class":223},": [\n",[217,252,254],{"class":219,"line":253},4,[217,255,256],{"class":223},"    {\n",[217,258,260,263,265,268],{"class":219,"line":259},5,[217,261,262],{"class":230},"      \"Effect\"",[217,264,234],{"class":223},[217,266,267],{"class":237},"\"Allow\"",[217,269,241],{"class":223},[217,271,273,276,279,282,285,288],{"class":219,"line":272},6,[217,274,275],{"class":230},"      \"Action\"",[217,277,278],{"class":223},": [",[217,280,281],{"class":237},"\"s3:PutObject\"",[217,283,284],{"class":223},", ",[217,286,287],{"class":237},"\"s3:GetObject\"",[217,289,290],{"class":223},"],\n",[217,292,294,297,299],{"class":219,"line":293},7,[217,295,296],{"class":230},"      \"Resource\"",[217,298,234],{"class":223},[217,300,301],{"class":237},"\"arn:aws:s3:::YOUR_BUCKET\u002F*\"\n",[217,303,305],{"class":219,"line":304},8,[217,306,307],{"class":223},"    }\n",[217,309,311],{"class":219,"line":310},9,[217,312,313],{"class":223},"  ]\n",[217,315,317],{"class":219,"line":316},10,[217,318,319],{"class":223},"}\n",[24,321,323],{"id":322},"notes","Notes",[29,325,326,332,342],{},[32,327,328,331],{},[20,329,330],{},"CORS:"," If assets are loaded by the browser from your CDN\u002Fbucket, allow your app origin in the bucket's CORS configuration.",[32,333,334,337,338,341],{},[20,335,336],{},"Caching:"," Bannx uploads objects with a long, immutable ",[127,339,340],{},"Cache-Control"," (object keys are unique), so a CDN in front of the bucket can cache them aggressively.",[32,343,344,347],{},[20,345,346],{},"Security:"," Credentials are stored against your space and used only server-side.",[349,350,351],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":213,"searchDepth":227,"depth":227,"links":353},[354,355,356,357,358,359],{"id":26,"depth":227,"text":27},{"id":43,"depth":227,"text":44},{"id":70,"depth":227,"text":71},{"id":188,"depth":227,"text":189},{"id":202,"depth":227,"text":203},{"id":322,"depth":227,"text":323},"Store rendered images and template thumbnails in your own AWS S3 (or S3-compatible) bucket, optionally served from your CDN.","md",{"layout":363,"updatedAt":364},"docs","2026-06-07",true,"\u002Fdocs\u002Fintegrations\u002Faws-s3",{"title":6,"description":360},"docs\u002Fintegrations\u002Faws-s3","2TdKRR0nkqrm_ztnYPXiB7EkpYFoHUcd1toA1wjF7SQ",{"success":365,"routes":371,"total":875,"generatedAt":876,"canViewAllApiDocs":446},[372,419,431,488,545,566,587,602,618,630,642,659,687,713,732,744,755,766,775,781,799,810,825,837,850],{"path":373,"method":374,"summary":375,"description":376,"tags":377,"group":378,"requestBody":379,"responses":411,"actionName":418},"\u002Fapi\u002Fcode-to-image","POST","Render code snippets as images","Render source code with syntax highlighting to PNG, WebP, or JPEG. No API token required.",[378],"Rendering",{"content":380},{"application\u002Fjson":381},{"schema":382},{"type":383,"required":384,"properties":385},"object",[127],{"code":386,"lang":389,"theme":391,"format":393,"width":399,"height":402,"quality":404,"showLineNumbers":406,"style":409},{"type":387,"description":388},"string","Source code to render",{"type":387,"description":390},"Language for highlighting (e.g. js, ts). Default: js",{"type":387,"description":392},"Shiki theme. Default: github-dark",{"type":387,"enum":394,"description":398},[395,396,397],"png","webp","jpeg","Output format. Default: png (lossless)",{"type":400,"description":401},"number","Optional width",{"type":400,"description":403},"Optional height",{"type":400,"description":405},"Optional quality",{"type":407,"description":408},"boolean","Show 1-based line numbers in a left gutter. Default: false",{"type":383,"description":410},"Optional style options",{"200":412,"400":414,"500":416},{"description":413},"Image (data URL or binary depending on format)",{"description":415},"Missing or invalid code",{"description":417},"Failed to generate code image","codeToImage",{"path":420,"method":374,"summary":421,"description":422,"tags":423,"group":424,"responses":425,"actionName":430},"\u002Fapi\u002Fdemo-token","Create a demo token","Mint a short-lived, IP-throttled demo token (stored only in Redis, ~1h TTL) that can render PUBLIC templates with tweaks — no account or API key required. Each client IP can mint up to 5 tokens per rolling hour. Use the token as `Authorization: Bearer demo_…` or `?demo=demo_…` against GET \u002Fapi\u002Frender\u002F:pageId.",[424],"Authentication",{"200":426,"429":428},{"description":427},"Demo token created",{"description":429},"Per-IP demo token quota reached","createDemoToken",{"path":432,"method":433,"summary":434,"description":435,"tags":436,"group":378,"parameters":437,"responses":480,"actionName":487},"\u002Fapi\u002Frender\u002F:pageId","GET","Render a page by ID (GET)","Render by page ID with query parameters. Supports draft (database), publish cache, png\u002Fsvg\u002Fwebp\u002Fpdf, JSON or binary responses, tweaks, and brandTweaks — aligned with POST \u002Fapi\u002Frender. Auth depends on template requireGetRenderToken (Bearer or gt query).",[378],[438,443,453,457,463,467,471,475],{"in":439,"name":440,"required":365,"schema":441,"description":442},"path","pageId",{"type":387},"Page ID to render",{"in":444,"name":445,"required":446,"schema":447,"description":452},"query","format",false,{"type":387,"enum":448},[395,449,396,450,451],"svg","pdf","pptx","Output format. Default: png",{"in":444,"name":454,"required":446,"schema":455,"description":456},"draft",{"type":407},"If true, load from database instead of publish cache. Default: false",{"in":444,"name":458,"required":446,"schema":459,"description":462},"output",{"type":387,"enum":460},[212,461],"binary","Response shape: omit or json for metadata + file URL; binary for raw bytes",{"in":444,"name":464,"required":446,"schema":465,"description":466},"pageIds",{"type":387},"Comma-separated page IDs for merged PDF (format=pdf only)",{"in":444,"name":468,"required":446,"schema":469,"description":470},"gt",{"type":387},"GET render secret when Bearer is not used",{"in":444,"name":472,"required":446,"schema":473,"description":474},"scale",{"type":400,"minimum":220,"maximum":253},"Output resolution multiplier for png\u002Fwebp. Overrides the template's renderScale. Default 2.",{"in":444,"name":476,"required":446,"schema":477,"description":479},"quality",{"type":400,"minimum":220,"maximum":478},100,"WebP encode quality (webp only). Overrides the template's webpQuality. Default 90.",{"200":481,"401":483,"404":485},{"description":482},"Binary image\u002FPDF or application\u002Fjson with file metadata",{"description":484},"Missing or invalid API token",{"description":486},"Page or template not found","renderPage",{"path":489,"method":374,"summary":490,"description":491,"tags":492,"group":378,"requestBody":493,"responses":537,"actionName":544},"\u002Fapi\u002Frender\u002Findex","Render by page ID","Render a page by ID from the publish cache, or from the database when draft: true. Requires API token. Does not accept full inline page payloads. Optional body field renderType (satori, takumi, html) overrides the template's rendererType. Use format pdf for the HTML + Chrome PDF pipeline (always uses the HTML renderer). For multi-page PDFs, send pageIds and optional tweaksPerPage (same length). Default response is application\u002Fjson with timings and a URL to the uploaded render; set output to binary (or binary: true) to return raw bytes.",[378],{"content":494},{"application\u002Fjson":495},{"schema":496},{"type":383,"description":497,"properties":498},"For raster formats use pageId. For PDF use pageId (single) or pageIds (merged in order).",{"pageId":499,"pageIds":501,"tweaksPerPage":505,"templateId":508,"draft":510,"format":512,"tweaks":516,"brandTweaks":518,"emojiType":520,"output":522,"binary":527,"renderType":529,"scale":534,"quality":536},{"type":387,"description":500},"Page ID to render (required for png\u002Fsvg\u002Fwebp; optional for pdf when pageIds is set)",{"type":502,"items":503,"description":504},"array",{"type":387},"Multiple pages for merged PDF (format pdf only)",{"type":502,"items":506,"description":507},{"type":383},"Per-page tweaks aligned with pageIds when rendering PDF",{"type":387,"description":509},"Optional; when set, validated against the page's template (parallel fetch)",{"type":407,"description":511},"If true, load page and template from database instead of publish cache. Default: false",{"type":387,"enum":513,"description":515},[395,449,396,450,451,514],"html5","Output format. Default: png. pdf and pptx use HTML + Chrome; pptx builds an editable slide per page. html5 returns a self-contained HTML5 display-ad ZIP (Google Ads) for a single page.",{"type":383,"description":517},"Variable overrides; keys may use dot paths for nested values. For Variable[] sources, use \"varId.nested.path\" when patching object values.",{"type":383,"description":519},"Brand token overrides by dot path (e.g. logos.icon or brand.logos.icon). Merged into resolved brand tokens before render.",{"type":387,"description":521},"e.g. twemoji, openmoji. Default from template",{"type":387,"enum":523,"description":526},[212,461,524,525],"image","raw","Response shape: omit or json for application\u002Fjson (default); binary, image, or raw for raw body",{"type":407,"description":528},"If true, same as output binary (raw body)",{"type":387,"enum":530,"description":533},[531,532],"takumi","html","Override rendering engine (takumi or html). Ignored when format is pdf (HTML pipeline). Legacy value 'satori' is accepted and treated as 'takumi'.",{"type":400,"minimum":220,"maximum":253,"description":535},"Output resolution multiplier for png\u002Fwebp (logical layout unchanged, pixels multiplied). Overrides the template's renderScale. Default 2.",{"type":400,"minimum":220,"maximum":478,"description":479},{"200":538,"400":540,"401":542,"404":543},{"description":539},"Default: application\u002Fjson with render metadata and file.url. With output binary: image\u002Fpng, image\u002Fsvg+xml, image\u002Fwebp, or application\u002Fpdf.",{"description":541},"Invalid body (e.g. missing pageId)",{"description":484},{"description":486},"render",{"path":546,"method":433,"summary":547,"description":548,"tags":549,"parameters":550,"responses":560,"actionName":565},"\u002Fapi\u002Frenders\u002Flogs","List render logs","Render request logs for the current space, optionally narrowed to one template. Session-authenticated (dashboard login) — not callable with an API key.",[378],[551,554,557],{"in":444,"name":552,"required":446,"description":553},"templateId","Only logs for this template's pages",{"in":444,"name":555,"required":446,"description":556},"page","Page number",{"in":444,"name":558,"required":446,"description":559},"perPage","Items per page (default 50)",{"200":561,"401":563},{"description":562},"JSON { list, count, pagesCount } of render log entries",{"description":564},"Not logged in","listRenderLogs",{"path":567,"method":433,"summary":568,"description":569,"tags":570,"parameters":572,"responses":583,"actionName":586},"\u002Fapi\u002Ftemplates\u002Fpublic","List public templates","List published community\u002Fgallery templates with their cached pages. No authentication required.",[571],"Templates",[573,576,579,581],{"in":444,"name":574,"required":446,"description":575},"search","Search name + description (max 80 chars)",{"in":444,"name":577,"required":446,"description":578},"category","Category filter, or \"featured\" for the curated list",{"in":444,"name":555,"required":446,"description":580},"Page number (default 1)",{"in":444,"name":558,"required":446,"description":582},"Items per page (max 100)",{"200":584},{"description":585},"JSON { list, count, pagesCount } — templates include their published pages","listPublicTemplates",{"path":588,"method":433,"summary":589,"description":590,"tags":591,"parameters":592,"responses":596,"actionName":601},"\u002Fapi\u002Ftemplates\u002Fpublic\u002F:id","Get a public template","Fetch one published template (with its pages) by ID. Only templates with publicOn are returned; private IDs 404. No authentication required.",[571],[593],{"in":439,"name":594,"required":365,"description":595},"id","Template ID",{"200":597,"404":599},{"description":598},"JSON { doc } — the template including published pages",{"description":600},"Template not found or not public","getPublicTemplateDoc",{"path":603,"method":604,"summary":605,"description":606,"tags":607,"parameters":609,"responses":612,"actionName":617},"\u002Fapi\u002Fv1\u002Fbrands\u002F:id","DELETE","Delete a brand","Soft-delete a brand by ID. Session-authenticated (dashboard login; owner\u002Fadmin\u002Feditor).",[608],"Brands",[610],{"in":439,"name":594,"required":365,"description":611},"Brand ID",{"200":613,"404":615},{"description":614},"JSON { success: true, id }",{"description":616},"Brand not found in your space","deleteBrand",{"path":603,"method":433,"summary":619,"description":620,"tags":621,"parameters":622,"responses":624,"actionName":629},"Get a brand","Fetch one brand by ID. Public brands (publicOn set) need no auth; private brands require a dashboard session in the owning space. For API-key access use the MCP server (get_brand).",[608],[623],{"in":439,"name":594,"required":365,"description":611},{"200":625,"404":627},{"description":626},"JSON { doc } — the brand with its token sets",{"description":628},"Not found or not accessible","getBrand",{"path":603,"method":631,"summary":632,"description":633,"tags":634,"parameters":635,"responses":637,"actionName":641},"PUT","Update a brand","Update a brand by ID (same fields as create, all optional). Session-authenticated (dashboard login; owner\u002Fadmin\u002Feditor). For API-key access use the MCP server (update_brand).",[608],[636],{"in":439,"name":594,"required":365,"description":611},{"200":638,"404":640},{"description":639},"JSON { doc } — the updated brand",{"description":616},"updateBrand",{"path":643,"method":433,"summary":644,"description":645,"tags":646,"parameters":647,"responses":653,"actionName":658},"\u002Fapi\u002Fv1\u002Fbrands\u002Findex","List brands","List brands in the current space. Session-authenticated (dashboard login); pass ?public=true to list public brands anonymously. For API-key access use the MCP server (list_brands).",[608],[648,649,650],{"in":444,"name":555,"required":446,"description":580},{"in":444,"name":558,"required":446,"description":559},{"in":444,"name":651,"required":446,"description":652},"public","\"true\" to list public brands without auth",{"200":654,"401":656},{"description":655},"JSON { list, count, pagesCount } of brands",{"description":657},"Not logged in (when public is not set)","listBrands",{"path":643,"method":374,"summary":660,"description":661,"tags":662,"requestBody":663,"responses":682,"actionName":686},"Create a brand","Create a brand in the current space. Session-authenticated (dashboard login; owner\u002Fadmin\u002Feditor). External image URLs inside tokens are re-uploaded to your space. For API-key access use the MCP server (create_brand).",[608],{"content":664},{"application\u002Fjson":665},{"schema":666},{"type":383,"required":667,"properties":669},[668],"name",{"name":670,"draftTokens":672,"liveTokens":674,"tokens":676,"variants":678,"extra":680},{"type":387,"description":671},"Brand name",{"type":383,"description":673},"Token kit being edited (logos, colors, typography, …)",{"type":383,"description":675},"Token kit used by published renders",{"type":383,"description":677},"Resolved token set",{"type":383,"description":679},"Named variations of the kit",{"type":383,"description":681},"Free-form extras",{"200":683,"401":685},{"description":684},"JSON { doc } — the created brand",{"description":564},"createBrand",{"path":688,"method":433,"summary":689,"description":690,"tags":691,"group":692,"parameters":693,"responses":706,"actionName":712},"\u002Fapi\u002Fv1\u002Fpublic\u002Fcontent\u002F:collection\u002F:slug","Get an entry","Read a single PUBLISHED entry by slug. Authenticate with a content.read API key (Authorization: Bearer …). The slug is matched against the collection's configured preview.slugField (defaults to \"slug\"). Only liveData is returned; use ?depth= to resolve references and ?variants= for personalization.",[692],"Content",[694,697,700,703],{"in":439,"name":695,"required":365,"description":696},"collection","The collection's API name",{"in":439,"name":698,"required":365,"description":699},"slug","The entry slug",{"in":444,"name":701,"required":446,"description":702},"depth","Resolve reference fields to this depth (default 0)",{"in":444,"name":704,"required":446,"description":705},"variants","Comma-separated variant ids; returns personalization-resolved values",{"200":707,"401":709,"404":710},{"description":708},"JSON { doc } — the published entry",{"description":484},{"description":711},"Collection or entry not found","publicGetContentBySlug",{"path":714,"method":433,"summary":715,"description":716,"tags":717,"group":692,"parameters":718,"responses":725,"actionName":731},"\u002Fapi\u002Fv1\u002Fpublic\u002Fcontent\u002F:collection\u002Findex","List entries","List PUBLISHED entries in a collection. Authenticate with a content.read API key (Authorization: Bearer …). Only liveData is returned — never drafts. Values under `data` are keyed by field API name. Supports pagination, reference resolution (?depth=), and personalization (?variants=).",[692],[719,720,721,723,724],{"in":439,"name":695,"required":365,"description":696},{"in":444,"name":555,"required":446,"description":580},{"in":444,"name":558,"required":446,"description":722},"Items per page (default 20)",{"in":444,"name":701,"required":446,"description":702},{"in":444,"name":704,"required":446,"description":705},{"200":726,"401":728,"404":729},{"description":727},"JSON { list, count, pagesCount } of published entries",{"description":484},{"description":730},"Collection not found","publicListContent",{"path":733,"method":604,"summary":734,"description":735,"tags":736,"responses":738,"actionName":743},"\u002Fapi\u002Fv1\u002Fshaders\u002F:id","Delete a shader","Permanently remove a shader from the current space.",[737],"Shaders",{"200":739,"404":741},{"description":740},"JSON { deleted: true }",{"description":742},"No such shader","deleteShader",{"path":733,"method":433,"summary":745,"description":746,"tags":747,"responses":748,"actionName":754},"Get a shader","Fetch one shader including its vetted `program` — the only source a client should compile. Status is enforced here on every request, which is what makes revoking a working kill switch: a quarantined or revoked shader returns 410 and stops loading everywhere immediately.",[737],{"200":749,"404":751,"410":752},{"description":750},"JSON { doc }",{"description":742},{"description":753},"Shader is quarantined or revoked","getShader",{"path":733,"method":631,"summary":756,"description":757,"tags":758,"responses":759,"actionName":765},"Update a shader","Editing source or the manifest re-runs the full vetting pipeline and produces a new content hash — an existing approval is never inherited by code that was not vetted. A rejected edit returns 422 and leaves the stored shader untouched.",[737],{"200":760,"404":762,"422":763},{"description":761},"JSON { doc, report }",{"description":742},{"description":764},"Rejected by vetting; the stored shader is unchanged","updateShader",{"path":767,"method":374,"summary":768,"description":769,"tags":770,"responses":771,"actionName":774},"\u002Fapi\u002Fv1\u002Fshaders\u002F:id\u002Fstatus","Set a shader status (kill switch)","Revoke, quarantine, or restore a shader. Because GET enforces status on every fetch, revoking stops the shader loading everywhere at once — there is no client deny-list to push and no cache to wait out. Restoring also clears the accumulated watchdog trips.",[737],{"200":772,"404":773},{"description":750},{"description":742},"setShaderStatus",{"path":776,"method":374,"summary":777,"description":778,"tags":779,"actionName":780},"\u002Fapi\u002Fv1\u002Fshaders\u002F:id\u002Ftelemetry","Report a client watchdog trip","Called by a viewer whose frame watchdog disabled this shader. Enough independent trips ",[737],"reportShaderTrip",{"path":782,"method":433,"summary":783,"description":784,"tags":785,"parameters":786,"responses":794,"actionName":798},"\u002Fapi\u002Fv1\u002Fshaders\u002Findex","List shaders","List the current space's shaders. Pass ?public=true to list published shaders anonymously. Program source is omitted from the listing — fetch a single shader to obtain it.",[737],[787,788,789,792],{"in":444,"name":555,"required":446,"description":580},{"in":444,"name":558,"required":446,"description":559},{"in":444,"name":790,"required":446,"description":791},"status","approved | quarantined | revoked",{"in":444,"name":651,"required":446,"description":793},"\"true\" to list public shaders without auth",{"200":795,"401":797},{"description":796},"JSON { list, count, pagesCount }",{"description":657},"listShaders",{"path":782,"method":374,"summary":800,"description":801,"tags":802,"responses":803,"actionName":809},"Create a shader","Submit a GLSL fragment body plus a typed input manifest. The shader is vetted before anything is stored: byte caps, a static policy scan, loop normalisation to constant bounds, a cost model, then a real compile and fuzz render inside a sandboxed child process. A rejected submission returns 422 with the stage-by-stage report and is not saved.",[737],{"200":804,"401":806,"422":807},{"description":805},"JSON { doc, report } — the stored shader and its vetting report",{"description":564},{"description":808},"Rejected by vetting; data.report explains which stage refused","createShader",{"path":811,"method":604,"summary":812,"description":813,"tags":814,"parameters":816,"responses":819,"actionName":824},"\u002Fapi\u002Fv1\u002Ftokens\u002Fapi-keys\u002F:id","Delete an API key","Revoke an API key immediately. Session-authenticated (dashboard login; owner\u002Fadmin).",[815],"Tokens",[817],{"in":439,"name":594,"required":365,"description":818},"Token ID",{"200":820,"404":822},{"description":821},"JSON { message } confirmation",{"description":823},"Token not found in your space","deleteApiKey",{"path":811,"method":826,"summary":827,"description":828,"tags":829,"parameters":830,"responses":832,"actionName":836},"PATCH","Update an API key","Rename, change expiry, or adjust scopes of an API key. Session-authenticated (dashboard login; owner\u002Fadmin).",[815],[831],{"in":439,"name":594,"required":365,"description":818},{"200":833,"404":835},{"description":834},"JSON { doc } — the updated key",{"description":823},"updateApiKey",{"path":838,"method":433,"summary":839,"description":840,"tags":841,"parameters":842,"responses":845,"actionName":849},"\u002Fapi\u002Fv1\u002Ftokens\u002Fapi-keys\u002Findex","List API keys","List API keys for the current space. Session-authenticated (dashboard login; owner\u002Fadmin).",[815],[843,844],{"in":444,"name":555,"required":446,"description":580},{"in":444,"name":558,"required":446,"description":722},{"200":846,"401":848},{"description":847},"JSON { list, count, pagesCount } of API keys",{"description":564},"listApiKeys",{"path":838,"method":374,"summary":851,"description":852,"tags":853,"requestBody":854,"responses":870,"actionName":874},"Create an API key","Create a space-scoped API key. The plain token is returned once, on creation. Session-authenticated (dashboard login; owner\u002Fadmin).",[815],{"content":855},{"application\u002Fjson":856},{"schema":857},{"type":383,"required":858,"properties":859},[668],{"name":860,"expiresAt":862,"neverExpires":864,"shortToken":866,"scopes":868},{"type":387,"description":861},"Key name (max 200 chars)",{"type":387,"description":863},"ISO datetime expiry",{"type":407,"description":865},"No expiry. Default: false",{"type":407,"description":867},"Shorter ~96-bit token instead of ~256-bit. Default: false",{"type":383,"description":869},"Optional restrictions: { mcpTools?: string[], apiEndpoints?: string[] }. Omit a list to allow all; [] denies all.",{"200":871,"401":873},{"description":872},"JSON { doc } including the plain token (shown only once)",{"description":564},"createApiKey",25,"2026-08-19T19:21:11.342Z",1787167272953]