{"openapi":"3.1.0","info":{"title":"Zenex CMS Management API","version":"1.0.0"},"paths":{"/api/v1/blogs/{blogId}/media":{"post":{"operationId":"upload_image","description":"Upload a public JPEG, PNG, GIF, WebP or SVG image as base64 (maximum 700 KiB decoded; SVG is stored as PNG). purpose \"content\" (default) returns a URL usable in coverImage or Editor.js. purpose \"logo\" or \"style_reference\" stores a brand asset for Autopilot images and needs seo:write; then set it with seo_update_settings (logoUrl, thumbnailReferenceUrls or inlineImageReferenceUrls). Do not upload confidential files; images are public even for drafts. Required scope: media:write.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mimeType":{"type":"string","enum":["image/jpeg","image/png","image/gif","image/webp","image/svg+xml"]},"base64":{"type":"string","minLength":4,"maxLength":956000,"pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$"},"purpose":{"default":"content","type":"string","enum":["content","logo","style_reference"]}},"required":["mimeType","base64"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}}},"/api/v1/blogs/{blogId}":{"get":{"operationId":"get_blog","description":"Read this blog’s public identifier and metadata. Required scope: content:read.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"}],"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}},"patch":{"operationId":"update_blog","description":"Update this blog’s name, slug, description, languages (the first is the main one) or automatic translation sync. Cannot change ownership. Required scope: blog:write.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":300},"slug":{"type":"string","minLength":1,"maxLength":200,"description":"URL slug made of letters or numbers, optionally separated by a single hyphen or underscore. Unicode letters are supported; spaces and slashes are not."},"description":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]},"languages":{"minItems":1,"maxItems":10,"type":"array","items":{"type":"string"}},"autoSyncTranslations":{"type":"boolean"}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}}},"/api/v1/blogs/{blogId}/posts":{"get":{"operationId":"list_posts","description":"List posts, paginated. Includes drafts and scheduled posts; filter a calendar range with publishedFrom/publishedTo. Results are private. Required scope: content:read.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"page","in":"query","required":false,"schema":{"default":1,"type":"integer","minimum":1,"maximum":10000}},{"name":"limit","in":"query","required":false,"schema":{"default":20,"type":"integer","minimum":1,"maximum":100}},{"name":"status","in":"query","required":false,"schema":{"default":"all","type":"string","enum":["draft","scheduled","published","all"]}},{"name":"publishedFrom","in":"query","required":false,"schema":{"description":"Only posts whose publishedAt is on or after this instant","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},{"name":"publishedTo","in":"query","required":false,"schema":{"description":"Only posts whose publishedAt is before this instant","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},{"name":"language","in":"query","required":false,"schema":{"type":"string"}},{"name":"categoryId","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":128}},{"name":"tagId","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":128}},{"name":"search","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":200}}],"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}},"post":{"operationId":"create_posts","description":"Create posts. Posts default to draft; publishing or scheduling additionally requires content:publish. To schedule, set status \"scheduled\" and a future publishedAt; the post goes live automatically. Use Editor.js JSON for content. Supports every dashboard block, including tables with header rows. MCP clients: call get_editor_guide for examples. Required scope: content:write.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":300},"slug":{"type":"string","minLength":1,"maxLength":200,"description":"URL slug made of letters or numbers, optionally separated by a single hyphen or underscore. Unicode letters are supported; spaces and slashes are not."},"content":{"type":"object","properties":{"time":{"type":"number"},"version":{"type":"string","maxLength":32},"blocks":{"maxItems":1000,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","maxLength":128},"type":{"type":"string","minLength":1,"maxLength":64},"data":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"tunes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema1"}}},"required":["type","data"],"additionalProperties":false}}},"required":["blocks"],"additionalProperties":false,"description":"Editor.js JSON: {blocks:[{type,data}]}, not a Markdown string. Supports paragraph, header, list, quote, code, table, linkTool, image, raw and delimiter. Call get_editor_guide for exact examples. Tables use data.content as string[][] and data.withHeadings=true for a bold, shaded first row. When supplied to update_posts, content replaces ALL blocks: get_posts first, preserve untouched blocks and their IDs, then submit the complete content."},"language":{"default":"en","type":"string"},"status":{"default":"draft","description":"draft, published, or scheduled. A scheduled post needs a future publishedAt and goes live automatically at that time.","type":"string","enum":["draft","scheduled","published"]},"featured":{"type":"boolean"},"excerpt":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]},"coverImage":{"anyOf":[{"anyOf":[{"type":"string","const":""},{"type":"string","maxLength":2048,"format":"uri"}]},{"type":"null"}]},"authorId":{"anyOf":[{"type":"string","minLength":1,"maxLength":128},{"type":"null"}]},"publishedAt":{"description":"ISO 8601 date-time. For status scheduled, the future instant the post is published.","anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"translationGroupId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"coverTitle":{"description":"Short headline drawn on the generated thumbnail, a few words. The title is drawn when this is unset.","anyOf":[{"type":"string","maxLength":60},{"type":"null"}]},"metaTitle":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]},"metaDescription":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]},"ogImage":{"anyOf":[{"anyOf":[{"type":"string","const":""},{"type":"string","maxLength":2048,"format":"uri"}]},{"type":"null"}]},"ogTitle":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]},"ogDescription":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]},"canonicalUrl":{"anyOf":[{"anyOf":[{"type":"string","const":""},{"type":"string","maxLength":2048,"format":"uri"}]},{"type":"null"}]},"keywords":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]},"categoryIds":{"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":128}},"tagIds":{"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":128}}},"required":["title","slug","content"],"additionalProperties":false,"$defs":{"__schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema0"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}]},"__schema1":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema1"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema1"}}]}}}}}},"responses":{"201":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}}},"/api/v1/blogs/{blogId}/posts/{id}":{"get":{"operationId":"get_posts","description":"Read one posts record by its internal ID, not its slug. Required scope: content:read.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal record ID, not slug"}],"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}},"patch":{"operationId":"update_posts","description":"Update only supplied fields in posts. Editing a published or scheduled post, or changing publication state or date, additionally requires content:publish. Reschedule by updating publishedAt; unschedule by setting status \"draft\". Supplied content replaces the entire document: call get_posts first and preserve untouched blocks. MCP clients: call get_editor_guide for block formats. Required scope: content:write.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal record ID, not slug"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":300},"slug":{"type":"string","minLength":1,"maxLength":200,"description":"URL slug made of letters or numbers, optionally separated by a single hyphen or underscore. Unicode letters are supported; spaces and slashes are not."},"content":{"type":"object","properties":{"time":{"type":"number"},"version":{"type":"string","maxLength":32},"blocks":{"maxItems":1000,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","maxLength":128},"type":{"type":"string","minLength":1,"maxLength":64},"data":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"tunes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema1"}}},"required":["type","data"],"additionalProperties":false}}},"required":["blocks"],"additionalProperties":false,"description":"Editor.js JSON: {blocks:[{type,data}]}, not a Markdown string. Supports paragraph, header, list, quote, code, table, linkTool, image, raw and delimiter. Call get_editor_guide for exact examples. Tables use data.content as string[][] and data.withHeadings=true for a bold, shaded first row. When supplied to update_posts, content replaces ALL blocks: get_posts first, preserve untouched blocks and their IDs, then submit the complete content."},"language":{"type":"string"},"status":{"type":"string","enum":["draft","scheduled","published"]},"featured":{"type":"boolean"},"excerpt":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]},"coverImage":{"anyOf":[{"anyOf":[{"type":"string","const":""},{"type":"string","maxLength":2048,"format":"uri"}]},{"type":"null"}]},"authorId":{"anyOf":[{"type":"string","minLength":1,"maxLength":128},{"type":"null"}]},"publishedAt":{"description":"ISO 8601 date-time. For status scheduled, the future instant the post is published.","anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"translationGroupId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"coverTitle":{"description":"Short headline drawn on the generated thumbnail, a few words. The title is drawn when this is unset.","anyOf":[{"type":"string","maxLength":60},{"type":"null"}]},"metaTitle":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]},"metaDescription":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]},"ogImage":{"anyOf":[{"anyOf":[{"type":"string","const":""},{"type":"string","maxLength":2048,"format":"uri"}]},{"type":"null"}]},"ogTitle":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]},"ogDescription":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]},"canonicalUrl":{"anyOf":[{"anyOf":[{"type":"string","const":""},{"type":"string","maxLength":2048,"format":"uri"}]},{"type":"null"}]},"keywords":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]},"categoryIds":{"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":128}},"tagIds":{"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":128}}},"required":[],"additionalProperties":false,"$defs":{"__schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema0"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}]},"__schema1":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema1"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema1"}}]}}}}}},"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}},"delete":{"operationId":"delete_posts","description":"Permanently delete posts. Published posts additionally require content:publish. Ask the user before deleting. Required scope: content:delete.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal record ID, not slug"}],"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}}},"/api/v1/blogs/{blogId}/categories":{"get":{"operationId":"list_categories","description":"List categories, paginated. Results are private. Required scope: content:read.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"page","in":"query","required":false,"schema":{"default":1,"type":"integer","minimum":1,"maximum":10000}},{"name":"limit","in":"query","required":false,"schema":{"default":20,"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}},"post":{"operationId":"create_categories","description":"Create categories. Posts default to draft; publishing or scheduling additionally requires content:publish. To schedule, set status \"scheduled\" and a future publishedAt; the post goes live automatically. Use Editor.js JSON for content. Required scope: content:write.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":300},"slug":{"type":"string","minLength":1,"maxLength":200,"description":"URL slug made of letters or numbers, optionally separated by a single hyphen or underscore. Unicode letters are supported; spaces and slashes are not."},"description":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]}},"required":["name","slug"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}}},"/api/v1/blogs/{blogId}/categories/{id}":{"get":{"operationId":"get_categories","description":"Read one categories record by its internal ID, not its slug. Required scope: content:read.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal record ID, not slug"}],"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}},"patch":{"operationId":"update_categories","description":"Update only supplied fields in categories. Editing a published or scheduled post, or changing publication state or date, additionally requires content:publish. Reschedule by updating publishedAt; unschedule by setting status \"draft\". Required scope: content:write.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal record ID, not slug"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":300},"slug":{"type":"string","minLength":1,"maxLength":200,"description":"URL slug made of letters or numbers, optionally separated by a single hyphen or underscore. Unicode letters are supported; spaces and slashes are not."},"description":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]}},"required":[],"additionalProperties":false}}}},"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}},"delete":{"operationId":"delete_categories","description":"Permanently delete categories. Published posts additionally require content:publish. Ask the user before deleting. Required scope: content:delete.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal record ID, not slug"}],"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}}},"/api/v1/blogs/{blogId}/tags":{"get":{"operationId":"list_tags","description":"List tags, paginated. Results are private. Required scope: content:read.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"page","in":"query","required":false,"schema":{"default":1,"type":"integer","minimum":1,"maximum":10000}},{"name":"limit","in":"query","required":false,"schema":{"default":20,"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}},"post":{"operationId":"create_tags","description":"Create tags. Posts default to draft; publishing or scheduling additionally requires content:publish. To schedule, set status \"scheduled\" and a future publishedAt; the post goes live automatically. Use Editor.js JSON for content. Required scope: content:write.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":300},"slug":{"type":"string","minLength":1,"maxLength":200,"description":"URL slug made of letters or numbers, optionally separated by a single hyphen or underscore. Unicode letters are supported; spaces and slashes are not."}},"required":["name","slug"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}}},"/api/v1/blogs/{blogId}/tags/{id}":{"get":{"operationId":"get_tags","description":"Read one tags record by its internal ID, not its slug. Required scope: content:read.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal record ID, not slug"}],"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}},"patch":{"operationId":"update_tags","description":"Update only supplied fields in tags. Editing a published or scheduled post, or changing publication state or date, additionally requires content:publish. Reschedule by updating publishedAt; unschedule by setting status \"draft\". Required scope: content:write.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal record ID, not slug"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":300},"slug":{"type":"string","minLength":1,"maxLength":200,"description":"URL slug made of letters or numbers, optionally separated by a single hyphen or underscore. Unicode letters are supported; spaces and slashes are not."}},"required":[],"additionalProperties":false}}}},"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}},"delete":{"operationId":"delete_tags","description":"Permanently delete tags. Published posts additionally require content:publish. Ask the user before deleting. Required scope: content:delete.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal record ID, not slug"}],"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}}},"/api/v1/blogs/{blogId}/authors":{"get":{"operationId":"list_authors","description":"List authors, paginated. Results are private. Required scope: content:read.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"page","in":"query","required":false,"schema":{"default":1,"type":"integer","minimum":1,"maximum":10000}},{"name":"limit","in":"query","required":false,"schema":{"default":20,"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}},"post":{"operationId":"create_authors","description":"Create authors. Posts default to draft; publishing or scheduling additionally requires content:publish. To schedule, set status \"scheduled\" and a future publishedAt; the post goes live automatically. Use Editor.js JSON for content. Required scope: content:write.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":300},"slug":{"type":"string","minLength":1,"maxLength":200,"description":"URL slug made of letters or numbers, optionally separated by a single hyphen or underscore. Unicode letters are supported; spaces and slashes are not."},"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"bio":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]},"avatar":{"anyOf":[{"anyOf":[{"type":"string","const":""},{"type":"string","maxLength":2048,"format":"uri"}]},{"type":"null"}]}},"required":["name","slug","email"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}}},"/api/v1/blogs/{blogId}/authors/{id}":{"get":{"operationId":"get_authors","description":"Read one authors record by its internal ID, not its slug. Required scope: content:read.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal record ID, not slug"}],"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}},"patch":{"operationId":"update_authors","description":"Update only supplied fields in authors. Editing a published or scheduled post, or changing publication state or date, additionally requires content:publish. Reschedule by updating publishedAt; unschedule by setting status \"draft\". Required scope: content:write.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal record ID, not slug"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":300},"slug":{"type":"string","minLength":1,"maxLength":200,"description":"URL slug made of letters or numbers, optionally separated by a single hyphen or underscore. Unicode letters are supported; spaces and slashes are not."},"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"bio":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]},"avatar":{"anyOf":[{"anyOf":[{"type":"string","const":""},{"type":"string","maxLength":2048,"format":"uri"}]},{"type":"null"}]}},"required":[],"additionalProperties":false}}}},"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}},"delete":{"operationId":"delete_authors","description":"Permanently delete authors. Published posts additionally require content:publish. Ask the user before deleting. Required scope: content:delete.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal record ID, not slug"}],"responses":{"200":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}}},"/api/v1/blogs/{blogId}/posts/{id}/translations":{"post":{"operationId":"translate_posts","description":"Translate one post into one or more languages with AI, creating a linked translation per language. Translations keep the slug, status, publication date, media, author, categories and tags, so a scheduled post yields translations scheduled for the same time; translating a published or scheduled post therefore also requires content:publish. Existing languages are skipped and a failure in one language does not affect the others. Takes up to a minute per language: translate one post per call and review the result before publishing. Required scope: content:write.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal record ID, not slug"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"languages":{"minItems":1,"maxItems":10,"type":"array","items":{"type":"string"},"description":"ISO 639-1 codes to translate into, e.g. [\"en\", \"it\"]"},"paths":{"description":"Optional per-language link prefix rewrites for the translated content, so links point to localized pages. Example: {\"en\": {\"/es/productos\": \"/en/products\", \"/es/blog/\": \"/en/blog/\"}}. The longest matching prefix wins; other links stay unchanged.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string","maxLength":300,"pattern":"^\\/"},"additionalProperties":{"type":"string","maxLength":2048}}}},"required":["languages"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}}},"/api/v1/blogs/{blogId}/posts/{id}/images":{"post":{"operationId":"add_post_images","description":"Attach an uploaded cover and/or multiple inline images in one atomic operation. Inline images are inserted after their current Editor.js block IDs (or appended) without resending or replacing article text. Retrying the same URLs does not duplicate them. This is the safe operation for image-only build/API updates. Required scope: content:write.","security":[{"bearerAuth":[]}],"parameters":[{"name":"blogId","in":"path","required":true,"schema":{"type":"string"},"description":"Public Blog ID (UUID), not a key"},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal record ID, not slug"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"coverImage":{"anyOf":[{"anyOf":[{"type":"string","const":""},{"type":"string","maxLength":2048,"format":"uri"}]},{"type":"null"}]},"ogImage":{"anyOf":[{"anyOf":[{"type":"string","const":""},{"type":"string","maxLength":2048,"format":"uri"}]},{"type":"null"}]},"images":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"url":{"type":"string","maxLength":2048,"format":"uri"},"alt":{"default":"","type":"string","maxLength":240},"caption":{"default":"","type":"string","maxLength":300},"afterBlockId":{"type":"string","minLength":1,"maxLength":128}},"required":["url"],"additionalProperties":false}}},"required":[],"additionalProperties":false}}}},"responses":{"201":{"description":"Success: data (and pagination on lists)"},"400":{"description":"Invalid input"},"401":{"description":"Invalid, missing, expired or revoked API key"},"403":{"description":"Insufficient scope or wrong blog/origin"},"404":{"description":"Record not found"},"409":{"description":"Duplicate slug/email"},"413":{"description":"Body too large"},"415":{"description":"Expected JSON"},"429":{"description":"Rate limit exceeded; Retry-After: 60"},"500":{"description":"Internal server error"},"503":{"description":"Media storage not configured"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Zenex API key"}}}}