{"openapi":"3.0.3","info":{"title":"Geppetto Site API","version":"1.0.0","description":"The Geppetto Site API lets a site creator — or an agent acting on their behalf —\nread and modify an **existing site**: pages, blocks, layouts, partials, post\ntypes, folders, and media, plus snapshots and preview builds.\n\n## Base URL\n\nRequests have **no `/api` prefix**. Use the server URL shown in the selector\nabove (your production host, or `http://localhost:3001` in development).\n\n## Authentication\n\nEvery request requires a **site API token** sent as a bearer token:\n\n```\nAuthorization: Bearer mst_...\n```\n\n- Create a token from your **site settings → API tokens**.\n- Each token is **scoped to one site** — a request for a different site\n  returns `403`.\n- Tokens are shown once at creation and stored only as a hash — keep them\n  secret.\n\n## Errors\n\nFailures return a consistent JSON envelope:\n\n```json\n{ \"success\": false, \"message\": \"Resource not found\", \"code\": \"NOT_FOUND\" }\n```\n\nCommon codes: `VALIDATION_ERROR` (400), `UNAUTHORIZED` (401),\n`FORBIDDEN` (403), `NOT_FOUND` (404), `STALE_VERSION` (409),\n`RATE_LIMITED` (429).\n\n## Conventions\n\n- **Content type:** request and response bodies are `application/json`\n  (except media upload, which is `multipart/form-data`).\n- **Identifiers:** `_id` fields are 24-character hex MongoDB ObjectIds.\n- **Timestamps:** ISO 8601 (e.g. `2026-06-18T10:30:00Z`).\n- **Listing & paging:** list endpoints take `site_id` (or `siteId`) plus\n  optional filters; `activity-log` accepts a `limit` (max 200).\n- **Optimistic concurrency:** `PATCH /pages/{id}` accepts the `__v` you\n  received from a prior `GET`; a mismatch returns `409 STALE_VERSION` so you\n  don't clobber a concurrent edit.\n- **Rate limiting:** a global per-IP limit applies; a `429` response means\n  back off and retry.\n\n## Not available via API token\n\nProduction deploys (`POST /static-site/deploy`) cannot be triggered with an API\ntoken — publish from the dashboard. Preview builds are available (see **Deploy**)."},"servers":[{"url":"http://localhost:3001","description":"Local development"}],"tags":[{"name":"Sites","description":"Site metadata, theme, and AI-token allotments."},{"name":"Pages","description":"Pages and posts: content, settings, and OG images."},{"name":"Blocks","description":"Reusable content blocks (template + fields)."},{"name":"Layouts","description":"Page layouts (header/footer block composition)."},{"name":"Partials","description":"Reusable Handlebars partials."},{"name":"Post Types","description":"Blog-style collections of posts."},{"name":"Folders","description":"Page-tree organization folders."},{"name":"Files","description":"Media library: upload, list, and manage assets."},{"name":"Snapshots","description":"Point-in-time site backups and restore."},{"name":"Activity Log","description":"Audit trail of changes (used for conflict detection)."},{"name":"Deploy","description":"Preview builds and deployment status. Production deploys are not available via API token."}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Site API token, prefixed `mst_`. Each token is scoped to a single site and carries a fixed role (Owner/Developer/Publisher/Contributor)."}},"schemas":{"ErrorResponse":{"type":"object","description":"Standard error envelope returned by every endpoint on failure.","required":["success","message"],"properties":{"success":{"type":"boolean","enum":[false]},"message":{"type":"string","description":"Human-readable error message."},"code":{"type":"string","description":"Machine-readable error code (e.g. NOT_FOUND, VALIDATION_ERROR, FORBIDDEN).","example":"NOT_FOUND"},"details":{"description":"Optional structured error detail (shape varies)."}},"example":{"success":false,"message":"Resource not found","code":"NOT_FOUND"}},"Field":{"type":"object","description":"A content field on a block. `items`/`group` fields nest child fields under `config.fields`.","properties":{"id":{"type":"string","description":"Stable field id (UUID)."},"type":{"type":"string","enum":["text","textarea","select","image","video","items","group","number","date","wysiwyg"]},"name":{"type":"string"},"value":{"type":"string","description":"Default/seed value for the field."},"placeholderType":{"type":"string","nullable":true,"enum":["avatar","gradient"],"description":"For image fields: which seed placeholder fills the field when empty."},"config":{"type":"object","additionalProperties":true,"description":"Field-type-specific config. For `items`/`group`, holds a nested `fields` array."}}},"PageBlock":{"type":"object","description":"An instance of a block placed on a page, with its content values.","properties":{"id":{"type":"string","description":"Stable instance id (UUID)."},"blockId":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"The block definition this instance renders.","example":"507f1f77bcf86cd799439011"},"name":{"type":"string"},"content":{"type":"object","additionalProperties":{"type":"object","properties":{"value":{"type":"string"}}},"description":"Field values keyed by field id: `{ \"<fieldId>\": { \"value\": \"...\" } }`."}}},"Theme":{"type":"object","additionalProperties":true,"description":"Site theme tokens (fonts, colors, typography, spacing, cards, buttons, prose). A large nested object; PATCH /sites/{id} merges it at the top level (only provided keys change). Top-level keys include: fonts, systemColors, customColors, headingTypography, bodyTypography, buttons, prose, spacing, cards, images, customCSS."},"Page":{"type":"object","properties":{"_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"name":{"type":"string"},"site_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"parentId":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"Parent page/folder, if nested.","example":"507f1f77bcf86cd799439011","nullable":true},"postTypeId":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"Set when this page is a post of a post type.","example":"507f1f77bcf86cd799439011","nullable":true},"slug":{"type":"string"},"blocks":{"type":"array","items":{"$ref":"#/components/schemas/PageBlock"}},"content":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{"type":"object","properties":{"value":{"type":"string"}}}},"description":"Content keyed by block name then field name."},"settings":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"layoutId":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011","nullable":true},"allowRobots":{"type":"boolean"},"ogImage":{"type":"string"}}},"updatedAt":{"type":"string","format":"date-time"},"__v":{"type":"integer","description":"Document version. Pass this as `__v` in PATCH /pages/{id} for optimistic-concurrency (stale-save) protection — a mismatch returns 409 STALE_VERSION."}}},"Block":{"type":"object","properties":{"_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"name":{"type":"string"},"site_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"template":{"type":"string","description":"Handlebars template markup."},"fields":{"type":"array","items":{"$ref":"#/components/schemas/Field"}},"category":{"type":"string","enum":["section","header","footer"],"nullable":true},"blockCategory":{"type":"string","nullable":true,"description":"Library category for the Add Block panel."},"thumbnailType":{"type":"string"},"aiDescription":{"type":"string","nullable":true},"tags":{"type":"array","items":{"type":"string"}},"contentCapacity":{"type":"object","nullable":true,"properties":{"idealItemsMin":{"type":"integer","nullable":true},"idealItemsMax":{"type":"integer","nullable":true}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Layout":{"type":"object","properties":{"_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"name":{"type":"string"},"site_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"headerBlocks":{"type":"array","items":{"$ref":"#/components/schemas/PageBlock"}},"footerBlocks":{"type":"array","items":{"$ref":"#/components/schemas/PageBlock"}},"padding":{"type":"object","properties":{"top":{"type":"number"},"bottom":{"type":"number"},"left":{"type":"number"},"right":{"type":"number"}}},"paddingBreakpoint":{"type":"number"},"customCSS":{"type":"string"},"isDefault":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Partial":{"type":"object","properties":{"_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"name":{"type":"string"},"site_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"template":{"type":"string","description":"Handlebars partial markup."}}},"PostType":{"type":"object","properties":{"_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"name":{"type":"string"},"site_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"detailPageId":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"Template page rendered for each post.","example":"507f1f77bcf86cd799439011"},"indexPageId":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"Index/listing page for the post type.","example":"507f1f77bcf86cd799439011"},"postIds":{"type":"array","items":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"description":"Post page ids belonging to this type."}}},"Folder":{"type":"object","properties":{"_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"name":{"type":"string"},"site_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"parentId":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"Parent folder for nesting.","example":"507f1f77bcf86cd799439011","nullable":true}}},"File":{"type":"object","description":"A media-library asset stored in R2.","properties":{"_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"site_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"fileKey":{"type":"string","description":"R2 object key, \"{siteId}/{fileName}\"."},"fileName":{"type":"string","description":"Display name."},"fullPath":{"type":"string","description":"Full CDN URL."},"mimeType":{"type":"string","example":"image/webp"},"size":{"type":"integer","description":"Bytes."},"tags":{"type":"array","items":{"type":"string"}},"resolution":{"type":"object","properties":{"width":{"type":"integer"},"height":{"type":"integer"}}},"uploadedAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Snapshot":{"type":"object","description":"A point-in-time site backup (list view; full data via the export endpoint).","properties":{"_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"siteId":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"label":{"type":"string"},"createdBy":{"type":"object","properties":{"userId":{"type":"string"},"name":{"type":"string"}}},"sizeBytes":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}}},"ActivityLogEntry":{"type":"object","description":"An audit-trail entry. Query with `since` to detect changes before pushing edits.","properties":{"_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"siteId":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"action":{"type":"string","enum":["create","update","delete","duplicate","deploy","restore","import"]},"entityType":{"type":"string","enum":["block","page","partial","layout","postType","post","theme","site","deploy","snapshot","dataStore","apiToken"]},"entityId":{"type":"string","nullable":true},"entityName":{"type":"string","nullable":true},"actor":{"type":"object","properties":{"type":{"type":"string","enum":["user","api_token"]},"id":{"type":"string"},"name":{"type":"string","nullable":true}}},"meta":{"nullable":true,"description":"Action-specific metadata (shape varies)."},"createdAt":{"type":"string","format":"date-time"}}},"Site":{"type":"object","description":"A site document. Secret-bearing fields (API token hashes, Turnstile/Stripe secrets) are masked by the API and omitted here.","properties":{"_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"ownerId":{"type":"string","description":"Clerk user id of the owner."},"name":{"type":"string"},"theme":{"$ref":"#/components/schemas/Theme"},"blocks":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"name":{"type":"string"}}},"description":"Denormalized block summaries."},"partials":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"name":{"type":"string"}}}},"pages":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"name":{"type":"string"}}}},"layouts":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"MongoDB ObjectId (24-character hex).","example":"507f1f77bcf86cd799439011"},"name":{"type":"string"}}}},"postTypes":{"type":"array","items":{"$ref":"#/components/schemas/PostType"}},"users":{"type":"array","items":{"type":"object","properties":{"userId":{"type":"string"},"authLevel":{"type":"integer","enum":[0,1,2,3],"description":"0=Owner, 1=Developer, 2=Publisher, 3=Contributor."},"email":{"type":"string","nullable":true},"name":{"type":"string"}}}},"customCode":{"type":"object","properties":{"head":{"type":"string","nullable":true},"body":{"type":"string","nullable":true}}},"subscription":{"type":"object","properties":{"tier":{"type":"string","enum":["free","starter","standard"]},"status":{"type":"string","enum":["active","past_due"]}}},"staticSite":{"type":"object","additionalProperties":true,"description":"Deployment config + status (enabled, domain, deploymentStatus, lastDeployed, ...)."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}},"paths":{"/pages":{"get":{"tags":["Pages"],"summary":"List pages and posts","parameters":[{"name":"site_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"parentId","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"search","in":"query","required":false,"schema":{"type":"string","maxLength":100}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Page"}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["Pages"],"summary":"Create a page or post","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"site_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"},"parentId":{"type":"string","nullable":true},"postTypeId":{"type":"string","nullable":true},"settings":{"type":"object","properties":{"title":{"type":"string","maxLength":200,"nullable":true},"description":{"type":"string","maxLength":500,"nullable":true},"layoutId":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","nullable":true},"allowRobots":{"type":"boolean"},"ogImage":{"type":"string","nullable":true}},"additionalProperties":false}},"required":["name","site_id"],"additionalProperties":false},"example":{"name":"About","site_id":"507f1f77bcf86cd799439011","settings":{"title":"About Us"}}}}},"responses":{"201":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/pages/{id}":{"get":{"tags":["Pages"],"summary":"Get a page or post","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["Pages"],"summary":"Update a page or post","description":"Pass `__v` (from a prior GET) for optimistic-concurrency; a stale value returns 409 STALE_VERSION.","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"content":{"type":"object","properties":{},"additionalProperties":false},"settings":{"type":"object","properties":{"title":{"type":"string","maxLength":200,"nullable":true},"description":{"type":"string","maxLength":500,"nullable":true},"layoutId":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","nullable":true},"allowRobots":{"type":"boolean"},"ogImage":{"type":"string","nullable":true}},"additionalProperties":false},"blocks":{"type":"array","items":{}},"parentId":{"type":"string","nullable":true},"postTypeId":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","nullable":true},"__v":{"type":"integer","minimum":0}},"additionalProperties":false},"example":{"blocks":[{"id":"7c1b…","blockId":"507f1f77bcf86cd799439022","name":"Hero","content":{"f1":{"value":"About Us"}}}]}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Pages"],"summary":"Delete a page or post","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/pages/{id}/set-content":{"patch":{"tags":["Pages"],"summary":"Set page content by block + field name","description":"Bulk content setter keyed by block name then field name (not field id). Simple fields take scalar values; `items` fields take an array of flat objects and `group` fields a flat object, with sub-fields keyed by name or slug. Item entries get an `id` generated when omitted. Page block order is always preserved; within a targeted block, provided fields merge over existing content (send null to clear a field); blocks not yet on the page are appended at the end.","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{},"additionalProperties":{"anyOf":[{"anyOf":[{"type":"string","maxLength":100000},{"type":"number","format":"float"},{"type":"boolean"},{"nullable":true}]},{"type":"array","items":{"type":"object","properties":{},"additionalProperties":{"anyOf":[{"type":"string","maxLength":100000},{"type":"number","format":"float"},{"type":"boolean"},{"nullable":true}]}},"maxItems":200},{"type":"object","properties":{},"additionalProperties":{"anyOf":[{"type":"string","maxLength":100000},{"type":"number","format":"float"},{"type":"boolean"},{"nullable":true}]}}]}}},"example":{"Hero":{"Headline":"About Us","Subhead":"Who we are"},"Features":{"Items":[{"Title":"Fast","Description":"Ships in seconds"},{"Title":"Safe","Description":"Snapshots built in"}]}}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/pages/{id}/duplicate":{"post":{"tags":["Pages"],"summary":"Duplicate a page","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100}},"required":["name"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/pages/{id}/og-image":{"patch":{"tags":["Pages"],"summary":"Upload page OG image","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["ogImage"],"properties":{"ogImage":{"type":"string","format":"binary","description":"Image file for the page OG/social preview."}}}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"ogImage":{"type":"string","description":"Stored OG image filename/URL."}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Pages"],"summary":"Remove page OG image","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/blocks":{"get":{"tags":["Blocks"],"summary":"List blocks for a site","parameters":[{"name":"site_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"parentId","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"search","in":"query","required":false,"schema":{"type":"string","maxLength":100}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Block"}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["Blocks"],"summary":"Create a block","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"thumbnailType":{"type":"string","maxLength":50},"category":{"type":"string","enum":["section","header","footer"]},"blockCategory":{"type":"string","enum":["bento","cards","contact","content","cta","ecommerce","faq","features","forms","gallery","hero","logos","posts","pricing","stats","team","testimonials","tools"],"nullable":true},"site_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"},"template":{"type":"string","maxLength":200000},"fields":{"type":"array","items":{}},"aiDescription":{"type":"string","maxLength":500},"tags":{"type":"array","items":{"type":"string","pattern":"^[a-zA-Z0-9-]+$","minLength":1,"maxLength":50}},"contentCapacity":{"type":"object","properties":{"idealItemsMin":{"type":"integer","minimum":1,"maximum":50,"nullable":true},"idealItemsMax":{"type":"integer","minimum":1,"maximum":50,"nullable":true}},"additionalProperties":false}},"required":["name","site_id"],"additionalProperties":false},"example":{"name":"Hero","site_id":"507f1f77bcf86cd799439011","template":"<section class=\"hero\">{{headline}}</section>","fields":[{"id":"a1","type":"text","name":"Headline","value":"Welcome","config":{}}],"category":"section"}}}},"responses":{"201":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Block"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/blocks/{id}":{"get":{"tags":["Blocks"],"summary":"Get block(s) by id","description":"The `id` may be a comma-separated list; the response is always an array.","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Block"}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["Blocks"],"summary":"Update a block","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"thumbnailType":{"type":"string","maxLength":50},"category":{"type":"string","enum":["section","header","footer"]},"blockCategory":{"type":"string","enum":["bento","cards","contact","content","cta","ecommerce","faq","features","forms","gallery","hero","logos","posts","pricing","stats","team","testimonials","tools"],"nullable":true},"template":{"type":"string","maxLength":200000},"fields":{"type":"array","items":{}},"aiDescription":{"type":"string","maxLength":500},"tags":{"type":"array","items":{"type":"string","pattern":"^[a-zA-Z0-9-]+$","minLength":1,"maxLength":50}},"library_folder_id":{"type":"string","nullable":true},"contentCapacity":{"type":"object","properties":{"idealItemsMin":{"type":"integer","minimum":1,"maximum":50,"nullable":true},"idealItemsMax":{"type":"integer","minimum":1,"maximum":50,"nullable":true}},"additionalProperties":false},"__v":{"type":"integer","minimum":0}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Block"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Blocks"],"summary":"Delete a block","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}},{"name":"force","in":"query","required":false,"description":"Delete even if the block is in use.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/blocks/public":{"get":{"tags":["Blocks"],"summary":"List the public seed block library","description":"Read-only starter blocks Geppetto ships (header/footer excluded — new sites already include those). Import one with POST /blocks/{id}/import-to-site.","responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Block"}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/blocks/{id}/import-to-site":{"post":{"tags":["Blocks"],"summary":"Import a seed library block into the site","description":"Copies a public seed block (from GET /blocks/public) into the site under the given name. Field ids are preserved. Fails with 409 if a block with that name already exists on the site.","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"site_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"},"name":{"type":"string","minLength":1,"maxLength":100}},"required":["site_id","name"],"additionalProperties":false},"example":{"site_id":"507f1f77bcf86cd799439011","name":"Hero Split"}}}},"responses":{"201":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Block"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/blocks/{id}/duplicate":{"post":{"tags":["Blocks"],"summary":"Duplicate a block","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100}},"required":["name"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Block"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/layouts":{"get":{"tags":["Layouts"],"summary":"List layouts for a site","parameters":[{"name":"site_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"parentId","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"search","in":"query","required":false,"schema":{"type":"string","maxLength":100}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Layout"}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["Layouts"],"summary":"Create a layout","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"site_id":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"},"headerBlocks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$"},"blockId":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$"},"name":{"type":"string","minLength":1,"maxLength":100},"content":{"type":"object","properties":{},"additionalProperties":false,"default":{}}},"required":["id","blockId","name"],"additionalProperties":false},"default":[]},"footerBlocks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$"},"blockId":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$"},"name":{"type":"string","minLength":1,"maxLength":100},"content":{"type":"object","properties":{},"additionalProperties":false,"default":{}}},"required":["id","blockId","name"],"additionalProperties":false},"default":[]},"padding":{"type":"object","properties":{"top":{"type":"number","format":"float","minimum":0},"bottom":{"type":"number","format":"float","minimum":0},"left":{"type":"number","format":"float","minimum":0},"right":{"type":"number","format":"float","minimum":0}},"additionalProperties":false,"default":{"top":0,"bottom":0,"left":0,"right":0}},"paddingBreakpoint":{"type":"number","format":"float","minimum":0,"default":1024},"customCSS":{"type":"string"},"isDefault":{"type":"boolean","default":false}},"required":["name","site_id"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Layout"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/layouts/{id}":{"get":{"tags":["Layouts"],"summary":"Get a layout","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Layout"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["Layouts"],"summary":"Update a layout","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"headerBlocks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$"},"blockId":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$"},"name":{"type":"string","minLength":1,"maxLength":100},"content":{"type":"object","properties":{},"additionalProperties":false,"default":{}}},"required":["id","blockId","name"],"additionalProperties":false}},"footerBlocks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$"},"blockId":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$"},"name":{"type":"string","minLength":1,"maxLength":100},"content":{"type":"object","properties":{},"additionalProperties":false,"default":{}}},"required":["id","blockId","name"],"additionalProperties":false}},"padding":{"type":"object","properties":{"top":{"type":"number","format":"float","minimum":0},"bottom":{"type":"number","format":"float","minimum":0},"left":{"type":"number","format":"float","minimum":0},"right":{"type":"number","format":"float","minimum":0}},"additionalProperties":false},"paddingBreakpoint":{"type":"number","format":"float","minimum":0},"customCSS":{"type":"string"},"isDefault":{"type":"boolean"}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Layout"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Layouts"],"summary":"Delete a layout","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"204":{"description":"Deleted (no content)."},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/layouts/{id}/duplicate":{"post":{"tags":["Layouts"],"summary":"Duplicate a layout","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100}},"required":["name"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Layout"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/partials/site/{siteId}":{"get":{"tags":["Partials"],"summary":"List partials for a site","parameters":[{"name":"siteId","in":"path","required":true,"description":"siteId (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"partials":{"type":"array","items":{"$ref":"#/components/schemas/Partial"}},"templateObject":{"type":"object","additionalProperties":{"type":"string"},"description":"Map of partial name → template."}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/partials":{"post":{"tags":["Partials"],"summary":"Create a partial","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"site_id":{"type":"string"},"template":{"type":"string"}},"required":["name","site_id"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Partial"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/partials/{id}":{"patch":{"tags":["Partials"],"summary":"Update a partial","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"template":{"type":"string"}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Partial"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Partials"],"summary":"Delete a partial","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/partials/{id}/duplicate":{"post":{"tags":["Partials"],"summary":"Duplicate a partial","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100}},"required":["name"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Partial"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/postTypes":{"get":{"tags":["Post Types"],"summary":"List post types","parameters":[{"name":"site_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"parentId","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"search","in":"query","required":false,"schema":{"type":"string","maxLength":100}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PostType"}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["Post Types"],"summary":"Create a post type","description":"Creates the detail template, index page, and first post page. Requires a paid tier.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"site_id":{"type":"string"},"detailPageId":{"type":"string"},"indexPageId":{"type":"string"}},"required":["name","site_id"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostType"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/postTypes/{id}/delete-impact":{"get":{"tags":["Post Types"],"summary":"Preview deletion impact","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"postType":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"}}},"detailPageId":{"type":"string","nullable":true},"indexPageId":{"type":"string","nullable":true},"postCount":{"type":"integer"},"posts":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"}}}},"truncated":{"type":"boolean","description":"True if more than 50 posts exist (list is capped)."}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/postTypes/{id}":{"patch":{"tags":["Post Types"],"summary":"Update a post type","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"detailPageId":{"type":"string"},"indexPageId":{"type":"string"},"postIds":{"type":"array","items":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostType"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Post Types"],"summary":"Delete a post type","description":"Deletes the post type plus all its posts and template pages.","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/postTypes/{id}/posts":{"get":{"tags":["Post Types"],"summary":"Get posts data","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}},{"name":"site_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"parentId","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"search","in":"query","required":false,"schema":{"type":"string","maxLength":100}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","description":"Aggregated posts data for the post type.","properties":{"postTypeId":{"type":"string"},"postTypeName":{"type":"string"},"totalCount":{"type":"integer"},"lastUpdated":{"type":"string"},"articles":{"type":"array","items":{"type":"object","additionalProperties":true,"description":"Post metadata (id, title, slug, url, publishedDate, + custom fields)."}}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/folders":{"get":{"tags":["Folders"],"summary":"List folders","parameters":[{"name":"site_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"parentId","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"search","in":"query","required":false,"schema":{"type":"string","maxLength":100}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Folder"}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["Folders"],"summary":"Create a folder","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"site_id":{"type":"string"},"parentId":{"type":"string","nullable":true}},"required":["name","site_id"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Folder"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/folders/{id}":{"patch":{"tags":["Folders"],"summary":"Rename or move a folder","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"parentId":{"type":"string","nullable":true}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Folder"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Folders"],"summary":"Delete a folder","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}},{"name":"cascade","in":"query","required":false,"description":"Also delete child pages.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/files/generate-upload-url":{"post":{"tags":["Files"],"summary":"Get a signed upload URL","description":"Returns a pre-signed PUT URL and the storage key for the upload. Keys are unique per upload (a short random suffix is appended to the filename), so always use the returned fileKey — do not derive it from the filename.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"fileName":{"type":"string","pattern":"^[a-zA-Z0-9._-]+$","minLength":1,"maxLength":255},"fileType":{"type":"string","enum":["image/jpeg","image/png","image/gif","image/webp","image/svg+xml","application/pdf","text/plain","video/mp4","font/woff2","font/woff","font/ttf","font/otf"]},"size":{"type":"integer","minimum":0},"siteId":{"type":"string"}},"required":["fileName","fileType","size","siteId"],"additionalProperties":false}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"Pre-signed R2 PUT URL. Upload the file to this URL, then call /files/register."},"fileKey":{"type":"string","description":"Storage key assigned to this upload. Pass this exact value to /files/register."},"cacheControl":{"type":"string","description":"Send this as the Cache-Control header on the PUT — it is part of the signed request."}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/files/register":{"post":{"tags":["Files"],"summary":"Register an uploaded file","description":"Creates the media library record after a successful PUT. fileKey must be the value returned by /files/generate-upload-url.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"siteId":{"type":"string","pattern":"^[a-fA-F0-9]{24}$"},"fileKey":{"type":"string","pattern":"^[a-zA-Z0-9._/-]+$","minLength":1,"maxLength":500},"fileName":{"type":"string","minLength":1,"maxLength":255},"fullPath":{"type":"string","maxLength":1000},"mimeType":{"type":"string","enum":["image/jpeg","image/png","image/gif","image/webp","image/svg+xml","video/mp4","font/woff2","font/woff","font/ttf","font/otf"]},"size":{"type":"integer","minimum":0},"resolution":{"type":"object","properties":{"width":{"type":"integer","minimum":0},"height":{"type":"integer","minimum":0}},"required":["width","height"],"additionalProperties":false},"fileType":{"type":"string","enum":["image","video","font"],"default":"image"},"duration":{"type":"number","format":"float","minimum":0}},"required":["siteId","fileKey","fileName","mimeType","size"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/File"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/files/upload-from-urls":{"post":{"tags":["Files"],"summary":"Upload images from URLs","description":"Server fetches each URL, resizes (max 3000×3000), and converts to WebP. Max 20 per request.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"siteId":{"type":"string","pattern":"^[a-fA-F0-9]{24}$"},"images":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"fileName":{"type":"string","maxLength":255}},"required":["url"],"additionalProperties":false},"minItems":1,"maxItems":20}},"required":["siteId","images"],"additionalProperties":false}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"success":{"type":"boolean"},"fullPath":{"type":"string"},"error":{"type":"string"}}}}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/files/tags":{"get":{"tags":["Files"],"summary":"List media tags","parameters":[{"name":"siteId","in":"query","required":true,"description":"Site id.","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/files/usage":{"get":{"tags":["Files"],"summary":"List in-use media URLs","parameters":[{"name":"siteId","in":"query","required":true,"description":"Site id.","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"usedUrls":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/files":{"get":{"tags":["Files"],"summary":"List media files","parameters":[{"name":"folder","in":"query","required":true,"schema":{"type":"string","maxLength":200}},{"name":"fileType","in":"query","required":false,"schema":{"type":"string","enum":["image","document","video"]}},{"name":"search","in":"query","required":false,"schema":{"type":"string","maxLength":100}},{"name":"tags","in":"query","required":false,"schema":{"type":"string","maxLength":500}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"files":{"type":"array","items":{"$ref":"#/components/schemas/File"}}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Files"],"summary":"Delete a media file","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"fileKey":{"type":"string","pattern":"^[a-zA-Z0-9._/-]+$","minLength":1,"maxLength":500}},"required":["fileKey"],"additionalProperties":false}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/snapshots":{"get":{"tags":["Snapshots"],"summary":"List snapshots","description":"Returns the 10 most recent snapshots. Requires a paid tier.","parameters":[{"name":"site_id","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Snapshot"}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["Snapshots"],"summary":"Create a snapshot","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"siteId":{"type":"string"},"label":{"type":"string","maxLength":200}},"required":["siteId"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Snapshot"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/snapshots/{id}/export":{"get":{"tags":["Snapshots"],"summary":"Export a snapshot","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Snapshot"},{"type":"object","properties":{"data":{"type":"object","additionalProperties":true,"description":"Full serialized site content (pages, blocks, partials, layouts, postTypes, folders, theme)."}}}]}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/snapshots/import":{"post":{"tags":["Snapshots"],"summary":"Import a snapshot into a site","description":"Cross-environment restore: wipes the target site and re-creates content with fresh ids.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"siteId":{"type":"string"},"label":{"type":"string","maxLength":200},"data":{"type":"object","properties":{"version":{"type":"number","format":"float","enum":[1]},"site":{"type":"object","properties":{}},"pages":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string","nullable":true},"name":{"type":"string"},"parentId":{"type":"string","nullable":true},"postTypeId":{"type":"string","nullable":true},"blocks":{"type":"array","items":{},"maxItems":500},"content":{},"settings":{"type":"object","properties":{}}},"required":["name"]},"maxItems":500,"default":[]},"blocks":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string","nullable":true},"name":{"type":"string"},"template":{"type":"string"}},"required":["name","template"]},"maxItems":500,"default":[]},"partials":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string","nullable":true},"name":{"type":"string"},"template":{"type":"string"}},"required":["name","template"]},"maxItems":200,"default":[]},"layouts":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string","nullable":true},"name":{"type":"string"}},"required":["name"]},"maxItems":50,"default":[]},"postTypes":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string","nullable":true},"name":{"type":"string"},"detailPageId":{"type":"string","nullable":true},"indexPageId":{"type":"string","nullable":true},"postIds":{"type":"array","items":{"type":"string"},"maxItems":2000}},"required":["name"]},"maxItems":50,"default":[]},"folders":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string","nullable":true},"name":{"type":"string"},"parentId":{"type":"string","nullable":true}},"required":["name"]},"maxItems":200,"default":[]},"capturedAt":{}},"required":["version"]}},"required":["siteId","data"],"additionalProperties":false}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/snapshots/{id}/restore":{"post":{"tags":["Snapshots"],"summary":"Restore a snapshot","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/activity-log":{"get":{"tags":["Activity Log"],"summary":"List activity-log entries","description":"Newest first. Use `since` (ISO 8601) to detect changes before pushing edits.","parameters":[{"name":"site_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"entityType","in":"query","required":false,"schema":{"type":"string","enum":["block","page","partial","layout","postType","post","theme","site","deploy","snapshot","dataStore"]}},{"name":"action","in":"query","required":false,"schema":{"type":"string","enum":["create","update","delete","duplicate","deploy","restore","import"]}},{"name":"since","in":"query","required":false,"schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ActivityLogEntry"}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/sites/{id}":{"get":{"tags":["Sites"],"summary":"Get a site","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Site"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["Sites"],"summary":"Update site name, theme, or settings","description":"Theme is merged at the top level — only the keys you send change.","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"template":{"type":"string"},"theme":{"type":"object","properties":{"customColors":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"value":{"type":"string"}},"required":["id","name","value"]}}}},"dataStores":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string","minLength":1,"maxLength":100},"slug":{"type":"string","minLength":1,"maxLength":100},"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string","maxLength":200},"value":{"type":"string","maxLength":200}},"required":["id","key","value"],"additionalProperties":false},"default":[]}},"required":["name","slug"],"additionalProperties":false}},"redirects":{"type":"array","items":{"type":"object","properties":{"from":{"type":"string","maxLength":500},"to":{"type":"string","maxLength":500},"status":{"type":"number","format":"float","enum":[301,302],"default":301}},"required":["from","to"],"additionalProperties":false}},"customCode":{"type":"object","properties":{"head":{"type":"string","maxLength":100000,"nullable":true},"body":{"type":"string","maxLength":100000,"nullable":true}},"additionalProperties":false},"form_notification_email":{"type":"string","maxLength":500,"nullable":true},"forms_retention_days":{"type":"integer","minimum":1,"maximum":3650,"nullable":true},"ecommerce":{"type":"object","properties":{"stripeSecretKey":{"type":"string","nullable":true},"stripePublishableKey":{"type":"string","nullable":true},"successUrl":{"type":"string","maxLength":500},"cancelUrl":{"type":"string","maxLength":500},"currency":{"type":"string","maxLength":3},"enabled":{"type":"boolean"}},"additionalProperties":false}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Site"}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/sites/{id}/allotments":{"get":{"tags":["Sites"],"summary":"Get AI-token allotments","parameters":[{"name":"id","in":"path","required":true,"description":"id (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"periodStart":{"type":"string","format":"date-time"},"periodEnd":{"type":"string","format":"date-time"},"used":{"type":"object","additionalProperties":{"type":"integer"},"description":"Per-action usage counts for the current period."}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/static-site/preview/{siteId}":{"post":{"tags":["Deploy"],"summary":"Build a preview","parameters":[{"name":"siteId","in":"path","required":true,"description":"siteId (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"previewUrl":{"type":"string"},"failedPosts":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/static-site/preview-status/{siteId}":{"get":{"tags":["Deploy"],"summary":"Get preview status","parameters":[{"name":"siteId","in":"path","required":true,"description":"siteId (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"isStale":{"type":"boolean","description":"True if the site changed since the last preview build."},"lastPreviewed":{"type":"string","format":"date-time","nullable":true},"previewUrl":{"type":"string","nullable":true}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/static-site/status/{siteId}":{"get":{"tags":["Deploy"],"summary":"Get deployment status","parameters":[{"name":"siteId","in":"path","required":true,"description":"siteId (ObjectId).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"status":{"type":"string","enum":["not_deployed","deploying","deployed","failed"]},"lastDeployed":{"type":"string","format":"date-time","nullable":true},"domain":{"type":"string","nullable":true},"customDomain":{"type":"string","nullable":true}}}}}},"400":{"description":"Validation error or malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token is not permitted for this action, or is scoped to a different site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}