{"openapi":"3.1.0","info":{"title":"PageHub API","version":"1.0.0","description":"The public REST API of PageHub, called with a `ph_` API key (`Authorization: Bearer ph_...`), plus the one webhook the platform calls on a white-label tenant's server (`fetchPage`).\n\nA site is HTML + Liquid files: its pages are files (`pages/*.liquid`), written through the files API; the page routes manage each page's record (name, slug, SEO, access). Writes land in the site's draft; `PUT /api/v1/sites/{id}` with `published: true` makes the draft live.\n\nThe headless content API (`/api/v1/content/...`) is described by its own document (`GET /api/v1/content/openapi.json`); `GET /api/openapi.json` serves this document with it merged in.\n\nThe routes also accept the dashboard's session cookie from the dashboard itself; only the API key is the public contract.\n\nEvery operation carries two extension fields. `x-auth` says who may call it: `api-key` (a `ph_` key), `session-or-key` (the dashboard's own session, or a key), `optional-key` (public, a key widens what you see), `admin-key` (a platform admin's key), `session` (the dashboard session only; an API key is refused) or `public`. `x-rate-limit` names the rate-limit rule (see the rate-limiting guide) when the route has one; most routes limit per user, public routes per IP.","contact":{"name":"PageHub","url":"https://pagehub.dev/developers"}},"servers":[{"url":"https://pagehub.dev","description":"PageHub"}],"tags":[{"name":"Sites","description":"Create, read, update, publish and delete sites."},{"name":"Files","description":"A site's draft files (pages, partials, `theme.css`, `site.json`, ...)."},{"name":"Pages","description":"The page records of a site: name, slug, URL pattern, SEO, access policy."},{"name":"Components","description":"Reusable components (partials) of a site."},{"name":"Collections","description":"A site's collections and their rows. Reading them publicly is the content API."},{"name":"Releases","description":"Releases built by publishing."},{"name":"Media","description":"A site's media library and direct uploads."},{"name":"Format","description":"The HTML + Liquid authoring reference."},{"name":"Content","description":"The public headless content API."},{"name":"Tenant webhooks","description":"Called by PageHub on a white-label tenant's own server."},{"name":"Stock","description":"Public stock image and video search."},{"name":"Blocks","description":"The block library and placing a block into a page."},{"name":"Templates","description":"The template catalog."},{"name":"Presets","description":"Curated theme bundles."},{"name":"Components library","description":"The shared component library (blocks stored as Liquid)."},{"name":"Team","description":"Site members and invites."},{"name":"Domain","description":"A site's custom domain."},{"name":"Chat","description":"The site's chat bot."},{"name":"Emails","description":"The emails a site sends, and the address they come from."},{"name":"Storefront","description":"Stripe catalog, checkout, orders and file downloads."},{"name":"History","description":"A site's revision timeline, what changed in each version, and restoring one to the draft."},{"name":"Review","description":"Client review comments pinned to a site's preview."},{"name":"Forms","description":"A site's form submissions and the webhooks that deliver them."}],"security":[{"bearerAuth":[]}],"paths":{"/api/v1/sites":{"get":{"tags":["Sites"],"summary":"List sites","description":"The sites the key's user is a member of, or the sites of the key's tenant, newest update first.","operationId":"listSites","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"sites":{"type":"array","items":{"$ref":"#/components/schemas/SiteSummary"}}},"required":["sites"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"}},"x-auth":"api-key"},"post":{"tags":["Sites"],"summary":"Create a site","description":"Creates a site from a template's stored files, or the blank starter when no template is given.","operationId":"createSite","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The site's name (its URL slug)."},"title":{"type":"string"},"description":{"type":"string"},"template":{"type":"string","description":"Slug of the template to create from."},"sourceTemplate":{"type":"object","deprecated":true,"description":"An older spelling of `template`: `{ slug }` or `{ template }`.","properties":{"slug":{"type":"string"},"template":{"type":"string"}}}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"draftId":{"type":"string"},"name":{"type":["string","null"]},"title":{"type":["string","null"]},"description":{"type":["string","null"]},"url":{"type":"string","description":"The site in the dashboard editor."},"staticUrl":{"type":"string"},"previewUrl":{"type":"string","description":"The draft preview (`/view/<draftId>`), on the site's own preview host when per-site previews are on. Share this link; never build a preview URL by hand."}},"required":["id","draftId","url","staticUrl","previewUrl"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"api-key"}},"/api/v1/sites/{id}":{"parameters":[{"$ref":"#/components/parameters/SiteId"}],"get":{"tags":["Sites"],"summary":"Read a site","description":"The site's own fields. Its pages are files: read them through the files API. `mountKey` is returned only to a tenant key or a user with the owner or agency role.","operationId":"getSite","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Site"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"optional-key"},"put":{"tags":["Sites"],"summary":"Update or publish a site","description":"Sets the site's own fields. `published: true` builds a release from the draft and makes it live (the first publish is subject to the plan's site limit); `published: false` unpublishes. Pages are not sent here: use the files and pages routes.","operationId":"updateSite","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"ogImage":{"description":"The site's social image.","type":["object","string","null"]},"portal":{"type":["object","null"]},"crawlPolicy":{"type":"object","description":"Crawl and indexing policy; merged into the current one."},"timezone":{"type":["string","null"],"description":"The business's IANA zone, e.g. `America/Los_Angeles`. Liquid dates (`date`, `time_tag`), `{{ year }}`, `{{ site.timezone }}` and the times in notification emails use it; unset or empty means UTC. Empty or null clears it."},"language":{"type":["string","null"],"description":"BCP 47 tag for `<html lang>` (`en`, `pt-BR`); empty or null clears it."},"mountOrigin":{"type":["string","null"],"description":"Origin of a host app that mounts the site; empty or null clears it. Owner or agency role."},"rotateMountKey":{"type":"boolean","description":"Issue a new mount key. Owner or agency role."},"published":{"type":"boolean"},"expectedUpdatedAt":{"type":"string","format":"date-time","description":"The `updatedAt` you last read; a stale value answers 409 `STALE_REVISION`."}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteUpdated"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The site changed since `expectedUpdatedAt` (`code: STALE_REVISION`), or a publish conflicted with another one (`error: release_conflict`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"The publish could not build a release (`no_source`, `release_failed`, `release_too_large`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key"},"delete":{"tags":["Sites"],"summary":"Delete a site","description":"Permanently deletes the site and everything under it. Site owners only.","operationId":"deleteSite","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"},"id":{"type":"string"}},"required":["deleted","id"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"api-key"}},"/api/v1/sites/{id}/files":{"parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/Stage"}],"get":{"tags":["Files"],"summary":"List draft files, read several, or search them","description":"Without `q` or `read`: every file path of the draft with its version (a content hash); `glob` keeps only the matching paths. With `read`: the text and version of up to 40 files in one call (400,000 characters at most; the rest comes back as `skipped`). With `q`: the hits of the text (plain, case-insensitive) or, with `regex`, of a JavaScript regular expression run under a time limit; each hit has its line and column and the text around the match. Up to 100 hits.","operationId":"listSiteFiles","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":200},"description":"Text to search for."},{"name":"path","in":"query","required":false,"schema":{"type":"string"},"description":"With `q`: only files whose path starts with this, e.g. `pages/`."},{"name":"read","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated paths to read in one call (1-40), e.g. `pages/index.liquid,partials/header.liquid`."},{"name":"regex","in":"query","required":false,"schema":{"type":"string","enum":["1","true"]},"description":"With `q`: the text is a JavaScript regular expression (case-insensitive). An invalid or too slow pattern answers 400 `bad_query`."},{"name":"glob","in":"query","required":false,"schema":{"type":"string","maxLength":200},"description":"Only paths matching, for the listing and for `q`: `*` within a folder, `**` across folders, `?`, `{a,b}`. A glob with no `/` matches the file name at any depth (`*.liquid`)."},{"name":"context","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":5},"description":"With `q`: lines of context before and after each hit."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/FileList"},{"$ref":"#/components/schemas/FileSearch"},{"$ref":"#/components/schemas/FileRead"}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/SiteFormatChanged"}},"x-auth":"session-or-key"},"post":{"tags":["Files"],"summary":"Save a batch of files atomically","description":"Writes and deletes up to 500 files in one atomic save. `baseVersions` says which version of each file you edited; a file someone else changed since answers 409 `version_conflict` and nothing is written. Files are linted and sanitized; invalid HTML nesting is repaired and reported in `repaired`, so compare the stored text before the next edit. `pages` also creates a page record for each new page file in the same write, so a new page and the link to it land together; a page that can't be created (taken URL, plan cap) answers 400 or 403 and writes nothing. Not available in review mode.","operationId":"saveSiteFiles","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"changes":{"type":"array","minItems":1,"maxItems":500,"items":{"oneOf":[{"type":"object","properties":{"path":{"type":"string"},"text":{"type":"string"}},"required":["path","text"]},{"type":"object","properties":{"path":{"type":"string"},"delete":{"const":true}},"required":["path","delete"]}]}},"baseVersions":{"type":"object","additionalProperties":{"type":["string","null"]},"description":"`{ [path]: version }`; `null` (or a missing path) means the file must not exist yet."},"pages":{"type":"array","items":{"$ref":"#/components/schemas/PageFromFile"},"description":"Pages to create for new `pages/….liquid` files in `changes` (each with no base version)."}},"required":["changes"]}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"type":"object","properties":{"versions":{"type":"object","additionalProperties":{"type":"string"},"description":"The new version of each written path."},"version":{"type":"integer","description":"The draft's shared version."},"staged":{"const":true,"description":"Present when the write went into the staged AI layer."},"repaired":{"$ref":"#/components/schemas/NestingRepairs"},"pages":{"type":"array","items":{"$ref":"#/components/schemas/SitePage"},"description":"The page records created for `pages`."}},"required":["versions","version"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"`version_conflict` (nothing written) or `site_format_changed`.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"error":{"const":"version_conflict"},"conflicts":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"theirs":{"type":["string","null"]},"version":{"type":"integer"}}}}},"required":["error","conflicts"]},{"$ref":"#/components/schemas/ApiError"}]}}}},"413":{"$ref":"#/components/responses/WriteRejected"},"422":{"$ref":"#/components/responses/WriteRejected"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"site_save"}},"/api/v1/sites/{id}/files/{path}":{"parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/Stage"},{"name":"path","in":"path","required":true,"schema":{"type":"string"},"allowReserved":true,"description":"The file's path in the site, one URL segment per folder, e.g. `pages/shop/[handle].liquid`."}],"get":{"tags":["Files"],"summary":"Read one draft file","operationId":"getSiteFile","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"version":{"type":"string","description":"The file's content hash."}},"required":["text","version"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The site, or the file in its draft (`file_not_found`), does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"$ref":"#/components/responses/SiteFormatChanged"}},"x-auth":"session-or-key"},"put":{"tags":["Files"],"summary":"Write one draft file","description":"Same rules as the batch save, for a single file. `page` on a new `pages/<slug>.liquid` file also creates its page record.","operationId":"putSiteFile","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"baseVersion":{"type":["string","null"],"description":"The version you edited; `null` means the file must not exist yet."},"page":{"type":"object","description":"Create the page record for this new page file: `{ name?, seo?, pathPattern? }` (an empty object takes the defaults).","properties":{"name":{"type":"string"},"seo":{"type":"object"},"pathPattern":{"type":["string","null"]}}}},"required":["text"]}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"string"},"staged":{"const":true},"repaired":{"$ref":"#/components/schemas/NestingRepairs"},"pages":{"type":"array","items":{"$ref":"#/components/schemas/SitePage"}}},"required":["version"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"`version_conflict` (nothing written) or `site_format_changed`.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"error":{"const":"version_conflict"},"theirs":{"type":["string","null"]},"version":{"type":"integer"}},"required":["error"]},{"$ref":"#/components/schemas/ApiError"}]}}}},"413":{"$ref":"#/components/responses/WriteRejected"},"422":{"$ref":"#/components/responses/WriteRejected"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"site_save"}},"/api/v1/sites/{id}/source-pages":{"parameters":[{"$ref":"#/components/parameters/SiteId"}],"get":{"tags":["Pages"],"summary":"List pages","operationId":"listSitePages","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"pages":{"type":"array","items":{"$ref":"#/components/schemas/SitePage"}}},"required":["pages"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/SiteFormatChanged"}},"x-auth":"session-or-key"},"post":{"tags":["Pages"],"summary":"Create a page","description":"Creates the page's file (it must not exist yet) and its record. The first page of a site becomes the home page. Send `text`, or `blank: true` to start from the site's own header and footer around a heading.","operationId":"createSitePage","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string","description":"Defaults to a slug of `name`."},"pathPattern":{"type":["string","null"],"description":"URL tail after the page's slug for a detail page, e.g. `:handle`."},"text":{"type":"string","description":"The page's HTML + Liquid."},"blank":{"type":"boolean"},"seo":{"type":"object","additionalProperties":true},"file":{"type":"string","description":"The page file path; defaults to `pages/<slug>.liquid`."}},"required":["name"]}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"page":{"$ref":"#/components/schemas/SitePage"},"version":{"type":"string"}},"required":["page"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The page's file already exists, or the site format changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"$ref":"#/components/responses/WriteRejected"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"site_save"}},"/api/v1/sites/{id}/source-pages/{pageId}":{"parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"pageId","in":"path","required":true,"schema":{"type":"string"},"description":"The page's `pageId` from the pages list."}],"get":{"tags":["Pages"],"summary":"Read a page's record and settings","operationId":"getSitePage","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"page":{"$ref":"#/components/schemas/SitePage"},"policy":{"type":["object","null"],"additionalProperties":true,"description":"The page's access policy, without the password hash."},"hasPassword":{"type":"boolean"},"file":{"type":["object","null"],"additionalProperties":true,"description":"Settings kept in the page's own file: `hideHeader`, `hideFooter`, `headCode`, `footerCode`."}},"required":["page","hasPassword"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"},"patch":{"tags":["Pages"],"summary":"Change a page's record","description":"`seo` and `policy` merge into the current values; a null value removes a key. `file` edits the page's own file. Clients (content-only role) cannot change page settings.","operationId":"updateSitePage","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string"},"pathPattern":{"type":["string","null"]},"isHomePage":{"type":"boolean"},"is404Page":{"type":"boolean"},"isHidden":{"type":"boolean"},"seo":{"type":"object","additionalProperties":true},"policy":{"type":"object","additionalProperties":true},"file":{"type":"object","properties":{"hideHeader":{"type":"boolean"},"hideFooter":{"type":"boolean"},"headCode":{"type":"string"},"footerCode":{"type":"string"}}}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"page":{"$ref":"#/components/schemas/SitePage"}},"required":["page"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/SiteFormatChanged"},"422":{"$ref":"#/components/responses/WriteRejected"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"site_save"},"delete":{"tags":["Pages"],"summary":"Delete a page","description":"Deletes the page's file, then its record. The home page cannot be deleted.","operationId":"deleteSitePage","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"page":{"$ref":"#/components/schemas/SitePage"}},"required":["page"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/SiteFormatChanged"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"site_save"}},"/api/v1/sites/{id}/components":{"parameters":[{"$ref":"#/components/parameters/SiteId"}],"get":{"tags":["Components"],"summary":"List components","description":"The reusable components (partials) of the site's draft and where each is used.","operationId":"listSiteComponents","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"components":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"usage":{"type":"object","properties":{"instances":{"type":"integer"},"pages":{"type":"integer"}}}},"required":["id","name"]}}},"required":["components"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/SiteFormatChanged"}},"x-auth":"session-or-key"}},"/api/v1/sites/{id}/outline":{"parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/Stage"}],"get":{"tags":["Files"],"summary":"Outline a page","description":"The structure of one page of the draft: sections and landmarks with their line ranges, headings, and every `{% render %}` / `{% section %}` of a partial, expanded in place with its own sections and headings (anchored to the partial's file and lines) unless `expand=0`. Rendered partials that are components carry their use counts.","operationId":"outlineSitePage","parameters":[{"name":"page","in":"query","required":true,"schema":{"type":"string"},"description":"A page id, its URL (`/about`) or a file path (`pages/about.liquid`, `partials/header.liquid`)."},{"name":"expand","in":"query","required":false,"schema":{"type":"string","enum":["0","1"]},"description":"`0` names each render without expanding it."},{"name":"classes","in":"query","required":false,"schema":{"type":"string","enum":["1","true"]},"description":"Full class strings; by default the first three and a count."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageOutline"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The site, or the page or file, does not exist (`page_not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"$ref":"#/components/responses/SiteFormatChanged"}},"x-auth":"session-or-key"}},"/api/v1/sites/{id}/components/{name}":{"parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The component's `id`."}],"patch":{"tags":["Components"],"summary":"Rename a component","description":"Renames the component and every `{% render %}` tag of it, as one write.","operationId":"renameSiteComponent","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}}}},"responses":{"200":{"$ref":"#/components/responses/ComponentId"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/ComponentConflict"},"422":{"$ref":"#/components/responses/WriteRejected"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"site_save"},"delete":{"tags":["Components"],"summary":"Delete a component","description":"Refused with 409 `in_use` while anything still renders it.","operationId":"deleteSiteComponent","responses":{"200":{"$ref":"#/components/responses/ComponentId"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/ComponentConflict"},"422":{"$ref":"#/components/responses/WriteRejected"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"site_save"}},"/api/v1/sites/{id}/collections":{"parameters":[{"$ref":"#/components/parameters/SiteId"}],"get":{"tags":["Collections"],"summary":"List collections","operationId":"listCollections","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"collections":{"type":"array","items":{"$ref":"#/components/schemas/Collection"}},"allowance":{"$ref":"#/components/schemas/Allowance"}},"required":["collections"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"},"post":{"tags":["Collections"],"summary":"Create a collection","description":"Needs the owner or agency role on the site and a plan that includes collections (and the chosen source type).","operationId":"createCollection","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string","pattern":"^[a-z][a-z0-9-]*$"},"name":{"type":"string"},"description":{"type":"string"},"schema":{"type":"array","items":{"$ref":"#/components/schemas/CollectionField"},"description":"The collection's fields."},"source":{"type":"object","additionalProperties":true,"description":"Where rows come from; `{ type: \"manual\" }` by default."},"isPublic":{"type":"boolean","default":true}},"required":["slug","name"]}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"collection":{"$ref":"#/components/schemas/Collection"}},"required":["collection"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The slug already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/collections/{colId}":{"parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"}],"get":{"tags":["Collections"],"summary":"Read a collection","operationId":"getCollection","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"collection":{"$ref":"#/components/schemas/Collection"}},"required":["collection"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"},"patch":{"tags":["Collections"],"summary":"Update a collection","description":"`name` and `description` are open to any editor; changing the slug, schema, source, visibility or member gates needs the owner or agency role.","operationId":"updateCollection","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"slug":{"type":"string","pattern":"^[a-z][a-z0-9-]*$"},"schema":{"type":"array","items":{"$ref":"#/components/schemas/CollectionField"}},"source":{"type":"object","additionalProperties":true},"isPublic":{"type":"boolean"},"requiresCustomerLogin":{"type":"boolean"},"requiredPlans":{"type":"array","items":{"type":"string"},"description":"Membership plans that unlock the collection."},"memberFields":{"type":"array","items":{"type":"string"},"description":"Fields only members see."}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"collection":{"$ref":"#/components/schemas/Collection"}},"required":["collection"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The new slug already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"delete":{"tags":["Collections"],"summary":"Delete a collection and its rows","description":"Owner or agency role.","operationId":"deleteCollection","responses":{"200":{"$ref":"#/components/responses/Ok"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/collections/{colId}/rows":{"parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"}],"get":{"tags":["Collections"],"summary":"List rows","description":"Cursor-paginated, in `order`. Fields the reader's role cannot read are redacted.","operationId":"listCollectionRows","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"The `nextCursor` of the previous page."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/CollectionRow"}},"nextCursor":{"type":["string","null"]},"hasMore":{"type":"boolean"},"totalCount":{"type":"integer"},"allowance":{"$ref":"#/components/schemas/Allowance"}},"required":["rows","nextCursor","hasMore"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"},"post":{"tags":["Collections"],"summary":"Create rows","description":"Send one row (`{ data }` or the bare field object), an array of rows, or `{ rows: [...] }`; up to 500 per call. Each row is validated against the collection's schema.","operationId":"createCollectionRows","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/RowInput"},{"type":"array","items":{"$ref":"#/components/schemas/RowInput"},"minItems":1,"maxItems":500},{"type":"object","properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/RowInput"},"minItems":1,"maxItems":500}},"required":["rows"]}]}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/CollectionRow"}},"created":{"type":"integer"}},"required":["rows","created"]}}}},"400":{"description":"A row failed validation (`field` and `rowIndex` say where) or the batch is empty or over 500.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/collections/{colId}/rows/{rowId}":{"parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"},{"name":"rowId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-fA-F0-9]{24}$"}}],"patch":{"tags":["Collections"],"summary":"Update a row","description":"`data` merges into the row's current data and is validated against the schema.","operationId":"updateCollectionRow","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","additionalProperties":true},"order":{"type":"number"},"externalId":{"type":["string","null"]}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"row":{"$ref":"#/components/schemas/CollectionRow"}},"required":["row"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"delete":{"tags":["Collections"],"summary":"Delete a row","operationId":"deleteCollectionRow","responses":{"200":{"$ref":"#/components/responses/Ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/releases/{releaseId}":{"parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"releaseId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Releases"],"summary":"Read a release","description":"A publish too big to build inside the request answers `release.queued`; poll the release until `status` is `ready` or `failed`.","operationId":"getRelease","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"number":{"type":"integer"},"kind":{"type":"string"},"status":{"type":"string"},"error":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"scheduledFor":{"type":["string","null"],"format":"date-time"},"stats":{"type":["object","null"],"additionalProperties":true},"live":{"type":"boolean","description":"Whether this release is the one being served."}},"required":["id","number","status","live"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"},"post":{"tags":["Releases"],"summary":"Make a release live again","description":"Rollback: the undo for a bad publish. Needs permission to publish the site.","operationId":"rollbackRelease","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"const":"rollback"}},"required":["action"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"live":{"type":"boolean"}},"required":["id","live"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"This release cannot be made live.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/media":{"parameters":[{"$ref":"#/components/parameters/SiteId"}],"get":{"tags":["Media"],"summary":"Read the media library","description":"The site's media items and folders. `?summary=1` returns the library's tab counts and `?usage=1` where each file is used, instead.","operationId":"getSiteMedia","parameters":[{"name":"kind","in":"query","schema":{"const":"image"},"description":"Only images."},{"name":"summary","in":"query","schema":{"const":"1"}},{"name":"usage","in":"query","schema":{"const":"1"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"media":{"type":"array","items":{"type":"object","additionalProperties":true}},"folders":{"type":"array","items":{"type":"object","additionalProperties":true}}},"description":"With `summary` or `usage`, the summary object or `{ usage }` instead."}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"},"post":{"tags":["Media"],"summary":"Upload media: sign, confirm, import a URL, or register items","description":"Large files go straight to the CDN in two steps, so the bytes never pass through the API:\n\n1. `{ step: \"sign\", size, contentType, filename? }` returns an `uploadURL`. Send the file there: images as a multipart `POST` with field `formField` (`file`); everything else as a raw `PUT` whose `Content-Type` matches `contentType` exactly.\n2. `{ step: \"confirm\", ticket, filename?, width?, height? }` finalizes the upload and registers it in the library. Confirm is not optional.\n\n`private: true` (digital downloads) works only through sign then confirm. Without `step`: `{ fileUrl }` (or `imageUrl`) imports a public URL, `{ dataBase64, mimeType, filename }` uploads small bytes inline (a few MB at most), and `{ items }` registers already-uploaded files in the library.","operationId":"uploadSiteMedia","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"title":"Sign","type":"object","properties":{"step":{"const":"sign"},"size":{"type":"number","exclusiveMinimum":0,"description":"Bytes."},"contentType":{"type":"string"},"filename":{"type":"string"},"private":{"type":"boolean"}},"required":["step","size"]},{"title":"Confirm","type":"object","properties":{"step":{"const":"confirm"},"ticket":{"type":"string","description":"The `ticket` from the sign response."},"filename":{"type":"string"},"width":{"type":"integer"},"height":{"type":"integer"},"private":{"type":"boolean"}},"required":["step","ticket"]},{"title":"Import","type":"object","properties":{"fileUrl":{"type":"string","format":"uri"},"imageUrl":{"type":"string","format":"uri","description":"Older alias of `fileUrl`."},"dataBase64":{"type":"string"},"mimeType":{"type":"string"},"filename":{"type":"string"}}},{"title":"Register","type":"object","properties":{"items":{"type":"array","items":{"type":"object","additionalProperties":true}}},"required":["items"]}]}}}},"responses":{"200":{"description":"The sign response, the confirmed or imported file, or the registered items.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/MediaSigned"},{"$ref":"#/components/schemas/MediaFile"},{"type":"object","properties":{"media":{"type":"array","items":{"type":"object","additionalProperties":true}},"added":{"type":"integer"}},"required":["media","added"]}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"No access to the site, or the plan or storage quota refuses the file.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"$ref":"#/components/responses/NotFound"},"413":{"description":"The file is too large for the plan or the import limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"Image CDN, R2 or private storage is not configured, or writes are paused for maintenance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"x-auth":"session-or-key"},"patch":{"tags":["Media"],"summary":"Reorder or move library items","operationId":"updateSiteMedia","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"},"description":"Media ids in the new order."},"move":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"}},"folderId":{"type":["string","null"]}},"required":["ids"]}}}}}},"responses":{"200":{"description":"The updated library state.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"media_delete"}},"/api/v1/format/liquid":{"get":{"tags":["Format"],"summary":"The HTML + Liquid authoring reference","description":"Public; no key needed. The tags, filters, objects and lint rules are generated from the renderer's catalogues; the conventions (files, runtime `data-*` attributes with examples, site JavaScript, forms and form webhooks, dates and timezone, maps) are written by hand and checked against the runtime. The same Markdown is the MCP `get_style_reference` topic `liquid` and the `/docs/schema` page.","operationId":"getLiquidReference","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"}},"required":["text"]}}}}},"x-auth":"public"}},"/api/v1/stock-images":{"get":{"tags":["Stock"],"summary":"Search stock images","description":"Public. Searches the verified stock library; returned images get their `uses` count bumped.","operationId":"searchStockImages","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"Full-text search."},{"name":"category","in":"query","required":false,"schema":{"type":"string"}},{"name":"orientation","in":"query","required":false,"schema":{"type":"string"}},{"name":"source","in":"query","required":false,"schema":{"type":"string","enum":["unsplash","pexels","custom"]}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["popular","newest"],"default":"popular"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"maximum":500}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":20,"default":10}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"images":{"type":"array","items":{"$ref":"#/components/schemas/StockImage"}},"total":{"type":"integer"},"page":{"type":"integer"},"pages":{"type":"integer"}},"required":["images","total","page","pages"]}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"public","x-rate-limit":"stock_search","security":[]},"post":{"tags":["Stock"],"summary":"Upsert stock images (admin)","description":"Platform admins only. Each item needs `photoId` and `url`; an item without them comes back as a result with an `error` instead of failing the call.","operationId":"upsertStockImages","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/StockImage"},{"type":"array","items":{"$ref":"#/components/schemas/StockImage"}}]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"images":{"type":"array","items":{"type":"object","additionalProperties":true}},"count":{"type":"integer"}},"required":["images","count"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"x-auth":"admin-key"}},"/api/v1/stock-images/search-external":{"post":{"tags":["Stock"],"summary":"Search a stock image provider","description":"Public. Searches an external provider (Pexels by default) and caches the results in the stock library. A provider that is unavailable or fails answers 200 with an empty list and a `note`.","operationId":"searchExternalStockImages","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"q":{"type":"string"},"count":{"type":"integer","minimum":1,"maximum":10,"default":5},"orientation":{"type":"string"},"provider":{"type":"string","default":"pexels"}},"required":["q"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"images":{"type":"array","items":{"$ref":"#/components/schemas/StockImage"}},"total":{"type":"integer"},"note":{"type":"string"}},"required":["images"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"public","x-rate-limit":"stock_search_external","security":[]}},"/api/v1/stock-videos/search-external":{"post":{"tags":["Stock"],"summary":"Search a stock video provider","description":"Public. Like the image search; nothing is cached.","operationId":"searchExternalStockVideos","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"q":{"type":"string"},"count":{"type":"integer","minimum":1,"maximum":10,"default":5},"orientation":{"type":"string"},"minDuration":{"type":"number"},"maxDuration":{"type":"number"},"provider":{"type":"string","default":"pexels"}},"required":["q"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"videos":{"type":"array","items":{"$ref":"#/components/schemas/StockVideo"}},"total":{"type":"integer"},"note":{"type":"string"}},"required":["videos"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"public","x-rate-limit":"stock_search_external","security":[]}},"/api/v1/liquid-blocks":{"get":{"tags":["Blocks"],"summary":"Browse the block library","description":"The public library plus the caller's own saved blocks, without their source. Ask the block's route for the ones you use.","operationId":"listLiquidBlocks","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"category","in":"query","required":false,"schema":{"type":"string"}},{"name":"style","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"blocks":{"type":"array","items":{"$ref":"#/components/schemas/LiquidBlockSummary"}}},"required":["blocks"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"}},"x-auth":"session-or-key"}},"/api/v1/liquid-blocks/{slug}":{"get":{"tags":["Blocks"],"summary":"Read one block as Liquid","operationId":"getLiquidBlock","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The block's slug."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"block":{"type":"object","additionalProperties":true,"properties":{"slug":{"type":"string"},"name":{"type":"string"},"category":{"type":"string"},"styles":{"type":"array","items":{"type":"string"}},"description":{"type":"string"},"source":{"type":"string","description":"The block's Liquid."},"notes":{}},"required":["slug","name","source"]}},"required":["block"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"}},"/api/v1/templates":{"get":{"tags":["Templates"],"summary":"List templates","description":"Public templates; with a key, also the tenant's own (an admin key sees every template). `slugs=a,b,c` fetches those templates in full and ignores the other filters.","operationId":"listTemplates","parameters":[{"name":"category","in":"query","required":false,"schema":{"type":"string"}},{"name":"tag","in":"query","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"Full-text search."},{"name":"slugs","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated slugs (up to 100)."},{"name":"previewLimit","in":"query","required":false,"schema":{"type":"string","pattern":"^\\d+$"},"description":"Trims how many preview items each template carries."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"templates":{"type":"array","items":{"$ref":"#/components/schemas/TemplateSummary"}},"authenticated":{"const":true,"description":"Present when a valid key was sent."}},"required":["templates"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"}},"x-auth":"optional-key","security":[{},{"bearerAuth":[]}]},"post":{"tags":["Templates"],"summary":"Create a template","description":"A template is its files: send `files` (and `pages`), or `fromSite` to snapshot a site. The thumbnail is captured automatically after the response. Only platform admins publish into the public catalog (`isPublic`); a tenant's templates stay its own.","operationId":"createTemplate","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"tagline":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"isPublic":{"type":"boolean"},"sortOrder":{"type":"integer"},"files":{"description":"The template's files: `{ path: text }` or `[{ path, text }]`.","oneOf":[{"type":"object","additionalProperties":{"type":"string"}},{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"text":{"type":"string"}},"required":["path","text"]}}]},"pages":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Page records, as in a site bundle."},"fromSite":{"type":"string","description":"A site id to snapshot instead of sending `files`."}},"required":["slug","title"]}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"}},"required":["slug","title"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"A conflict (`error` says which).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"x-auth":"api-key"}},"/api/v1/templates/{slug}":{"get":{"tags":["Templates"],"summary":"Read a template","description":"A private template is visible only to its tenant or an admin. `fields=theme` answers just the palette and style guide; `format=liquid` answers the files and page records a site made from the template starts with.","operationId":"getTemplate","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The template's slug."},{"name":"fields","in":"query","required":false,"schema":{"const":"theme"}},{"name":"format","in":"query","required":false,"schema":{"const":"liquid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"tagline":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"format":{"const":"html"},"version":{"type":"integer"}},"required":["slug","title"]},{"type":"object","properties":{"slug":{"type":"string"},"theme":{"type":"object","properties":{"palette":{"type":"array","items":{"type":"object","additionalProperties":true}},"styleGuide":{"type":"object","additionalProperties":true}}}},"required":["slug","theme"]},{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"format":{"const":"liquid"},"from":{"type":"string"},"version":{"type":"integer"},"pages":{"type":"array","items":{"type":"object","additionalProperties":true}},"files":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"text":{"type":"string"}},"required":["path","text"]}}},"required":["slug","format","files"]}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"optional-key","security":[{},{"bearerAuth":[]}]},"put":{"tags":["Templates"],"summary":"Update a template","description":"The tenant's own template, or any for an admin. Send `files` / `pages` / `fromSite` to replace its content (the thumbnail is recaptured). `expectedVersion` guards against a concurrent edit.","operationId":"updateTemplate","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The template's slug."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"tagline":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"sortOrder":{"type":"integer"},"expectedVersion":{"type":"integer"},"files":{"oneOf":[{"type":"object","additionalProperties":{"type":"string"}},{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"text":{"type":"string"}},"required":["path","text"]}}]},"pages":{"type":"array","items":{"type":"object","additionalProperties":true}},"fromSite":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"version":{"type":"integer"},"updatedAt":{"type":"string","format":"date-time"}},"required":["slug","version"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A conflict (`error` says which).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"x-auth":"api-key"},"delete":{"tags":["Templates"],"summary":"Delete a template","operationId":"deleteTemplate","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The template's slug."}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"},"slug":{"type":"string"}},"required":["deleted","slug"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"api-key"}},"/api/v1/templates/{slug}/audit":{"get":{"tags":["Templates"],"summary":"Audit a template page","description":"Renders the template's stored page as its preview does, then runs the SEO or accessibility checks on it. A private template is visible only to its tenant or an admin.","operationId":"auditTemplate","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The template's slug."},{"name":"kind","in":"query","required":false,"schema":{"type":"string","enum":["seo","accessibility"],"default":"seo"}},{"name":"path","in":"query","required":false,"schema":{"type":"string","default":"/"},"description":"The page's URL path."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"optional-key","security":[{},{"bearerAuth":[]}]}},"/api/v1/presets":{"get":{"tags":["Presets"],"summary":"List presets","description":"Public presets; with a key, also the tenant's own.","operationId":"listPresets","parameters":[{"name":"mood","in":"query","required":false,"schema":{"type":"string"},"description":"Case-insensitive match on a mood."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"presets":{"type":"array","items":{"$ref":"#/components/schemas/Preset"}}},"required":["presets"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"optional-key","x-rate-limit":"v1_public_list","security":[{},{"bearerAuth":[]}]},"post":{"tags":["Presets"],"summary":"Create a preset (admin)","description":"Platform admins only: presets are global and public.","operationId":"createPreset","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"presetId":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"mood":{"type":"array","items":{"type":"string"}},"palette":{},"styleGuide":{"type":"object","additionalProperties":true},"fonts":{"type":"object","additionalProperties":true},"style":{}},"required":["presetId","name","palette"]}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"preset":{"$ref":"#/components/schemas/Preset"}},"required":["preset"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"A conflict (`error` says which).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"x-auth":"admin-key"}},"/api/v1/presets/{id}":{"get":{"tags":["Presets"],"summary":"Read a preset","description":"A private preset is visible only to its owner.","operationId":"getPreset","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The preset's `presetId`."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"preset":{"$ref":"#/components/schemas/Preset"}},"required":["preset"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"optional-key","security":[{},{"bearerAuth":[]}]},"put":{"tags":["Presets"],"summary":"Update a preset","description":"Only the preset's owner.","operationId":"updatePreset","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The preset's `presetId`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"mood":{"type":"array","items":{"type":"string"}},"palette":{},"styleGuide":{"type":"object","additionalProperties":true},"fonts":{"type":"object","additionalProperties":true},"style":{}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"preset":{"$ref":"#/components/schemas/Preset"}},"required":["preset"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"api-key"},"delete":{"tags":["Presets"],"summary":"Delete a preset","description":"Only the preset's owner.","operationId":"deletePreset","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The preset's `presetId`."}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"},"presetId":{"type":"string"}},"required":["deleted","presetId"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"api-key"}},"/api/v1/components":{"get":{"tags":["Components library"],"summary":"Search the component library","description":"Public blocks; with a key, also the caller's own. The Liquid `source` is left out: read one block for it.","operationId":"listLibraryComponents","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"Full-text search."},{"name":"tag","in":"query","required":false,"schema":{"type":"string"}},{"name":"featured","in":"query","required":false,"schema":{"const":"true"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["popular","newest","name","relevance"],"default":"popular"},"description":"`relevance` needs `q`."},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"category","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated for several."},{"name":"categories","in":"query","required":false,"schema":{"type":"string"}},{"name":"subcategory","in":"query","required":false,"schema":{"type":"string"}},{"name":"preset","in":"query","required":false,"schema":{"type":"string"}},{"name":"source","in":"query","required":false,"schema":{"type":"string"}},{"name":"group","in":"query","required":false,"schema":{"type":"string"}},{"name":"style","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated for several."},{"name":"styles","in":"query","required":false,"schema":{"type":"string"}},{"name":"blockType","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"components":{"type":"array","items":{"$ref":"#/components/schemas/LibraryComponent"}},"total":{"type":"integer"},"page":{"type":"integer"},"pages":{"type":"integer"}},"required":["components","total"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"}},"x-auth":"optional-key","security":[{},{"bearerAuth":[]}]},"post":{"tags":["Components library"],"summary":"Add a block to the library","description":"A block is its Liquid `source` (sanitized and linted like a site file). Only platform admins can publish one (`isPublic`) or mark it a category preview.","operationId":"createLibraryComponent","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"visual":{"type":"string"},"category":{"type":"string"},"subcategory":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"preset":{"type":"string"},"group":{"type":"string"},"style":{"type":"string"},"styles":{"type":"array","items":{"type":"string"}},"isPublic":{"type":"boolean"},"isCategoryPreview":{"type":"boolean"},"source":{"type":"string","description":"The block's Liquid."}},"required":["name","slug","category","source"]}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"component":{"$ref":"#/components/schemas/LibraryComponent"}},"required":["component"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"A conflict (`error` says which).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"$ref":"#/components/responses/WriteRejected"}},"x-auth":"api-key"}},"/api/v1/components/categories":{"get":{"tags":["Components library"],"summary":"List library categories","description":"Categories with counts, subcategories and styles, for a category-first block picker.","operationId":"listLibraryCategories","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"categories":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"total":{"type":"integer"},"subcategories":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"count":{"type":"integer"}}}},"styles":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"count":{"type":"integer"}}}}},"required":["id","name","total"]}}},"required":["categories"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"}},"x-auth":"optional-key","security":[{},{"bearerAuth":[]}]}},"/api/v1/components/{id}":{"get":{"tags":["Components library"],"summary":"Read a block with its Liquid","description":"By `_id` or slug. Bumps the block's `uses` count.","operationId":"getLibraryComponent","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The block's `_id` or slug."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"component":{"$ref":"#/components/schemas/LibraryComponent"}},"required":["component"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"optional-key","security":[{},{"bearerAuth":[]}]},"put":{"tags":["Components library"],"summary":"Update a block","description":"Only the block's owner (or an admin). `source` is sanitized and linted. `expectedVersion` guards against a concurrent edit. Only an admin can change `isPublic`, `isFeatured` or `isCategoryPreview`.","operationId":"updateLibraryComponent","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The block's `_id` or slug."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"visual":{"type":"string"},"category":{"type":"string"},"subcategory":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"preset":{"type":"string"},"group":{"type":"string"},"style":{"type":"string"},"isPublic":{"type":"boolean"},"isFeatured":{"type":"boolean"},"isCategoryPreview":{"type":"boolean"},"expectedVersion":{"type":"integer"},"source":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"component":{"$ref":"#/components/schemas/LibraryComponent"}},"required":["component"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A conflict (`error` says which).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"$ref":"#/components/responses/WriteRejected"}},"x-auth":"api-key"},"delete":{"tags":["Components library"],"summary":"Delete a block","description":"Only the block's owner (or an admin).","operationId":"deleteLibraryComponent","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The block's `_id` or slug."}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"}},"required":["deleted"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"api-key"}},"/api/v1/components/{id}/like":{"post":{"tags":["Components library"],"summary":"Like a public block","description":"Idempotent per caller: the first like counts, repeats answer `alreadyLiked`.","operationId":"likeLibraryComponent","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The block's `_id` or slug."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"likes":{"type":"integer"},"alreadyLiked":{"type":"boolean"}},"required":["likes"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"api-key"}},"/api/v1/sites/{id}/duplicate":{"post":{"tags":["Sites"],"summary":"Duplicate a site","description":"Copies the site's files and page rows into a new site owned by the caller. Needs edit access.","operationId":"duplicateSite","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"draftId":{"type":"string"},"name":{"type":["string","null"]},"title":{"type":["string","null"]},"description":{"type":["string","null"]},"sourceId":{"type":"string"},"url":{"type":"string"},"staticUrl":{"type":"string"},"previewUrl":{"type":"string","description":"The draft preview (`/view/<draftId>`), on the site's own preview host when per-site previews are on. Share this link; never build a preview URL by hand."}},"required":["id","draftId","sourceId","url","staticUrl","previewUrl"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"api-key"}},"/api/v1/sites/{id}/audit":{"get":{"tags":["Sites"],"summary":"Audit a draft page","description":"Renders the draft page as the preview does, then runs the SEO or accessibility checks on it.","operationId":"auditSite","parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"kind","in":"query","required":false,"schema":{"type":"string","enum":["seo","accessibility"],"default":"seo"}},{"name":"path","in":"query","required":false,"schema":{"type":"string","default":"/"},"description":"The page's URL path."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A conflict (`error` says which).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"x-auth":"session-or-key"}},"/api/v1/sites/{id}/screenshot":{"post":{"tags":["Sites"],"summary":"Screenshot the draft","description":"Renders the draft preview in a real browser and returns the image as base64. Nothing is stored.","operationId":"screenshotSite","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"path":{"type":"string","description":"A page's URL path under the site, e.g. `pricing`."},"width":{"type":"integer","minimum":320,"maximum":1920,"default":1440},"height":{"type":"integer","minimum":480,"maximum":2400,"default":900},"fullPage":{"type":"boolean"},"selector":{"type":"string","maxLength":200,"description":"Shoot one element instead of the page."}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"contentType":{"type":"string"},"dataBase64":{"type":"string"}},"required":["url","contentType","dataBase64"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"An upstream service (Stripe, Vercel, a mail server, a stock provider) failed or refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"x-auth":"api-key","x-rate-limit":"media_describe"}},"/api/v1/sites/{id}/blocks":{"post":{"tags":["Blocks"],"summary":"Place a library block into a page","description":"Adds the block's Liquid (with fresh ids) to a page file of the draft as one write. `pageId` or `path` picks the page, the home page by default. `position` is `end` (default, before the footer) or `start`; `before` or `after` is text that occurs once in the page.","operationId":"placeSiteBlock","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string"},"pageId":{"type":"string"},"path":{"type":"string"},"position":{"type":"string","enum":["start","end"]},"before":{"type":"string"},"after":{"type":"string"}},"required":["slug"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"path":{"type":"string"},"version":{"type":"string"},"source":{"type":"string"}},"required":["path","version","source"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A conflict (`error` says which).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"$ref":"#/components/responses/WriteRejected"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"site_save"}},"/api/v1/sites/{id}/source-settings":{"put":{"tags":["Sites"],"summary":"Change theme, favicon, language or integrations","description":"Settings that live in the site's files: `theme` sets custom properties in `theme.css`, `favicon` sets or clears `site.json` favicon, `language` is `site.json` `lang`, `integrations` merges provider blocks. Send at least one.","operationId":"updateSiteSourceSettings","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"theme":{"type":"object","additionalProperties":true,"description":"`palette`, `darkPalette`, `styleGuide`, `typography`, `breakpoints`, `daisyui`."},"favicon":{"description":"`{ href, type }`, `{ content, type }`, or null to clear.","type":["object","null"],"additionalProperties":true},"language":{"type":["string","null"],"description":"BCP 47 tag; empty clears it."},"integrations":{"type":"object","additionalProperties":true,"description":"Provider blocks merged over the site's integrations."}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"theme":{"type":"object","properties":{"set":{"type":"array","items":{"type":"string"}}}},"favicon":{"const":true},"language":{"type":["string","null"]},"integrations":{"type":"object","additionalProperties":true},"live":{"type":"string","description":"Set when a published site's live release was rebuilt."},"version":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A conflict (`error` says which).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"The request or file is too large.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"$ref":"#/components/responses/WriteRejected"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"site_save"}},"/api/v1/sites/{id}/ai-review":{"get":{"tags":["Sites"],"summary":"List AI edits waiting for review","description":"In review mode PageHub AI stages its file edits instead of writing them. `count=1` answers only the number.","operationId":"getSiteAiReview","parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"count","in":"query","required":false,"schema":{"const":"1"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"files":{"type":"array","items":{"$ref":"#/components/schemas/StagedFile"}},"updatedAt":{"type":["string","null"]}},"required":["files"]},{"type":"object","properties":{"count":{"type":"integer"},"updatedAt":{"type":["string","null"]}},"required":["count"]}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A conflict (`error` says which).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"x-auth":"session-or-key"},"post":{"tags":["Sites"],"summary":"Keep or discard staged AI edits","description":"Keep writes a staged file to the draft (as an AI write, against `bases`); discard drops it. `keep` and `discard` take file paths or `\"all\"`. A file that changed meanwhile answers 409 `version_conflict` and nothing is written.","operationId":"resolveSiteAiReview","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"keep":{"oneOf":[{"type":"array","items":{"type":"string"}},{"const":"all"}]},"discard":{"oneOf":[{"type":"array","items":{"type":"string"}},{"const":"all"}]},"bases":{"type":"object","additionalProperties":{"type":["string","null"]},"description":"`{ [path]: version }` the person reviewed."}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"kept":{"type":"array","items":{"type":"string"}},"discarded":{"type":"array","items":{"type":"string"}}},"required":["kept","discarded"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A conflict (`error` says which).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"$ref":"#/components/responses/WriteRejected"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"site_save"}},"/api/v1/sites/{id}/og-card":{"get":{"tags":["Sites"],"summary":"Read the social image design","description":"The site-wide Social Image designer scene. Claimed sites need an editor session; an unclaimed site passes.","operationId":"getSiteOgCard","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"scene":{"type":["object","null"],"additionalProperties":true},"feature":{"type":"object","properties":{"perPage":{"type":"boolean"}}}},"required":["scene","feature"]}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session","security":[]},"put":{"tags":["Sites"],"summary":"Save the social image design","operationId":"putSiteOgCard","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"scene":{"type":"object","additionalProperties":true,"description":"Needs `objects` (array) and `background`."}},"required":["scene"]}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session","security":[]}},"/api/v1/sites/{id}/favicon-design":{"get":{"tags":["Sites"],"summary":"Read the favicon design","operationId":"getSiteFaviconDesign","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"scene":{"type":["object","null"],"additionalProperties":true},"renderedMediaId":{"type":["string","null"]}},"required":["scene","renderedMediaId"]}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session","security":[]},"put":{"tags":["Sites"],"summary":"Save the favicon design","operationId":"putSiteFaviconDesign","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"scene":{"type":"object","additionalProperties":true,"description":"Needs `bg` and `mark`."},"renderedMediaId":{"type":"string"}},"required":["scene"]}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session","security":[]}},"/api/v1/sites/{id}/invites":{"get":{"tags":["Team"],"summary":"List members and pending invites","description":"Needs a user key (not a tenant key) with the owner role on the site.","operationId":"listSiteInvites","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"siteId":{"type":"string"},"name":{"type":["string","null"]},"members":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"role":{"type":"string"}}}},"invites":{"type":"array","items":{"$ref":"#/components/schemas/SheetInvite"}}},"required":["siteId","members","invites"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"api-key"},"post":{"tags":["Team"],"summary":"Invite someone to the site","operationId":"inviteSiteMember","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"role":{"type":"string","description":"The role to grant, e.g. `editor`."}},"required":["email","role"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"email":{"type":"string"},"role":{"type":"string"},"alreadyMember":{"type":"boolean"}},"required":["ok"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"502":{"description":"An upstream service (Stripe, Vercel, a mail server, a stock provider) failed or refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"x-auth":"api-key"},"delete":{"tags":["Team"],"summary":"Revoke a pending invite","operationId":"revokeSiteInvite","parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"inviteId","in":"query","required":true,"schema":{"type":"string"},"description":"The invite's id."}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"api-key"}},"/api/v1/sites/{id}/domain":{"get":{"tags":["Domain"],"summary":"Read a site's domain, or preflight one","description":"The attached domain with the DNS records still to create for each variant. `check=<domain>` preflights a candidate instead; a candidate held by another site answers only `available: false`.","operationId":"getSiteDomain","parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"check","in":"query","required":false,"schema":{"type":"string"},"description":"A domain to preflight."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainState"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"api-key","x-rate-limit":"dashboard_settings_save"},"patch":{"tags":["Domain"],"summary":"Attach a domain or change its redirect mode","description":"Needs the owner or agency role. Attaching registers both variants of the pair with the host, and the answer carries the DNS records to create; attaching does not publish the site. Repeating the same call is a no-op that reports the DNS state.","operationId":"setSiteDomain","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":["string","null"],"description":"The domain to attach; null detaches it."},"domainRedirectMode":{"type":"string","enum":["none","to-apex","to-www"]}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainState"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A conflict (`error` says which).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"An upstream service (Stripe, Vercel, a mail server, a stock provider) failed or refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"x-auth":"api-key","x-rate-limit":"dashboard_settings_save"},"delete":{"tags":["Domain"],"summary":"Detach the domain","description":"Needs the owner or agency role. `detachFailures` lists any variant still left on the host project.","operationId":"clearSiteDomain","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"id":{"type":"string"},"domain":{"type":"null"},"domainRedirectMode":{"const":"none"},"detachFailures":{"type":"array","items":{"type":"object","additionalProperties":true}}},"required":["ok","id"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"api-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/chat":{"get":{"tags":["Chat"],"summary":"Read the site chat bot's settings","operationId":"getSiteChat","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/SiteChatConfig"},"creditsAvailable":{"type":"boolean"},"dailyCreditCapDefault":{"type":"integer"},"spentToday":{"type":"number"}},"required":["config"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"},"put":{"tags":["Chat"],"summary":"Update the site chat bot's settings","description":"A partial update; an unknown setting is refused.","operationId":"updateSiteChat","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"},"assistantName":{"type":"string","minLength":1,"maxLength":60},"greeting":{"type":"string","minLength":1,"maxLength":280},"instructions":{"type":"string","maxLength":2000},"leadFields":{"type":"array","items":{"type":"string"},"description":"Needs `email` or `phone`."},"leadCollectionSlug":{"type":["string","null"]},"notifyEmail":{"type":["string","null"]},"dailyCreditCap":{"type":["integer","null"],"minimum":0,"maximum":1000000}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/SiteChatConfig"},"creditsAvailable":{"type":"boolean"},"dailyCreditCapDefault":{"type":"integer"},"spentToday":{"type":"number"}},"required":["config"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/emails":{"get":{"tags":["Emails"],"summary":"List the site's emails","description":"Every email kind, whether the owner customized it, and whether it is sent today.","operationId":"listSiteEmails","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"kind":{"$ref":"#/components/schemas/SiteEmailKind"},"group":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"customized":{"type":"boolean"},"enabled":{"type":"boolean"},"reason":{"type":"string"}},"required":["kind","name","customized","enabled"]}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/emails/brand":{"get":{"tags":["Emails"],"summary":"Read the look of the site's emails","operationId":"getSiteEmailBrand","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailBrandView"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"put":{"tags":["Emails"],"summary":"Change the look of the site's emails","description":"Logo on or off and the button colour, on every plan.","operationId":"putSiteEmailBrand","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"brand":{"type":"object","properties":{"showLogo":{"type":"boolean"},"buttonColor":{"type":"string","enum":["primary","secondary","accent","neutral"]}},"required":["showLogo","buttonColor"]}},"required":["brand"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailBrandView"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/emails/{kind}":{"get":{"tags":["Emails"],"summary":"Read one email","description":"The stored design (null = default) with file versions, the defaults, the kind's variables and slots, sample data, and `feature.customDesigns` (whether the owner's plan saves custom designs).","operationId":"getSiteEmail","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/EmailKind"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"html":{"type":["boolean","string","null"]},"subject":{"type":["string","null"]},"preheader":{"type":["string","null"]},"source":{"type":["string","null"]},"versions":{"type":"object","additionalProperties":true},"defaults":{"type":"object","properties":{"subject":{"type":"string"},"preheader":{"type":"string"},"source":{"type":"string"}}},"variables":{"type":"array","items":{"type":"object","additionalProperties":true}},"slots":{"type":"array","items":{"type":"object","additionalProperties":true}},"sample":{"type":"object","additionalProperties":true},"theme":{"type":["object","null"],"properties":{"text":{"type":"string"},"version":{"type":"string"}}},"image":{"type":"string"},"feature":{"type":"object","properties":{"customDesigns":{"type":"boolean"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"put":{"tags":["Emails"],"summary":"Save one email","description":"`source` (the design) needs a plan with custom designs and an HTML-first site; `subject` and `preheader` save on every plan. The design is checked before saving; problems come back as `errors`. `base` carries the file versions the edit started from.","operationId":"putSiteEmail","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/EmailKind"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"subject":{"type":["string","null"]},"preheader":{"type":["string","null"]},"source":{"type":["string","null"]},"base":{"type":"object","properties":{"source":{"type":"string"},"meta":{"type":"string"}}}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"errors":{"type":"array","items":{"type":"object","additionalProperties":true}},"warnings":{"type":"array","items":{"type":"object","additionalProperties":true}}},"required":["ok"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A conflict (`error` says which).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"The request or file is too large.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"$ref":"#/components/responses/WriteRejected"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"delete":{"tags":["Emails"],"summary":"Reset one email to the default","description":"Both of the email's files are removed.","operationId":"resetSiteEmail","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/EmailKind"}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/emails/{kind}/preview":{"post":{"tags":["Emails"],"summary":"Preview an unsaved email","description":"Renders the draft with sample data. A field the body leaves out uses what is saved. A design with errors renders the default (`usedDefault`).","operationId":"previewSiteEmail","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/EmailKind"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDraft"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailPreview"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/emails/{kind}/test":{"post":{"tags":["Emails"],"summary":"Send a test of an unsaved email","description":"Sends the draft, with sample data, to the signed-in user only. Dashboard session only: an API key is refused. Refused while the design has errors.","operationId":"testSiteEmail","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/EmailKind"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDraft"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"to":{"type":"string"}},"required":["ok","to"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"An upstream service (Stripe, Vercel, a mail server, a stock provider) failed or refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session","x-rate-limit":"dashboard_settings_save","security":[]}},"/api/v1/sites/{id}/email-sender":{"get":{"tags":["Emails"],"summary":"Read the address the site's emails come from","description":"How mail goes out now, the saved SMTP settings (password masked), the last error, and what PageHub sending looks like for this site.","operationId":"getSiteEmailSender","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailSender"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"put":{"tags":["Emails"],"summary":"Save the site's own mail server","description":"Owner or agency role, on a plan with own sender. Saved unverified: nothing changes until the test succeeds. Without `password`, the stored one is kept only if host, port and username are unchanged.","operationId":"putSiteEmailSender","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"fromName":{"type":"string"},"fromEmail":{"type":"string"},"replyTo":{"type":"string"},"host":{"type":"string"},"port":{"type":"integer","description":"One of the allowed SMTP ports (587 for most servers)."},"user":{"type":"string"},"password":{"type":"string","maxLength":512}},"required":["fromEmail","host","port"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailSender"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"delete":{"tags":["Emails"],"summary":"Remove the site's own mail server","description":"Back to PageHub sending. Open on every plan.","operationId":"deleteSiteEmailSender","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailSender"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/email-sender/test":{"post":{"tags":["Emails"],"summary":"Test the site's mail server","description":"Connects, logs in, and sends one email to the signed-in user from the owner's address. Success turns the sender on. Dashboard session only; owner or agency role.","operationId":"testSiteEmailSender","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"to":{"type":"string"}},"required":["ok","to"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A conflict (`error` says which).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"An upstream service (Stripe, Vercel, a mail server, a stock provider) failed or refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"x-auth":"session","x-rate-limit":"dashboard_settings_save","security":[]}},"/api/v1/sites/{id}/stripe":{"get":{"tags":["Storefront"],"summary":"Search the Stripe catalog","description":"Full-catalog tooling (not the visitor storefront). `action` picks the read: `search`, `categories` or `product`.","operationId":"queryStripeCatalog","parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"action","in":"query","required":true,"schema":{"type":"string","enum":["search","categories","product"]},"description":"Required."},{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"With `search`."},{"name":"category","in":"query","required":false,"schema":{"type":"string"},"description":"With `search`."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"With `search`."},{"name":"sort","in":"query","required":false,"schema":{"type":"string"},"description":"With `search`."},{"name":"id","in":"query","required":false,"schema":{"type":"string"},"description":"With `product`: the product id."},{"name":"slug","in":"query","required":false,"schema":{"type":"string"},"description":"With `product`."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","additionalProperties":true}}},"required":["items"]},{"type":"object","properties":{"item":{"type":["object","null"],"additionalProperties":true}},"required":["item"]}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"description":"The method is not allowed for this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"An upstream service (Stripe, Vercel, a mail server, a stock provider) failed or refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"x-auth":"session-or-key","x-rate-limit":"stripe_search_editor"},"post":{"tags":["Storefront"],"summary":"Refresh the products collection from Stripe","description":"`action=refresh` syncs the live catalog into the site's `products` collection.","operationId":"refreshStripeCatalog","parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"action","in":"query","required":true,"schema":{"const":"refresh"},"description":"Required."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"productCount":{"type":"integer"},"added":{"type":"integer"},"updated":{"type":"integer"},"deleted":{"type":"integer"},"syncedAt":{"type":"string","format":"date-time"}},"required":["productCount","syncedAt"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"description":"The method is not allowed for this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"An upstream service (Stripe, Vercel, a mail server, a stock provider) failed or refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"x-auth":"session-or-key","x-rate-limit":"stripe_search_editor"}},"/api/v1/sites/{id}/storefront-settings":{"get":{"tags":["Storefront"],"summary":"Read storefront settings","description":"The switches for the buyer receipt, gift cards, shipping and Stripe Tax, which are all off until turned on.","operationId":"getStorefrontSettings","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorefrontSettings"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"patch":{"tags":["Storefront"],"summary":"Update storefront settings","description":"Merges any subset of the keys. Needs the owner or agency role.","operationId":"updateStorefrontSettings","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"allowPromoCodes":{"type":"boolean"},"orderEmail":{"type":"object","properties":{"enabled":{"type":"boolean"}}},"giftCard":{"type":"object","properties":{"enabled":{"type":"boolean"},"priceIds":{"type":"array","items":{"type":"string"}},"expiryDays":{"type":"integer"}}},"shipping":{"type":"object","additionalProperties":true},"tax":{"type":"object","additionalProperties":true},"collectEmailConsent":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorefrontSettings"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/checkout":{"get":{"tags":["Storefront"],"summary":"List checkout configs","operationId":"listCheckoutConfigs","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"configs":{"type":"array","items":{"$ref":"#/components/schemas/CheckoutConfig"}}},"required":["configs"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"},"post":{"tags":["Storefront"],"summary":"Point checkout at a collection","description":"Maps a collection's price field so its rows can be sold. Map a `priceId` field, or give the collection a numeric `price` field to sell via price data.","operationId":"setCheckoutConfig","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"collectionSlug":{"type":"string"},"provider":{"const":"stripe","default":"stripe"},"fieldMap":{"type":"object","properties":{"priceId":{"type":"string"},"price":{"type":"string"},"inventory":{"type":"string"},"variants":{"type":"string"},"allowPriceData":{"type":"boolean"},"cancellationPolicy":{"description":"A preset key (`flexible`, `moderate`, `firm`, `strict`) or `{ name, tiers }`. Frozen onto each booking at checkout; the Orders screen suggests a refund from it. The owner still presses Refund.","oneOf":[{"type":"string","enum":["flexible","moderate","firm","strict"]},{"type":"object","properties":{"name":{"type":"string"},"tiers":{"type":"array","items":{"type":"object","properties":{"daysBefore":{"type":"integer"},"refundPercent":{"type":"integer"}},"required":["daysBefore","refundPercent"]}}},"required":["name","tiers"]}]}}}},"required":["collectionSlug"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/CheckoutConfig"}},"required":["config"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"delete":{"tags":["Storefront"],"summary":"Remove a collection's checkout config","operationId":"clearCheckoutConfig","parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"collectionSlug","in":"query","required":true,"schema":{"type":"string"},"description":"The collection's slug."}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/orders":{"get":{"tags":["Storefront"],"summary":"List storefront orders","description":"Newest first, read from the site's Stripe account. `stripeConnected: false` when none is connected. Needs the owner or agency role.","operationId":"listSiteOrders","parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"The `nextCursor` of the previous page (a Checkout Session id)."},{"name":"email","in":"query","required":false,"schema":{"type":"string","maxLength":254},"description":"Only this buyer."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"stripeConnected":{"type":"boolean"},"orders":{"type":"array","items":{"$ref":"#/components/schemas/SiteOrder"}},"nextCursor":{"type":["string","null"]},"hasMore":{"type":"boolean"}},"required":["stripeConnected","orders","hasMore"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"An upstream service (Stripe, Vercel, a mail server, a stock provider) failed or refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/orders/{orderId}/fulfilment":{"post":{"tags":["Storefront"],"summary":"Mark an order shipped","description":"Also re-saves tracking. The buyer's shipped email goes out after the response, only from the request that applied the change, and only with `notifyBuyer`.","operationId":"markOrderShipped","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/OrderId"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"carrier":{"type":"string"},"tracking":{"type":"string"},"notifyBuyer":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/SiteOrder"},"emailed":{"type":"boolean"}},"required":["order"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"delete":{"tags":["Storefront"],"summary":"Mark an order not shipped","operationId":"markOrderUnshipped","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/OrderId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/SiteOrder"}},"required":["order"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/orders/{orderId}/refund":{"post":{"tags":["Storefront"],"summary":"Refund an order","description":"Refunds through the site's own Stripe account. This moves money. `amount` is in the smallest currency unit; omit it to refund everything not yet refunded.","operationId":"refundOrder","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/OrderId"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/SiteOrder"}},"required":["order"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/downloads":{"get":{"tags":["Storefront"],"summary":"List hosted file downloads","operationId":"listSiteDownloads","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"fileDownloads":{"type":"string","description":"What the plan allows: `none`, files, or files and executables."},"downloads":{"type":"array","items":{"$ref":"#/components/schemas/Download"}}},"required":["downloads"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"post":{"tags":["Storefront"],"summary":"Publish a download or add a version","description":"Needs the owner or agency role and a plan that hosts files. Send `{ mediaId, filename, name?, downloadId?, label?, notes? }` to publish or add a version, `{ downloadId, currentVersionId }` to roll back or forward, or `{ downloadId, name }` to rename. Any of these may carry `access: { members, plans }`; `access: null` makes it public. The file is a non-image upload by a site member, from private storage or a public one that is moved private here.","operationId":"setSiteDownload","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mediaId":{"type":"string"},"filename":{"type":"string"},"name":{"type":"string"},"downloadId":{"type":"string"},"label":{"type":"string"},"notes":{"type":"string"},"currentVersionId":{"type":"string"},"access":{"type":["object","null"],"properties":{"members":{"type":"boolean"},"plans":{"type":"array","items":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"download":{"$ref":"#/components/schemas/Download"}},"required":["download"]}}}},"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"download":{"$ref":"#/components/schemas/Download"}},"required":["download"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A conflict (`error` says which).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"delete":{"tags":["Storefront"],"summary":"Remove a download or one of its versions","description":"Files no other version uses are deleted too. The current version cannot be removed alone.","operationId":"removeSiteDownload","parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"downloadId","in":"query","required":true,"schema":{"type":"string"}},{"name":"versionId","in":"query","required":false,"schema":{"type":"string"},"description":"Remove only this non-current version."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"deletedFiles":{"type":"array","items":{"type":"string"}}},"required":["ok","deletedFiles"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/collections/sheet-csv":{"post":{"tags":["Collections"],"summary":"Fetch a Google Sheet as CSV","description":"Only for Google Sheets links shared as \"Anyone with the link\". The server fetches the export (the browser can't) through the SSRF guard, up to 8 MB.","operationId":"fetchSheetCsv","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"link":{"type":"string"}},"required":["link"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"csv":{"type":"string"}},"required":["csv"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/collections/{colId}/fields":{"get":{"tags":["Collections"],"summary":"Read a collection's fields and bindings","operationId":"getCollectionFields","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"fields":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"type":{"type":"string"},"required":{"type":"boolean"}},"required":["key","type"]}},"bindings":{"type":"array","items":{"type":"string"}}},"required":["slug","fields","bindings"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"}},"/api/v1/sites/{id}/collections/{colId}/export.csv":{"get":{"tags":["Collections"],"summary":"Export a collection's rows as CSV","description":"One column per schema field, in row order. Served as a file download.","operationId":"exportCollectionCsv","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"}],"responses":{"200":{"description":"The CSV.","content":{"text/csv":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"}},"/api/v1/sites/{id}/collections/{colId}/import":{"post":{"tags":["Collections"],"summary":"Import rows from CSV","description":"Send CSV text as a `text/csv` body or `{ csv }`, or `{ mediaId }` for an uploaded CSV, TSV or XLSX the site's members uploaded. Columns map to schema fields by name. `mode`: `append` (default), `replace` (deletes the rows first), or `upsert` (matches on `externalId` or `id`).","operationId":"importCollectionRows","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"},{"name":"mode","in":"query","required":false,"schema":{"type":"string","enum":["append","replace","upsert"],"default":"append"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"csv":{"type":"string"},"mediaId":{"type":"string"}}}},"text/csv":{"schema":{"type":"string"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"mode":{"type":"string"},"added":{"type":"integer"},"updated":{"type":"integer"},"deleted":{"type":"integer"},"rowCount":{"type":"integer"}},"required":["ok","mode","added","updated","deleted","rowCount"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/collections/{colId}/sync":{"post":{"tags":["Collections"],"summary":"Sync a collection from its source","description":"Runs the collection's source sync (the source type must be allowed on the plan). The answer is the sync result.","operationId":"syncCollection","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"}],"responses":{"200":{"description":"The sync result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/collections/{colId}/sync-logs":{"get":{"tags":["Collections"],"summary":"List a collection's recent syncs","description":"The last 50 runs, newest first.","operationId":"listCollectionSyncLogs","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"logs":{"type":"array","items":{"$ref":"#/components/schemas/CollectionSyncLog"}}},"required":["logs"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"}},"/api/v1/sites/{id}/collections/{colId}/credentials":{"get":{"tags":["Collections"],"summary":"Whether a collection has source credentials","description":"Answers only a boolean; the secrets are never returned.","operationId":"getCollectionCredentials","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"},{"name":"sourceType","in":"query","required":false,"schema":{"type":"string"},"description":"Defaults to the collection's source type."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"hasSecrets":{"type":"boolean"}},"required":["hasSecrets"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"post":{"tags":["Collections"],"summary":"Save a collection's source credentials","description":"Owner or agency role.","operationId":"setCollectionCredentials","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"},{"name":"sourceType","in":"query","required":false,"schema":{"type":"string"},"description":"Defaults to the collection's source type."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"secrets":{"type":"object","additionalProperties":true},"sourceType":{"type":"string"}},"required":["secrets"]}}}},"responses":{"200":{"$ref":"#/components/responses/Ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"delete":{"tags":["Collections"],"summary":"Delete a collection's source credentials","description":"Owner or agency role.","operationId":"deleteCollectionCredentials","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"},{"name":"sourceType","in":"query","required":false,"schema":{"type":"string"},"description":"Defaults to the collection's source type."}],"responses":{"200":{"$ref":"#/components/responses/Ok"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/collections/{colId}/rows/{rowId}/history":{"get":{"tags":["Collections"],"summary":"List a row's revisions","operationId":"listRowHistory","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"},{"name":"rowId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-fA-F0-9]{24}$"},"description":"The row's id."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"revisions":{"type":"array","items":{"$ref":"#/components/schemas/RowRevision"}},"currentVersion":{"type":["integer","null"]}},"required":["revisions"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"post":{"tags":["Collections"],"summary":"Restore a row revision","description":"A normal update seeded from the snapshot, so it validates against the current schema and records a new revision.","operationId":"restoreRowRevision","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"},{"name":"rowId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-fA-F0-9]{24}$"},"description":"The row's id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"revisionId":{"type":"string"}},"required":["revisionId"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"row":{"$ref":"#/components/schemas/CollectionRow"},"restoredFrom":{"type":"integer"}},"required":["row","restoredFrom"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/media/folders":{"get":{"tags":["Media"],"summary":"List media folders","operationId":"listMediaFolders","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"folders":{"type":"array","items":{"$ref":"#/components/schemas/MediaFolder"}}},"required":["folders"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"},"put":{"tags":["Media"],"summary":"Replace the media folders","description":"Replaces all folders. Items in a removed folder move to the top level.","operationId":"replaceMediaFolders","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"folders":{"type":"array","items":{"$ref":"#/components/schemas/MediaFolder"}}},"required":["folders"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"folders":{"type":"array","items":{"$ref":"#/components/schemas/MediaFolder"}}},"required":["folders"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"media_delete"}},"/api/v1/sites/{id}/media/unattached":{"get":{"tags":["Media"],"summary":"List the user's unattached files","description":"The signed-in user's stored files that no library lists. An API key gets an empty list. `canManage` is true for the site's owner, who may add them to the library or delete them.","operationId":"listUnattachedMedia","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"files":{"type":"array","items":{"$ref":"#/components/schemas/UnattachedFile"}},"canManage":{"type":"boolean"}},"required":["files","canManage"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"}},"/api/v1/sites/{id}/media/{mediaId}":{"patch":{"tags":["Media"],"summary":"Update a library file","description":"`metadata` merges key by key; a null value removes a key.","operationId":"updateMediaItem","parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"mediaId","in":"path","required":true,"schema":{"type":"string"},"description":"The file's media id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"order":{"type":"number"},"type":{"type":"string"},"cdnId":{"type":"string"},"metadata":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"media":{"type":"object","additionalProperties":true}},"required":["media"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"media_delete"},"delete":{"tags":["Media"],"summary":"Remove a file from the library","description":"Drops the entry and, for a signed-in caller, deletes the stored file when that is safe. An API key only drops the entry (`deleted: false`). A file in no library is deleted only by the site's owner, and only when nothing uses it (409 `in-use`).","operationId":"deleteMediaItem","parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"mediaId","in":"path","required":true,"schema":{"type":"string"},"description":"The file's media id."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"deleted":{"type":"boolean"},"reason":{"type":"string"},"removed":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A conflict (`error` says which).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"media_delete"}},"/api/v1/sites/{id}/history":{"get":{"tags":["History"],"summary":"List a site's revisions","description":"The timeline, newest first: one row per editing session, publish or restore. `restorable` is false for a version saved before the HTML editor (it can be listed, not diffed or restored). `retention` is the site owner's plan cap. Needs write access to the site (owner, editor, agency).","operationId":"listSiteHistory","parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1},"description":"Page number."},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Rows per page."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"revisions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"integer"},"trigger":{"type":"string","enum":["save","publish","restore"]},"author":{"type":"string"},"source":{"type":["string","null"],"description":"`editor`, `mcp`, `ai`, `api` or null."},"summary":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"restorable":{"type":"boolean"},"pages":{"type":"array","items":{"type":"object","properties":{"pageNodeId":{"type":"string"},"name":{"type":"string"}}}}}}},"pagination":{"type":"object","additionalProperties":true},"retention":{"type":"object","properties":{"plan":{"type":"string"},"cap":{"type":"integer"}}},"allowance":{"type":"object","additionalProperties":true}},"required":["revisions","pagination"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"}},"/api/v1/sites/{id}/history/{revId}":{"parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"revId","in":"path","required":true,"schema":{"type":"string"},"description":"A revision's `id` from the history list."}],"get":{"tags":["History"],"summary":"Read one revision, optionally with what changed","description":"With `diff=prev` (or another revision's id) the answer groups the changes by page: added, removed, changed and moved elements, with class changes as token sets. Capped at 500 changes (`truncated`, `totalChanges`). 409 `LEGACY_REVISION` for a version saved before the HTML editor.","operationId":"getSiteRevision","parameters":[{"name":"diff","in":"query","required":false,"schema":{"type":"string"},"description":"`prev`, or the id of the revision to compare against."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"revision":{"type":"object","additionalProperties":true},"baseline":{"type":["object","null"],"additionalProperties":true},"groups":{"type":"array","items":{"type":"object","additionalProperties":true}},"stats":{"type":["object","null"],"additionalProperties":true},"truncated":{"type":"boolean"},"totalChanges":{"type":"integer"}},"required":["revision"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"LEGACY_REVISION: saved before the HTML editor, so it cannot be compared."}},"x-auth":"session-or-key"}},"/api/v1/sites/{id}/history/{revId}/restore":{"parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"revId","in":"path","required":true,"schema":{"type":"string"},"description":"A revision's `id` from the history list."}],"post":{"tags":["History"],"summary":"Restore a revision into the draft","description":"Writes the revision's files forward into the DRAFT and records a `restore` revision, so a restore is itself undoable; the live release is never touched, publish to make it live. A full `pre-restore` backup is taken first: if it fails the answer is 500 `PRE_RESTORE_BACKUP_FAILED` and nothing changes. `scope: \"page\"` restores one page (needs `pageNodeId`). Owner, editor and agency; a client or viewer cannot.","operationId":"restoreSiteRevision","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"scope":{"type":"string","enum":["site","page"],"default":"site"},"pageNodeId":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"scope":{"type":"string"},"restoredPages":{"type":"array","items":{"type":"string"}},"fromVersion":{"type":"integer"},"newVersion":{"type":["integer","null"]},"revisionId":{"type":["string","null"]},"summary":{"type":"string"}},"required":["ok"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"LEGACY_REVISION or BLOBS_MISSING: this version cannot be restored."},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The pre-restore backup could not be taken; nothing was changed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/review-threads":{"get":{"tags":["Review"],"summary":"List a site's review comments","description":"The client-review threads pinned to the site's preview, newest first, each with its messages (author emails included: you can moderate). The same list the dashboard's Review inbox shows. A signed-in team member's key (owner, editor, agency); a tenant key is refused.","operationId":"listSiteReviewThreads","parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["open","resolved","closed"]},"description":"Default: every status."},{"name":"pageSlug","in":"query","required":false,"schema":{"type":"string"},"description":"A page's slug. Present and empty: the home page. Absent: every page."},{"name":"summary","in":"query","required":false,"schema":{"type":"string","enum":["1"]},"description":"Add `summary` (open, resolved, total, people, lastCommentAt)."},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1},"description":"Page number."},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":100},"description":"Rows per page."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"threads":{"type":"array","items":{"type":"object","additionalProperties":true}},"pagination":{"type":"object","additionalProperties":true},"openCount":{"type":"integer"},"summary":{"type":"object","additionalProperties":true},"actor":{"type":"object","additionalProperties":true}},"required":["threads"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"}},"/api/v1/sites/{id}/review-threads/{threadId}":{"parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"threadId","in":"path","required":true,"schema":{"type":"string"},"description":"A thread's `id` from the review-threads list."}],"post":{"tags":["Review"],"summary":"Reply to a review comment","description":"Posts as the key's user (max 5,000 characters, 100 replies per thread), emails the site's other owners and lands in History > Activity, like a reply from the dashboard. Replying does not reopen a resolved thread. A tenant key is refused.","operationId":"replySiteReviewThread","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string"}},"required":["body"]}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"thread":{"type":"object","additionalProperties":true}},"required":["thread"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"patch":{"tags":["Review"],"summary":"Open, resolve or close a review comment","operationId":"setSiteReviewThreadStatus","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["open","resolved","closed"]}},"required":["status"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"thread":{"type":"object","additionalProperties":true}},"required":["thread"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/submissions":{"get":{"tags":["Forms"],"summary":"List a site's form submissions","description":"The form inbox, newest first, filtered to one state (default `open`). Each row carries its webhook delivery status when the form has a webhook. `formNames` and `summary` ride on the first page. Reading never marks a row read. Visitor details: owner, editor and agency members.","operationId":"listSiteSubmissions","parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"state","in":"query","required":false,"schema":{"type":"string","enum":["open","done","archived","spam"],"default":"open"},"description":"Inbox state."},{"name":"formName","in":"query","required":false,"schema":{"type":"string"},"description":"Only this form."},{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"Matches anywhere in the answers."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"`nextCursor` of the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Rows per page."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"submissions":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"webhook":{"type":"object","additionalProperties":true,"description":"`{ url (origin + path), status: pending|delivered|failed, attempts, lastHttpStatus, lastError, lastAttemptAt, nextAttemptAt, deliveredAt }`."}}}},"hasMore":{"type":"boolean"},"nextCursor":{"type":["string","null"]},"formNames":{"type":"array","items":{"type":"string"}},"summary":{"type":"object","additionalProperties":true}},"required":["submissions","hasMore"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"}},"/api/v1/sites/{id}/submissions/{submissionId}":{"parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"submissionId","in":"path","required":true,"schema":{"type":"string"},"description":"A submission's `_id` from the list."}],"get":{"tags":["Forms"],"summary":"Read one form submission","description":"The answers, how the visitor arrived (`attribution`), the notes and state changes (`activity`) and the webhook delivery. Does not mark the row read.","operationId":"getSiteSubmission","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"submission":{"type":"object","additionalProperties":true,"properties":{"webhook":{"type":"object","additionalProperties":true,"description":"`{ url (origin + path), status: pending|delivered|failed, attempts, lastHttpStatus, lastError, lastAttemptAt, nextAttemptAt, deliveredAt }`."}}}},"required":["submission"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"}},"/api/v1/sites/{id}/submissions/{submissionId}/webhook":{"parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"submissionId","in":"path","required":true,"schema":{"type":"string"},"description":"A submission's `_id` from the list."}],"post":{"tags":["Forms"],"summary":"Resend a submission's webhook","description":"Resets the attempts and queues the delivery again from the start, to the form's current webhook URL. The `submissionId` in the payload never changes. Owners only: a delivery carries the lead's details to a third party. 409 when the submission has no webhook; 502 when it could not be queued.","operationId":"resendSiteSubmissionWebhook","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"webhook":{"type":"object","additionalProperties":true,"description":"`{ url (origin + path), status: pending|delivered|failed, attempts, lastHttpStatus, lastError, lastAttemptAt, nextAttemptAt, deliveredAt }`."}},"required":["webhook"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"This submission has no webhook delivery."},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"The resend could not be queued.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/form-webhooks":{"parameters":[{"$ref":"#/components/parameters/SiteId"}],"get":{"tags":["Forms"],"summary":"List the form webhook destinations and whether each has a signing secret","description":"The destinations the forms on the LIVE release post to (URL without its query string), with the forms that use each. A secret is never returned. Owners only.","operationId":"listSiteFormWebhooks","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"destinations":{"type":"array","items":{"type":"object","properties":{"endpoint":{"type":"string"},"url":{"type":"string"},"forms":{"type":"array","items":{"type":"string"}},"hasSecret":{"type":"boolean"},"rotatedAt":{"type":["string","null"]}}}}},"required":["destinations"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"x-auth":"session-or-key"},"post":{"tags":["Forms"],"summary":"Rotate or set a form webhook's signing secret","description":"`rotate` mints a new secret and returns it ONCE; `set` stores the secret the receiver issued (16 to 256 characters, no spaces) and returns nothing. `endpoint` is the key the list returned: the URL never comes from the caller. Receivers verify `X-PageHub-Signature: sha256=<hex HMAC-SHA256 of \"<X-PageHub-Timestamp>.<raw body>\">`. Owners only.","operationId":"changeSiteFormWebhookSecret","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"endpoint":{"type":"string"},"action":{"type":"string","enum":["rotate","set"]},"secret":{"type":"string"}},"required":["endpoint","action"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"secret":{"type":"string","description":"Only for `rotate`."},"ok":{"type":"boolean","description":"Only for `set`."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Maintenance"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/apps/rss":{"post":{"tags":["Collections"],"summary":"Connect RSS or Atom feed","description":"Fills a collection with the latest posts from a feed. Checks what was sent with the app (a bad key is a 400 with a message to show as is), makes or reuses the collection it fills, stores secrets encrypted, and runs the first sync. Needs an owner or agency role on the site, and an app the site's plan includes (403 with `upgrade: true` otherwise). Secrets are never returned.","operationId":"connectRssApp","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"The feed's address, or a blog's own address (PageHub looks for its feed)."},"name":{"type":"string","description":"The collection's name. Leave it out to use the app's default."}},"required":["url"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppConnectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/apps/notion":{"post":{"tags":["Collections"],"summary":"Connect Notion database","description":"Fills a collection from a Notion database. Checks what was sent with the app (a bad key is a 400 with a message to show as is), makes or reuses the collection it fills, stores secrets encrypted, and runs the first sync. Needs an owner or agency role on the site, and an app the site's plan includes (403 with `upgrade: true` otherwise). Secrets are never returned.","operationId":"connectNotionApp","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"An internal connection token."},"databaseId":{"type":"string","description":"The database's link or id."},"name":{"type":"string","description":"The collection's name. Leave it out to use the app's default."}},"required":["token","databaseId"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppConnectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/apps/calcom":{"post":{"tags":["Collections"],"summary":"Connect Cal.com open times","description":"Shows the next open times of one Cal.com event. Checks what was sent with the app (a bad key is a 400 with a message to show as is), makes or reuses the collection it fills, stores secrets encrypted, and runs the first sync. Needs an owner or agency role on the site, and an app the site's plan includes (403 with `upgrade: true` otherwise). Secrets are never returned.","operationId":"connectCalComApp","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"apiKey":{"type":"string","description":"A Cal.com API key."},"eventLink":{"type":"string","description":"The event's booking link, like `cal.com/yourname/30min`."},"name":{"type":"string","description":"The collection's name. Leave it out to use the app's default."}},"required":["apiKey","eventLink"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppConnectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/apps/eventbrite":{"post":{"tags":["Collections"],"summary":"Connect Eventbrite events","description":"Fills a collection with upcoming Eventbrite events. Checks what was sent with the app (a bad key is a 400 with a message to show as is), makes or reuses the collection it fills, stores secrets encrypted, and runs the first sync. Needs an owner or agency role on the site, and an app the site's plan includes (403 with `upgrade: true` otherwise). Secrets are never returned.","operationId":"connectEventbriteApp","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"An Eventbrite private token."},"organizationId":{"type":"string","description":"Only if there is more than one organization; the first is used otherwise."},"name":{"type":"string","description":"The collection's name. Leave it out to use the app's default."}},"required":["token"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppConnectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/apps/tickettailor":{"post":{"tags":["Collections"],"summary":"Connect Ticket Tailor events","description":"Fills a collection with upcoming Ticket Tailor events. Checks what was sent with the app (a bad key is a 400 with a message to show as is), makes or reuses the collection it fills, stores secrets encrypted, and runs the first sync. Needs an owner or agency role on the site, and an app the site's plan includes (403 with `upgrade: true` otherwise). Secrets are never returned.","operationId":"connectTicketTailorApp","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"apiKey":{"type":"string","description":"A Ticket Tailor API key."},"name":{"type":"string","description":"The collection's name. Leave it out to use the app's default."}},"required":["apiKey"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppConnectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/apps/supabase":{"post":{"tags":["Collections"],"summary":"Connect Supabase table","description":"Fills a collection from a table in a Supabase project's public schema. Checks what was sent with the app (a bad key is a 400 with a message to show as is), makes or reuses the collection it fills, stores secrets encrypted, and runs the first sync. Needs an owner or agency role on the site, and an app the site's plan includes (403 with `upgrade: true` otherwise). Secrets are never returned.","operationId":"connectSupabaseApp","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"projectUrl":{"type":"string","description":"The project URL, like `https://yourproject.supabase.co`."},"table":{"type":"string","description":"The table's name."},"apiKey":{"type":"string","description":"The project's anon public key (not the service role key)."},"name":{"type":"string","description":"The collection's name. Leave it out to use the app's default."}},"required":["projectUrl","table","apiKey"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppConnectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/apps/baserow":{"post":{"tags":["Collections"],"summary":"Connect Baserow table","description":"Fills a collection from a Baserow table. Checks what was sent with the app (a bad key is a 400 with a message to show as is), makes or reuses the collection it fills, stores secrets encrypted, and runs the first sync. Needs an owner or agency role on the site, and an app the site's plan includes (403 with `upgrade: true` otherwise). Secrets are never returned.","operationId":"connectBaserowApp","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"A Baserow database token."},"tableId":{"type":"string","description":"The table's id or its address."},"baseUrl":{"type":"string","description":"Only for a self-hosted Baserow; Baserow's cloud is used otherwise."},"name":{"type":"string","description":"The collection's name. Leave it out to use the app's default."}},"required":["token","tableId"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppConnectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/apps/nocodb":{"post":{"tags":["Collections"],"summary":"Connect NocoDB table","description":"Fills a collection from a NocoDB table. Checks what was sent with the app (a bad key is a 400 with a message to show as is), makes or reuses the collection it fills, stores secrets encrypted, and runs the first sync. Needs an owner or agency role on the site, and an app the site's plan includes (403 with `upgrade: true` otherwise). Secrets are never returned.","operationId":"connectNocoDbApp","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"A NocoDB API token."},"baseUrl":{"type":"string","description":"The address NocoDB is opened at."},"tableId":{"type":"string","description":"The table's id."},"name":{"type":"string","description":"The collection's name. Leave it out to use the app's default."}},"required":["token","baseUrl","tableId"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppConnectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/apps/shopify":{"post":{"tags":["Collections"],"summary":"Connect Shopify products","description":"Fills a collection with a Shopify store's products. Checks what was sent with the app (a bad key is a 400 with a message to show as is), makes or reuses the collection it fills, stores secrets encrypted, and runs the first sync. Needs an owner or agency role on the site, and an app the site's plan includes (403 with `upgrade: true` otherwise). Secrets are never returned.","operationId":"connectShopifyApp","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shop":{"type":"string","description":"The store's myshopify.com address."},"token":{"type":"string","description":"A storefront access token allowed to read products."},"name":{"type":"string","description":"The collection's name. Leave it out to use the app's default."}},"required":["shop","token"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppConnectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/apps/woocommerce":{"post":{"tags":["Collections"],"summary":"Connect WooCommerce products","description":"Fills a collection with a WooCommerce store's products. Checks what was sent with the app (a bad key is a 400 with a message to show as is), makes or reuses the collection it fills, stores secrets encrypted, and runs the first sync. Needs an owner or agency role on the site, and an app the site's plan includes (403 with `upgrade: true` otherwise). Secrets are never returned.","operationId":"connectWooCommerceApp","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"storeUrl":{"type":"string","description":"The store's public address."},"consumerKey":{"type":"string","description":"A REST API consumer key with read access."},"consumerSecret":{"type":"string","description":"The consumer secret shown with the key."},"name":{"type":"string","description":"The collection's name. Leave it out to use the app's default."}},"required":["storeUrl","consumerKey","consumerSecret"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppConnectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/apps/square":{"post":{"tags":["Collections"],"summary":"Connect Square items","description":"Fills a collection with a Square account's items. Checks what was sent with the app (a bad key is a 400 with a message to show as is), makes or reuses the collection it fills, stores secrets encrypted, and runs the first sync. Needs an owner or agency role on the site, and an app the site's plan includes (403 with `upgrade: true` otherwise). Secrets are never returned.","operationId":"connectSquareApp","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"A production access token."},"storeUrl":{"type":"string","description":"The store's link, where items without their own page link to."},"name":{"type":"string","description":"The collection's name. Leave it out to use the app's default."}},"required":["token","storeUrl"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppConnectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/apps/gumroad":{"post":{"tags":["Collections"],"summary":"Connect Gumroad products","description":"Fills a collection with a Gumroad account's products. Checks what was sent with the app (a bad key is a 400 with a message to show as is), makes or reuses the collection it fills, stores secrets encrypted, and runs the first sync. Needs an owner or agency role on the site, and an app the site's plan includes (403 with `upgrade: true` otherwise). Secrets are never returned.","operationId":"connectGumroadApp","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"A Gumroad access token."},"name":{"type":"string","description":"The collection's name. Leave it out to use the app's default."}},"required":["token"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppConnectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/apps/lemonsqueezy":{"post":{"tags":["Collections"],"summary":"Connect Lemon Squeezy products","description":"Fills a collection with a Lemon Squeezy store's products. Checks what was sent with the app (a bad key is a 400 with a message to show as is), makes or reuses the collection it fills, stores secrets encrypted, and runs the first sync. Needs an owner or agency role on the site, and an app the site's plan includes (403 with `upgrade: true` otherwise). Secrets are never returned.","operationId":"connectLemonSqueezyApp","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"apiKey":{"type":"string","description":"A Lemon Squeezy API key."},"storeId":{"type":"string","description":"Only if there is more than one store; the first is used otherwise."},"name":{"type":"string","description":"The collection's name. Leave it out to use the app's default."}},"required":["apiKey"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppConnectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/apps/airtable":{"post":{"tags":["Collections"],"summary":"Connect an Airtable table","description":"Fills a collection from an Airtable table. Reads the table's first records, which checks the token. Checks what was sent with the app (a bad key is a 400 with a message to show as is), makes or reuses the collection it fills, stores secrets encrypted, and runs the first sync. Needs an owner or agency role on the site, on a plan that includes Airtable (Business and up; 403 with `upgrade: true` otherwise). Secrets are never returned.","operationId":"connectAirtableApp","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"An Airtable personal access token."},"base":{"type":"string","description":"The base's id, or the table's address copied from the browser."},"table":{"type":"string","description":"The table's id or its name."},"name":{"type":"string","description":"The collection's name. Leave it out to use the app's default."}},"required":["token","base","table"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppConnectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/apps/hospitable":{"post":{"tags":["Collections"],"summary":"Connect Hospitable","description":"Fills the `properties` collection with the homes in a Hospitable account. Checks what was sent with the app (a bad key is a 400 with a message to show as is), makes or reuses the collection it fills, stores secrets encrypted, and runs the first sync. Needs an owner or agency role on the site, on Pro and up (403 with `upgrade: true` otherwise). Secrets are never returned.","operationId":"connectHospitableApp","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"A Hospitable access token with property read and calendar write access."},"bookingUrl":{"type":"string","description":"Where guests book, an http(s) address. Optional.","format":"uri"}},"required":["token"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppConnectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/apps/hostaway":{"post":{"tags":["Collections"],"summary":"Connect Hostaway","description":"Fills the `properties` collection with the homes in a Hostaway account. Checks what was sent with the app (a bad key is a 400 with a message to show as is), makes or reuses the collection it fills, stores secrets encrypted, and runs the first sync. Needs an owner or agency role on the site, on Pro and up (403 with `upgrade: true` otherwise). Secrets are never returned.","operationId":"connectHostawayApp","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"accountId":{"type":"string","description":"The Hostaway account id."},"apiKey":{"type":"string","description":"The Hostaway API key."},"bookingUrl":{"type":"string","description":"Where guests book, an http(s) address. Optional.","format":"uri"}},"required":["accountId","apiKey"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppConnectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/apps/guesty":{"post":{"tags":["Collections"],"summary":"Connect Guesty","description":"Fills the `properties` collection with the homes in a Guesty account. Checks what was sent with the app (a bad key is a 400 with a message to show as is), makes or reuses the collection it fills, stores secrets encrypted, and runs the first sync. Needs an owner or agency role on the site, on Pro and up (403 with `upgrade: true` otherwise). Secrets are never returned.","operationId":"connectGuestyApp","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"clientId":{"type":"string","description":"The Guesty application's client id."},"clientSecret":{"type":"string","description":"The application's client secret."},"bookingUrl":{"type":"string","description":"Where guests book, an http(s) address. Optional.","format":"uri"}},"required":["clientId","clientSecret"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppConnectResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/privacy":{"get":{"tags":["Sites"],"summary":"Read a site's privacy settings","operationId":"getSitePrivacy","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"privacy":{"$ref":"#/components/schemas/SitePrivacy"}}}}}}}},"put":{"tags":["Sites"],"summary":"Update a site's privacy settings","description":"Send only the fields to change.","operationId":"updateSitePrivacy","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SitePrivacy"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"live":{"type":"boolean"},"privacy":{"$ref":"#/components/schemas/SitePrivacy"}}}}}},"400":{"description":"A field can't be saved. The error says which."}}}},"/api/v1/sites/{id}/apps/calendar-feed":{"get":{"tags":["Collections"],"summary":"List the calendar links a site reads","description":"The links themselves are secret and never returned; each entry carries only its host.","operationId":"listCalendarFeeds","parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"kind","in":"query","required":true,"schema":{"type":"string","enum":["availability","events"]},"description":"`availability` for rental calendars (Airbnb, Vrbo, Booking.com), `events` for Google Calendar, Outlook and Apple Calendar."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"feeds":{"type":"array","items":{"$ref":"#/components/schemas/CalendarFeed"}}},"required":["feeds"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"post":{"tags":["Collections"],"summary":"Connect a calendar link","description":"Reads the link once, adds it (as a home or a calendar), makes the `properties` or `events` collection if the site has none, and runs the first sync. Needs an owner or agency role and Pro or up.","operationId":"connectCalendarFeed","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"app":{"$ref":"#/components/schemas/CalendarAppSlug"},"name":{"type":"string","description":"What to call the home or calendar."},"url":{"type":"string","description":"The calendar's secret link (an iCal address)."},"bookingUrl":{"type":"string","description":"Where guests book. Optional."}},"required":["app","name","url"]}}}},"responses":{"200":{"description":"Connected and synced once.","content":{"application/json":{"schema":{"type":"object","properties":{"collectionId":{"type":"string"},"created":{"type":"boolean"},"found":{"type":"integer","description":"Events in the link just added (events), or booked stays in it (availability)."},"feeds":{"type":"array","items":{"$ref":"#/components/schemas/CalendarFeed"}},"sync":{"$ref":"#/components/schemas/SyncResult"}},"required":["collectionId","created","found","feeds","sync"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"delete":{"tags":["Collections"],"summary":"Stop reading a calendar link","description":"Its rows go with it. Answers with the same shape as connecting, listing the links that remain.","operationId":"removeCalendarFeed","parameters":[{"$ref":"#/components/parameters/SiteId"},{"name":"kind","in":"query","required":true,"schema":{"type":"string","enum":["availability","events"]},"description":"`availability` for rental calendars (Airbnb, Vrbo, Booking.com), `events` for Google Calendar, Outlook and Apple Calendar."},{"name":"feedId","in":"query","required":true,"schema":{"type":"string"},"description":"The link's id, from the list."}],"responses":{"200":{"description":"Removed.","content":{"application/json":{"schema":{"type":"object","properties":{"collectionId":{"type":"string"},"created":{"type":"boolean"},"found":{"type":"integer"},"feeds":{"type":"array","items":{"$ref":"#/components/schemas/CalendarFeed"}},"sync":{"$ref":"#/components/schemas/SyncResult"}},"required":["collectionId","feeds","sync"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/collections/{colId}/booking-pushes":{"get":{"tags":["Collections"],"summary":"List dates closed in a rental tool","description":"The collection's homes' dates that were closed (or failed to close) in their rental tool, newest first, up to 20.","operationId":"listBookingPushes","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"pushes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"home":{"type":"string","description":"The home's title."},"dates":{"type":"string","description":"A readable range of the dates."},"wanted":{"type":"string","description":"Whether the dates should be closed or reopened in the tool (`open` reopens)."},"status":{"type":"string","description":"Where the push stands."},"lastError":{"type":"string","description":"Empty when there was no error."}},"required":["id","home","dates","wanted","status","lastError"]}}},"required":["pushes"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"post":{"tags":["Collections"],"summary":"Run a failed date push again","operationId":"retryBookingPush","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pushId":{"type":"string","description":"The push's id.","pattern":"^[a-fA-F0-9]{24}$"}},"required":["pushId"]}}}},"responses":{"200":{"description":"Run again.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"That push isn't waiting for a retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/collections/{colId}/claim-feeds":{"get":{"tags":["Collections"],"summary":"List the calendar links a collection's homes share","description":"Up to 200 homes. `path` is null until a link is made for the home.","operationId":"listClaimFeeds","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"homes":{"type":"array","items":{"type":"object","properties":{"rowId":{"type":"string"},"title":{"type":"string"},"path":{"type":["string","null"],"description":"Path of the home's `.ics` link on the site's host, or null."}},"required":["rowId","title","path"]}}},"required":["homes"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"post":{"tags":["Collections"],"summary":"Make a home's calendar link","description":"Returns the path of the link other platforms read. With `rotate`, the link is replaced and the old one stops working.","operationId":"createClaimFeed","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rowId":{"type":"string","description":"The home's row id.","pattern":"^[a-fA-F0-9]{24}$"},"rotate":{"type":"boolean"}},"required":["rowId"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/collections/{colId}/rows/{rowId}/claims":{"get":{"tags":["Collections"],"summary":"List the dates taken or closed on a row","operationId":"listRowClaims","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"},{"$ref":"#/components/parameters/RowId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"claims":{"type":"array","items":{"type":"object","properties":{"claimId":{"type":"string"},"key":{"type":"string","description":"The date's key in the row's list."},"status":{"type":"string"},"note":{"type":["string","null"]}},"required":["claimId","key","status","note"]}}},"required":["claims"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"post":{"tags":["Collections"],"summary":"Close dates on a row","description":"Closes open dates by hand. Answers 409 when any of them is already taken.","operationId":"closeRowDates","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"},{"$ref":"#/components/parameters/RowId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"field":{"type":"string","description":"The row's field that lists the dates."},"keys":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":366,"description":"Date keys from that list."},"note":{"type":"string","description":"Shown with the closed dates; trimmed to 200 characters.","maxLength":200}},"required":["field","keys"]}}}},"responses":{"200":{"description":"Closed.","content":{"application/json":{"schema":{"type":"object","properties":{"claimId":{"type":"string"}},"required":["claimId"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"One of the dates is already taken.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"delete":{"tags":["Collections"],"summary":"Reopen dates closed on a row","description":"A claim tied to an order is refused with 409: unbook the order instead.","operationId":"reopenRowDates","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/CollectionId"},{"$ref":"#/components/parameters/RowId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"claimId":{"type":"string","description":"The claim to reopen."}},"required":["claimId"]}}}},"responses":{"200":{"description":"Reopened.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The claim is tied to an order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/orders/{orderId}/approval":{"post":{"tags":["Storefront"],"summary":"Approve or decline a held order","description":"For an order whose collection's checkout holds the payment. Approving charges the held amount through the site's Stripe account (this moves money) and settles the order like a paid one: stock, customer and receipt. Declining releases the hold and emails the guest. A site without Stripe connected answers 404.","operationId":"answerHeldOrder","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/OrderId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"approve":{"type":"boolean","description":"`true` approves, `false` declines."}},"required":["approve"]}}}},"responses":{"200":{"description":"The order after the answer.","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/SiteOrder"}},"required":["order"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/orders/{orderId}/booking":{"delete":{"tags":["Storefront"],"summary":"Reopen the dates an order holds","description":"Never refunds: refunding is its own call. A site without Stripe connected answers 404.","operationId":"unbookOrder","parameters":[{"$ref":"#/components/parameters/SiteId"},{"$ref":"#/components/parameters/OrderId"}],"responses":{"200":{"description":"The order after its dates reopened.","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/SiteOrder"}},"required":["order"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/sites/{id}/promo-codes":{"get":{"tags":["Storefront"],"summary":"List promo codes","description":"The promo codes on the site's own Stripe account, newest first, on and off. Needs the owner or agency role.","operationId":"listPromoCodes","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"promoCodes":{"type":"array","items":{"$ref":"#/components/schemas/PromoCode"}}},"required":["promoCodes"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"post":{"tags":["Storefront"],"summary":"Create a promo code","description":"Creates a coupon and a promotion code on the site's own Stripe account. Give `percentOff` (1 to 99) or `amountOff` in the smallest currency unit with `currency` (default `usd`). On a booking with lodging tax, Stripe takes the same share off every line, so tax is worked out on the price after the code.","operationId":"createPromoCode","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"3 to 32 letters, digits or dashes."},"percentOff":{"type":"integer"},"amountOff":{"type":"integer"},"currency":{"type":"string"},"maxRedemptions":{"type":"integer"},"expiresAt":{"type":"string","format":"date-time"}},"required":["code"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"promoCode":{"$ref":"#/components/schemas/PromoCode"}},"required":["promoCode"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"},"patch":{"tags":["Storefront"],"summary":"Turn a promo code off or on","description":"Redemptions already made stand.","operationId":"setPromoCodeActive","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"active":{"type":"boolean"}},"required":["id","active"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"promoCode":{"$ref":"#/components/schemas/PromoCode"}},"required":["promoCode"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-auth":"session-or-key","x-rate-limit":"dashboard_settings_save"}},"/api/v1/content/{site}/{collection}":{"get":{"summary":"List collection items","operationId":"listContentItems","security":[{},{"bearerAuth":[]}],"parameters":[{"name":"site","in":"path","required":true,"schema":{"type":"string"},"description":"Page id."},{"name":"collection","in":"path","required":true,"schema":{"type":"string"},"description":"Collection slug."},{"name":"q","in":"query","schema":{"type":"string"},"description":"Full-text search."},{"name":"category","in":"query","schema":{"type":"string"}},{"name":"sort","in":"query","schema":{"type":"string"},"description":"e.g. price-asc, newest."},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"minPrice","in":"query","schema":{"type":"integer"}},{"name":"maxPrice","in":"query","schema":{"type":"integer"}},{"name":"filter","in":"query","schema":{"type":"string"},"description":"JSON object of equality constraints, e.g. {\"brand\":\"acme\"}."},{"name":"facet.<key>","in":"query","schema":{"type":"string"},"description":"Comma-separated facet values, e.g. ?facet.color=red,blue."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/NormalizedItem"}},"meta":{"$ref":"#/components/schemas/ListMeta"}},"required":["data","meta"]}}}},"400":{"description":"Missing site or collection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Auth required (private collection)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not authorized for this site","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Collection not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Upstream fetch failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Content"]}},"/api/v1/content/{site}/{collection}/schema":{"get":{"summary":"Describe a collection's fields + response shape","operationId":"getContentSchema","security":[{},{"bearerAuth":[]}],"parameters":[{"name":"site","in":"path","required":true,"schema":{"type":"string"}},{"name":"collection","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"collection":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"isPublic":{"type":"boolean"}}},"fields":{"type":"array","items":{"$ref":"#/components/schemas/FieldDescriptor"}}}}},"required":["data"]}}}},"401":{"description":"Auth required (private collection)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not authorized for this site","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Collection not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Content"]}}},"webhooks":{"fetchPage":{"get":{"tags":["Tenant webhooks"],"summary":"Fetch a white-label tenant's site","description":"Implemented by a tenant on its own server; PageHub calls it. A tenant that owns a custom domain and sets `webhooks.fetchPage` serves that domain's site from its own system instead of a PageHub site. For each request PageHub sends `GET <webhooks.fetchPage>/<host>` (the visitor's host, URL-encoded as the last path segment) with the tenant's token in `x-pagehub-auth`, and renders the requested page from the bundle in the answer. Nothing is stored, and the site reads no PageHub collections, prices or customers. The server must be reachable on a public address; redirects are re-checked at every hop.\n\nAnything but a 200 JSON answer with files and pages (an error status, bad JSON, or a bundle with no files or no pages) is a 404 for the visitor.","operationId":"fetchPageWebhook","security":[{"tenantAuthToken":[]}],"parameters":[{"name":"host","in":"path","required":true,"schema":{"type":"string","example":"www.example.com"},"description":"The host the visitor asked for, lowercased, without a port."}],"responses":{"200":{"description":"The site as an HTML-first bundle.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantBundle"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A `ph_` API key (a user's key, or a tenant's)."},"tenantAuthToken":{"type":"apiKey","in":"header","name":"x-pagehub-auth","description":"The tenant's token, sent by PageHub on every webhook call so the tenant can check the request came from PageHub."}},"parameters":{"SiteId":{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The site's id."},"CollectionId":{"name":"colId","in":"path","required":true,"schema":{"type":"string"},"description":"The collection's id or slug."},"Stage":{"name":"X-PageHub-Stage","in":"header","required":false,"schema":{"const":"review"},"description":"With an API key: read the draft under the staged AI layer and write into that layer instead of the draft."},"EmailKind":{"name":"kind","in":"path","required":true,"schema":{"$ref":"#/components/schemas/SiteEmailKind"},"description":"Which email."},"OrderId":{"name":"orderId","in":"path","required":true,"schema":{"type":"string"},"description":"The order's Stripe Checkout Session id."},"RowId":{"name":"rowId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-fA-F0-9]{24}$"},"description":"The row's id."}},"responses":{"BadRequest":{"description":"The request is malformed or refused (`error`, often `message`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"Unauthorized":{"description":"Missing, malformed or invalid `ph_` API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"Forbidden":{"description":"The key's user lacks the role this needs, or a plan limit applies.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"NotFound":{"description":"No such site, or the key has no access to it, or no such resource under it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"RateLimited":{"description":"Too many requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"Maintenance":{"description":"Writes are paused for maintenance (`error: maintenance`, `Retry-After` header).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"SiteFormatChanged":{"description":"The site is not an HTML-first site (`error: site_format_changed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"WriteRejected":{"description":"A file write was refused: `lint` (422, with `issues` and `path`), too large (413), or another `rejected` reason.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"Ok":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"ComponentId":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]}}}},"ComponentConflict":{"description":"`in_use` (with `usage`) or `version_conflict` (a file changed meanwhile).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","enum":["in_use","version_conflict","bad_request"]},"message":{"type":"string"},"usage":{"type":"object","additionalProperties":true}},"required":["error"]}}}}},"schemas":{"ApiError":{"type":"object","description":"The error body of the management routes. The content API uses `Error` instead.","properties":{"error":{"type":"string","description":"A message, or a code such as `version_conflict`, `lint`, `maintenance`, `site_format_changed`."},"message":{"type":"string"},"code":{"type":"string"},"upgrade":{"type":"boolean","description":"True when the plan is the reason."}},"required":["error"],"additionalProperties":true},"SiteSummary":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":["string","null"]},"domain":{"type":["string","null"]},"title":{"type":["string","null"]},"description":{"type":["string","null"]},"draftId":{"type":"string"},"appVersion":{"type":["string","number","null"]},"published":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["_id"]},"Site":{"type":"object","properties":{"id":{"type":"string"},"draftId":{"type":"string"},"name":{"type":["string","null"]},"title":{"type":["string","null"]},"description":{"type":["string","null"]},"ogImage":{"type":["object","string","null"]},"domain":{"type":["string","null"]},"previewUrl":{"type":"string","description":"The draft preview (`/view/<draftId>`), on the site's own preview host when per-site previews are on. Share this link; never build a preview URL by hand."},"portal":{"type":["object","null"]},"crawlPolicy":{"type":"object","additionalProperties":true},"timezone":{"type":["string","null"],"description":"The business's IANA zone, e.g. `America/Los_Angeles`. Liquid dates (`date`, `time_tag`), `{{ year }}`, `{{ site.timezone }}` and the times in notification emails use it; unset or empty means UTC."},"language":{"type":["string","null"]},"mountOrigin":{"type":["string","null"]},"mountKey":{"type":"string","description":"Only for a tenant key or an owner or agency user."},"published":{"type":"boolean"},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","published","updatedAt"]},"SiteUpdated":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]},"title":{"type":["string","null"]},"description":{"type":["string","null"]},"ogImage":{"type":["object","string","null"]},"portal":{"type":["object","null"]},"crawlPolicy":{"type":"object","additionalProperties":true},"timezone":{"type":["string","null"]},"language":{"type":["string","null"],"description":"Present when `language` was sent."},"mountOrigin":{"type":["string","null"]},"mountKey":{"type":"string","description":"Present only when this request issued a new key."},"published":{"type":"boolean"},"updatedAt":{"type":"string","format":"date-time"},"hasUnpublishedChanges":{"type":"boolean","description":"Whether the draft differs from the live release."},"unpublishedPages":{"description":"Which pages hold unpublished changes."},"lastPublishedAt":{"type":["string","null"],"format":"date-time"},"release":{"type":"object","description":"Present after a publish: the new release, or `{ queued: true }` when it builds in the background (poll the release).","properties":{"queued":{"type":"boolean"},"releaseId":{"type":"string"},"number":{"type":"integer"},"activated":{"type":"boolean"}}}},"required":["id","published","updatedAt"]},"FileList":{"type":"object","properties":{"paths":{"type":"array","items":{"type":"string"}},"versions":{"type":"object","additionalProperties":{"type":"string"}},"version":{"type":"integer"}},"required":["paths","versions","version"]},"FileSearch":{"type":"object","properties":{"matches":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"line":{"type":"integer","description":"1-based."},"text":{"type":"string","description":"The line around the match: up to 80 characters each side, `…` where cut."},"column":{"type":"integer","description":"1-based column of the match in the line."},"before":{"type":"array","items":{"type":"string"},"description":"With `context`: the lines before."},"after":{"type":"array","items":{"type":"string"},"description":"With `context`: the lines after."}},"required":["path","line","column","text"]}},"truncated":{"type":"boolean"}},"required":["matches","truncated"]},"SitePage":{"type":"object","properties":{"pageId":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string","description":"The page's own URL segment; empty for the home page."},"file":{"type":["string","null"],"description":"The page's file in the draft."},"url":{"type":"string"},"isHomePage":{"type":"boolean"},"is404Page":{"type":"boolean"},"isHidden":{"type":"boolean"},"pathPattern":{"type":["string","null"]},"seo":{"type":["object","null"],"additionalProperties":true}},"required":["pageId","name","slug","file","url","isHomePage","is404Page","isHidden","pathPattern","seo"]},"Allowance":{"type":"object","description":"How much of the owner's plan cap is used.","additionalProperties":true},"CollectionField":{"type":"object","description":"One field of a collection's schema (validated by the server).","properties":{"key":{"type":"string"},"label":{"type":"string"},"type":{"type":"string"},"required":{"type":"boolean"}},"required":["key"],"additionalProperties":true},"Collection":{"type":"object","properties":{"_id":{"type":"string"},"pageId":{"type":"string","description":"The site's id."},"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"schema":{"type":"array","items":{"$ref":"#/components/schemas/CollectionField"}},"source":{"type":"object","additionalProperties":true},"isPublic":{"type":"boolean"},"requiresCustomerLogin":{"type":"boolean"},"rowCount":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["_id","slug","name"],"additionalProperties":true},"CollectionRow":{"type":"object","properties":{"_id":{"type":"string"},"collectionId":{"type":"string"},"data":{"type":"object","additionalProperties":true,"description":"The row's values by field key."},"order":{"type":"number"},"externalId":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["_id","data"],"additionalProperties":true},"RowInput":{"type":"object","description":"A row: `{ data, order?, externalId? }`, or the bare field object.","properties":{"data":{"type":"object","additionalProperties":true},"order":{"type":"number"},"externalId":{"type":"string"}},"additionalProperties":true},"MediaSigned":{"type":"object","description":"The sign step's answer.","properties":{"mediaId":{"type":"string"},"uploadURL":{"type":"string","format":"uri"},"destination":{"type":"string","enum":["cf-images","r2","r2-private"]},"type":{"type":"string","enum":["cdn","r2","r2-private"]},"method":{"type":"string","enum":["POST","PUT"]},"formField":{"type":"string","description":"Images: the multipart field to send the file in."},"contentType":{"type":"string","description":"R2: the `Content-Type` the `PUT` must send."},"url":{"type":"string","description":"Where the file is served once uploaded (not for private files)."},"ticket":{"type":"string","description":"Send this to the confirm step."}},"required":["mediaId","uploadURL","destination","method","ticket"]},"MediaFile":{"type":"object","description":"A confirmed or imported file.","properties":{"siteId":{"type":"string"},"mediaId":{"type":"string"},"type":{"type":"string","enum":["cdn","r2","r2-private"]},"url":{"type":"string"},"contentType":{"type":"string"},"size":{"type":"number","description":"Private files only."},"registered":{"type":"boolean","description":"Whether the file was added to the site's library."}},"required":["mediaId","type","registered"]},"TenantBundle":{"type":"object","description":"A site as HTML-first files plus its page rows (the shape a template stores).","properties":{"title":{"type":"string"},"description":{"type":"string"},"files":{"description":"The site's files: `{ path: text }`, or a list of `{ path, text }`. Paths are the site's own: `site.json`, `theme.css`, `pages/index.liquid`, `partials/header.liquid`, ...","oneOf":[{"type":"object","additionalProperties":{"type":"string"}},{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"text":{"type":"string"}},"required":["path","text"]}}]},"pages":{"type":"array","items":{"$ref":"#/components/schemas/TenantPageRow"},"minItems":1}},"required":["files","pages"]},"TenantPageRow":{"type":"object","description":"One page of the bundle. Rows without a `pageNodeId`, or without a `sourcePath` (unless `is404Page`), are ignored.","properties":{"pageNodeId":{"type":"string"},"sourcePath":{"type":["string","null"],"description":"The page's file, e.g. `pages/about.liquid`."},"displayName":{"type":["string","null"]},"pageSlug":{"type":["string","null"],"description":"The page's URL segment; empty for the home page."},"isHomePage":{"type":"boolean"},"is404Page":{"type":"boolean"},"isHidden":{"type":"boolean"},"pathPattern":{"type":["string","null"],"description":"URL tail after the slug for a detail page, e.g. `:handle`."},"policyDraft":{"type":["object","null"],"additionalProperties":true},"seoDraft":{"type":["object","null"],"additionalProperties":true}},"required":["pageNodeId"]},"StockImage":{"type":"object","description":"A stock photo record.","additionalProperties":true,"properties":{"photoId":{"type":"string"},"url":{"type":"string"},"category":{"type":"string"},"orientation":{"type":"string"},"source":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"uses":{"type":"integer"},"verified":{"type":"boolean"}}},"StockVideo":{"type":"object","description":"A stock video from a provider.","additionalProperties":true},"TemplateSummary":{"type":"object","description":"A template in the catalog.","additionalProperties":true,"properties":{"slug":{"type":"string"},"title":{"type":"string"}}},"Preset":{"type":"object","description":"A curated theme bundle.","properties":{"presetId":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"mood":{"type":"array","items":{"type":"string"}},"palette":{"description":"The palette (shape defined by the theme system)."},"styleGuide":{"type":"object","additionalProperties":true},"fonts":{"type":"object","additionalProperties":true},"style":{},"isPublic":{"type":"boolean"},"tenant":{"type":["string","null"]}},"required":["presetId","name"],"additionalProperties":true},"LibraryComponent":{"type":"object","description":"A block in the component library. A block is its Liquid `source`.","properties":{"_id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"visual":{"type":"string"},"category":{"type":"string"},"subcategory":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"styles":{"type":"array","items":{"type":"string"}},"preset":{"type":"string"},"group":{"type":"string"},"blockType":{"type":"string"},"source":{"type":"string","description":"The block's Liquid (omitted from lists)."},"isPublic":{"type":"boolean"},"isFeatured":{"type":"boolean"},"isCategoryPreview":{"type":"boolean"},"likes":{"type":"integer"},"uses":{"type":"integer"},"version":{"type":"integer"},"author":{"type":"string"},"tenant":{"type":["string","null"]}},"required":["_id","slug","name"],"additionalProperties":true},"LiquidBlockSummary":{"type":"object","additionalProperties":true,"properties":{"slug":{"type":"string"},"name":{"type":"string"},"category":{"type":"string"},"subcategory":{"type":"string"},"styles":{"type":"array","items":{"type":"string"}},"description":{"type":"string"},"saved":{"type":"boolean"}},"required":["slug","name","category"]},"AuditResult":{"type":"object","properties":{"path":{"type":"string"},"status":{"type":"integer"},"kind":{"type":"string","enum":["seo","accessibility"]},"results":{"type":"array","items":{"type":"object","additionalProperties":true}},"redirect":{"type":"string","description":"Present when the page redirects (`status: 302`)."}},"required":["path","status","kind","results"]},"Download":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"currentVersionId":{"type":"string"},"downloadCount":{"type":"integer"},"lastDownloadedAt":{"type":["string","null"],"format":"date-time"},"access":{"type":"object","properties":{"members":{"type":"boolean"},"plans":{"type":"array","items":{"type":"string"}}}},"versions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"notes":{"type":["string","null"]},"filename":{"type":"string"},"size":{"type":"number"},"createdAt":{"type":"string","format":"date-time"},"current":{"type":"boolean"}}}}},"required":["id","name","url","currentVersionId","versions"]},"StorefrontSettings":{"type":"object","properties":{"allowPromoCodes":{"type":"boolean"},"orderEmail":{"type":"object","properties":{"enabled":{"type":"boolean"}}},"giftCard":{"type":"object","properties":{"enabled":{"type":"boolean"},"priceIds":{"type":"array","items":{"type":"string"}},"expiryDays":{"type":"integer"}}},"shipping":{"type":"object","additionalProperties":true,"description":"Shipping settings (`enabled`, the countries shipped to, rates)."},"tax":{"type":"object","additionalProperties":true,"description":"Stripe Tax settings (`enabled`, ...)."},"taxStatus":{"type":["string","null"],"description":"Stripe Tax's status for the account; null when tax is off or Stripe can't be asked."},"collectEmailConsent":{"type":"boolean","description":"Ask buyers for email consent with Stripe's own box (US buyers). Checkout leaves the box out until the Stripe account has accepted Stripe's terms for it."}}},"SiteOrder":{"type":"object","properties":{"id":{"type":"string","description":"The Stripe Checkout Session id."},"created":{"type":"string","format":"date-time"},"mode":{"type":"string"},"buyer":{"type":"object","properties":{"name":{"type":["string","null"]},"email":{"type":["string","null"]}}},"items":{"type":"array","items":{"type":"object","properties":{"description":{"type":"string"},"quantity":{"type":"integer"},"amount":{"type":"number"}}}},"moreItems":{"type":"boolean"},"amount":{"type":"number","description":"Smallest currency unit."},"currency":{"type":"string"},"payment":{"type":"string","enum":["paid","pending","refunded","partially_refunded"]},"amountRefunded":{"type":"number"},"refundable":{"type":"boolean"},"shipping":{"type":["object","null"],"properties":{"name":{"type":["string","null"]},"address":{"type":"string"}}},"fulfilment":{"type":"object","properties":{"status":{"type":"string","enum":["unfulfilled","shipped"]},"shippedAt":{"type":["string","null"],"format":"date-time"},"carrier":{"type":["string","null"]},"tracking":{"type":["string","null"]},"trackingUrl":{"type":["string","null"]}}},"cancellation":{"type":["object","null"],"description":"The cancellation policy this booking was made under, frozen at checkout; null when the home had none. `refundable` is what was paid after any promo code, split into the nights with their tax and the cleaning fee.","properties":{"name":{"type":"string"},"tiers":{"type":"array","items":{"type":"object","properties":{"daysBefore":{"type":"integer"},"refundPercent":{"type":"integer"}}}},"sentence":{"type":"string"},"checkIn":{"type":"string","format":"date"},"timeZone":{"type":["string","null"]},"refundable":{"type":"object","properties":{"nightsAndTax":{"type":"number"},"fee":{"type":"number"}}}}}},"required":["id","created","amount","currency","payment","fulfilment"]},"CheckoutConfig":{"type":"object","properties":{"pageId":{"type":"string"},"collectionSlug":{"type":"string"},"provider":{"type":"string"},"fieldMap":{"type":"object","properties":{"priceId":{"type":"string"},"price":{"type":"string"},"inventory":{"type":"string"},"variants":{"type":"string"},"allowPriceData":{"type":"boolean"},"cancellationPolicy":{"type":"object","description":"The stored policy: `{ name, tiers: [{ daysBefore, refundPercent }] }`, most days first.","properties":{"name":{"type":"string"},"tiers":{"type":"array","items":{"type":"object","properties":{"daysBefore":{"type":"integer"},"refundPercent":{"type":"integer"}}}}}}}}},"additionalProperties":true},"DomainVariant":{"type":"object","description":"One hostname of the domain pair and its Vercel and DNS state.","properties":{"name":{"type":"string"},"attachedToPagehub":{"type":"boolean"},"available":{"type":"boolean"},"redirect":{"type":["string","null"]},"redirectStatusCode":{"type":["integer","null"]},"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}},"misconfigured":{"type":"boolean"},"live":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","additionalProperties":true}},"configError":{"type":"string"}},"required":["name","attachedToPagehub","available"],"additionalProperties":true},"DomainState":{"type":"object","properties":{"ok":{"type":"boolean"},"id":{"type":"string"},"domain":{"type":["string","null"]},"domainRedirectMode":{"type":"string","enum":["none","to-apex","to-www"]},"checked":{"type":"string"},"pair":{"type":["object","null"],"description":"`{ apex, www }` for an apex or www domain, else `{ primary }`.","additionalProperties":true},"variants":{"type":"array","items":{"$ref":"#/components/schemas/DomainVariant"}},"allowance":{"$ref":"#/components/schemas/Allowance","description":"The owner's custom-domain meter; null when no plan can be resolved."},"noop":{"type":"boolean"},"detachFailures":{"type":"array","items":{"type":"object","additionalProperties":true}},"available":{"type":"boolean"},"reason":{"type":"string"},"message":{"type":"string"}},"required":["id","domain"],"additionalProperties":true},"SiteEmailKind":{"type":"string","enum":["order_receipt","subscription_welcome","order_shipped","booking_confirmed","booking_requested","booking_declined","gift_card_gifted","gift_card_self","customer_login","download_delivery","download_update"]},"EmailPreview":{"type":"object","properties":{"subject":{"type":"string"},"html":{"type":"string"},"text":{"type":"string"},"errors":{"type":"array","items":{"type":"object","additionalProperties":true}},"warnings":{"type":"array","items":{"type":"object","additionalProperties":true}},"usedDefault":{"type":"boolean"}},"required":["subject","html","text"],"additionalProperties":true},"EmailDraft":{"type":"object","properties":{"subject":{"type":["string","null"]},"preheader":{"type":["string","null"]},"source":{"type":["string","null"],"description":"The email's Liquid design; `null` or empty resets it to the default."}}},"MailSender":{"type":"object","properties":{"mode":{"type":"string","enum":["pagehub","smtp"]},"fromName":{"type":"string"},"fromEmail":{"type":"string"},"replyTo":{"type":"string"},"smtp":{"type":["object","null"],"properties":{"host":{"type":"string"},"port":{"type":"integer"},"userMasked":{"type":"string"},"hasPassword":{"type":"boolean"}}},"verifiedAt":{"type":["string","null"],"format":"date-time"},"lastError":{"type":"string"},"lastErrorAt":{"type":["string","null"],"format":"date-time"},"consecutiveFailures":{"type":"integer"},"pagehub":{"type":"object","properties":{"from":{"type":"string"},"replyTo":{"type":["string","null"]}}},"feature":{"type":"object","properties":{"enabled":{"type":"boolean"}}}},"required":["mode","pagehub","feature"]},"SiteChatConfig":{"type":"object","properties":{"enabled":{"type":"boolean"},"assistantName":{"type":"string"},"greeting":{"type":"string"},"instructions":{"type":"string"},"leadFields":{"type":"array","items":{"type":"string"}},"leadCollectionSlug":{"type":["string","null"]},"notifyEmail":{"type":["string","null"]},"dailyCreditCap":{"type":["integer","null"],"minimum":0,"maximum":1000000,"description":"Credits per UTC day; null uses the plan's default."},"updatedAt":{"type":["string","null"],"format":"date-time"}}},"StagedFile":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","changed","deleted"]},"before":{"type":["string","null"]},"after":{"type":["string","null"]},"base":{"type":["string","null"]}},"required":["path","change"]},"SheetInvite":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"role":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","email","role"]},"RowRevision":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"integer"},"status":{"type":"string"},"author":{"type":"string"},"summary":{"type":"string"},"data":{"type":"object","additionalProperties":true},"createdAt":{"type":"string","format":"date-time"}},"required":["id","version"]},"CollectionSyncLog":{"type":"object","additionalProperties":true,"description":"One sync run of a collection (`startedAt`, outcome, counts)."},"UnattachedFile":{"type":"object","additionalProperties":true,"properties":{"mediaId":{"type":"string"},"destination":{"type":"string"},"url":{"type":["string","null"]}}},"MediaFolder":{"type":"object","additionalProperties":true},"EmailBrandView":{"type":"object","properties":{"brand":{"type":"object","properties":{"showLogo":{"type":"boolean"},"buttonColor":{"type":"string","enum":["primary","secondary","accent","neutral"]}},"required":["showLogo","buttonColor"]},"swatches":{"type":"object","additionalProperties":{"type":"string"},"description":"The site theme's real colour (`#hex`) for each button token."}},"required":["brand","swatches"]},"FileRead":{"type":"object","properties":{"files":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"text":{"type":"string"},"version":{"type":"string"}},"required":["path","text","version"]}},"missing":{"type":"array","items":{"type":"string"},"description":"Asked for, not in the draft."},"skipped":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"chars":{"type":"integer"}},"required":["path","chars"]},"description":"In the draft, left out because the answer reached its size cap; read them alone."}},"required":["files","missing","skipped"]},"NestingRepairs":{"type":"array","description":"The nesting fixes the server made to the text it stored (a stray `</p>`, an unclosed `<li>`), per file. Absent when it stored exactly what was sent.","items":{"type":"object","properties":{"path":{"type":"string"},"fixes":{"type":"array","items":{"type":"object","properties":{"message":{"type":"string"},"line":{"type":"integer"}}}}},"required":["path","fixes"]}},"PageFromFile":{"type":"object","properties":{"path":{"type":"string","description":"A new `pages/<slug>.liquid` file in `changes` (`pages/shop/[handle].liquid` makes `/shop/:handle`). Its route is read from the path."},"name":{"type":"string","description":"Display name; default from the file name."},"seo":{"type":"object","description":"The page's SEO, as the pages route takes it."},"pathPattern":{"type":["string","null"]}},"required":["path"]},"OutlineNode":{"type":"object","properties":{"kind":{"type":"string","enum":["section","heading","render","block"]},"file":{"type":"string","description":"The page file, or the partial the node was expanded from."},"line":{"type":"integer"},"endLine":{"type":"integer"},"tag":{"type":"string"},"label":{"type":"string"},"id":{"type":"string"},"classes":{"type":"string"},"component":{"type":"object","properties":{"id":{"type":"string"},"uses":{"type":"integer"},"pages":{"type":"integer"}}},"missing":{"type":"boolean","description":"A render of a partial that has no file."},"children":{"type":"array","items":{"$ref":"#/components/schemas/OutlineNode"}}},"required":["kind","file","line","endLine","tag","label","children"]},"PageOutline":{"type":"object","properties":{"page":{"type":"object","properties":{"pageId":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"file":{"type":"string"}},"required":["file"]},"outline":{"type":"object","properties":{"path":{"type":"string"},"lines":{"type":"integer"},"nodes":{"type":"array","items":{"$ref":"#/components/schemas/OutlineNode"}},"truncated":{"type":"boolean"}},"required":["path","lines","nodes","truncated"]}},"required":["page","outline"]},"SitePrivacy":{"type":"object","properties":{"consentMode":{"type":"string","enum":["off","eu","all"],"description":"When the site's third-party tags wait for the visitor's consent: never, for EU and UK visitors, or for everyone."},"privacyUrl":{"type":"string","maxLength":500,"description":"The site's privacy page, a path like /privacy or an https address. Linked from the built-in cookie banner."},"builtInAnalytics":{"type":"boolean","description":"PageHub's own visitor analytics. False removes its script from the site's pages."},"submissionRetentionDays":{"type":"integer","enum":[0,30,90,180,365],"description":"Days before form submissions are deleted. 0 keeps them until someone deletes them."}}},"SyncResult":{"type":"object","description":"What one run of a collection's source sync did.","properties":{"status":{"type":"string","enum":["ok","error","partial"]},"rowsAdded":{"type":"integer"},"rowsUpdated":{"type":"integer"},"rowsDeleted":{"type":"integer"},"error":{"type":"string","description":"Why the sync failed or only partly ran."},"logId":{"type":"string","description":"The sync log entry (see `…/sync-logs`)."}},"required":["status","rowsAdded","rowsUpdated","rowsDeleted","logId"]},"AppConnectResult":{"type":"object","description":"The answer to a one-step app connect: the collection the app fills and the first sync. The count field depends on the app family: `count` (data-source apps), `records` (Airtable), `homes` (rental tools).","properties":{"collectionId":{"type":"string","description":"The collection the app fills."},"created":{"type":"boolean","description":"False when an existing collection for this app was reused."},"count":{"type":"integer","description":"Rows in the first read (data-source apps)."},"records":{"type":"integer","description":"Records in the sample Airtable returned, up to 100; the sync reads them all (Airtable)."},"homes":{"type":"integer","description":"Homes the credentials can read (rental tools)."},"sync":{"$ref":"#/components/schemas/SyncResult"}},"required":["collectionId","created","sync"]},"CalendarFeed":{"type":"object","description":"A calendar link a site reads. The link is secret and never returned; only its host is.","properties":{"id":{"type":"string"},"name":{"type":"string"},"app":{"$ref":"#/components/schemas/CalendarAppSlug"},"host":{"type":"string","description":"Where the link points, without its path: the path is what makes it private."}},"required":["id","name","host"]},"CalendarAppSlug":{"type":"string","enum":["airbnb","vrbo","booking-com","google-calendar","outlook","apple-calendar"]},"PromoCode":{"type":"object","properties":{"id":{"type":"string","description":"The Stripe promotion code id (`promo_...`)."},"code":{"type":"string"},"active":{"type":"boolean"},"percentOff":{"type":["number","null"]},"amountOff":{"type":["number","null"],"description":"Smallest currency unit."},"currency":{"type":["string","null"]},"timesRedeemed":{"type":"integer"},"maxRedemptions":{"type":["integer","null"]},"expiresAt":{"type":["string","null"],"format":"date-time"}},"required":["id","code","active","timesRedeemed"]},"NormalizedItem":{"type":"object","description":"A collection row mapped to the canonical item shape. Non-reserved fields ALSO appear under `metadata.<key>` and flat at `item.<key>`; use the /schema endpoint to learn each field's path.","properties":{"id":{"type":"string"},"title":{"type":"string"},"slug":{"type":"string"},"image":{"type":"string","description":"First image field, if any."},"images":{"type":"array","items":{"type":"string"}},"price":{"type":"object","properties":{"amount":{"type":"number","description":"Minor units (e.g. cents)."},"currency":{"type":"string"},"formatted":{"type":"string"}}},"metadata":{"type":"object","additionalProperties":true,"description":"Every non-reserved field, keyed by its field key."}},"required":["id","title","metadata"],"additionalProperties":true},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]},"ListMeta":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"count":{"type":"integer"},"totalCount":{"type":"integer"},"totalPages":{"type":"integer"},"hasMore":{"type":"boolean"}},"required":["page","limit","count","totalCount","totalPages","hasMore"]},"FieldDescriptor":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"type":{"type":"string"},"required":{"type":"boolean"},"responsePath":{"type":"string","description":"Where the field reads in an item, e.g. \"title\", \"price.amount\", \"metadata.color\"."},"flatAlias":{"type":"boolean","description":"Also available at item.<key>."}},"required":["key","label","type","required","responsePath","flatAlias"]}}}}