{
	"openapi": "3.1.0",
	"info": {
		"title": "Cactal API",
		"version": "1.0.0",
		"description": "The Cactal public API. Create, edit, publish, and operate websites programmatically. Authenticate every request with an API key sent as `Authorization: Bearer <key>`."
	},
	"servers": [
		{
			"url": "https://api.cactal.ai/v1"
		}
	],
	"security": [
		{
			"apiKey": []
		}
	],
	"tags": [
		{
			"name": "Documentation",
			"description": "Search and read the Cactal product documentation for concepts, guides, agent workflows, platform behavior, and API operations."
		},
		{
			"name": "Feedback",
			"description": "Submit free-form product feedback from users and agents to the Cactal feedback inbox."
		},
		{
			"name": "Websites",
			"description": "Create and manage websites — the top-level resource that owns source code, content, assets, domains, and analytics."
		},
		{
			"name": "Website editors",
			"description": "Grant, list, and revoke website-scoped editor access, and invite collaborators to a single website by email."
		},
		{
			"name": "API keys",
			"description": "Create, scope, rotate, and revoke the API keys that authenticate programmatic and agent access."
		},
		{
			"name": "Source code",
			"description": "Read and edit the framework source files of a website draft. Mutations require an edit lease."
		},
		{
			"name": "Publishing",
			"description": "Validate, build, publish, and roll back website versions."
		},
		{
			"name": "CMS collections",
			"description": "Define the content model: collections of structured content owned by a website."
		},
		{
			"name": "CMS fields",
			"description": "Manage the typed fields that make up a collection schema."
		},
		{
			"name": "CMS items",
			"description": "Create, query, publish, and organize the content entries inside a collection."
		},
		{
			"name": "Domains",
			"description": "Manage platform subdomains and custom domains, including DNS verification and the primary domain."
		},
		{
			"name": "Assets",
			"description": "Upload and manage website files and images served from the Cactal CDN."
		},
		{
			"name": "Media generation",
			"description": "Generate reference-guided website images that are stored as ordinary Cactal CDN assets."
		},
		{
			"name": "Web research",
			"description": "Look at the public web for a website's work: screenshots and measurements of external pages, a business's own pages read into profiles, and many images at once on numbered contact sheets."
		},
		{
			"name": "Project Context",
			"description": "Upload private durable reference material that the website agent can search, read, and inspect."
		},
		{
			"name": "Analytics",
			"description": "Read first-party traffic analytics for a website and export datasets as CSV."
		},
		{
			"name": "Forms",
			"description": "Read and triage website form submissions, manage notification recipients, and configure per-form settings. Visitors submit to `POST /_cactal/forms/{name}` on the website itself."
		},
		{
			"name": "Server functions",
			"description": "Inspect a website's server function runs and run its `http` functions on the draft. Visitors call `http` functions with `POST /_framework/server/{name}` on the website itself."
		},
		{
			"name": "Secrets",
			"description": "Manage a website's write-only secrets, which its server functions read from `env`. Values can be set and deleted but are never returned."
		},
		{
			"name": "Organizations",
			"description": "Manage organizations, members, and organization-wide invitations."
		},
		{
			"name": "Audit log",
			"description": "Read the immutable record of actions performed in an organization by users and API keys."
		},
		{
			"name": "Billing",
			"description": "Read billing state and manage plans, site capacity, and prepaid usage balance. Every billing operation requires the organization owner role, which API keys cannot hold — today these operations are performed from the dashboard, return 403 for API-key callers, and are hidden from MCP tool lists."
		}
	],
	"paths": {
		"/billing/state": {
			"get": {
				"operationId": "billing.state.get",
				"summary": "Get billing state",
				"description": "Returns the organization’s provider-neutral billing state: subscription status, plan-differentiated capability flags, the AI credit meter, the prepaid credit balance purchased through top-ups, credits granted by Cactal support with their remaining balance and expiry, and capacity for websites with custom domains. Requires the organization owner.",
				"tags": [
					"Billing"
				],
				"parameters": [
					{
						"name": "organizationId",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Current billing state for the organization.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"organizationId": {
											"type": "string",
											"description": "Organization this billing state belongs to."
										},
										"status": {
											"type": "string",
											"enum": [
												"no_subscription",
												"active",
												"billing_grace"
											],
											"description": "Provider-neutral subscription status. `billing_grace` means past due: existing websites keep serving, but paid changes and metered consumption are blocked. The auto-enabled free plan reports `no_subscription`."
										},
										"complimentary": {
											"type": "boolean",
											"description": "True when the organization has the manually assigned, no-charge complimentary plan."
										},
										"subscription": {
											"description": "Current paid subscription metadata. Omitted on the free plan or when the provider does not report one.",
											"type": "object",
											"properties": {
												"plan": {
													"type": "string",
													"enum": [
														"pro",
														"enterprise"
													],
													"description": "Current plan. `enterprise` is a per-customer custom plan managed by sales."
												},
												"interval": {
													"description": "Billing interval. Omitted for enterprise custom plans when the provider does not report a public interval.",
													"type": "string",
													"enum": [
														"monthly",
														"annual"
													]
												},
												"aiCreditTier": {
													"description": "Monthly AI credit allowance granted by the subscription. Omitted when the provider does not report it.",
													"type": "number"
												},
												"currentPeriodEnd": {
													"description": "Current billing period end as Unix time in milliseconds. Omitted when the provider does not report it.",
													"type": "number"
												},
												"canceledAtPeriodEnd": {
													"type": "boolean",
													"description": "True when cancellation is scheduled for the end of the current billing period."
												}
											},
											"required": [
												"plan",
												"canceledAtPeriodEnd"
											],
											"additionalProperties": false
										},
										"capabilities": {
											"type": "object",
											"propertyNames": {
												"type": "string",
												"enum": [
													"analytics_view"
												]
											},
											"additionalProperties": {
												"type": "boolean"
											},
											"description": "Plan-differentiated product capabilities currently available to the organization."
										},
										"meters": {
											"type": "object",
											"propertyNames": {
												"type": "string",
												"enum": [
													"ai_credits"
												]
											},
											"additionalProperties": {
												"type": "object",
												"properties": {
													"included": {
														"type": "number",
														"description": "Amount included with the current plan."
													},
													"prepaid": {
														"type": "number",
														"description": "Amount purchased as prepaid balance."
													},
													"used": {
														"type": "number",
														"description": "Amount consumed in the current period."
													},
													"remainingIncluded": {
														"type": "number",
														"description": "Unused portion of the included amount."
													},
													"remainingPrepaid": {
														"type": "number",
														"description": "Unused portion of the prepaid amount."
													},
													"remaining": {
														"type": "number",
														"description": "Total currently spendable amount after temporary reservations."
													},
													"settledRemaining": {
														"type": "number",
														"description": "Total remaining amount after settled consumption, excluding temporary reservations."
													},
													"nextResetAt": {
														"description": "Next balance reset as Unix time in milliseconds. Omitted for balances that do not reset.",
														"type": "number"
													},
													"unlimited": {
														"type": "boolean",
														"description": "True when the meter is not limited on the current plan."
													}
												},
												"required": [
													"included",
													"prepaid",
													"used",
													"remainingIncluded",
													"remainingPrepaid",
													"remaining",
													"settledRemaining",
													"unlimited"
												],
												"additionalProperties": false,
												"description": "Balance state for one usage meter, in the meter’s native unit."
											},
											"description": "Customer-facing usage meters keyed by meter ID."
										},
										"usageBalance": {
											"description": "Prepaid credit balance purchased through top-ups. These credits never expire and are consumed after the monthly allowance. Omitted when the provider does not report it.",
											"type": "object",
											"properties": {
												"included": {
													"type": "number",
													"description": "Amount included with the current plan."
												},
												"prepaid": {
													"type": "number",
													"description": "Amount purchased as prepaid balance."
												},
												"used": {
													"type": "number",
													"description": "Amount consumed in the current period."
												},
												"remainingIncluded": {
													"type": "number",
													"description": "Unused portion of the included amount."
												},
												"remainingPrepaid": {
													"type": "number",
													"description": "Unused portion of the prepaid amount."
												},
												"remaining": {
													"type": "number",
													"description": "Total currently spendable amount after temporary reservations."
												},
												"settledRemaining": {
													"type": "number",
													"description": "Total remaining amount after settled consumption, excluding temporary reservations."
												},
												"nextResetAt": {
													"description": "Next balance reset as Unix time in milliseconds. Omitted for balances that do not reset.",
													"type": "number"
												},
												"unlimited": {
													"type": "boolean",
													"description": "True when the meter is not limited on the current plan."
												}
											},
											"required": [
												"included",
												"prepaid",
												"used",
												"remainingIncluded",
												"remainingPrepaid",
												"remaining",
												"settledRemaining",
												"unlimited"
											],
											"additionalProperties": false
										},
										"creditGrants": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string",
														"description": "Credit grant identifier."
													},
													"credits": {
														"type": "number",
														"description": "Credits originally granted."
													},
													"remaining": {
														"type": "number",
														"description": "Unspent credits on this grant."
													},
													"expiresAt": {
														"description": "Expiry as Unix time in milliseconds. Omitted when the grant never expires.",
														"type": "number"
													}
												},
												"required": [
													"id",
													"credits",
													"remaining"
												],
												"additionalProperties": false
											},
											"description": "Credits granted to the organization by Cactal support. Spent after the monthly allowance and before purchased credits; grants with an expiry are spent soonest first."
										},
										"billableSiteCapacity": {
											"description": "Total capacity for websites with custom domains. Omitted when the provider does not report it.",
											"type": "number"
										},
										"billableSiteCount": {
											"description": "Number of websites with at least one custom domain.",
											"type": "number"
										}
									},
									"required": [
										"organizationId",
										"status",
										"complimentary",
										"capabilities",
										"meters",
										"creditGrants"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "billing.state.get",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/billing/plan/preview": {
			"post": {
				"operationId": "billing.plan.previewChange",
				"summary": "Preview a plan change",
				"description": "Projects the price of switching to the `pro` plan (`monthly` or `annual`) without charging anything. `aiCreditTier` selects the monthly AI credit allowance (default 1,000). Website capacity is computed from existing custom-domain sites and cannot be chosen here. Rejected while billing is past due. Requires the organization owner.",
				"tags": [
					"Billing"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"plan": {
										"type": "string",
										"enum": [
											"pro"
										]
									},
									"interval": {
										"type": "string",
										"enum": [
											"monthly",
											"annual"
										]
									},
									"organizationId": {
										"type": "string",
										"minLength": 1
									},
									"aiCreditTier": {
										"type": "number",
										"enum": [
											1000,
											2000,
											4000,
											8000,
											12000,
											20000,
											30000,
											40000,
											50000,
											75000,
											100000
										]
									},
									"successUrl": {
										"type": "string",
										"minLength": 1
									},
									"idempotencyKey": {
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"plan",
									"interval",
									"organizationId"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Price projection for the plan change.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"total": {
											"description": "Total amount due now; `null` when the provider does not return one.",
											"type": [
												"number",
												"null"
											]
										},
										"currency": {
											"description": "ISO currency code, e.g. `usd`.",
											"type": [
												"string",
												"null"
											]
										},
										"lineItems": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"title": {
														"description": "Line item title.",
														"type": "string"
													},
													"description": {
														"description": "Line item detail text.",
														"type": "string"
													},
													"amount": {
														"description": "Line item amount.",
														"type": "number"
													},
													"quantity": {
														"description": "Line item quantity.",
														"type": "number"
													}
												},
												"additionalProperties": false
											},
											"description": "Itemized charges that make up the total."
										}
									},
									"required": [
										"total",
										"currency",
										"lineItems"
									],
									"additionalProperties": false,
									"description": "Read-only price projection. Nothing is charged or changed."
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "billing.plan.previewChange",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/billing/plan/checkout": {
			"post": {
				"operationId": "billing.plan.checkoutChange",
				"summary": "Start a plan change checkout",
				"description": "Applies a plan change or starts hosted checkout. Monthly-to-annual switches apply immediately; annual-to-monthly at renewal. Redirect the customer to `paymentUrl` when non-null; null means the change applied without a payment step. Rejected while billing is past due; requires the organization owner.",
				"tags": [
					"Billing"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"plan": {
										"type": "string",
										"enum": [
											"pro"
										]
									},
									"interval": {
										"type": "string",
										"enum": [
											"monthly",
											"annual"
										]
									},
									"organizationId": {
										"type": "string",
										"minLength": 1
									},
									"aiCreditTier": {
										"type": "number",
										"enum": [
											1000,
											2000,
											4000,
											8000,
											12000,
											20000,
											30000,
											40000,
											50000,
											75000,
											100000
										]
									},
									"successUrl": {
										"type": "string",
										"minLength": 1
									},
									"idempotencyKey": {
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"plan",
									"interval",
									"organizationId"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Checkout result for the plan change.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"paymentUrl": {
											"description": "Hosted checkout URL to redirect the customer to. `null` when the change was applied directly without a payment step.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"paymentUrl"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				}
			}
		},
		"/billing/ai-credit-tier/preview": {
			"post": {
				"operationId": "billing.aiCreditTier.previewChange",
				"summary": "Preview an AI credit tier change",
				"description": "Projects the price of a monthly AI credit tier change without charging anything. Requires an active Pro subscription and the organization owner.",
				"tags": [
					"Billing"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"organizationId": {
										"type": "string",
										"minLength": 1
									},
									"aiCreditTier": {
										"type": "number",
										"enum": [
											1000,
											2000,
											4000,
											8000,
											12000,
											20000,
											30000,
											40000,
											50000,
											75000,
											100000
										]
									},
									"successUrl": {
										"type": "string",
										"minLength": 1
									},
									"idempotencyKey": {
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"organizationId",
									"aiCreditTier"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Price projection for the credit tier change.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"total": {
											"description": "Total amount due now; `null` when the provider does not return one.",
											"type": [
												"number",
												"null"
											]
										},
										"currency": {
											"description": "ISO currency code, e.g. `usd`.",
											"type": [
												"string",
												"null"
											]
										},
										"lineItems": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"title": {
														"description": "Line item title.",
														"type": "string"
													},
													"description": {
														"description": "Line item detail text.",
														"type": "string"
													},
													"amount": {
														"description": "Line item amount.",
														"type": "number"
													},
													"quantity": {
														"description": "Line item quantity.",
														"type": "number"
													}
												},
												"additionalProperties": false
											},
											"description": "Itemized charges that make up the total."
										}
									},
									"required": [
										"total",
										"currency",
										"lineItems"
									],
									"additionalProperties": false,
									"description": "Read-only price projection. Nothing is charged or changed."
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "billing.aiCreditTier.previewChange",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/billing/ai-credit-tier": {
			"post": {
				"operationId": "billing.aiCreditTier.update",
				"summary": "Change the AI credit tier",
				"description": "Changes the monthly AI credit tier. Increases apply immediately, prorated, keeping the unused balance; decreases take effect at renewal. Requires an active Pro subscription and the organization owner.",
				"tags": [
					"Billing"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"organizationId": {
										"type": "string",
										"minLength": 1
									},
									"aiCreditTier": {
										"type": "number",
										"enum": [
											1000,
											2000,
											4000,
											8000,
											12000,
											20000,
											30000,
											40000,
											50000,
											75000,
											100000
										]
									},
									"successUrl": {
										"type": "string",
										"minLength": 1
									},
									"idempotencyKey": {
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"organizationId",
									"aiCreditTier"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Checkout result for the credit tier change.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"paymentUrl": {
											"description": "Hosted checkout URL to redirect the customer to. `null` when the change was applied directly without a payment step.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"paymentUrl"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				}
			}
		},
		"/billing/portal": {
			"post": {
				"operationId": "billing.portal.open",
				"summary": "Open the billing customer portal",
				"description": "Creates a hosted customer billing portal session for managing payment details, invoices, and subscription state. Redirect the customer to the returned URL; they come back to `returnUrl` when provided. Requires the organization owner.",
				"tags": [
					"Billing"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"organizationId": {
										"type": "string",
										"minLength": 1
									},
									"returnUrl": {
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"organizationId"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The hosted portal session.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"url": {
											"type": "string",
											"description": "Hosted billing portal URL to redirect the customer to."
										}
									},
									"required": [
										"url"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "billing.portal.open",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/billing/usage-top-up/preview": {
			"post": {
				"operationId": "billing.usageTopUp.preview",
				"summary": "Preview a credit top-up",
				"description": "Projects the price of a prepaid `credits` top-up (minimum 400) without charging anything. Requires active billing and the organization owner.",
				"tags": [
					"Billing"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"organizationId": {
										"type": "string",
										"minLength": 1
									},
									"credits": {
										"type": "integer",
										"minimum": 400,
										"maximum": 9007199254740991
									},
									"successUrl": {
										"type": "string",
										"minLength": 1
									},
									"idempotencyKey": {
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"organizationId",
									"credits"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Price projection for the top-up.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"total": {
											"description": "Total amount due now; `null` when the provider does not return one.",
											"type": [
												"number",
												"null"
											]
										},
										"currency": {
											"description": "ISO currency code, e.g. `usd`.",
											"type": [
												"string",
												"null"
											]
										},
										"lineItems": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"title": {
														"description": "Line item title.",
														"type": "string"
													},
													"description": {
														"description": "Line item detail text.",
														"type": "string"
													},
													"amount": {
														"description": "Line item amount.",
														"type": "number"
													},
													"quantity": {
														"description": "Line item quantity.",
														"type": "number"
													}
												},
												"additionalProperties": false
											},
											"description": "Itemized charges that make up the total."
										}
									},
									"required": [
										"total",
										"currency",
										"lineItems"
									],
									"additionalProperties": false,
									"description": "Read-only price projection. Nothing is charged or changed."
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "billing.usageTopUp.preview",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/billing/usage-top-up/checkout": {
			"post": {
				"operationId": "billing.usageTopUp.checkout",
				"summary": "Start a credit top-up checkout",
				"description": "Starts hosted checkout to add prepaid `credits` (minimum 400). Top-up credits never expire and are spent after the monthly allowance. Pass `idempotencyKey` so retries do not duplicate purchases; exact duplicates may return a conflict. Requires active billing and the organization owner.",
				"tags": [
					"Billing"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"organizationId": {
										"type": "string",
										"minLength": 1
									},
									"credits": {
										"type": "integer",
										"minimum": 400,
										"maximum": 9007199254740991
									},
									"successUrl": {
										"type": "string",
										"minLength": 1
									},
									"idempotencyKey": {
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"organizationId",
									"credits"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Checkout result for the top-up.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"paymentUrl": {
											"description": "Hosted checkout URL to redirect the customer to. `null` when the change was applied directly without a payment step.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"paymentUrl"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				}
			}
		},
		"/apiKeys": {
			"get": {
				"operationId": "apiKeys.list",
				"summary": "List API keys",
				"description": "Returns API keys newest first with keyset pagination and their full access configuration. Pass exactly one of `organizationId` (the full organization inventory) or `websiteId` (only keys whose access kind is `websites` with a direct grant on that website — organization-wide keys can also act on the website but appear only in the organization inventory). Revoked keys are excluded by default; pass `revoked=only` to list them instead. Page size defaults to 20 (maximum 100). Secrets are never returned — only metadata.",
				"tags": [
					"API keys"
				],
				"parameters": [
					{
						"name": "organizationId",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Organization whose full API key inventory to list. Provide either this or `websiteId`.",
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "websiteId",
						"in": "query",
						"required": false,
						"schema": {
							"description": "List only API keys with a direct grant on this website (access kind `websites`). Organization-wide keys can also act on the website but are not included; list those with `organizationId`. Provide either this or `organizationId`.",
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "revoked",
						"in": "query",
						"required": false,
						"schema": {
							"default": "exclude",
							"type": "string",
							"enum": [
								"exclude",
								"only"
							]
						}
					},
					{
						"name": "cursor",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"default": 20,
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					}
				],
				"responses": {
					"200": {
						"description": "One page of API keys, newest first.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string",
														"description": "Unique id of the API key."
													},
													"name": {
														"type": "string",
														"description": "Human-readable label, 1–200 characters."
													},
													"organizationId": {
														"type": "string",
														"description": "Id of the organization that owns the key."
													},
													"access": {
														"oneOf": [
															{
																"type": "object",
																"properties": {
																	"kind": {
																		"type": "string",
																		"const": "organization",
																		"description": "Grants one role across the entire organization."
																	},
																	"role": {
																		"type": "string",
																		"enum": [
																			"admin",
																			"full_editor",
																			"content_editor"
																		],
																		"description": "Organization-wide role held by the key."
																	}
																},
																"required": [
																	"kind",
																	"role"
																],
																"additionalProperties": false
															},
															{
																"type": "object",
																"properties": {
																	"kind": {
																		"type": "string",
																		"const": "websites",
																		"description": "Grants one role on an explicit set of websites."
																	},
																	"websiteIds": {
																		"minItems": 1,
																		"type": "array",
																		"items": {
																			"type": "string",
																			"minLength": 1
																		},
																		"description": "Ids of the websites the key can access. All must belong to the organization of the key."
																	},
																	"role": {
																		"type": "string",
																		"enum": [
																			"content_editor",
																			"full_editor"
																		],
																		"description": "Role held by the key on each listed website."
																	}
																},
																"required": [
																	"kind",
																	"websiteIds",
																	"role"
																],
																"additionalProperties": false
															}
														],
														"description": "Access configuration of an API key: either one organization-wide role, or one role across an explicit set of websites. The `owner` role can never be granted to an API key."
													},
													"expiresAt": {
														"anyOf": [
															{
																"type": "string",
																"format": "date-time"
															},
															{
																"type": "null"
															}
														],
														"description": "When the key stops authenticating. `null` when it never expires."
													},
													"revokedAt": {
														"anyOf": [
															{
																"type": "string",
																"format": "date-time"
															},
															{
																"type": "null"
															}
														],
														"description": "When the key was revoked. `null` while the key is active."
													},
													"createdAt": {
														"description": "When the key was created.",
														"type": "string",
														"format": "date-time"
													}
												},
												"required": [
													"id",
													"name",
													"organizationId",
													"access",
													"expiresAt",
													"revokedAt",
													"createdAt"
												],
												"additionalProperties": false
											}
										},
										"nextCursor": {
											"description": "Opaque cursor for the next page. Pass it as the `cursor` parameter on the next request. `null` when this is the last page.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"items",
										"nextCursor"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "apiKeys.list",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			},
			"post": {
				"operationId": "apiKeys.create",
				"summary": "Create an API key",
				"description": "Creates an API key that authenticates as its own principal within the organization. `access` grants either one organization-wide role (`admin`, `full_editor`, or `content_editor`) or one role (`full_editor` or `content_editor`) on at least one website in the organization; the `owner` role can never be granted, and website ids outside the organization fail with 400. `name` is 1–200 characters and `expiresAt` is an ISO 8601 timestamp or `null` for a key that never expires. The response contains the plaintext secret exactly once — only a SHA-256 hash is stored, so it cannot be retrieved again.",
				"tags": [
					"API keys"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"organizationId": {
										"type": "string",
										"minLength": 1
									},
									"name": {
										"type": "string",
										"minLength": 1,
										"maxLength": 200
									},
									"expiresAt": {
										"anyOf": [
											{},
											{
												"type": "string",
												"format": "date-time",
												"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
											},
											{
												"type": "null"
											}
										]
									},
									"access": {
										"oneOf": [
											{
												"type": "object",
												"properties": {
													"kind": {
														"type": "string",
														"const": "organization",
														"description": "Grants one role across the entire organization."
													},
													"role": {
														"type": "string",
														"enum": [
															"admin",
															"full_editor",
															"content_editor"
														],
														"description": "Organization-wide role held by the key."
													}
												},
												"required": [
													"kind",
													"role"
												]
											},
											{
												"type": "object",
												"properties": {
													"kind": {
														"type": "string",
														"const": "websites",
														"description": "Grants one role on an explicit set of websites."
													},
													"websiteIds": {
														"minItems": 1,
														"type": "array",
														"items": {
															"type": "string",
															"minLength": 1
														},
														"description": "Ids of the websites the key can access. All must belong to the organization of the key."
													},
													"role": {
														"type": "string",
														"enum": [
															"content_editor",
															"full_editor"
														],
														"description": "Role held by the key on each listed website."
													}
												},
												"required": [
													"kind",
													"websiteIds",
													"role"
												]
											}
										],
										"description": "Access configuration of an API key: either one organization-wide role, or one role across an explicit set of websites. The `owner` role can never be granted to an API key."
									}
								},
								"required": [
									"organizationId",
									"name",
									"expiresAt",
									"access"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The created key and its `plaintextKey` secret. Store the secret now; it is never shown again.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"apiKey": {
											"type": "object",
											"properties": {
												"id": {
													"type": "string",
													"description": "Unique id of the API key."
												},
												"name": {
													"type": "string",
													"description": "Human-readable label, 1–200 characters."
												},
												"organizationId": {
													"type": "string",
													"description": "Id of the organization that owns the key."
												},
												"access": {
													"oneOf": [
														{
															"type": "object",
															"properties": {
																"kind": {
																	"type": "string",
																	"const": "organization",
																	"description": "Grants one role across the entire organization."
																},
																"role": {
																	"type": "string",
																	"enum": [
																		"admin",
																		"full_editor",
																		"content_editor"
																	],
																	"description": "Organization-wide role held by the key."
																}
															},
															"required": [
																"kind",
																"role"
															],
															"additionalProperties": false
														},
														{
															"type": "object",
															"properties": {
																"kind": {
																	"type": "string",
																	"const": "websites",
																	"description": "Grants one role on an explicit set of websites."
																},
																"websiteIds": {
																	"minItems": 1,
																	"type": "array",
																	"items": {
																		"type": "string",
																		"minLength": 1
																	},
																	"description": "Ids of the websites the key can access. All must belong to the organization of the key."
																},
																"role": {
																	"type": "string",
																	"enum": [
																		"content_editor",
																		"full_editor"
																	],
																	"description": "Role held by the key on each listed website."
																}
															},
															"required": [
																"kind",
																"websiteIds",
																"role"
															],
															"additionalProperties": false
														}
													],
													"description": "Access configuration of an API key: either one organization-wide role, or one role across an explicit set of websites. The `owner` role can never be granted to an API key."
												},
												"expiresAt": {
													"anyOf": [
														{
															"type": "string",
															"format": "date-time"
														},
														{
															"type": "null"
														}
													],
													"description": "When the key stops authenticating. `null` when it never expires."
												},
												"revokedAt": {
													"anyOf": [
														{
															"type": "string",
															"format": "date-time"
														},
														{
															"type": "null"
														}
													],
													"description": "When the key was revoked. `null` while the key is active."
												},
												"createdAt": {
													"description": "When the key was created.",
													"type": "string",
													"format": "date-time"
												}
											},
											"required": [
												"id",
												"name",
												"organizationId",
												"access",
												"expiresAt",
												"revokedAt",
												"createdAt"
											],
											"additionalProperties": false,
											"description": "Metadata and access configuration of the key."
										},
										"plaintextKey": {
											"type": "string",
											"description": "The plaintext secret, a 32-byte base64url string. Store it now; it is never shown again, and only a SHA-256 hash is kept."
										}
									},
									"required": [
										"apiKey",
										"plaintextKey"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "apiKeys.create",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/apiKeys/{apiKeyId}": {
			"patch": {
				"operationId": "apiKeys.update",
				"summary": "Update an API key",
				"description": "Updates `name`, `expiresAt`, and/or `access`; omitted fields are left unchanged, and sending none returns the current metadata. Providing `access` replaces the entire access configuration atomically — previous grants are removed and the change applies to the next request that uses the key; any website id belonging to another organization fails with 400. On a revoked or already-expired key, setting `expiresAt` (including `null` to remove expiry) or `access` fails with 409, because inactive keys cannot be made active again; renames still succeed.",
				"tags": [
					"API keys"
				],
				"parameters": [
					{
						"name": "apiKeyId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": false,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"name": {
										"type": "string",
										"minLength": 1,
										"maxLength": 200
									},
									"expiresAt": {
										"anyOf": [
											{},
											{
												"type": "string",
												"format": "date-time",
												"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
											},
											{
												"type": "null"
											}
										]
									},
									"access": {
										"description": "When provided, replaces the entire access configuration of the key atomically; previous grants are removed. Omit to leave access unchanged.",
										"oneOf": [
											{
												"type": "object",
												"properties": {
													"kind": {
														"type": "string",
														"const": "organization",
														"description": "Grants one role across the entire organization."
													},
													"role": {
														"type": "string",
														"enum": [
															"admin",
															"full_editor",
															"content_editor"
														],
														"description": "Organization-wide role held by the key."
													}
												},
												"required": [
													"kind",
													"role"
												]
											},
											{
												"type": "object",
												"properties": {
													"kind": {
														"type": "string",
														"const": "websites",
														"description": "Grants one role on an explicit set of websites."
													},
													"websiteIds": {
														"minItems": 1,
														"type": "array",
														"items": {
															"type": "string",
															"minLength": 1
														},
														"description": "Ids of the websites the key can access. All must belong to the organization of the key."
													},
													"role": {
														"type": "string",
														"enum": [
															"content_editor",
															"full_editor"
														],
														"description": "Role held by the key on each listed website."
													}
												},
												"required": [
													"kind",
													"websiteIds",
													"role"
												]
											}
										]
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The updated key metadata.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique id of the API key."
										},
										"name": {
											"type": "string",
											"description": "Human-readable label, 1–200 characters."
										},
										"organizationId": {
											"type": "string",
											"description": "Id of the organization that owns the key."
										},
										"access": {
											"oneOf": [
												{
													"type": "object",
													"properties": {
														"kind": {
															"type": "string",
															"const": "organization",
															"description": "Grants one role across the entire organization."
														},
														"role": {
															"type": "string",
															"enum": [
																"admin",
																"full_editor",
																"content_editor"
															],
															"description": "Organization-wide role held by the key."
														}
													},
													"required": [
														"kind",
														"role"
													],
													"additionalProperties": false
												},
												{
													"type": "object",
													"properties": {
														"kind": {
															"type": "string",
															"const": "websites",
															"description": "Grants one role on an explicit set of websites."
														},
														"websiteIds": {
															"minItems": 1,
															"type": "array",
															"items": {
																"type": "string",
																"minLength": 1
															},
															"description": "Ids of the websites the key can access. All must belong to the organization of the key."
														},
														"role": {
															"type": "string",
															"enum": [
																"content_editor",
																"full_editor"
															],
															"description": "Role held by the key on each listed website."
														}
													},
													"required": [
														"kind",
														"websiteIds",
														"role"
													],
													"additionalProperties": false
												}
											],
											"description": "Access configuration of an API key: either one organization-wide role, or one role across an explicit set of websites. The `owner` role can never be granted to an API key."
										},
										"expiresAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											],
											"description": "When the key stops authenticating. `null` when it never expires."
										},
										"revokedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											],
											"description": "When the key was revoked. `null` while the key is active."
										},
										"createdAt": {
											"description": "When the key was created.",
											"type": "string",
											"format": "date-time"
										}
									},
									"required": [
										"id",
										"name",
										"organizationId",
										"access",
										"expiresAt",
										"revokedAt",
										"createdAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "apiKeys.update",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/apiKeys/{apiKeyId}/rotate": {
			"post": {
				"operationId": "apiKeys.rotate",
				"summary": "Rotate an API key secret",
				"description": "Generates a new 32-byte base64url secret for the key and invalidates the old secret immediately; name, expiry, and access are unchanged. The response contains the new plaintext secret exactly once. Revoked or expired keys cannot be rotated (409).",
				"tags": [
					"API keys"
				],
				"parameters": [
					{
						"name": "apiKeyId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "The key metadata and the new `plaintextKey` secret. Store the secret now; it is never shown again.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"apiKey": {
											"type": "object",
											"properties": {
												"id": {
													"type": "string",
													"description": "Unique id of the API key."
												},
												"name": {
													"type": "string",
													"description": "Human-readable label, 1–200 characters."
												},
												"organizationId": {
													"type": "string",
													"description": "Id of the organization that owns the key."
												},
												"access": {
													"oneOf": [
														{
															"type": "object",
															"properties": {
																"kind": {
																	"type": "string",
																	"const": "organization",
																	"description": "Grants one role across the entire organization."
																},
																"role": {
																	"type": "string",
																	"enum": [
																		"admin",
																		"full_editor",
																		"content_editor"
																	],
																	"description": "Organization-wide role held by the key."
																}
															},
															"required": [
																"kind",
																"role"
															],
															"additionalProperties": false
														},
														{
															"type": "object",
															"properties": {
																"kind": {
																	"type": "string",
																	"const": "websites",
																	"description": "Grants one role on an explicit set of websites."
																},
																"websiteIds": {
																	"minItems": 1,
																	"type": "array",
																	"items": {
																		"type": "string",
																		"minLength": 1
																	},
																	"description": "Ids of the websites the key can access. All must belong to the organization of the key."
																},
																"role": {
																	"type": "string",
																	"enum": [
																		"content_editor",
																		"full_editor"
																	],
																	"description": "Role held by the key on each listed website."
																}
															},
															"required": [
																"kind",
																"websiteIds",
																"role"
															],
															"additionalProperties": false
														}
													],
													"description": "Access configuration of an API key: either one organization-wide role, or one role across an explicit set of websites. The `owner` role can never be granted to an API key."
												},
												"expiresAt": {
													"anyOf": [
														{
															"type": "string",
															"format": "date-time"
														},
														{
															"type": "null"
														}
													],
													"description": "When the key stops authenticating. `null` when it never expires."
												},
												"revokedAt": {
													"anyOf": [
														{
															"type": "string",
															"format": "date-time"
														},
														{
															"type": "null"
														}
													],
													"description": "When the key was revoked. `null` while the key is active."
												},
												"createdAt": {
													"description": "When the key was created.",
													"type": "string",
													"format": "date-time"
												}
											},
											"required": [
												"id",
												"name",
												"organizationId",
												"access",
												"expiresAt",
												"revokedAt",
												"createdAt"
											],
											"additionalProperties": false,
											"description": "Metadata and access configuration of the key."
										},
										"plaintextKey": {
											"type": "string",
											"description": "The plaintext secret, a 32-byte base64url string. Store it now; it is never shown again, and only a SHA-256 hash is kept."
										}
									},
									"required": [
										"apiKey",
										"plaintextKey"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "apiKeys.rotate",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/apiKeys/{apiKeyId}/revoke": {
			"post": {
				"operationId": "apiKeys.revoke",
				"summary": "Revoke an API key",
				"description": "Permanently deactivates the key by setting `revokedAt`; requests that use it fail with 401 immediately. Revocation is irreversible — rotate instead if the key should keep working with a new secret. Revoking an already-revoked key is a no-op that returns the current metadata.",
				"tags": [
					"API keys"
				],
				"parameters": [
					{
						"name": "apiKeyId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "The revoked key metadata.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique id of the API key."
										},
										"name": {
											"type": "string",
											"description": "Human-readable label, 1–200 characters."
										},
										"organizationId": {
											"type": "string",
											"description": "Id of the organization that owns the key."
										},
										"access": {
											"oneOf": [
												{
													"type": "object",
													"properties": {
														"kind": {
															"type": "string",
															"const": "organization",
															"description": "Grants one role across the entire organization."
														},
														"role": {
															"type": "string",
															"enum": [
																"admin",
																"full_editor",
																"content_editor"
															],
															"description": "Organization-wide role held by the key."
														}
													},
													"required": [
														"kind",
														"role"
													],
													"additionalProperties": false
												},
												{
													"type": "object",
													"properties": {
														"kind": {
															"type": "string",
															"const": "websites",
															"description": "Grants one role on an explicit set of websites."
														},
														"websiteIds": {
															"minItems": 1,
															"type": "array",
															"items": {
																"type": "string",
																"minLength": 1
															},
															"description": "Ids of the websites the key can access. All must belong to the organization of the key."
														},
														"role": {
															"type": "string",
															"enum": [
																"content_editor",
																"full_editor"
															],
															"description": "Role held by the key on each listed website."
														}
													},
													"required": [
														"kind",
														"websiteIds",
														"role"
													],
													"additionalProperties": false
												}
											],
											"description": "Access configuration of an API key: either one organization-wide role, or one role across an explicit set of websites. The `owner` role can never be granted to an API key."
										},
										"expiresAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											],
											"description": "When the key stops authenticating. `null` when it never expires."
										},
										"revokedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											],
											"description": "When the key was revoked. `null` while the key is active."
										},
										"createdAt": {
											"description": "When the key was created.",
											"type": "string",
											"format": "date-time"
										}
									},
									"required": [
										"id",
										"name",
										"organizationId",
										"access",
										"expiresAt",
										"revokedAt",
										"createdAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "apiKeys.revoke",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/organizations/{organizationId}/audit-log": {
			"get": {
				"operationId": "auditLog.list",
				"summary": "List audit log events",
				"description": "Returns the organization’s immutable audit trail newest first as a paginated set, recording mutations performed by users and API keys across organizations, members, invitations, websites, and billing. Filter with the `eventType`, `resourceType`, `actorKind`, `actorPrincipalId`, and `websiteId` query parameters; `websiteId` narrows the trail to one website, including events recorded against its nested resources. Page size defaults to 20 (maximum 100). Requires a role with audit log access (owner or admin).",
				"tags": [
					"Audit log"
				],
				"parameters": [
					{
						"name": "organizationId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "eventType",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "resourceType",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "actorKind",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string",
							"enum": [
								"user",
								"api_key",
								"system",
								"agent"
							]
						}
					},
					{
						"name": "actorPrincipalId",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "websiteId",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Return only events about this website — either the website itself or a resource belonging to it.",
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "cursor",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"default": 20,
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					}
				],
				"responses": {
					"200": {
						"description": "One page of audit events, newest first.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string",
														"description": "Unique event ID."
													},
													"occurredAt": {
														"description": "When the action happened.",
														"type": "string",
														"format": "date-time"
													},
													"eventType": {
														"type": "string",
														"description": "Stable event identifier, e.g. `organizations.members.remove`. Also usable as the `eventType` filter."
													},
													"summary": {
														"description": "Human-readable one-line summary of the action.",
														"type": [
															"string",
															"null"
														]
													},
													"actorKind": {
														"type": "string",
														"enum": [
															"user",
															"api_key",
															"system",
															"agent"
														],
														"description": "What kind of principal performed the action."
													},
													"actorPrincipalId": {
														"description": "Principal ID of the actor, when known. Usable as the `actorPrincipalId` filter to follow one actor.",
														"type": [
															"string",
															"null"
														]
													},
													"actorDisplay": {
														"anyOf": [
															{
																"type": "object",
																"properties": {
																	"label": {
																		"type": "string",
																		"description": "Display name of the actor: the user’s name or the API key’s name."
																	},
																	"email": {
																		"description": "Email of user actors; `null` for API key actors.",
																		"type": [
																			"string",
																			"null"
																		]
																	}
																},
																"required": [
																	"label",
																	"email"
																],
																"additionalProperties": false
															},
															{
																"type": "null"
															}
														],
														"description": "Resolved display info for the actor; `null` when the actor cannot be resolved (for example a deleted user, or `system` and `agent` actors)."
													},
													"resourceType": {
														"description": "Type of the affected resource, e.g. `organization_member`.",
														"type": [
															"string",
															"null"
														]
													},
													"resourceId": {
														"description": "ID of the affected resource.",
														"type": [
															"string",
															"null"
														]
													},
													"metadata": {
														"anyOf": [
															{
																"type": "object",
																"propertyNames": {
																	"type": "string"
																},
																"additionalProperties": {}
															},
															{
																"type": "null"
															}
														],
														"description": "Event-specific structured details."
													},
													"durationMs": {
														"type": "integer",
														"description": "How long the recorded operation took, in milliseconds."
													}
												},
												"required": [
													"id",
													"occurredAt",
													"eventType",
													"summary",
													"actorKind",
													"actorPrincipalId",
													"actorDisplay",
													"resourceType",
													"resourceId",
													"metadata",
													"durationMs"
												],
												"additionalProperties": false
											}
										},
										"nextCursor": {
											"description": "Opaque cursor for the next page. Pass it as the `cursor` parameter on the next request. `null` when this is the last page.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"items",
										"nextCursor"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "auditLog.list",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/feedback": {
			"post": {
				"operationId": "feedback.submit",
				"summary": "Submit product feedback",
				"description": "Adds user-requested free-form feedback to the Cactal product-feedback inbox. Describe what happened and what would have made the experience better. Agents must set `authorKind` to `agent` even when acting through a user OAuth connection. Never include credentials, secrets, raw sensitive user content, or unnecessary personal or customer data.",
				"tags": [
					"Feedback"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"authorKind": {
										"type": "string",
										"enum": [
											"user",
											"agent"
										],
										"description": "Who wrote the feedback. An AI agent must pass \"agent\" even when connected through a user OAuth token."
									},
									"feedback": {
										"type": "string",
										"minLength": 1,
										"maxLength": 12000,
										"description": "Concise free-form feedback describing the Cactal problem or limitation and what would have made the experience better."
									}
								},
								"required": [
									"authorKind",
									"feedback"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The id and receipt time of the accepted feedback.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"feedbackId": {
											"type": "string",
											"description": "Generated id of the accepted feedback."
										},
										"receivedAt": {
											"description": "When Cactal accepted the feedback.",
											"type": "string",
											"format": "date-time"
										}
									},
									"required": [
										"feedbackId",
										"receivedAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": null,
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/organizations/find": {
			"get": {
				"operationId": "organizations.get",
				"summary": "Get an organization",
				"description": "Fetches a single organization by `organizationId`, or by its URL `slug` when starting from a URL — provide exactly one. The response includes `currentMember`, the caller’s own membership record with its role (`null` when the caller has no organization-level membership). Lookups by ID require organization membership; slug lookups additionally work for principals whose only access is a direct website editor grant in the organization, who receive only its ID, name, and URL slug. Inaccessible or deleted organizations return 404.",
				"tags": [
					"Organizations"
				],
				"parameters": [
					{
						"name": "organizationId",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Organization id. Provide either this or `slug`.",
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "slug",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Organization URL slug. Provide either this or `organizationId`.",
							"type": "string"
						}
					}
				],
				"responses": {
					"200": {
						"description": "The requested organization with the caller’s membership.",
						"content": {
							"application/json": {
								"schema": {
									"anyOf": [
										{
											"type": "object",
											"properties": {
												"id": {
													"type": "string",
													"description": "Unique organization ID."
												},
												"name": {
													"type": "string",
													"description": "Display name."
												},
												"slug": {
													"type": "string",
													"description": "Unique URL-safe identifier (lowercase letters, digits, and hyphens)."
												},
												"logo": {
													"description": "Logo URL, or `null` when unset.",
													"type": [
														"string",
														"null"
													]
												},
												"metadata": {
													"anyOf": [
														{
															"type": "object",
															"propertyNames": {
																"type": "string"
															},
															"additionalProperties": {}
														},
														{
															"type": "null"
														}
													],
													"description": "Arbitrary key–value metadata attached to the organization. `null` when unset."
												},
												"createdAt": {
													"description": "When the organization was created.",
													"type": "string",
													"format": "date-time"
												},
												"currentMember": {
													"anyOf": [
														{
															"type": "object",
															"properties": {
																"principalId": {
																	"type": "string",
																	"description": "Principal ID of the member. Stable across role changes."
																},
																"role": {
																	"type": "string",
																	"enum": [
																		"owner",
																		"admin",
																		"full_editor",
																		"content_editor"
																	],
																	"description": "The member’s organization role. Every organization has exactly one `owner`."
																},
																"createdAt": {
																	"description": "When the membership was created.",
																	"type": "string",
																	"format": "date-time"
																}
															},
															"required": [
																"principalId",
																"role",
																"createdAt"
															],
															"additionalProperties": false
														},
														{
															"type": "null"
														}
													],
													"description": "The calling principal’s own membership (role and join date), or `null` when the caller has no organization-level membership."
												}
											},
											"required": [
												"id",
												"name",
												"slug",
												"logo",
												"metadata",
												"createdAt",
												"currentMember"
											],
											"additionalProperties": false
										},
										{
											"type": "object",
											"properties": {
												"id": {
													"type": "string",
													"description": "Unique organization ID."
												},
												"name": {
													"type": "string",
													"description": "Display name."
												},
												"slug": {
													"type": "string",
													"description": "Unique URL-safe identifier (lowercase letters, digits, and hyphens)."
												},
												"currentMember": {
													"type": "null",
													"description": "Always `null`: the caller’s only access is a website-scoped editor grant."
												}
											},
											"required": [
												"id",
												"name",
												"slug",
												"currentMember"
											],
											"additionalProperties": false
										}
									]
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "organization.view",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/organizations/invitations/{invitationId}/cancel": {
			"post": {
				"operationId": "organizations.invitations.cancel",
				"summary": "Cancel a pending invitation",
				"description": "Cancels a pending invitation so its acceptance link stops working. Only pending invitations can be canceled; accepted, rejected, or already-canceled invitations return 409. Requires a role with member management (owner or admin) in the invitation’s organization.",
				"tags": [
					"Organizations"
				],
				"parameters": [
					{
						"name": "invitationId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "The invitation that was canceled.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique invitation ID."
										},
										"email": {
											"type": "string",
											"description": "Invited email address, normalized to lowercase."
										},
										"role": {
											"type": "string",
											"enum": [
												"admin",
												"full_editor",
												"content_editor"
											],
											"description": "Organization role granted on acceptance. `owner` is never invitable."
										},
										"status": {
											"type": "string",
											"enum": [
												"pending",
												"accepted",
												"rejected",
												"canceled"
											],
											"description": "Lifecycle status. Only pending invitations can be accepted."
										},
										"expiresAt": {
											"description": "When the invitation stops being acceptable (48 hours after creation by default).",
											"type": "string",
											"format": "date-time"
										},
										"createdAt": {
											"description": "When the invitation was created.",
											"type": "string",
											"format": "date-time"
										},
										"organizationId": {
											"type": "string",
											"description": "Organization the invitation belonged to."
										}
									},
									"required": [
										"id",
										"email",
										"role",
										"status",
										"expiresAt",
										"createdAt",
										"organizationId"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "organization.manageMembers",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/organizations": {
			"get": {
				"operationId": "organizations.list",
				"summary": "List your organizations",
				"description": "Lists every active organization the authenticated principal is a member of, newest first as a paginated set. Soft-deleted organizations are excluded. Page size defaults to 20 (maximum 100).",
				"tags": [
					"Organizations"
				],
				"parameters": [
					{
						"name": "cursor",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"default": 20,
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					}
				],
				"responses": {
					"200": {
						"description": "One page of organizations the caller belongs to.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string",
														"description": "Unique organization ID."
													},
													"name": {
														"type": "string",
														"description": "Display name."
													},
													"slug": {
														"type": "string",
														"description": "Unique URL-safe identifier (lowercase letters, digits, and hyphens)."
													},
													"logo": {
														"description": "Logo URL, or `null` when unset.",
														"type": [
															"string",
															"null"
														]
													},
													"metadata": {
														"anyOf": [
															{
																"type": "object",
																"propertyNames": {
																	"type": "string"
																},
																"additionalProperties": {}
															},
															{
																"type": "null"
															}
														],
														"description": "Arbitrary key–value metadata attached to the organization. `null` when unset."
													},
													"createdAt": {
														"description": "When the organization was created.",
														"type": "string",
														"format": "date-time"
													}
												},
												"required": [
													"id",
													"name",
													"slug",
													"logo",
													"metadata",
													"createdAt"
												],
												"additionalProperties": false
											}
										},
										"nextCursor": {
											"description": "Opaque cursor for the next page. Pass it as the `cursor` parameter on the next request. `null` when this is the last page.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"items",
										"nextCursor"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "organization.view",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/organizations/{organizationId}": {
			"patch": {
				"operationId": "organizations.update",
				"summary": "Update an organization",
				"description": "Partially updates the organization’s name, slug, logo, or metadata; omitted fields are left unchanged. The slug must be lowercase URL-safe and unique across all organizations (409 when taken). Requires a role with member management (owner or admin).",
				"tags": [
					"Organizations"
				],
				"parameters": [
					{
						"name": "organizationId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": false,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"name": {
										"type": "string",
										"minLength": 1,
										"maxLength": 200
									},
									"slug": {
										"type": "string"
									},
									"logo": {
										"anyOf": [
											{
												"type": "string",
												"format": "uri"
											},
											{
												"type": "null"
											}
										]
									},
									"metadata": {
										"anyOf": [
											{
												"type": "object",
												"propertyNames": {
													"type": "string"
												},
												"additionalProperties": {}
											},
											{
												"type": "null"
											}
										]
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The updated organization.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique organization ID."
										},
										"name": {
											"type": "string",
											"description": "Display name."
										},
										"slug": {
											"type": "string",
											"description": "Unique URL-safe identifier (lowercase letters, digits, and hyphens)."
										},
										"logo": {
											"description": "Logo URL, or `null` when unset.",
											"type": [
												"string",
												"null"
											]
										},
										"metadata": {
											"anyOf": [
												{
													"type": "object",
													"propertyNames": {
														"type": "string"
													},
													"additionalProperties": {}
												},
												{
													"type": "null"
												}
											],
											"description": "Arbitrary key–value metadata attached to the organization. `null` when unset."
										},
										"createdAt": {
											"description": "When the organization was created.",
											"type": "string",
											"format": "date-time"
										}
									},
									"required": [
										"id",
										"name",
										"slug",
										"logo",
										"metadata",
										"createdAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "organization.manageMembers",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			},
			"delete": {
				"operationId": "organizations.delete",
				"summary": "Delete an organization",
				"description": "Soft-deletes the organization: it immediately disappears from list and read endpoints and all pending invitations are canceled. Data is retained for a 30-day restore window, after which the organization becomes eligible for permanent purge. Requires the organization owner.",
				"tags": [
					"Organizations"
				],
				"parameters": [
					{
						"name": "organizationId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Confirmation of the deletion with the purge deadline; the organization can be restored until then.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Id of the deleted organization."
										},
										"name": {
											"type": "string",
											"description": "Display name of the deleted organization."
										},
										"deletedAt": {
											"description": "When the organization was soft-deleted.",
											"type": "string",
											"format": "date-time"
										},
										"purgeEligibleAt": {
											"description": "When the organization becomes eligible for permanent purge. It can be restored until then.",
											"type": "string",
											"format": "date-time"
										}
									},
									"required": [
										"id",
										"name",
										"deletedAt",
										"purgeEligibleAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "organization.delete",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/organizations/{organizationId}/restore": {
			"post": {
				"operationId": "organizations.restore",
				"summary": "Restore a deleted organization",
				"description": "Restores a soft-deleted organization within its 30-day restore window, bringing back its websites, members, and data as they were. Pending invitations canceled by the deletion stay canceled. Requires the organization owner.",
				"tags": [
					"Organizations"
				],
				"parameters": [
					{
						"name": "organizationId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "The restored organization.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique organization ID."
										},
										"name": {
											"type": "string",
											"description": "Display name."
										},
										"slug": {
											"type": "string",
											"description": "Unique URL-safe identifier (lowercase letters, digits, and hyphens)."
										},
										"logo": {
											"description": "Logo URL, or `null` when unset.",
											"type": [
												"string",
												"null"
											]
										},
										"metadata": {
											"anyOf": [
												{
													"type": "object",
													"propertyNames": {
														"type": "string"
													},
													"additionalProperties": {}
												},
												{
													"type": "null"
												}
											],
											"description": "Arbitrary key–value metadata attached to the organization. `null` when unset."
										},
										"createdAt": {
											"description": "When the organization was created.",
											"type": "string",
											"format": "date-time"
										}
									},
									"required": [
										"id",
										"name",
										"slug",
										"logo",
										"metadata",
										"createdAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "organization.delete",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/organizations/{organizationId}/members": {
			"get": {
				"operationId": "organizations.members.list",
				"summary": "List organization members",
				"description": "Lists user-account organization members newest first as a paginated set. API keys and invitations are managed separately and are not included. Page size defaults to 20 (maximum 100). Any organization member can call this.",
				"tags": [
					"Organizations"
				],
				"parameters": [
					{
						"name": "organizationId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "cursor",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"default": 20,
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					}
				],
				"responses": {
					"200": {
						"description": "One page of organization members.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"principalId": {
														"type": "string",
														"description": "Principal ID of the member. Stable across role changes."
													},
													"role": {
														"type": "string",
														"enum": [
															"owner",
															"admin",
															"full_editor",
															"content_editor"
														],
														"description": "The member’s organization role. Every organization has exactly one `owner`."
													},
													"createdAt": {
														"description": "When the membership was created.",
														"type": "string",
														"format": "date-time"
													},
													"name": {
														"type": "string",
														"description": "Account display name."
													},
													"email": {
														"type": "string",
														"description": "Account email address."
													}
												},
												"required": [
													"principalId",
													"role",
													"createdAt",
													"name",
													"email"
												],
												"additionalProperties": false
											}
										},
										"nextCursor": {
											"description": "Opaque cursor for the next page. Pass it as the `cursor` parameter on the next request. `null` when this is the last page.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"items",
										"nextCursor"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "organization.view",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/organizations/{organizationId}/members/{memberPrincipalId}": {
			"patch": {
				"operationId": "organizations.members.updateRole",
				"summary": "Update a member’s role",
				"description": "Sets the member’s organization role. Requires a role with member management (owner or admin). Assigning `owner` transfers ownership: only the current owner can do this, the target member becomes the owner, and the caller is demoted to `admin`; transferring ownership to yourself is rejected. The current owner cannot be demoted directly (409) — transfer ownership by promoting another member instead.",
				"tags": [
					"Organizations"
				],
				"parameters": [
					{
						"name": "organizationId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "memberPrincipalId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"role": {
										"type": "string",
										"enum": [
											"owner",
											"admin",
											"full_editor",
											"content_editor"
										]
									}
								},
								"required": [
									"role"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The member’s new role together with the previous one.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"principalId": {
											"type": "string",
											"description": "Principal ID of the updated member."
										},
										"role": {
											"type": "string",
											"enum": [
												"owner",
												"admin",
												"full_editor",
												"content_editor"
											],
											"description": "Role the member now holds."
										},
										"previousRole": {
											"type": "string",
											"enum": [
												"owner",
												"admin",
												"full_editor",
												"content_editor"
											],
											"description": "Role the member held before this change."
										}
									},
									"required": [
										"principalId",
										"role",
										"previousRole"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "organization.manageMembers",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			},
			"delete": {
				"operationId": "organizations.members.remove",
				"summary": "Remove a member",
				"description": "Removes the member from the organization, revoking both their organization role and any website-scoped editor grants within it. The owner cannot be removed (409); transfer ownership first. Requires a role with member management (owner or admin).",
				"tags": [
					"Organizations"
				],
				"parameters": [
					{
						"name": "organizationId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "memberPrincipalId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Confirmation that the member was removed.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"removed": {
											"type": "boolean",
											"const": true,
											"description": "Always `true`; the membership and any website-scoped grants in the organization were revoked."
										}
									},
									"required": [
										"removed"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "organization.manageMembers",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/organizations/{organizationId}/invitations": {
			"get": {
				"operationId": "organizations.invitations.list",
				"summary": "List organization invitations",
				"description": "Lists organization invitations newest first with stable cursor pagination. Defaults to pending invitations; filter by a lifecycle status or pass `all` for the complete invitation history. Requires a role with member management (owner or admin). Page size defaults to 20 (maximum 100).",
				"tags": [
					"Organizations"
				],
				"parameters": [
					{
						"name": "organizationId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "status",
						"in": "query",
						"required": false,
						"schema": {
							"default": "pending",
							"description": "Invitation lifecycle status to return. Use `all` for complete history.",
							"type": "string",
							"enum": [
								"pending",
								"accepted",
								"rejected",
								"canceled",
								"all"
							]
						}
					},
					{
						"name": "cursor",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"default": 20,
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					}
				],
				"responses": {
					"200": {
						"description": "One page of organization invitations.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string",
														"description": "Unique invitation ID."
													},
													"email": {
														"type": "string",
														"description": "Invited email address, normalized to lowercase."
													},
													"role": {
														"type": "string",
														"enum": [
															"admin",
															"full_editor",
															"content_editor"
														],
														"description": "Organization role granted on acceptance. `owner` is never invitable."
													},
													"status": {
														"type": "string",
														"enum": [
															"pending",
															"accepted",
															"rejected",
															"canceled"
														],
														"description": "Lifecycle status. Only pending invitations can be accepted."
													},
													"expiresAt": {
														"description": "When the invitation stops being acceptable (48 hours after creation by default).",
														"type": "string",
														"format": "date-time"
													},
													"createdAt": {
														"description": "When the invitation was created.",
														"type": "string",
														"format": "date-time"
													}
												},
												"required": [
													"id",
													"email",
													"role",
													"status",
													"expiresAt",
													"createdAt"
												],
												"additionalProperties": false
											}
										},
										"nextCursor": {
											"description": "Opaque cursor for the next page. Pass it as the `cursor` parameter on the next request. `null` when this is the last page.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"items",
										"nextCursor"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "organization.manageMembers",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			},
			"post": {
				"operationId": "organizations.invitations.create",
				"summary": "Invite a member by email",
				"description": "Creates an invitation and, by default, emails the recipient an acceptance link; pass `sendEmail: false` to skip the email and deliver the returned `url` through your own channel. The response always includes the acceptance `url`. Requires a role with member management (owner or admin). The invited role defaults to `full_editor` and cannot be `owner`; invitations expire after 48 hours unless a custom `expiresAt` is set. Returns 409 when a pending invitation already exists for the email (pass `resend: true` to cancel it and issue a new one) or when the email already belongs to an organization member. List pending invitations with `organizations.invitations.list`.",
				"tags": [
					"Organizations"
				],
				"parameters": [
					{
						"name": "organizationId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"email": {
										"type": "string",
										"format": "email",
										"pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
									},
									"role": {
										"default": "full_editor",
										"type": "string",
										"enum": [
											"admin",
											"full_editor",
											"content_editor"
										]
									},
									"expiresAt": {
										"anyOf": [
											{},
											{
												"type": "string",
												"format": "date-time",
												"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
											}
										]
									},
									"resend": {
										"type": "boolean"
									},
									"callbackUrl": {
										"type": "string",
										"format": "uri"
									},
									"sendEmail": {
										"description": "Whether to email the recipient an acceptance link. Defaults to `true`; pass `false` to create the invitation silently and deliver the returned `url` yourself.",
										"type": "boolean"
									}
								},
								"required": [
									"email"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The created invitation with its acceptance URL.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique invitation ID."
										},
										"email": {
											"type": "string",
											"description": "Invited email address, normalized to lowercase."
										},
										"role": {
											"type": "string",
											"enum": [
												"admin",
												"full_editor",
												"content_editor"
											],
											"description": "Organization role granted on acceptance. `owner` is never invitable."
										},
										"status": {
											"type": "string",
											"enum": [
												"pending",
												"accepted",
												"rejected",
												"canceled"
											],
											"description": "Lifecycle status. Only pending invitations can be accepted."
										},
										"expiresAt": {
											"description": "When the invitation stops being acceptable (48 hours after creation by default).",
											"type": "string",
											"format": "date-time"
										},
										"createdAt": {
											"description": "When the invitation was created.",
											"type": "string",
											"format": "date-time"
										},
										"url": {
											"type": "string",
											"description": "Acceptance URL for the invited email. Anyone signed in with that verified email can accept through it."
										}
									},
									"required": [
										"id",
										"email",
										"role",
										"status",
										"expiresAt",
										"createdAt",
										"url"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "organization.manageMembers",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/websites": {
			"get": {
				"operationId": "websites.list",
				"summary": "List websites",
				"description": "Lists accessible websites. By default returns active websites: use `organizationId` to scope one organization, `scope=shared` without it for direct grants outside your memberships, or `scope=all` with it to combine both; supports `sort` by `createdAt`, `updatedAt`, or `name` and case-insensitive `search`, and each item includes `organizationSlug`, `primaryHostname`, and whether it is `shared`. Pass `status=deleted` with `organizationId` (requires organization-level trash access) to list restorable soft-deleted websites instead, each with its deletion time and purge deadline; deleted rows drop out when their 30-day restore window expires. Both modes use cursor pagination (default 20 per page, maximum 100). Fetch one website with `websites.get`.",
				"tags": [
					"Websites"
				],
				"parameters": [
					{
						"name": "organizationId",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "cursor",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"default": 20,
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					},
					{
						"name": "sort",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string",
							"enum": [
								"createdAt",
								"updatedAt",
								"name"
							]
						}
					},
					{
						"name": "search",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string",
							"maxLength": 200
						}
					},
					{
						"name": "scope",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string",
							"enum": [
								"shared",
								"all"
							]
						}
					},
					{
						"name": "status",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Which websites to list. Defaults to `active`. `deleted` returns the organization’s restorable soft-deleted websites (requires `organizationId` and organization-level trash access) with their deletion time and purge deadline.",
							"type": "string",
							"enum": [
								"active",
								"deleted"
							]
						}
					}
				],
				"responses": {
					"200": {
						"description": "A page of websites: active rows with hostname and sharing info, or soft-deleted rows with trash timing when `status=deleted`.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"anyOf": [
													{
														"type": "object",
														"properties": {
															"id": {
																"type": "string",
																"description": "Unique website id."
															},
															"organizationId": {
																"type": "string",
																"description": "Id of the organization that owns the website."
															},
															"name": {
																"type": "string",
																"description": "Display name, 1–200 characters."
															},
															"slug": {
																"type": "string",
																"description": "URL-safe identifier unique among active websites in the organization."
															},
															"customDomainsEnabled": {
																"type": "boolean",
																"description": "Whether custom domains are enabled. Enabling is owner-only and makes the website a billable site; custom domains can only be attached while `true`."
															},
															"headVersion": {
																"type": "integer",
																"description": "Latest draft source version number; starts at 1."
															},
															"publishedVersion": {
																"anyOf": [
																	{
																		"type": "integer"
																	},
																	{
																		"type": "null"
																	}
																],
																"description": "Currently published version. `null` when the website is not published."
															},
															"createdAt": {
																"description": "When the website was created.",
																"type": "string",
																"format": "date-time"
															},
															"updatedAt": {
																"description": "When the website was last modified.",
																"type": "string",
																"format": "date-time"
															},
															"primaryHostname": {
																"description": "Primary hostname the website is served from. `null` when none is assigned.",
																"type": [
																	"string",
																	"null"
																]
															},
															"organizationSlug": {
																"type": "string",
																"description": "URL slug of the organization that owns the website."
															},
															"shared": {
																"type": "boolean",
																"description": "`true` when access comes from a direct editor grant outside the caller’s organization memberships."
															},
															"faviconUrl": {
																"description": "Public URL of the current favicon image. `null` when no available favicon is configured. Change it with the favicon endpoint.",
																"type": [
																	"string",
																	"null"
																]
															},
															"building": {
																"type": "boolean",
																"description": "`true` while the built-in Cactal agent has a queued or running run on the website."
															}
														},
														"required": [
															"id",
															"organizationId",
															"name",
															"slug",
															"customDomainsEnabled",
															"headVersion",
															"publishedVersion",
															"createdAt",
															"updatedAt",
															"primaryHostname",
															"organizationSlug",
															"shared",
															"faviconUrl",
															"building"
														],
														"additionalProperties": false
													},
													{
														"type": "object",
														"properties": {
															"id": {
																"type": "string",
																"description": "Unique website id."
															},
															"organizationId": {
																"type": "string",
																"description": "Id of the organization that owns the website."
															},
															"name": {
																"type": "string",
																"description": "Display name, 1–200 characters."
															},
															"slug": {
																"type": "string",
																"description": "URL-safe identifier unique among active websites in the organization."
															},
															"customDomainsEnabled": {
																"type": "boolean",
																"description": "Whether custom domains are enabled. Enabling is owner-only and makes the website a billable site; custom domains can only be attached while `true`."
															},
															"headVersion": {
																"type": "integer",
																"description": "Latest draft source version number; starts at 1."
															},
															"publishedVersion": {
																"anyOf": [
																	{
																		"type": "integer"
																	},
																	{
																		"type": "null"
																	}
																],
																"description": "Currently published version. `null` when the website is not published."
															},
															"createdAt": {
																"description": "When the website was created.",
																"type": "string",
																"format": "date-time"
															},
															"updatedAt": {
																"description": "When the website was last modified.",
																"type": "string",
																"format": "date-time"
															},
															"deletedAt": {
																"description": "When the website was moved to the trash.",
																"type": "string",
																"format": "date-time"
															},
															"purgeEligibleAt": {
																"description": "When the trashed website and its data are permanently deleted. Restore before this deadline with `websites.restore`.",
																"type": "string",
																"format": "date-time"
															}
														},
														"required": [
															"id",
															"organizationId",
															"name",
															"slug",
															"customDomainsEnabled",
															"headVersion",
															"publishedVersion",
															"createdAt",
															"updatedAt",
															"deletedAt",
															"purgeEligibleAt"
														],
														"additionalProperties": false
													}
												]
											}
										},
										"nextCursor": {
											"description": "Opaque cursor for the next page. Pass it as the `cursor` parameter on the next request. `null` when this is the last page.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"items",
										"nextCursor"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websites.get",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			},
			"post": {
				"operationId": "websites.create",
				"summary": "Create a website",
				"description": "Creates a website in the organization. Provide either a `name` (trimmed, 1–200 characters) or a creation `prompt`; a prompt returns immediately with the temporary name `Untitled site`, which the built-in naming request attempts to replace on its next run together with a matching available slug and platform hostname. The hostname receives a short suffix when the clean hostname is unavailable. An omitted `slug` is allocated from the initial name; an explicit slug must be lowercase, URL-safe, unreserved, and unique among active websites in the organization. The website starts as an unpublished draft at `headVersion` 1. Send an `idempotencyKey` to make retries return the same website. Edit its source through the Source code operations, then publish with `websiteSourceCode.head.publish`.",
				"tags": [
					"Websites"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"organizationId": {
										"type": "string",
										"minLength": 1
									},
									"name": {
										"type": "string",
										"minLength": 1,
										"maxLength": 200
									},
									"prompt": {
										"type": "string",
										"minLength": 1,
										"maxLength": 100000
									},
									"slug": {
										"type": "string"
									},
									"idempotencyKey": {
										"description": "Caller-generated key, such as a UUID, that makes creation safe to retry. Send the same key on every retry of one creation attempt: when this organization already has a website created with the key, that website is returned and no second website is created. A key whose website is in the trash returns a 409 with `idempotencyKeyStatus: \"deleted\"`; retry with a new key.",
										"type": "string",
										"minLength": 1,
										"maxLength": 200
									}
								},
								"required": [
									"organizationId"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The newly created website, or the website an earlier request with the same `idempotencyKey` created.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique website id."
										},
										"organizationId": {
											"type": "string",
											"description": "Id of the organization that owns the website."
										},
										"name": {
											"type": "string",
											"description": "Display name, 1–200 characters."
										},
										"slug": {
											"type": "string",
											"description": "URL-safe identifier unique among active websites in the organization."
										},
										"customDomainsEnabled": {
											"type": "boolean",
											"description": "Whether custom domains are enabled. Enabling is owner-only and makes the website a billable site; custom domains can only be attached while `true`."
										},
										"headVersion": {
											"type": "integer",
											"description": "Latest draft source version number; starts at 1."
										},
										"publishedVersion": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											],
											"description": "Currently published version. `null` when the website is not published."
										},
										"createdAt": {
											"description": "When the website was created.",
											"type": "string",
											"format": "date-time"
										},
										"updatedAt": {
											"description": "When the website was last modified.",
											"type": "string",
											"format": "date-time"
										}
									},
									"required": [
										"id",
										"organizationId",
										"name",
										"slug",
										"customDomainsEnabled",
										"headVersion",
										"publishedVersion",
										"createdAt",
										"updatedAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websites.create",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/websites/find": {
			"get": {
				"operationId": "websites.get",
				"summary": "Get a website",
				"description": "Returns a single website by `websiteId`, or by its `organizationSlug` + `websiteSlug` pair when starting from a URL — provide exactly one of the two identifications. The response includes whether the caller can manage the website’s CMS schema and domains. Websites in the trash remain retrievable by id (see `websites.list` with `status=deleted` for their purge timing); slug lookups only resolve active websites, and inaccessible or deleted resources return 404.",
				"tags": [
					"Websites"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Website id. Provide either this or the `organizationSlug` + `websiteSlug` pair.",
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "organizationSlug",
						"in": "query",
						"required": false,
						"schema": {
							"description": "URL slug of the owning organization. Pair with `websiteSlug` to look the website up by slug instead of id.",
							"type": "string"
						}
					},
					{
						"name": "websiteSlug",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Website URL slug within the organization. Pair with `organizationSlug`.",
							"type": "string"
						}
					}
				],
				"responses": {
					"200": {
						"description": "The requested website with the caller’s schema- and domain-management flags.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique website id."
										},
										"organizationId": {
											"type": "string",
											"description": "Id of the organization that owns the website."
										},
										"name": {
											"type": "string",
											"description": "Display name, 1–200 characters."
										},
										"slug": {
											"type": "string",
											"description": "URL-safe identifier unique among active websites in the organization."
										},
										"customDomainsEnabled": {
											"type": "boolean",
											"description": "Whether custom domains are enabled. Enabling is owner-only and makes the website a billable site; custom domains can only be attached while `true`."
										},
										"headVersion": {
											"type": "integer",
											"description": "Latest draft source version number; starts at 1."
										},
										"publishedVersion": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											],
											"description": "Currently published version. `null` when the website is not published."
										},
										"createdAt": {
											"description": "When the website was created.",
											"type": "string",
											"format": "date-time"
										},
										"updatedAt": {
											"description": "When the website was last modified.",
											"type": "string",
											"format": "date-time"
										},
										"canManageSchema": {
											"type": "boolean",
											"description": "Whether the caller can create and modify the CMS schema."
										},
										"canManageDomains": {
											"type": "boolean",
											"description": "Whether the caller can create and manage domains."
										},
										"faviconUrl": {
											"description": "Public URL of the current favicon image. `null` when no available favicon is configured. Change it with the favicon endpoint.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"id",
										"organizationId",
										"name",
										"slug",
										"customDomainsEnabled",
										"headVersion",
										"publishedVersion",
										"createdAt",
										"updatedAt",
										"canManageSchema",
										"canManageDomains",
										"faviconUrl"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websites.get",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websites/{websiteId}/editor-invitations": {
			"get": {
				"operationId": "websites.editorInvitations.list",
				"summary": "List website editor invitations",
				"description": "Lists website editor invitations newest first with cursor pagination (default 20 per page, maximum 100). The default `status=pending` returns open invitations; use `accepted`, `canceled`, or `all` to inspect invitation history. Cancel a pending invitation with `websites.editors.remove` and its `invitationId`.",
				"tags": [
					"Website editors"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "status",
						"in": "query",
						"required": false,
						"schema": {
							"default": "pending",
							"type": "string",
							"enum": [
								"pending",
								"accepted",
								"canceled",
								"all"
							]
						}
					},
					{
						"name": "cursor",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"default": 20,
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					}
				],
				"responses": {
					"200": {
						"description": "A page of website editor invitations.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string",
														"description": "Unique invitation id."
													},
													"email": {
														"type": "string",
														"description": "Invited email address, normalized to lowercase."
													},
													"role": {
														"type": "string",
														"enum": [
															"full",
															"content"
														],
														"description": "Editor role granted when the invitation is accepted."
													},
													"status": {
														"type": "string",
														"enum": [
															"pending",
															"accepted",
															"canceled"
														],
														"description": "Lifecycle status of the invitation."
													},
													"expiresAt": {
														"description": "When the invitation can no longer be accepted.",
														"type": "string",
														"format": "date-time"
													},
													"createdAt": {
														"description": "When the invitation was created.",
														"type": "string",
														"format": "date-time"
													}
												},
												"required": [
													"id",
													"email",
													"role",
													"status",
													"expiresAt",
													"createdAt"
												],
												"additionalProperties": false
											}
										},
										"nextCursor": {
											"description": "Opaque cursor for the next page. Pass it as the `cursor` parameter on the next request. `null` when this is the last page.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"items",
										"nextCursor"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websites.editors.list",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websites/{websiteId}": {
			"patch": {
				"operationId": "websites.rename",
				"summary": "Rename a website",
				"description": "Updates the website’s display name and, when supplied, its URL slug. The slug must be lowercase, URL-safe, unreserved, and unique among active websites in the organization.",
				"tags": [
					"Websites"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"name": {
										"type": "string",
										"minLength": 1,
										"maxLength": 200
									},
									"slug": {
										"type": "string"
									}
								},
								"required": [
									"name"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The renamed website.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique website id."
										},
										"organizationId": {
											"type": "string",
											"description": "Id of the organization that owns the website."
										},
										"name": {
											"type": "string",
											"description": "Display name, 1–200 characters."
										},
										"slug": {
											"type": "string",
											"description": "URL-safe identifier unique among active websites in the organization."
										},
										"customDomainsEnabled": {
											"type": "boolean",
											"description": "Whether custom domains are enabled. Enabling is owner-only and makes the website a billable site; custom domains can only be attached while `true`."
										},
										"headVersion": {
											"type": "integer",
											"description": "Latest draft source version number; starts at 1."
										},
										"publishedVersion": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											],
											"description": "Currently published version. `null` when the website is not published."
										},
										"createdAt": {
											"description": "When the website was created.",
											"type": "string",
											"format": "date-time"
										},
										"updatedAt": {
											"description": "When the website was last modified.",
											"type": "string",
											"format": "date-time"
										}
									},
									"required": [
										"id",
										"organizationId",
										"name",
										"slug",
										"customDomainsEnabled",
										"headVersion",
										"publishedVersion",
										"createdAt",
										"updatedAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websites.rename",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			},
			"delete": {
				"operationId": "websites.delete",
				"summary": "Delete a website",
				"description": "Soft-deletes only the website: it moves to the trash for 30 days and disappears from the default list. Its custom domains are disconnected, which frees the website’s plan slot and releases each hostname for use on another website; restoring the website does not restore them. CMS content, assets, source history, and deployments keep their existing lifecycle state but become inaccessible through the deleted parent. After the purge deadline shown by `websites.list` with `status=deleted`, the website and its dependent data are permanently deleted. Deleting a website that is already in the trash returns 404. Restore it before expiry with `POST /websites/{websiteId}/restore`.",
				"tags": [
					"Websites"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Confirmation that the website was deleted.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"deleted": {
											"type": "boolean",
											"const": true,
											"description": "Always `true` once the website is in the trash."
										}
									},
									"required": [
										"deleted"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websites.delete",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/websites/{websiteId}/restore": {
			"post": {
				"operationId": "websites.restore",
				"summary": "Restore a deleted website",
				"description": "Restores a soft-deleted website before its 30-day restore window expires: the deletion and purge-eligibility markers are cleared and the website returns to the active list. Calling this on an active website returns a 404. An expired restore window or an active website claiming the same slug returns a 409.",
				"tags": [
					"Websites"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "The restored website.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique website id."
										},
										"organizationId": {
											"type": "string",
											"description": "Id of the organization that owns the website."
										},
										"name": {
											"type": "string",
											"description": "Display name, 1–200 characters."
										},
										"slug": {
											"type": "string",
											"description": "URL-safe identifier unique among active websites in the organization."
										},
										"customDomainsEnabled": {
											"type": "boolean",
											"description": "Whether custom domains are enabled. Enabling is owner-only and makes the website a billable site; custom domains can only be attached while `true`."
										},
										"headVersion": {
											"type": "integer",
											"description": "Latest draft source version number; starts at 1."
										},
										"publishedVersion": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											],
											"description": "Currently published version. `null` when the website is not published."
										},
										"createdAt": {
											"description": "When the website was created.",
											"type": "string",
											"format": "date-time"
										},
										"updatedAt": {
											"description": "When the website was last modified.",
											"type": "string",
											"format": "date-time"
										}
									},
									"required": [
										"id",
										"organizationId",
										"name",
										"slug",
										"customDomainsEnabled",
										"headVersion",
										"publishedVersion",
										"createdAt",
										"updatedAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websites.restore",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/websites/{websiteId}/transfer": {
			"post": {
				"operationId": "websites.transfer",
				"summary": "Transfer a website to another organization",
				"description": "Moves the website to a different organization. The target must not be the current owner, and you need transfer permission on the website plus website-creation permission in the target organization. When the website has a custom domain, the target organization must have active billing with a free plan slot. If the target organization already has an active website with the same slug, transfer returns a 409. All website-scoped editor grants are removed as part of the transfer.",
				"tags": [
					"Websites"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"targetOrganizationId": {
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"targetOrganizationId"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The website under its new organization.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique website id."
										},
										"organizationId": {
											"type": "string",
											"description": "Id of the organization that owns the website."
										},
										"name": {
											"type": "string",
											"description": "Display name, 1–200 characters."
										},
										"slug": {
											"type": "string",
											"description": "URL-safe identifier unique among active websites in the organization."
										},
										"customDomainsEnabled": {
											"type": "boolean",
											"description": "Whether custom domains are enabled. Enabling is owner-only and makes the website a billable site; custom domains can only be attached while `true`."
										},
										"headVersion": {
											"type": "integer",
											"description": "Latest draft source version number; starts at 1."
										},
										"publishedVersion": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											],
											"description": "Currently published version. `null` when the website is not published."
										},
										"createdAt": {
											"description": "When the website was created.",
											"type": "string",
											"format": "date-time"
										},
										"updatedAt": {
											"description": "When the website was last modified.",
											"type": "string",
											"format": "date-time"
										}
									},
									"required": [
										"id",
										"organizationId",
										"name",
										"slug",
										"customDomainsEnabled",
										"headVersion",
										"publishedVersion",
										"createdAt",
										"updatedAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websites.transfer",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/websites/{websiteId}/editors": {
			"get": {
				"operationId": "websites.editors.list",
				"summary": "List website editors",
				"description": "Returns the website’s direct user-account editor grants, newest first as a paginated set. API keys, organization members who inherit access, and email invitations are separate resources. List invitations with `websites.editorInvitations.list`, add editors with `websites.editors.add`, and revoke a grant with `websites.editors.remove`. Page size defaults to 20 (maximum 100).",
				"tags": [
					"Website editors"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "cursor",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"default": 20,
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					}
				],
				"responses": {
					"200": {
						"description": "One page of the website’s direct editor grants.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"principalId": {
														"type": "string",
														"description": "Id of the principal holding the grant."
													},
													"role": {
														"type": "string",
														"enum": [
															"full",
															"content"
														],
														"description": "`full` allows full website editing; `content` allows content editing only."
													},
													"createdAt": {
														"description": "When the grant was created.",
														"type": "string",
														"format": "date-time"
													},
													"name": {
														"type": "string",
														"description": "Account display name."
													},
													"email": {
														"type": "string",
														"description": "Account email address."
													}
												},
												"required": [
													"principalId",
													"role",
													"createdAt",
													"name",
													"email"
												],
												"additionalProperties": false
											}
										},
										"nextCursor": {
											"description": "Opaque cursor for the next page. Pass it as the `cursor` parameter on the next request. `null` when this is the last page.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"items",
										"nextCursor"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websites.editors.list",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			},
			"post": {
				"operationId": "websites.editors.add",
				"summary": "Add a website editor by email",
				"description": "Gives an email address `full` or `content` editor access to this single website. When the email already belongs to an account, the grant is created immediately (`granted`) — or nothing changes if that account already inherits access through its organization role (`already_inherited`). Otherwise a `pending` invitation is created and emailed; it expires after 48 hours unless you set `expiresAt`. Re-adding an email with a pending invitation refreshes that invitation instead of creating a duplicate. Change an existing editor’s role with `websites.editors.setRole`.",
				"tags": [
					"Website editors"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"email": {
										"type": "string",
										"format": "email",
										"pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
									},
									"role": {
										"type": "string",
										"enum": [
											"full",
											"content"
										]
									},
									"expiresAt": {
										"anyOf": [
											{},
											{
												"type": "string",
												"format": "date-time",
												"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
											}
										]
									},
									"callbackUrl": {
										"type": "string",
										"format": "uri"
									}
								},
								"required": [
									"email",
									"role"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The immediate grant, the already-covered principal, or the pending invitation.",
						"content": {
							"application/json": {
								"schema": {
									"oneOf": [
										{
											"type": "object",
											"properties": {
												"status": {
													"type": "string",
													"const": "granted",
													"description": "The email belongs to an existing account; the editor grant was created immediately."
												},
												"grant": {
													"type": "object",
													"properties": {
														"websiteId": {
															"type": "string",
															"description": "Id of the website the grant applies to."
														},
														"principalId": {
															"type": "string",
															"description": "Id of the principal holding the grant."
														},
														"role": {
															"type": "string",
															"enum": [
																"full",
																"content"
															],
															"description": "`full` allows full website editing; `content` allows content editing only."
														},
														"createdAt": {
															"description": "When the grant was created.",
															"type": "string",
															"format": "date-time"
														}
													},
													"required": [
														"websiteId",
														"principalId",
														"role",
														"createdAt"
													],
													"additionalProperties": false
												}
											},
											"required": [
												"status",
												"grant"
											],
											"additionalProperties": false
										},
										{
											"type": "object",
											"properties": {
												"status": {
													"type": "string",
													"const": "already_inherited",
													"description": "The account already inherits website access through its organization role; nothing changed."
												},
												"principalId": {
													"type": "string",
													"description": "Id of the principal that already has access."
												}
											},
											"required": [
												"status",
												"principalId"
											],
											"additionalProperties": false
										},
										{
											"type": "object",
											"properties": {
												"status": {
													"type": "string",
													"const": "pending",
													"description": "No account exists for the email; a pending invitation was created and emailed."
												},
												"invitation": {
													"type": "object",
													"properties": {
														"id": {
															"type": "string",
															"description": "Unique invitation id."
														},
														"email": {
															"type": "string",
															"description": "Invited email address, normalized to lowercase."
														},
														"role": {
															"type": "string",
															"enum": [
																"full",
																"content"
															],
															"description": "Editor role granted when the invitation is accepted."
														},
														"status": {
															"type": "string",
															"enum": [
																"pending",
																"accepted",
																"canceled"
															],
															"description": "Lifecycle status of the invitation."
														},
														"expiresAt": {
															"description": "When the invitation can no longer be accepted.",
															"type": "string",
															"format": "date-time"
														},
														"createdAt": {
															"description": "When the invitation was created.",
															"type": "string",
															"format": "date-time"
														}
													},
													"required": [
														"id",
														"email",
														"role",
														"status",
														"expiresAt",
														"createdAt"
													],
													"additionalProperties": false
												}
											},
											"required": [
												"status",
												"invitation"
											],
											"additionalProperties": false
										}
									]
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websites.editors.add",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			},
			"delete": {
				"operationId": "websites.editors.remove",
				"summary": "Remove a website editor or cancel an invitation",
				"description": "Revokes a website-scoped editor grant when called with `principalId`, or cancels a pending invitation (so its emailed link stops working) when called with `invitationId` — provide exactly one. Returns 404 when the principal has no direct grant or the invitation does not exist; canceling an invitation that was already accepted or canceled returns 409. Access inherited from an organization role is unaffected. Grants are listed by `websites.editors.list`; invitations are listed by `websites.editorInvitations.list`.",
				"tags": [
					"Website editors"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": false,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"principalId": {
										"description": "Principal whose direct editor grant should be revoked. Provide either this or `invitationId`.",
										"type": "string",
										"minLength": 1
									},
									"invitationId": {
										"description": "Pending invitation to cancel so its emailed link stops working. Provide either this or `principalId`.",
										"type": "string",
										"minLength": 1
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The removed editor grant or the canceled invitation.",
						"content": {
							"application/json": {
								"schema": {
									"oneOf": [
										{
											"type": "object",
											"properties": {
												"status": {
													"type": "string",
													"const": "removed",
													"description": "The principal’s direct editor grant was revoked."
												},
												"grant": {
													"type": "object",
													"properties": {
														"websiteId": {
															"type": "string",
															"description": "Id of the website the grant applies to."
														},
														"principalId": {
															"type": "string",
															"description": "Id of the principal holding the grant."
														},
														"role": {
															"type": "string",
															"enum": [
																"full",
																"content"
															],
															"description": "`full` allows full website editing; `content` allows content editing only."
														},
														"createdAt": {
															"description": "When the grant was created.",
															"type": "string",
															"format": "date-time"
														}
													},
													"required": [
														"websiteId",
														"principalId",
														"role",
														"createdAt"
													],
													"additionalProperties": false
												}
											},
											"required": [
												"status",
												"grant"
											],
											"additionalProperties": false
										},
										{
											"type": "object",
											"properties": {
												"status": {
													"type": "string",
													"const": "canceled",
													"description": "The pending invitation was canceled; its emailed link no longer works."
												},
												"invitation": {
													"type": "object",
													"properties": {
														"id": {
															"type": "string",
															"description": "Unique invitation id."
														},
														"email": {
															"type": "string",
															"description": "Invited email address, normalized to lowercase."
														},
														"role": {
															"type": "string",
															"enum": [
																"full",
																"content"
															],
															"description": "Editor role granted when the invitation is accepted."
														},
														"status": {
															"type": "string",
															"enum": [
																"pending",
																"accepted",
																"canceled"
															],
															"description": "Lifecycle status of the invitation."
														},
														"expiresAt": {
															"description": "When the invitation can no longer be accepted.",
															"type": "string",
															"format": "date-time"
														},
														"createdAt": {
															"description": "When the invitation was created.",
															"type": "string",
															"format": "date-time"
														}
													},
													"required": [
														"id",
														"email",
														"role",
														"status",
														"expiresAt",
														"createdAt"
													],
													"additionalProperties": false
												}
											},
											"required": [
												"status",
												"invitation"
											],
											"additionalProperties": false
										}
									]
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websites.editors.remove",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/websites/{websiteId}/editors/{principalId}": {
			"put": {
				"operationId": "websites.editors.setRole",
				"summary": "Set a website editor’s role",
				"description": "Sets the principal’s editor role on this single website, replacing any existing grant for that principal. `role` is `full` (full website editing) or `content` (content editing only). Principals who already inherit access through their organization role are rejected with a validation error. Use `websites.editors.add` to add someone by email instead of principal id.",
				"tags": [
					"Website editors"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "principalId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"role": {
										"type": "string",
										"enum": [
											"full",
											"content"
										]
									}
								},
								"required": [
									"role"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The saved editor grant.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"websiteId": {
											"type": "string",
											"description": "Id of the website the grant applies to."
										},
										"principalId": {
											"type": "string",
											"description": "Id of the principal holding the grant."
										},
										"role": {
											"type": "string",
											"enum": [
												"full",
												"content"
											],
											"description": "`full` allows full website editing; `content` allows content editing only."
										},
										"createdAt": {
											"description": "When the grant was created.",
											"type": "string",
											"format": "date-time"
										}
									},
									"required": [
										"websiteId",
										"principalId",
										"role",
										"createdAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websites.editors.setRole",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/websites/{websiteId}/domains": {
			"get": {
				"operationId": "websiteDomains.list",
				"summary": "List domains for a website",
				"description": "Returns every domain record attached to the website, oldest first: the automatically allocated platform subdomain plus any custom domains. Platform subdomains are always `active`; custom domains carry their current verification status and the DNS records still expected.",
				"tags": [
					"Domains"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "All domain records for the website, oldest first.",
						"content": {
							"application/json": {
								"schema": {
									"type": "array",
									"items": {
										"type": "object",
										"properties": {
											"id": {
												"type": "string",
												"description": "Unique domain id."
											},
											"hostname": {
												"type": "string",
												"description": "Fully-qualified lowercase hostname, e.g. `blog.example.com`."
											},
											"status": {
												"type": "string",
												"enum": [
													"pending_dns",
													"pending_verification",
													"active",
													"failed",
													"removing",
													"suspended"
												],
												"description": "`pending_dns`: waiting for the required DNS records; `pending_verification`: DNS found, waiting for the edge host to become ready; `active`: serving traffic; `failed`: verification failed (see `failureMessage`); `removing`: being deregistered from the edge; `suspended`: disabled by Cactal trust & safety. Platform subdomains are always `active`."
											},
											"kind": {
												"type": "string",
												"enum": [
													"platform_subdomain",
													"custom"
												],
												"description": "Every website automatically gets one `platform_subdomain`; `custom` domains are added through this API."
											},
											"isPrimary": {
												"type": "boolean",
												"description": "Whether this is the canonical custom domain that published traffic redirects to. Always `false` for platform subdomains."
											},
											"dnsRecords": {
												"type": "array",
												"items": {
													"type": "object",
													"properties": {
														"type": {
															"type": "string",
															"enum": [
																"TXT",
																"CNAME",
																"A"
															],
															"description": "DNS record type to create."
														},
														"name": {
															"type": "string",
															"description": "Fully-qualified name (host) to create the record on."
														},
														"value": {
															"type": "string",
															"description": "Exact value the record must resolve to."
														},
														"purpose": {
															"type": "string",
															"enum": [
																"ownership",
																"routing"
															],
															"description": "`ownership` records prove control of the hostname (TXT); `routing` records point traffic at the Cactal edge (CNAME for subdomains, A for apex domains)."
														}
													},
													"required": [
														"type",
														"name",
														"value",
														"purpose"
													],
													"additionalProperties": false
												},
												"description": "DNS records the domain owner must create for verification to succeed. Empty for platform subdomains."
											},
											"failureMessage": {
												"description": "What the most recent verification check found wrong, including whether ownership is missing, the hostname is unreachable, or traffic reaches a service other than Cactal. `null` when nothing is wrong.",
												"type": [
													"string",
													"null"
												]
											},
											"lastCheckedAt": {
												"anyOf": [
													{
														"type": "string",
														"format": "date-time"
													},
													{
														"type": "null"
													}
												],
												"description": "When DNS and edge status were last checked. `null` before the first check."
											},
											"createdAt": {
												"description": "When the domain record was created.",
												"type": "string",
												"format": "date-time"
											}
										},
										"required": [
											"id",
											"hostname",
											"status",
											"kind",
											"isPrimary",
											"dnsRecords",
											"failureMessage",
											"lastCheckedAt",
											"createdAt"
										],
										"additionalProperties": false
									}
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteDomains.list",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			},
			"post": {
				"operationId": "websiteDomains.createCustomDomain",
				"summary": "Add a custom domain",
				"description": "Claims `hostname` for this website and registers it with the Cactal edge. Custom domains must first be enabled for the website (`websiteDomains.setEnabled`, owner-only — enabling is the billing moment); the request fails with 403 `custom_domains_disabled` otherwise. Adding or removing domains on an enabled website never changes billing. The domain is created in `pending_dns` status, and the returned `dnsRecords` list exactly what to create at your DNS provider: an ownership TXT record on `_cactal.<hostname>` plus a routing record (a CNAME for subdomains or an A record for apex domains). Hostnames must be lowercase ASCII domains (not IP addresses or Cactal platform hosts), each website can have at most 10 custom domains, and a hostname can be claimed by only one website; claims that never verify expire after 7 days. After creating the records, call the verify endpoint or wait for the automatic re-checks.",
				"tags": [
					"Domains"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"hostname": {
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"hostname"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The created domain in `pending_dns` status. Next: create the `dnsRecords` at your DNS provider, then call `websiteDomains.verifyCustomDomain`.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique domain id."
										},
										"hostname": {
											"type": "string",
											"description": "Fully-qualified lowercase hostname, e.g. `blog.example.com`."
										},
										"status": {
											"type": "string",
											"enum": [
												"pending_dns",
												"pending_verification",
												"active",
												"failed",
												"removing",
												"suspended"
											],
											"description": "`pending_dns`: waiting for the required DNS records; `pending_verification`: DNS found, waiting for the edge host to become ready; `active`: serving traffic; `failed`: verification failed (see `failureMessage`); `removing`: being deregistered from the edge; `suspended`: disabled by Cactal trust & safety. Platform subdomains are always `active`."
										},
										"kind": {
											"type": "string",
											"enum": [
												"platform_subdomain",
												"custom"
											],
											"description": "Every website automatically gets one `platform_subdomain`; `custom` domains are added through this API."
										},
										"isPrimary": {
											"type": "boolean",
											"description": "Whether this is the canonical custom domain that published traffic redirects to. Always `false` for platform subdomains."
										},
										"dnsRecords": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"type": {
														"type": "string",
														"enum": [
															"TXT",
															"CNAME",
															"A"
														],
														"description": "DNS record type to create."
													},
													"name": {
														"type": "string",
														"description": "Fully-qualified name (host) to create the record on."
													},
													"value": {
														"type": "string",
														"description": "Exact value the record must resolve to."
													},
													"purpose": {
														"type": "string",
														"enum": [
															"ownership",
															"routing"
														],
														"description": "`ownership` records prove control of the hostname (TXT); `routing` records point traffic at the Cactal edge (CNAME for subdomains, A for apex domains)."
													}
												},
												"required": [
													"type",
													"name",
													"value",
													"purpose"
												],
												"additionalProperties": false
											},
											"description": "DNS records the domain owner must create for verification to succeed. Empty for platform subdomains."
										},
										"failureMessage": {
											"description": "What the most recent verification check found wrong, including whether ownership is missing, the hostname is unreachable, or traffic reaches a service other than Cactal. `null` when nothing is wrong.",
											"type": [
												"string",
												"null"
											]
										},
										"lastCheckedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											],
											"description": "When DNS and edge status were last checked. `null` before the first check."
										},
										"createdAt": {
											"description": "When the domain record was created.",
											"type": "string",
											"format": "date-time"
										}
									},
									"required": [
										"id",
										"hostname",
										"status",
										"kind",
										"isPrimary",
										"dnsRecords",
										"failureMessage",
										"lastCheckedAt",
										"createdAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteDomains.create",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/websites/{websiteId}/domains/enablement": {
			"post": {
				"operationId": "websiteDomains.setEnabled",
				"summary": "Enable or disable custom domains",
				"description": "Turns custom domains on or off for the website — the billing moment. Enabling makes it a billable site on the plan (first included with Pro, additional sites charged per site, prorated); disabling stops that charge with a prorated credit and disconnects every custom domain on the website. Owner-only. When `paymentUrl` is returned nothing changed: complete checkout there, then call again. Trashing a website disables custom domains automatically.",
				"tags": [
					"Domains"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"enabled": {
										"type": "boolean"
									},
									"successUrl": {
										"type": "string"
									}
								},
								"required": [
									"enabled"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The enablement state after the call.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"enabled": {
											"type": "boolean",
											"description": "Whether custom domains are enabled for the website after this call."
										},
										"paymentUrl": {
											"description": "Hosted checkout URL when enabling needs a payment step; custom domains stay disabled until checkout completes and the call is retried. `null` when applied directly.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"enabled",
										"paymentUrl"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				}
			}
		},
		"/websites/{websiteId}/domains/{domainId}/verify": {
			"post": {
				"operationId": "websiteDomains.verifyCustomDomain",
				"summary": "Verify a custom domain",
				"description": "Re-checks the ownership TXT record, edge readiness, and whether an HTTPS request to the hostname reaches this Cactal domain, then returns the updated status. A customer CDN or reverse proxy in front is supported when it forwards the verification request to Cactal. `failureMessage` distinguishes a missing ownership record, an unreachable hostname, and a hostname served somewhere else. Cactal asks the domain nameservers directly, so new records usually count on the next check, and this endpoint is safe to poll; Cactal also re-checks pending domains automatically (every minute for the first 2 hours, every 5 minutes for the first day, then every 15 minutes). The first custom domain to become active is promoted to primary automatically.",
				"tags": [
					"Domains"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "domainId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "The domain with its freshly checked status. When not yet `active`, `failureMessage` explains whether ownership, reachability, routing, or edge readiness needs attention.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique domain id."
										},
										"hostname": {
											"type": "string",
											"description": "Fully-qualified lowercase hostname, e.g. `blog.example.com`."
										},
										"status": {
											"type": "string",
											"enum": [
												"pending_dns",
												"pending_verification",
												"active",
												"failed",
												"removing",
												"suspended"
											],
											"description": "`pending_dns`: waiting for the required DNS records; `pending_verification`: DNS found, waiting for the edge host to become ready; `active`: serving traffic; `failed`: verification failed (see `failureMessage`); `removing`: being deregistered from the edge; `suspended`: disabled by Cactal trust & safety. Platform subdomains are always `active`."
										},
										"kind": {
											"type": "string",
											"enum": [
												"platform_subdomain",
												"custom"
											],
											"description": "Every website automatically gets one `platform_subdomain`; `custom` domains are added through this API."
										},
										"isPrimary": {
											"type": "boolean",
											"description": "Whether this is the canonical custom domain that published traffic redirects to. Always `false` for platform subdomains."
										},
										"dnsRecords": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"type": {
														"type": "string",
														"enum": [
															"TXT",
															"CNAME",
															"A"
														],
														"description": "DNS record type to create."
													},
													"name": {
														"type": "string",
														"description": "Fully-qualified name (host) to create the record on."
													},
													"value": {
														"type": "string",
														"description": "Exact value the record must resolve to."
													},
													"purpose": {
														"type": "string",
														"enum": [
															"ownership",
															"routing"
														],
														"description": "`ownership` records prove control of the hostname (TXT); `routing` records point traffic at the Cactal edge (CNAME for subdomains, A for apex domains)."
													}
												},
												"required": [
													"type",
													"name",
													"value",
													"purpose"
												],
												"additionalProperties": false
											},
											"description": "DNS records the domain owner must create for verification to succeed. Empty for platform subdomains."
										},
										"failureMessage": {
											"description": "What the most recent verification check found wrong, including whether ownership is missing, the hostname is unreachable, or traffic reaches a service other than Cactal. `null` when nothing is wrong.",
											"type": [
												"string",
												"null"
											]
										},
										"lastCheckedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											],
											"description": "When DNS and edge status were last checked. `null` before the first check."
										},
										"createdAt": {
											"description": "When the domain record was created.",
											"type": "string",
											"format": "date-time"
										}
									},
									"required": [
										"id",
										"hostname",
										"status",
										"kind",
										"isPrimary",
										"dnsRecords",
										"failureMessage",
										"lastCheckedAt",
										"createdAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteDomains.verify",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/websites/{websiteId}/domains/{domainId}/primary": {
			"post": {
				"operationId": "websiteDomains.setPrimaryDomain",
				"summary": "Set the primary custom domain",
				"description": "Makes this domain the canonical hostname for the published website: requests reaching the platform subdomain or any other custom domain are permanently redirected (HTTP 308) to it, preserving path and query. Only `active` custom domains can be made primary — the request fails with 409 otherwise. Any previous primary is cleared in the same transaction.",
				"tags": [
					"Domains"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "domainId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Confirmation that the domain is now primary.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Id of the domain that is now primary."
										},
										"hostname": {
											"type": "string",
											"description": "Hostname that published traffic now redirects to."
										},
										"isPrimary": {
											"type": "boolean",
											"const": true,
											"description": "The domain is now the canonical custom domain; any previous primary was cleared."
										}
									},
									"required": [
										"id",
										"hostname",
										"isPrimary"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteDomains.setPrimary",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/websites/{websiteId}/domains/{domainId}": {
			"delete": {
				"operationId": "websiteDomains.removeCustomDomain",
				"summary": "Remove a custom domain",
				"description": "Deregisters the hostname from the Cactal edge and deletes the domain record. If the removed domain was primary, the oldest remaining active custom domain is promoted automatically. If edge deregistration fails, the domain stays in `removing` status and a background sweep retries until it is gone. Platform subdomains cannot be removed.",
				"tags": [
					"Domains"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "domainId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Confirmation that the domain was removed.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"removed": {
											"type": "boolean",
											"const": true,
											"description": "The domain record was deleted and the hostname deregistered from the Cactal edge."
										},
										"id": {
											"type": "string",
											"description": "Id of the removed domain."
										},
										"hostname": {
											"type": "string",
											"description": "Hostname that was removed."
										}
									},
									"required": [
										"removed",
										"id",
										"hostname"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteDomains.delete",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/websites/{websiteId}/domains/platform/rename": {
			"post": {
				"operationId": "websiteDomains.renamePlatformDomain",
				"summary": "Rename the platform subdomain",
				"description": "Moves the website to `<label>.<platform domain>`, the free address every website is allocated at creation. `label` is just the leading part: 1-40 characters, lowercase letters, digits, and inner hyphens, and it may not be one of the labels Cactal reserves. The change takes effect immediately, on published and draft hosts alike. The previous hostname is released at the same moment, with no redirect and no grace period, so any existing links to it break and another website may claim it. Take care on a published site. Fails with 409 when the label is already taken by another website; renaming to the label the website already has is a no-op. Custom domains are unaffected, and if one of them is primary it stays the canonical address.",
				"tags": [
					"Domains"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"label": {
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"label"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The platform subdomain at its new hostname, plus `previousHostname`: the address that was just released.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique domain id."
										},
										"hostname": {
											"type": "string",
											"description": "Fully-qualified lowercase hostname, e.g. `blog.example.com`."
										},
										"status": {
											"type": "string",
											"enum": [
												"pending_dns",
												"pending_verification",
												"active",
												"failed",
												"removing",
												"suspended"
											],
											"description": "`pending_dns`: waiting for the required DNS records; `pending_verification`: DNS found, waiting for the edge host to become ready; `active`: serving traffic; `failed`: verification failed (see `failureMessage`); `removing`: being deregistered from the edge; `suspended`: disabled by Cactal trust & safety. Platform subdomains are always `active`."
										},
										"kind": {
											"type": "string",
											"enum": [
												"platform_subdomain",
												"custom"
											],
											"description": "Every website automatically gets one `platform_subdomain`; `custom` domains are added through this API."
										},
										"isPrimary": {
											"type": "boolean",
											"description": "Whether this is the canonical custom domain that published traffic redirects to. Always `false` for platform subdomains."
										},
										"dnsRecords": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"type": {
														"type": "string",
														"enum": [
															"TXT",
															"CNAME",
															"A"
														],
														"description": "DNS record type to create."
													},
													"name": {
														"type": "string",
														"description": "Fully-qualified name (host) to create the record on."
													},
													"value": {
														"type": "string",
														"description": "Exact value the record must resolve to."
													},
													"purpose": {
														"type": "string",
														"enum": [
															"ownership",
															"routing"
														],
														"description": "`ownership` records prove control of the hostname (TXT); `routing` records point traffic at the Cactal edge (CNAME for subdomains, A for apex domains)."
													}
												},
												"required": [
													"type",
													"name",
													"value",
													"purpose"
												],
												"additionalProperties": false
											},
											"description": "DNS records the domain owner must create for verification to succeed. Empty for platform subdomains."
										},
										"failureMessage": {
											"description": "What the most recent verification check found wrong, including whether ownership is missing, the hostname is unreachable, or traffic reaches a service other than Cactal. `null` when nothing is wrong.",
											"type": [
												"string",
												"null"
											]
										},
										"lastCheckedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											],
											"description": "When DNS and edge status were last checked. `null` before the first check."
										},
										"createdAt": {
											"description": "When the domain record was created.",
											"type": "string",
											"format": "date-time"
										},
										"previousHostname": {
											"type": "string",
											"description": "The hostname the website was reachable at before the rename. It is released immediately, so links to it stop working and another website may claim it."
										}
									},
									"required": [
										"id",
										"hostname",
										"status",
										"kind",
										"isPrimary",
										"dnsRecords",
										"failureMessage",
										"lastCheckedAt",
										"createdAt",
										"previousHostname"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteDomains.rename",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/websiteAssets": {
			"get": {
				"operationId": "websiteAssets.list",
				"summary": "List or search assets",
				"description": "Returns ready assets newest first as compact rows (`description`, `category`, dimensions, `url`, `srcSet`); fetch one asset for `originalUrl` and `variants`. Filter by `kind`, `category`, `type` (the broad MIME family: image, video, audio, font, or document), or `search`, which prefix-matches each term against the filename, description, and category; every term must match, else any term. Page size defaults to 50 (max 100).",
				"tags": [
					"Assets"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "kind",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string",
							"enum": [
								"image",
								"file"
							]
						}
					},
					{
						"name": "category",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Only images with this category.",
							"type": "string",
							"enum": [
								"logo",
								"icon",
								"photo",
								"illustration",
								"screenshot",
								"graphic",
								"background",
								"other"
							]
						}
					},
					{
						"name": "type",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Broad file type derived from the MIME type: image, video, audio, font, or document (everything else). Combines with kind, category, and search.",
							"type": "string",
							"enum": [
								"image",
								"video",
								"audio",
								"font",
								"document"
							]
						}
					},
					{
						"name": "search",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Matches filename, description, and category; each term matches as a prefix.",
							"type": "string",
							"minLength": 1,
							"maxLength": 200
						}
					},
					{
						"name": "cursor",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"default": 50,
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					}
				],
				"responses": {
					"200": {
						"description": "One page of ready assets, newest first.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string",
														"description": "Unique asset id. Pass it to websiteAssets.get and websiteAssets.delete."
													},
													"kind": {
														"type": "string",
														"enum": [
															"image",
															"file"
														],
														"description": "Asset kind. Each kind has its own MIME allowlist and size limit."
													},
													"filename": {
														"type": "string",
														"description": "Original filename supplied at upload (1–255 characters)."
													},
													"mimeType": {
														"type": "string",
														"description": "MIME type stored for the asset after upload normalization."
													},
													"description": {
														"description": "One-line description supplied at upload, edited later, or generated for dashboard uploads. `null` when absent.",
														"type": [
															"string",
															"null"
														]
													},
													"category": {
														"anyOf": [
															{
																"type": "string",
																"enum": [
																	"logo",
																	"icon",
																	"photo",
																	"illustration",
																	"screenshot",
																	"graphic",
																	"background",
																	"other"
																]
															},
															{
																"type": "null"
															}
														],
														"description": "Image category: logo, icon, photo, illustration, screenshot, graphic, background, or other. `null` for files and unclassified images."
													},
													"byteSize": {
														"type": "integer",
														"description": "Exact file size in bytes."
													},
													"width": {
														"anyOf": [
															{
																"type": "integer"
															},
															{
																"type": "null"
															}
														],
														"description": "Intrinsic pixel width, recorded at finalize for images. `null` for non-images and formats whose dimensions cannot be read."
													},
													"height": {
														"anyOf": [
															{
																"type": "integer"
															},
															{
																"type": "null"
															}
														],
														"description": "Intrinsic pixel height; see `width`."
													},
													"colors": {
														"anyOf": [
															{
																"maxItems": 8,
																"type": "array",
																"items": {
																	"type": "object",
																	"properties": {
																		"hex": {
																			"type": "string",
																			"description": "Uppercase sRGB hex, exact for flat colors."
																		},
																		"share": {
																			"anyOf": [
																				{
																					"type": "number",
																					"minimum": 0,
																					"maximum": 1
																				},
																				{
																					"type": "null"
																				}
																			],
																			"description": "Fraction of opaque pixels this color and its anti-aliasing cover. `null` for SVG, whose colors are read from the source instead of rendered."
																		},
																		"accent": {
																			"type": "boolean",
																			"description": "Saturated enough to matter as an accent even at a small share."
																		}
																	},
																	"required": [
																		"hex",
																		"share",
																		"accent"
																	],
																	"additionalProperties": false
																},
																"description": "Up to 8 representative colors sorted by share. `null` for files and for images that could not be analyzed."
															},
															{
																"type": "null"
															}
														]
													},
													"transparentShare": {
														"anyOf": [
															{
																"type": "number",
																"minimum": 0,
																"maximum": 1
															},
															{
																"type": "null"
															}
														],
														"description": "Fraction of the backdrop that shows through, weighting each pixel by its transparency: 0 for an opaque image, roughly the background area for a cutout, 0.5 for a uniform 50% overlay. `null` when unknown."
													},
													"translucentShare": {
														"anyOf": [
															{
																"type": "number",
																"minimum": 0,
																"maximum": 1
															},
															{
																"type": "null"
															}
														],
														"description": "Fraction of pixels with partial alpha (soft shadows, glows, fades, glass), excluding fully transparent and fully opaque pixels. `null` when unknown."
													},
													"sourceAssetId": {
														"description": "For a background-removed image, the id of the asset it was derived from.",
														"type": [
															"string",
															"null"
														]
													},
													"url": {
														"type": "string",
														"description": "Optimized public CDN URL for display. Transformable images use the 1920px default variant; other assets use `originalUrl`."
													},
													"srcSet": {
														"description": "Ready-to-use responsive image srcset. `null` when image transformation is unavailable.",
														"type": [
															"string",
															"null"
														]
													},
													"createdAt": {
														"description": "When the upload was started.",
														"type": "string",
														"format": "date-time"
													}
												},
												"required": [
													"id",
													"kind",
													"filename",
													"mimeType",
													"description",
													"category",
													"byteSize",
													"width",
													"height",
													"colors",
													"transparentShare",
													"translucentShare",
													"sourceAssetId",
													"url",
													"srcSet",
													"createdAt"
												],
												"additionalProperties": false
											}
										},
										"nextCursor": {
											"description": "Opaque cursor for the next page. Pass it as the `cursor` parameter on the next request. `null` when this is the last page.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"items",
										"nextCursor"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteAssets.list",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			},
			"post": {
				"operationId": "websiteAssets.beginUpload",
				"summary": "Begin an asset upload",
				"description": "Step 1 of the two-step upload: validates `kind`, `mimeType`, and `byteSize`, creates the asset as `pending`, and returns presigned upload instructions. Include a one-line `description` (and `category` for images) so the asset can be found later. PUT the raw bytes to `upload.url` with exactly `upload.headers` within 15 minutes, then finalize. `image` accepts PNG, JPEG, WebP, GIF, AVIF, SVG, BMP, and ICO up to 10 MiB; `file` accepts documents, fonts, audio, video, images, ZIP, `.riv`, and `.lottie` up to 100 MiB. Unfinalized uploads are cleaned up after about an hour.",
				"tags": [
					"Assets"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"kind": {
										"type": "string",
										"enum": [
											"image",
											"file"
										]
									},
									"filename": {
										"type": "string",
										"minLength": 1,
										"maxLength": 255
									},
									"mimeType": {
										"type": "string",
										"maxLength": 255
									},
									"byteSize": {
										"type": "integer",
										"exclusiveMinimum": 0,
										"maximum": 9007199254740991
									},
									"description": {
										"type": "string",
										"minLength": 1,
										"maxLength": 500,
										"description": "One sentence: subject, visible text, style, intended use."
									},
									"category": {
										"type": "string",
										"enum": [
											"logo",
											"icon",
											"photo",
											"illustration",
											"screenshot",
											"graphic",
											"background",
											"other"
										],
										"description": "What the image is. Images only."
									},
									"listed": {
										"description": "Pass `false` to keep the asset out of lists until the website uses it; it is then never described automatically. Cactal sets this for images attached to agent messages.",
										"type": "boolean"
									}
								},
								"required": [
									"websiteId",
									"kind",
									"filename",
									"mimeType",
									"byteSize"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The pending asset id and presigned upload instructions. Next: PUT the file bytes to `upload.url`, then call `websiteAssets.finalize` with `assetId`.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"assetId": {
											"type": "string",
											"description": "Id of the pending asset. After uploading the bytes, pass it to websiteAssets.finalize."
										},
										"upload": {
											"type": "object",
											"properties": {
												"url": {
													"type": "string",
													"description": "Presigned URL to PUT the raw file bytes to."
												},
												"method": {
													"type": "string",
													"const": "PUT",
													"description": "HTTP method to use against `url`."
												},
												"headers": {
													"type": "object",
													"propertyNames": {
														"type": "string"
													},
													"additionalProperties": {
														"type": "string"
													},
													"description": "Headers that must be sent exactly as given on the PUT because the signature is bound to the declared `Content-Type` and `Content-Length`."
												},
												"expiresAt": {
													"description": "When the presigned URL stops working (15 minutes after issue).",
													"type": "string",
													"format": "date-time"
												}
											},
											"required": [
												"url",
												"method",
												"headers",
												"expiresAt"
											],
											"additionalProperties": false,
											"description": "Presigned instructions for uploading the file bytes."
										}
									},
									"required": [
										"assetId",
										"upload"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteAssets.beginUpload",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/websiteAssets/{assetId}": {
			"get": {
				"operationId": "websiteAssets.get",
				"summary": "Get an asset",
				"description": "Returns the asset with `description`, `category`, and its CDN delivery shape: for transformable images `url` is the optimized 1920px default and `originalUrl` the original bytes; both never expire. MCP clients that render images also receive the image.",
				"tags": [
					"Assets"
				],
				"parameters": [
					{
						"name": "assetId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "The asset with its public CDN delivery URLs.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique asset id. Pass it to websiteAssets.get and websiteAssets.delete."
										},
										"kind": {
											"type": "string",
											"enum": [
												"image",
												"file"
											],
											"description": "Asset kind. Each kind has its own MIME allowlist and size limit."
										},
										"filename": {
											"type": "string",
											"description": "Original filename supplied at upload (1–255 characters)."
										},
										"mimeType": {
											"type": "string",
											"description": "MIME type stored for the asset after upload normalization."
										},
										"description": {
											"description": "One-line description supplied at upload, edited later, or generated for dashboard uploads. `null` when absent.",
											"type": [
												"string",
												"null"
											]
										},
										"category": {
											"anyOf": [
												{
													"type": "string",
													"enum": [
														"logo",
														"icon",
														"photo",
														"illustration",
														"screenshot",
														"graphic",
														"background",
														"other"
													]
												},
												{
													"type": "null"
												}
											],
											"description": "Image category: logo, icon, photo, illustration, screenshot, graphic, background, or other. `null` for files and unclassified images."
										},
										"byteSize": {
											"type": "integer",
											"description": "Exact file size in bytes."
										},
										"width": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											],
											"description": "Intrinsic pixel width, recorded at finalize for images. `null` for non-images and formats whose dimensions cannot be read."
										},
										"height": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											],
											"description": "Intrinsic pixel height; see `width`."
										},
										"colors": {
											"anyOf": [
												{
													"maxItems": 8,
													"type": "array",
													"items": {
														"type": "object",
														"properties": {
															"hex": {
																"type": "string",
																"description": "Uppercase sRGB hex, exact for flat colors."
															},
															"share": {
																"anyOf": [
																	{
																		"type": "number",
																		"minimum": 0,
																		"maximum": 1
																	},
																	{
																		"type": "null"
																	}
																],
																"description": "Fraction of opaque pixels this color and its anti-aliasing cover. `null` for SVG, whose colors are read from the source instead of rendered."
															},
															"accent": {
																"type": "boolean",
																"description": "Saturated enough to matter as an accent even at a small share."
															}
														},
														"required": [
															"hex",
															"share",
															"accent"
														],
														"additionalProperties": false
													},
													"description": "Up to 8 representative colors sorted by share. `null` for files and for images that could not be analyzed."
												},
												{
													"type": "null"
												}
											]
										},
										"transparentShare": {
											"anyOf": [
												{
													"type": "number",
													"minimum": 0,
													"maximum": 1
												},
												{
													"type": "null"
												}
											],
											"description": "Fraction of the backdrop that shows through, weighting each pixel by its transparency: 0 for an opaque image, roughly the background area for a cutout, 0.5 for a uniform 50% overlay. `null` when unknown."
										},
										"translucentShare": {
											"anyOf": [
												{
													"type": "number",
													"minimum": 0,
													"maximum": 1
												},
												{
													"type": "null"
												}
											],
											"description": "Fraction of pixels with partial alpha (soft shadows, glows, fades, glass), excluding fully transparent and fully opaque pixels. `null` when unknown."
										},
										"sourceAssetId": {
											"description": "For a background-removed image, the id of the asset it was derived from.",
											"type": [
												"string",
												"null"
											]
										},
										"url": {
											"type": "string",
											"description": "Optimized public CDN URL for display. Transformable images use the 1920px default variant; other assets use `originalUrl`."
										},
										"originalUrl": {
											"type": "string",
											"description": "Stable public CDN URL for the original bytes. It never expires."
										},
										"previewUrl": {
											"description": "For SVG images, the stable public CDN URL of a rasterized PNG preview (longest edge 1024px) that image models, MCP previews, and favicon fallbacks use. `null` for other assets and for SVGs that could not be rendered.",
											"type": [
												"string",
												"null"
											]
										},
										"srcSet": {
											"description": "Ready-to-use responsive image srcset. `null` when image transformation is unavailable.",
											"type": [
												"string",
												"null"
											]
										},
										"variants": {
											"type": "object",
											"propertyNames": {
												"type": "string"
											},
											"additionalProperties": {
												"type": "string"
											},
											"description": "Optimized image URLs keyed by pixel width. Empty when image transformation is unavailable."
										},
										"createdAt": {
											"description": "When the upload was started.",
											"type": "string",
											"format": "date-time"
										},
										"status": {
											"type": "string",
											"enum": [
												"pending",
												"ready"
											],
											"description": "`pending` until websiteAssets.finalize confirms the upload; `ready` once the asset is servable."
										}
									},
									"required": [
										"id",
										"kind",
										"filename",
										"mimeType",
										"description",
										"category",
										"byteSize",
										"width",
										"height",
										"colors",
										"transparentShare",
										"translucentShare",
										"sourceAssetId",
										"url",
										"originalUrl",
										"previewUrl",
										"srcSet",
										"variants",
										"createdAt",
										"status"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteAssets.get",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			},
			"patch": {
				"operationId": "websiteAssets.update",
				"summary": "Update an asset",
				"description": "Changes `filename`, `description`, or `category`. Pass only the fields to change; `null` clears a field. `category` is images only.",
				"tags": [
					"Assets"
				],
				"parameters": [
					{
						"name": "assetId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": false,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"filename": {
										"description": "Delivery URLs never change.",
										"type": "string",
										"minLength": 1,
										"maxLength": 255
									},
									"description": {
										"anyOf": [
											{
												"type": "string",
												"minLength": 1,
												"maxLength": 500,
												"description": "One sentence: subject, visible text, style, intended use."
											},
											{
												"type": "null"
											}
										]
									},
									"category": {
										"anyOf": [
											{
												"type": "string",
												"enum": [
													"logo",
													"icon",
													"photo",
													"illustration",
													"screenshot",
													"graphic",
													"background",
													"other"
												],
												"description": "What the image is. Images only."
											},
											{
												"type": "null"
											}
										]
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The updated asset with its public CDN delivery URLs.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique asset id. Pass it to websiteAssets.get and websiteAssets.delete."
										},
										"kind": {
											"type": "string",
											"enum": [
												"image",
												"file"
											],
											"description": "Asset kind. Each kind has its own MIME allowlist and size limit."
										},
										"filename": {
											"type": "string",
											"description": "Original filename supplied at upload (1–255 characters)."
										},
										"mimeType": {
											"type": "string",
											"description": "MIME type stored for the asset after upload normalization."
										},
										"description": {
											"description": "One-line description supplied at upload, edited later, or generated for dashboard uploads. `null` when absent.",
											"type": [
												"string",
												"null"
											]
										},
										"category": {
											"anyOf": [
												{
													"type": "string",
													"enum": [
														"logo",
														"icon",
														"photo",
														"illustration",
														"screenshot",
														"graphic",
														"background",
														"other"
													]
												},
												{
													"type": "null"
												}
											],
											"description": "Image category: logo, icon, photo, illustration, screenshot, graphic, background, or other. `null` for files and unclassified images."
										},
										"byteSize": {
											"type": "integer",
											"description": "Exact file size in bytes."
										},
										"width": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											],
											"description": "Intrinsic pixel width, recorded at finalize for images. `null` for non-images and formats whose dimensions cannot be read."
										},
										"height": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											],
											"description": "Intrinsic pixel height; see `width`."
										},
										"colors": {
											"anyOf": [
												{
													"maxItems": 8,
													"type": "array",
													"items": {
														"type": "object",
														"properties": {
															"hex": {
																"type": "string",
																"description": "Uppercase sRGB hex, exact for flat colors."
															},
															"share": {
																"anyOf": [
																	{
																		"type": "number",
																		"minimum": 0,
																		"maximum": 1
																	},
																	{
																		"type": "null"
																	}
																],
																"description": "Fraction of opaque pixels this color and its anti-aliasing cover. `null` for SVG, whose colors are read from the source instead of rendered."
															},
															"accent": {
																"type": "boolean",
																"description": "Saturated enough to matter as an accent even at a small share."
															}
														},
														"required": [
															"hex",
															"share",
															"accent"
														],
														"additionalProperties": false
													},
													"description": "Up to 8 representative colors sorted by share. `null` for files and for images that could not be analyzed."
												},
												{
													"type": "null"
												}
											]
										},
										"transparentShare": {
											"anyOf": [
												{
													"type": "number",
													"minimum": 0,
													"maximum": 1
												},
												{
													"type": "null"
												}
											],
											"description": "Fraction of the backdrop that shows through, weighting each pixel by its transparency: 0 for an opaque image, roughly the background area for a cutout, 0.5 for a uniform 50% overlay. `null` when unknown."
										},
										"translucentShare": {
											"anyOf": [
												{
													"type": "number",
													"minimum": 0,
													"maximum": 1
												},
												{
													"type": "null"
												}
											],
											"description": "Fraction of pixels with partial alpha (soft shadows, glows, fades, glass), excluding fully transparent and fully opaque pixels. `null` when unknown."
										},
										"sourceAssetId": {
											"description": "For a background-removed image, the id of the asset it was derived from.",
											"type": [
												"string",
												"null"
											]
										},
										"url": {
											"type": "string",
											"description": "Optimized public CDN URL for display. Transformable images use the 1920px default variant; other assets use `originalUrl`."
										},
										"originalUrl": {
											"type": "string",
											"description": "Stable public CDN URL for the original bytes. It never expires."
										},
										"previewUrl": {
											"description": "For SVG images, the stable public CDN URL of a rasterized PNG preview (longest edge 1024px) that image models, MCP previews, and favicon fallbacks use. `null` for other assets and for SVGs that could not be rendered.",
											"type": [
												"string",
												"null"
											]
										},
										"srcSet": {
											"description": "Ready-to-use responsive image srcset. `null` when image transformation is unavailable.",
											"type": [
												"string",
												"null"
											]
										},
										"variants": {
											"type": "object",
											"propertyNames": {
												"type": "string"
											},
											"additionalProperties": {
												"type": "string"
											},
											"description": "Optimized image URLs keyed by pixel width. Empty when image transformation is unavailable."
										},
										"createdAt": {
											"description": "When the upload was started.",
											"type": "string",
											"format": "date-time"
										},
										"status": {
											"type": "string",
											"enum": [
												"pending",
												"ready"
											],
											"description": "`pending` until websiteAssets.finalize confirms the upload; `ready` once the asset is servable."
										}
									},
									"required": [
										"id",
										"kind",
										"filename",
										"mimeType",
										"description",
										"category",
										"byteSize",
										"width",
										"height",
										"colors",
										"transparentShare",
										"translucentShare",
										"sourceAssetId",
										"url",
										"originalUrl",
										"previewUrl",
										"srcSet",
										"variants",
										"createdAt",
										"status"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteAssets.update",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			},
			"delete": {
				"operationId": "websiteAssets.delete",
				"summary": "Delete an asset",
				"description": "Archives the asset: it immediately stops appearing in list responses and its id returns 404 from every asset endpoint. Archival cannot be undone through the API; previously issued CDN URLs keep serving the file.",
				"tags": [
					"Assets"
				],
				"parameters": [
					{
						"name": "assetId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Confirmation that the asset was archived.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"deleted": {
											"type": "boolean",
											"const": true,
											"description": "The asset was archived: it no longer appears in lists and its id returns 404."
										}
									},
									"required": [
										"deleted"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteAssets.delete",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/websiteAssets/import": {
			"post": {
				"operationId": "websiteAssets.importFromUrl",
				"summary": "Import assets from public URLs",
				"description": "Fetches 1-10 public http(s) URLs server-side, one at a time, and stores each as a ready asset with no upload or finalize step. Media only: images, fonts, PDF, audio, video, dotLottie, and Rive; text, data, documents, and archives are refused. Images, fonts, and PDFs must parse as what they claim, hosts on private addresses are rejected, the upload kind and MIME rules apply, and each download is capped at 30 MiB (larger files must be uploaded directly). Each item may carry a `description` and, for images, a `category`. Name each item by `url`, or by `photo`: the number over a picture on contact sheets `webResearch.profile` or `webResearch.viewImage` showed you, which saves copying its long signed link. `assets` carries successes with CDN URLs and `sourceUrl`; `errors` carries failures typed `invalid_url`, `non_public_address`, `unreachable`, `too_large`, `unsupported_type`, `signature_expired`, or `unknown_photo` (a number you were not shown or that expired; look at the pictures again for current numbers). Import only assets you have the right to use.",
				"tags": [
					"Assets"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"items": {
										"minItems": 1,
										"maxItems": 10,
										"type": "array",
										"items": {
											"type": "object",
											"properties": {
												"url": {
													"description": "Public http(s) URL of the file to import.",
													"type": "string",
													"format": "uri"
												},
												"filename": {
													"description": "Stored filename; defaults to the URL path segment.",
													"type": "string",
													"minLength": 1,
													"maxLength": 255
												},
												"description": {
													"type": "string",
													"minLength": 1,
													"maxLength": 500,
													"description": "One sentence: subject, visible text, style, intended use."
												},
												"category": {
													"type": "string",
													"enum": [
														"logo",
														"icon",
														"photo",
														"illustration",
														"screenshot",
														"graphic",
														"background",
														"other"
													],
													"description": "What the image is. Images only."
												},
												"photo": {
													"description": "Instead of `url`: the number over a picture on contact sheets `webResearch.profile` or `webResearch.viewImage` showed you.",
													"type": "integer",
													"minimum": 1,
													"maximum": 9007199254740991
												}
											}
										},
										"description": "Each item succeeds or fails on its own."
									}
								},
								"required": [
									"websiteId",
									"items"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Imported assets with CDN delivery URLs, plus per-URL failures.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"assets": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string",
														"description": "Unique asset id. Pass it to websiteAssets.get and websiteAssets.delete."
													},
													"kind": {
														"type": "string",
														"enum": [
															"image",
															"file"
														],
														"description": "Asset kind. Each kind has its own MIME allowlist and size limit."
													},
													"filename": {
														"type": "string",
														"description": "Original filename supplied at upload (1–255 characters)."
													},
													"mimeType": {
														"type": "string",
														"description": "MIME type stored for the asset after upload normalization."
													},
													"description": {
														"description": "One-line description supplied at upload, edited later, or generated for dashboard uploads. `null` when absent.",
														"type": [
															"string",
															"null"
														]
													},
													"category": {
														"anyOf": [
															{
																"type": "string",
																"enum": [
																	"logo",
																	"icon",
																	"photo",
																	"illustration",
																	"screenshot",
																	"graphic",
																	"background",
																	"other"
																]
															},
															{
																"type": "null"
															}
														],
														"description": "Image category: logo, icon, photo, illustration, screenshot, graphic, background, or other. `null` for files and unclassified images."
													},
													"byteSize": {
														"type": "integer",
														"description": "Exact file size in bytes."
													},
													"width": {
														"anyOf": [
															{
																"type": "integer"
															},
															{
																"type": "null"
															}
														],
														"description": "Intrinsic pixel width, recorded at finalize for images. `null` for non-images and formats whose dimensions cannot be read."
													},
													"height": {
														"anyOf": [
															{
																"type": "integer"
															},
															{
																"type": "null"
															}
														],
														"description": "Intrinsic pixel height; see `width`."
													},
													"colors": {
														"anyOf": [
															{
																"maxItems": 8,
																"type": "array",
																"items": {
																	"type": "object",
																	"properties": {
																		"hex": {
																			"type": "string",
																			"description": "Uppercase sRGB hex, exact for flat colors."
																		},
																		"share": {
																			"anyOf": [
																				{
																					"type": "number",
																					"minimum": 0,
																					"maximum": 1
																				},
																				{
																					"type": "null"
																				}
																			],
																			"description": "Fraction of opaque pixels this color and its anti-aliasing cover. `null` for SVG, whose colors are read from the source instead of rendered."
																		},
																		"accent": {
																			"type": "boolean",
																			"description": "Saturated enough to matter as an accent even at a small share."
																		}
																	},
																	"required": [
																		"hex",
																		"share",
																		"accent"
																	],
																	"additionalProperties": false
																},
																"description": "Up to 8 representative colors sorted by share. `null` for files and for images that could not be analyzed."
															},
															{
																"type": "null"
															}
														]
													},
													"transparentShare": {
														"anyOf": [
															{
																"type": "number",
																"minimum": 0,
																"maximum": 1
															},
															{
																"type": "null"
															}
														],
														"description": "Fraction of the backdrop that shows through, weighting each pixel by its transparency: 0 for an opaque image, roughly the background area for a cutout, 0.5 for a uniform 50% overlay. `null` when unknown."
													},
													"translucentShare": {
														"anyOf": [
															{
																"type": "number",
																"minimum": 0,
																"maximum": 1
															},
															{
																"type": "null"
															}
														],
														"description": "Fraction of pixels with partial alpha (soft shadows, glows, fades, glass), excluding fully transparent and fully opaque pixels. `null` when unknown."
													},
													"sourceAssetId": {
														"description": "For a background-removed image, the id of the asset it was derived from.",
														"type": [
															"string",
															"null"
														]
													},
													"url": {
														"type": "string",
														"description": "Optimized public CDN URL for display. Transformable images use the 1920px default variant; other assets use `originalUrl`."
													},
													"originalUrl": {
														"type": "string",
														"description": "Stable public CDN URL for the original bytes. It never expires."
													},
													"previewUrl": {
														"description": "For SVG images, the stable public CDN URL of a rasterized PNG preview (longest edge 1024px) that image models, MCP previews, and favicon fallbacks use. `null` for other assets and for SVGs that could not be rendered.",
														"type": [
															"string",
															"null"
														]
													},
													"srcSet": {
														"description": "Ready-to-use responsive image srcset. `null` when image transformation is unavailable.",
														"type": [
															"string",
															"null"
														]
													},
													"variants": {
														"type": "object",
														"propertyNames": {
															"type": "string"
														},
														"additionalProperties": {
															"type": "string"
														},
														"description": "Optimized image URLs keyed by pixel width. Empty when image transformation is unavailable."
													},
													"createdAt": {
														"description": "When the upload was started.",
														"type": "string",
														"format": "date-time"
													},
													"sourceUrl": {
														"type": "string",
														"description": "The imported URL."
													},
													"photo": {
														"description": "The photo number it was named by.",
														"type": "integer"
													}
												},
												"required": [
													"id",
													"kind",
													"filename",
													"mimeType",
													"description",
													"category",
													"byteSize",
													"width",
													"height",
													"colors",
													"transparentShare",
													"translucentShare",
													"sourceAssetId",
													"url",
													"originalUrl",
													"previewUrl",
													"srcSet",
													"variants",
													"createdAt",
													"sourceUrl"
												],
												"additionalProperties": false
											},
											"description": "Ready assets."
										},
										"errors": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"url": {
														"type": "string"
													},
													"photo": {
														"type": "integer"
													},
													"type": {
														"type": "string",
														"enum": [
															"invalid_url",
															"non_public_address",
															"unreachable",
															"too_large",
															"unsupported_type",
															"signature_expired",
															"unknown_photo"
														]
													},
													"message": {
														"type": "string"
													}
												},
												"required": [
													"type",
													"message"
												],
												"additionalProperties": false
											},
											"description": "Failed items."
										}
									},
									"required": [
										"assets",
										"errors"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteAssets.beginUpload",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/websiteAssets/{assetId}/finalize": {
			"post": {
				"operationId": "websiteAssets.finalize",
				"summary": "Finalize an uploaded asset",
				"description": "Step 2 of the two-step upload: verifies the uploaded object and its size, then flips the asset from `pending` to `ready`, recording image `width`/`height` when readable. A raster image finalized without a `description` gets one, plus a `category`, written in the background as a best-effort courtesy with a monthly cap per organization. Returns 409 if the bytes have not arrived or the asset is already finalized, and 400 on a size mismatch.",
				"tags": [
					"Assets"
				],
				"parameters": [
					{
						"name": "assetId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "The finalized, ready-to-serve asset with optimized CDN delivery URLs.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique asset id. Pass it to websiteAssets.get and websiteAssets.delete."
										},
										"kind": {
											"type": "string",
											"enum": [
												"image",
												"file"
											],
											"description": "Asset kind. Each kind has its own MIME allowlist and size limit."
										},
										"filename": {
											"type": "string",
											"description": "Original filename supplied at upload (1–255 characters)."
										},
										"mimeType": {
											"type": "string",
											"description": "MIME type stored for the asset after upload normalization."
										},
										"description": {
											"description": "One-line description supplied at upload, edited later, or generated for dashboard uploads. `null` when absent.",
											"type": [
												"string",
												"null"
											]
										},
										"category": {
											"anyOf": [
												{
													"type": "string",
													"enum": [
														"logo",
														"icon",
														"photo",
														"illustration",
														"screenshot",
														"graphic",
														"background",
														"other"
													]
												},
												{
													"type": "null"
												}
											],
											"description": "Image category: logo, icon, photo, illustration, screenshot, graphic, background, or other. `null` for files and unclassified images."
										},
										"byteSize": {
											"type": "integer",
											"description": "Exact file size in bytes."
										},
										"width": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											],
											"description": "Intrinsic pixel width, recorded at finalize for images. `null` for non-images and formats whose dimensions cannot be read."
										},
										"height": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											],
											"description": "Intrinsic pixel height; see `width`."
										},
										"colors": {
											"anyOf": [
												{
													"maxItems": 8,
													"type": "array",
													"items": {
														"type": "object",
														"properties": {
															"hex": {
																"type": "string",
																"description": "Uppercase sRGB hex, exact for flat colors."
															},
															"share": {
																"anyOf": [
																	{
																		"type": "number",
																		"minimum": 0,
																		"maximum": 1
																	},
																	{
																		"type": "null"
																	}
																],
																"description": "Fraction of opaque pixels this color and its anti-aliasing cover. `null` for SVG, whose colors are read from the source instead of rendered."
															},
															"accent": {
																"type": "boolean",
																"description": "Saturated enough to matter as an accent even at a small share."
															}
														},
														"required": [
															"hex",
															"share",
															"accent"
														],
														"additionalProperties": false
													},
													"description": "Up to 8 representative colors sorted by share. `null` for files and for images that could not be analyzed."
												},
												{
													"type": "null"
												}
											]
										},
										"transparentShare": {
											"anyOf": [
												{
													"type": "number",
													"minimum": 0,
													"maximum": 1
												},
												{
													"type": "null"
												}
											],
											"description": "Fraction of the backdrop that shows through, weighting each pixel by its transparency: 0 for an opaque image, roughly the background area for a cutout, 0.5 for a uniform 50% overlay. `null` when unknown."
										},
										"translucentShare": {
											"anyOf": [
												{
													"type": "number",
													"minimum": 0,
													"maximum": 1
												},
												{
													"type": "null"
												}
											],
											"description": "Fraction of pixels with partial alpha (soft shadows, glows, fades, glass), excluding fully transparent and fully opaque pixels. `null` when unknown."
										},
										"sourceAssetId": {
											"description": "For a background-removed image, the id of the asset it was derived from.",
											"type": [
												"string",
												"null"
											]
										},
										"url": {
											"type": "string",
											"description": "Optimized public CDN URL for display. Transformable images use the 1920px default variant; other assets use `originalUrl`."
										},
										"originalUrl": {
											"type": "string",
											"description": "Stable public CDN URL for the original bytes. It never expires."
										},
										"previewUrl": {
											"description": "For SVG images, the stable public CDN URL of a rasterized PNG preview (longest edge 1024px) that image models, MCP previews, and favicon fallbacks use. `null` for other assets and for SVGs that could not be rendered.",
											"type": [
												"string",
												"null"
											]
										},
										"srcSet": {
											"description": "Ready-to-use responsive image srcset. `null` when image transformation is unavailable.",
											"type": [
												"string",
												"null"
											]
										},
										"variants": {
											"type": "object",
											"propertyNames": {
												"type": "string"
											},
											"additionalProperties": {
												"type": "string"
											},
											"description": "Optimized image URLs keyed by pixel width. Empty when image transformation is unavailable."
										},
										"createdAt": {
											"description": "When the upload was started.",
											"type": "string",
											"format": "date-time"
										},
										"status": {
											"type": "string",
											"enum": [
												"pending",
												"ready"
											],
											"description": "`pending` until websiteAssets.finalize confirms the upload; `ready` once the asset is servable."
										}
									},
									"required": [
										"id",
										"kind",
										"filename",
										"mimeType",
										"description",
										"category",
										"byteSize",
										"width",
										"height",
										"colors",
										"transparentShare",
										"translucentShare",
										"sourceAssetId",
										"url",
										"originalUrl",
										"previewUrl",
										"srcSet",
										"variants",
										"createdAt",
										"status"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteAssets.finalize",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/websiteAssets/{assetId}/removeBackground": {
			"post": {
				"operationId": "websiteAssets.removeBackground",
				"summary": "Remove an image background",
				"description": "Creates a new PNG asset with the main subject isolated on a transparent background, derived from a ready PNG, JPEG, WebP, GIF, or AVIF image of the same website. The source is unchanged; the result carries `sourceAssetId`, its own `colors` and `transparentShare`, the source category, and a description naming the source. Soft shadows and reflections are removed; `trim` crops to the subject bounds. Not billed to credits; limited to 10,000 per organization per calendar month. MCP clients that render images also receive the result.",
				"tags": [
					"Assets"
				],
				"parameters": [
					{
						"name": "assetId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": false,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"trim": {
										"default": false,
										"description": "Crop the result to the subject bounds instead of keeping the source dimensions.",
										"type": "boolean"
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The new transparent asset with its public CDN delivery URLs.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique asset id. Pass it to websiteAssets.get and websiteAssets.delete."
										},
										"kind": {
											"type": "string",
											"enum": [
												"image",
												"file"
											],
											"description": "Asset kind. Each kind has its own MIME allowlist and size limit."
										},
										"filename": {
											"type": "string",
											"description": "Original filename supplied at upload (1–255 characters)."
										},
										"mimeType": {
											"type": "string",
											"description": "MIME type stored for the asset after upload normalization."
										},
										"description": {
											"description": "One-line description supplied at upload, edited later, or generated for dashboard uploads. `null` when absent.",
											"type": [
												"string",
												"null"
											]
										},
										"category": {
											"anyOf": [
												{
													"type": "string",
													"enum": [
														"logo",
														"icon",
														"photo",
														"illustration",
														"screenshot",
														"graphic",
														"background",
														"other"
													]
												},
												{
													"type": "null"
												}
											],
											"description": "Image category: logo, icon, photo, illustration, screenshot, graphic, background, or other. `null` for files and unclassified images."
										},
										"byteSize": {
											"type": "integer",
											"description": "Exact file size in bytes."
										},
										"width": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											],
											"description": "Intrinsic pixel width, recorded at finalize for images. `null` for non-images and formats whose dimensions cannot be read."
										},
										"height": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											],
											"description": "Intrinsic pixel height; see `width`."
										},
										"colors": {
											"anyOf": [
												{
													"maxItems": 8,
													"type": "array",
													"items": {
														"type": "object",
														"properties": {
															"hex": {
																"type": "string",
																"description": "Uppercase sRGB hex, exact for flat colors."
															},
															"share": {
																"anyOf": [
																	{
																		"type": "number",
																		"minimum": 0,
																		"maximum": 1
																	},
																	{
																		"type": "null"
																	}
																],
																"description": "Fraction of opaque pixels this color and its anti-aliasing cover. `null` for SVG, whose colors are read from the source instead of rendered."
															},
															"accent": {
																"type": "boolean",
																"description": "Saturated enough to matter as an accent even at a small share."
															}
														},
														"required": [
															"hex",
															"share",
															"accent"
														],
														"additionalProperties": false
													},
													"description": "Up to 8 representative colors sorted by share. `null` for files and for images that could not be analyzed."
												},
												{
													"type": "null"
												}
											]
										},
										"transparentShare": {
											"anyOf": [
												{
													"type": "number",
													"minimum": 0,
													"maximum": 1
												},
												{
													"type": "null"
												}
											],
											"description": "Fraction of the backdrop that shows through, weighting each pixel by its transparency: 0 for an opaque image, roughly the background area for a cutout, 0.5 for a uniform 50% overlay. `null` when unknown."
										},
										"translucentShare": {
											"anyOf": [
												{
													"type": "number",
													"minimum": 0,
													"maximum": 1
												},
												{
													"type": "null"
												}
											],
											"description": "Fraction of pixels with partial alpha (soft shadows, glows, fades, glass), excluding fully transparent and fully opaque pixels. `null` when unknown."
										},
										"sourceAssetId": {
											"description": "For a background-removed image, the id of the asset it was derived from.",
											"type": [
												"string",
												"null"
											]
										},
										"url": {
											"type": "string",
											"description": "Optimized public CDN URL for display. Transformable images use the 1920px default variant; other assets use `originalUrl`."
										},
										"originalUrl": {
											"type": "string",
											"description": "Stable public CDN URL for the original bytes. It never expires."
										},
										"previewUrl": {
											"description": "For SVG images, the stable public CDN URL of a rasterized PNG preview (longest edge 1024px) that image models, MCP previews, and favicon fallbacks use. `null` for other assets and for SVGs that could not be rendered.",
											"type": [
												"string",
												"null"
											]
										},
										"srcSet": {
											"description": "Ready-to-use responsive image srcset. `null` when image transformation is unavailable.",
											"type": [
												"string",
												"null"
											]
										},
										"variants": {
											"type": "object",
											"propertyNames": {
												"type": "string"
											},
											"additionalProperties": {
												"type": "string"
											},
											"description": "Optimized image URLs keyed by pixel width. Empty when image transformation is unavailable."
										},
										"createdAt": {
											"description": "When the upload was started.",
											"type": "string",
											"format": "date-time"
										},
										"status": {
											"type": "string",
											"enum": [
												"pending",
												"ready"
											],
											"description": "`pending` until websiteAssets.finalize confirms the upload; `ready` once the asset is servable."
										}
									},
									"required": [
										"id",
										"kind",
										"filename",
										"mimeType",
										"description",
										"category",
										"byteSize",
										"width",
										"height",
										"colors",
										"transparentShare",
										"translucentShare",
										"sourceAssetId",
										"url",
										"originalUrl",
										"previewUrl",
										"srcSet",
										"variants",
										"createdAt",
										"status"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteAssets.removeBackground",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/mediaGeneration/profiles": {
			"get": {
				"operationId": "mediaGeneration.catalog",
				"summary": "List image-generation profiles",
				"description": "Lists stable Cactal image profiles and their supported controls. Provider and model identities are intentionally private.",
				"tags": [
					"Media generation"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "The current image profile catalog.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"version": {
											"type": "string"
										},
										"profiles": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string"
													},
													"label": {
														"type": "string"
													},
													"description": {
														"type": "string"
													},
													"maxReferences": {
														"type": "integer"
													},
													"maxOutputs": {
														"type": "integer"
													},
													"aspectRatios": {
														"type": "array",
														"items": {
															"type": "string"
														}
													}
												},
												"required": [
													"id",
													"label",
													"description",
													"maxReferences",
													"maxOutputs",
													"aspectRatios"
												],
												"additionalProperties": false
											}
										}
									},
									"required": [
										"version",
										"profiles"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "mediaGeneration.catalog",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/mediaGeneration": {
			"post": {
				"operationId": "mediaGeneration.generate",
				"summary": "Generate website images",
				"description": "Generates up to eight images in one batch. Items run concurrently, may use same-website image references, and successful outputs become ordinary website assets. An item with `background: \"transparent\"` stores each output as a native transparent PNG without a separate opaque copy.",
				"tags": [
					"Media generation"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"idempotencyKey": {
										"description": "Send the same key when retrying one request so it never generates or bills twice. Omit for a new request.",
										"type": "string",
										"minLength": 1,
										"maxLength": 200
									},
									"items": {
										"minItems": 1,
										"maxItems": 8,
										"type": "array",
										"items": {
											"type": "object",
											"properties": {
												"prompt": {
													"type": "string",
													"minLength": 1,
													"maxLength": 20000
												},
												"category": {
													"description": "Category stored on each generated asset (what it is, not where it is used).",
													"type": "string",
													"enum": [
														"logo",
														"icon",
														"photo",
														"illustration",
														"screenshot",
														"graphic",
														"background",
														"other"
													]
												},
												"profile": {
													"default": "website-image",
													"description": "Stable Cactal image profile: `website-image` for general imagery, `logo` for flat logos, marks, and icons.",
													"type": "string",
													"enum": [
														"website-image",
														"logo"
													]
												},
												"aspectRatio": {
													"default": "auto",
													"type": "string",
													"enum": [
														"auto",
														"1:1",
														"3:2",
														"2:3",
														"4:3",
														"3:4",
														"16:9",
														"9:16",
														"21:9"
													]
												},
												"background": {
													"default": "opaque",
													"description": "`transparent` generates and stores a native transparent PNG for each output, with its own `colors` and `transparentShare`; no separate opaque copy or background-removal pass is created.",
													"type": "string",
													"enum": [
														"opaque",
														"transparent"
													]
												},
												"outputs": {
													"default": 1,
													"type": "integer",
													"minimum": 1,
													"maximum": 8
												},
												"referenceAssetIds": {
													"default": [],
													"maxItems": 4,
													"type": "array",
													"items": {
														"type": "string",
														"minLength": 1
													}
												}
											},
											"required": [
												"prompt"
											],
											"additionalProperties": false
										}
									}
								},
								"required": [
									"websiteId",
									"items"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The completed image-generation result.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"version": {
											"type": "number",
											"const": 1
										},
										"kind": {
											"type": "string",
											"const": "image_generation"
										},
										"websiteId": {
											"type": "string"
										},
										"status": {
											"type": "string",
											"enum": [
												"ready",
												"partial",
												"failed"
											]
										},
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"status": {
														"type": "string",
														"enum": [
															"ready",
															"failed"
														]
													},
													"assets": {
														"type": "array",
														"items": {
															"type": "object",
															"properties": {
																"id": {
																	"type": "string"
																},
																"filename": {
																	"type": "string"
																},
																"mimeType": {
																	"type": "string"
																},
																"byteSize": {
																	"type": "integer"
																},
																"width": {
																	"anyOf": [
																		{
																			"type": "integer"
																		},
																		{
																			"type": "null"
																		}
																	]
																},
																"height": {
																	"anyOf": [
																		{
																			"type": "integer"
																		},
																		{
																			"type": "null"
																		}
																	]
																},
																"colors": {
																	"anyOf": [
																		{
																			"maxItems": 8,
																			"type": "array",
																			"items": {
																				"type": "object",
																				"properties": {
																					"hex": {
																						"type": "string",
																						"description": "Uppercase sRGB hex, exact for flat colors."
																					},
																					"share": {
																						"anyOf": [
																							{
																								"type": "number",
																								"minimum": 0,
																								"maximum": 1
																							},
																							{
																								"type": "null"
																							}
																						],
																						"description": "Fraction of opaque pixels this color and its anti-aliasing cover. `null` for SVG, whose colors are read from the source instead of rendered."
																					},
																					"accent": {
																						"type": "boolean",
																						"description": "Saturated enough to matter as an accent even at a small share."
																					}
																				},
																				"required": [
																					"hex",
																					"share",
																					"accent"
																				],
																				"additionalProperties": false
																			},
																			"description": "Up to 8 representative colors sorted by share. `null` for files and for images that could not be analyzed."
																		},
																		{
																			"type": "null"
																		}
																	]
																},
																"transparentShare": {
																	"anyOf": [
																		{
																			"type": "number",
																			"minimum": 0,
																			"maximum": 1
																		},
																		{
																			"type": "null"
																		}
																	],
																	"description": "Fraction of the backdrop that shows through, weighting each pixel by its transparency: 0 for an opaque image, roughly the background area for a cutout, 0.5 for a uniform 50% overlay. `null` when unknown."
																},
																"translucentShare": {
																	"anyOf": [
																		{
																			"type": "number",
																			"minimum": 0,
																			"maximum": 1
																		},
																		{
																			"type": "null"
																		}
																	],
																	"description": "Fraction of pixels with partial alpha (soft shadows, glows, fades, glass), excluding fully transparent and fully opaque pixels. `null` when unknown."
																},
																"sourceAssetId": {
																	"type": [
																		"string",
																		"null"
																	]
																},
																"url": {
																	"type": "string",
																	"description": "Optimized public CDN URL for display, the same `url` websiteAssets returns."
																},
																"originalUrl": {
																	"type": "string",
																	"description": "Stable public CDN URL for the original bytes. It never expires."
																},
																"srcSet": {
																	"description": "Ready-to-use responsive image srcset. `null` when image transformation is unavailable.",
																	"type": [
																		"string",
																		"null"
																	]
																}
															},
															"required": [
																"id",
																"filename",
																"mimeType",
																"byteSize",
																"width",
																"height",
																"colors",
																"transparentShare",
																"translucentShare",
																"sourceAssetId",
																"url",
																"originalUrl",
																"srcSet"
															],
															"additionalProperties": false
														}
													},
													"error": {
														"anyOf": [
															{
																"type": "object",
																"properties": {
																	"code": {
																		"type": "string"
																	},
																	"message": {
																		"type": "string"
																	},
																	"retryable": {
																		"type": "boolean"
																	}
																},
																"required": [
																	"code",
																	"message",
																	"retryable"
																],
																"additionalProperties": false
															},
															{
																"type": "null"
															}
														]
													},
													"referenceCount": {
														"type": "integer",
														"description": "Reference images the model received for this item."
													}
												},
												"required": [
													"status",
													"assets",
													"error",
													"referenceCount"
												],
												"additionalProperties": false
											}
										},
										"creditsCharged": {
											"type": "number"
										}
									},
									"required": [
										"version",
										"kind",
										"websiteId",
										"status",
										"items",
										"creditsCharged"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "mediaGeneration.generate",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/websiteKnowledge/projectKnowledge": {
			"get": {
				"operationId": "websiteKnowledge.projectKnowledge.get",
				"summary": "Get project knowledge",
				"description": "Returns the website’s durable project knowledge: the text the user maintains as `content`, plus the `agentNotes` Cactal distills from agent conversations. Both are included in every Cactal agent request. Read them before working on a website.",
				"tags": [
					"Project Context"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Project knowledge, agent notes, and their last update times.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"content": {
											"type": "string"
										},
										"updatedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											]
										},
										"agentNotes": {
											"type": "string"
										},
										"agentNotesUpdatedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											]
										}
									},
									"required": [
										"content",
										"updatedAt",
										"agentNotes",
										"agentNotesUpdatedAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteKnowledge.projectKnowledge.get",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			},
			"put": {
				"operationId": "websiteKnowledge.projectKnowledge.update",
				"summary": "Replace project knowledge",
				"description": "Replaces the whole project knowledge document. Send the `updatedAt` you loaded as `expectedUpdatedAt` to fail with `conflict` instead of overwriting newer text; send an empty string to clear it. Prefer the edit operation for targeted changes.",
				"tags": [
					"Project Context"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"content": {
										"type": "string",
										"maxLength": 20000
									},
									"expectedUpdatedAt": {
										"description": "The `updatedAt` you loaded (null when the text was empty). When supplied and stale, the request fails with `conflict` instead of overwriting newer text.",
										"anyOf": [
											{
												"type": "string",
												"format": "date-time",
												"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
											},
											{
												"type": "null"
											}
										]
									}
								},
								"required": [
									"websiteId",
									"content"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The saved project knowledge.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"content": {
											"type": "string"
										},
										"updatedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											]
										},
										"agentNotes": {
											"type": "string"
										},
										"agentNotesUpdatedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											]
										}
									},
									"required": [
										"content",
										"updatedAt",
										"agentNotes",
										"agentNotesUpdatedAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteKnowledge.projectKnowledge.update",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": true
			}
		},
		"/websiteKnowledge/projectKnowledge/edit": {
			"post": {
				"operationId": "websiteKnowledge.projectKnowledge.edit",
				"summary": "Edit project knowledge",
				"description": "Applies exact-match text edits to the user’s project knowledge text, in order; an empty `oldStr` appends. Change it only when the user asks; agent-discovered facts belong in agent notes. A passage that no longer matches fails with `validation` and returns the current content.",
				"tags": [
					"Project Context"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"edits": {
										"minItems": 1,
										"maxItems": 20,
										"type": "array",
										"items": {
											"type": "object",
											"properties": {
												"oldStr": {
													"type": "string",
													"maxLength": 20000,
													"description": "Exact text to find. Must match exactly once unless `replaceAll` is `true`. Empty appends `newStr` to the end."
												},
												"newStr": {
													"type": "string",
													"maxLength": 20000,
													"description": "Replacement text."
												},
												"replaceAll": {
													"description": "Replace every occurrence instead of requiring a unique match.",
													"type": "boolean"
												}
											},
											"required": [
												"oldStr",
												"newStr"
											]
										},
										"description": "Edits applied in order against the evolving text."
									}
								},
								"required": [
									"websiteId",
									"edits"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The full updated project knowledge.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"content": {
											"type": "string"
										},
										"updatedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											]
										},
										"agentNotes": {
											"type": "string"
										},
										"agentNotesUpdatedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											]
										}
									},
									"required": [
										"content",
										"updatedAt",
										"agentNotes",
										"agentNotesUpdatedAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteKnowledge.projectKnowledge.edit",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/websiteKnowledge/agentNotes": {
			"put": {
				"operationId": "websiteKnowledge.agentNotes.update",
				"summary": "Replace agent notes",
				"description": "Replaces the agent notes: the durable facts, decisions, rejected directions, and constraints agents keep for a website, up to 4,000 characters, included in every Cactal agent request. Send the `agentNotesUpdatedAt` you loaded as `expectedUpdatedAt` to fail with `conflict` instead of overwriting newer notes; send an empty string to clear them.",
				"tags": [
					"Project Context"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"notes": {
										"type": "string",
										"maxLength": 4000
									},
									"expectedUpdatedAt": {
										"description": "The `updatedAt` you loaded (null when the text was empty). When supplied and stale, the request fails with `conflict` instead of overwriting newer text.",
										"anyOf": [
											{
												"type": "string",
												"format": "date-time",
												"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
											},
											{
												"type": "null"
											}
										]
									}
								},
								"required": [
									"websiteId",
									"notes"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The saved project knowledge and agent notes.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"content": {
											"type": "string"
										},
										"updatedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											]
										},
										"agentNotes": {
											"type": "string"
										},
										"agentNotesUpdatedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											]
										}
									},
									"required": [
										"content",
										"updatedAt",
										"agentNotes",
										"agentNotesUpdatedAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteKnowledge.agentNotes.update",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websiteKnowledge/agentNotes/edit": {
			"post": {
				"operationId": "websiteKnowledge.agentNotes.edit",
				"summary": "Edit agent notes",
				"description": "Applies exact-match text edits to the agent notes, in order; an empty `oldStr` appends. Record only durable facts, decisions, rejected directions, and constraints, one short line each, never progress logs or anything derivable from the website source. A passage that no longer matches fails with `validation` and returns the current notes.",
				"tags": [
					"Project Context"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"edits": {
										"minItems": 1,
										"maxItems": 20,
										"type": "array",
										"items": {
											"type": "object",
											"properties": {
												"oldStr": {
													"type": "string",
													"maxLength": 20000,
													"description": "Exact text to find. Must match exactly once unless `replaceAll` is `true`. Empty appends `newStr` to the end."
												},
												"newStr": {
													"type": "string",
													"maxLength": 20000,
													"description": "Replacement text."
												},
												"replaceAll": {
													"description": "Replace every occurrence instead of requiring a unique match.",
													"type": "boolean"
												}
											},
											"required": [
												"oldStr",
												"newStr"
											]
										},
										"description": "Edits applied in order against the evolving text."
									}
								},
								"required": [
									"websiteId",
									"edits"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The full updated project knowledge and agent notes.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"content": {
											"type": "string"
										},
										"updatedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											]
										},
										"agentNotes": {
											"type": "string"
										},
										"agentNotesUpdatedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											]
										}
									},
									"required": [
										"content",
										"updatedAt",
										"agentNotes",
										"agentNotesUpdatedAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteKnowledge.agentNotes.edit",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/websiteKnowledge": {
			"get": {
				"operationId": "websiteKnowledge.list",
				"summary": "List project context",
				"description": "Lists the website’s private durable context entries: uploaded files and pasted text, each with a `title`, a generated `description`, and an `origin` of `upload` or `text`.",
				"tags": [
					"Project Context"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Up to 25 context entries, newest first.",
						"content": {
							"application/json": {
								"schema": {
									"maxItems": 25,
									"type": "array",
									"items": {
										"type": "object",
										"properties": {
											"id": {
												"type": "string"
											},
											"title": {
												"type": "string"
											},
											"origin": {
												"type": "string",
												"enum": [
													"upload",
													"text"
												]
											},
											"filename": {
												"type": "string"
											},
											"mimeType": {
												"type": "string",
												"enum": [
													"application/pdf",
													"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
													"text/plain",
													"text/markdown",
													"image/png",
													"image/jpeg",
													"image/webp",
													"image/svg+xml"
												]
											},
											"byteSize": {
												"type": "integer"
											},
											"description": {
												"type": "string"
											},
											"lineCount": {
												"anyOf": [
													{
														"type": "integer"
													},
													{
														"type": "null"
													}
												]
											},
											"colors": {
												"anyOf": [
													{
														"maxItems": 8,
														"type": "array",
														"items": {
															"type": "object",
															"properties": {
																"hex": {
																	"type": "string",
																	"description": "Uppercase sRGB hex, exact for flat colors."
																},
																"share": {
																	"anyOf": [
																		{
																			"type": "number",
																			"minimum": 0,
																			"maximum": 1
																		},
																		{
																			"type": "null"
																		}
																	],
																	"description": "Fraction of opaque pixels this color and its anti-aliasing cover. `null` for SVG, whose colors are read from the source instead of rendered."
																},
																"accent": {
																	"type": "boolean",
																	"description": "Saturated enough to matter as an accent even at a small share."
																}
															},
															"required": [
																"hex",
																"share",
																"accent"
															],
															"additionalProperties": false
														},
														"description": "Up to 8 representative colors sorted by share. `null` for files and for images that could not be analyzed."
													},
													{
														"type": "null"
													}
												]
											},
											"transparentShare": {
												"anyOf": [
													{
														"type": "number",
														"minimum": 0,
														"maximum": 1
													},
													{
														"type": "null"
													}
												],
												"description": "Fraction of the backdrop that shows through, weighting each pixel by its transparency: 0 for an opaque image, roughly the background area for a cutout, 0.5 for a uniform 50% overlay. `null` when unknown."
											},
											"translucentShare": {
												"anyOf": [
													{
														"type": "number",
														"minimum": 0,
														"maximum": 1
													},
													{
														"type": "null"
													}
												],
												"description": "Fraction of pixels with partial alpha (soft shadows, glows, fades, glass), excluding fully transparent and fully opaque pixels. `null` when unknown."
											},
											"createdAt": {
												"type": "string",
												"format": "date-time"
											}
										},
										"required": [
											"id",
											"title",
											"origin",
											"filename",
											"mimeType",
											"byteSize",
											"description",
											"lineCount",
											"colors",
											"transparentShare",
											"translucentShare",
											"createdAt"
										],
										"additionalProperties": false
									}
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteKnowledge.list",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			},
			"post": {
				"operationId": "websiteKnowledge.beginUpload",
				"summary": "Begin project context upload",
				"description": "Reserves one of the website’s 25 context slots and returns a private presigned upload request for a supported document up to 10 MiB or image up to 2 MB.",
				"tags": [
					"Project Context"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"filename": {
										"type": "string",
										"minLength": 1,
										"maxLength": 255
									},
									"mimeType": {
										"type": "string",
										"maxLength": 255
									},
									"byteSize": {
										"type": "integer",
										"exclusiveMinimum": 0,
										"maximum": 9007199254740991
									}
								},
								"required": [
									"websiteId",
									"filename",
									"mimeType",
									"byteSize"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The resource id and private upload instructions.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"resourceId": {
											"type": "string"
										},
										"upload": {
											"type": "object",
											"properties": {
												"url": {
													"type": "string"
												},
												"method": {
													"type": "string",
													"const": "PUT"
												},
												"headers": {
													"type": "object",
													"propertyNames": {
														"type": "string"
													},
													"additionalProperties": {
														"type": "string"
													}
												},
												"expiresAt": {
													"type": "string",
													"format": "date-time"
												}
											},
											"required": [
												"url",
												"method",
												"headers",
												"expiresAt"
											],
											"additionalProperties": false
										}
									},
									"required": [
										"resourceId",
										"upload"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				}
			}
		},
		"/websiteKnowledge/text": {
			"post": {
				"operationId": "websiteKnowledge.create",
				"summary": "Add project context from text",
				"description": "Keeps pasted or typed text as a context entry the agent searches and reads when relevant, using one of the website’s 25 slots. Cactal titles and describes it. Use this for material the user gives you to keep; your own findings belong in agent notes.",
				"tags": [
					"Project Context"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"text": {
										"type": "string",
										"maxLength": 1000000,
										"description": "The text to keep, as plain text or Markdown."
									}
								},
								"required": [
									"websiteId",
									"text"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The new context entry.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string"
										},
										"title": {
											"type": "string"
										},
										"origin": {
											"type": "string",
											"enum": [
												"upload",
												"text"
											]
										},
										"filename": {
											"type": "string"
										},
										"mimeType": {
											"type": "string",
											"enum": [
												"application/pdf",
												"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
												"text/plain",
												"text/markdown",
												"image/png",
												"image/jpeg",
												"image/webp",
												"image/svg+xml"
											]
										},
										"byteSize": {
											"type": "integer"
										},
										"description": {
											"type": "string"
										},
										"lineCount": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											]
										},
										"colors": {
											"anyOf": [
												{
													"maxItems": 8,
													"type": "array",
													"items": {
														"type": "object",
														"properties": {
															"hex": {
																"type": "string",
																"description": "Uppercase sRGB hex, exact for flat colors."
															},
															"share": {
																"anyOf": [
																	{
																		"type": "number",
																		"minimum": 0,
																		"maximum": 1
																	},
																	{
																		"type": "null"
																	}
																],
																"description": "Fraction of opaque pixels this color and its anti-aliasing cover. `null` for SVG, whose colors are read from the source instead of rendered."
															},
															"accent": {
																"type": "boolean",
																"description": "Saturated enough to matter as an accent even at a small share."
															}
														},
														"required": [
															"hex",
															"share",
															"accent"
														],
														"additionalProperties": false
													},
													"description": "Up to 8 representative colors sorted by share. `null` for files and for images that could not be analyzed."
												},
												{
													"type": "null"
												}
											]
										},
										"transparentShare": {
											"anyOf": [
												{
													"type": "number",
													"minimum": 0,
													"maximum": 1
												},
												{
													"type": "null"
												}
											],
											"description": "Fraction of the backdrop that shows through, weighting each pixel by its transparency: 0 for an opaque image, roughly the background area for a cutout, 0.5 for a uniform 50% overlay. `null` when unknown."
										},
										"translucentShare": {
											"anyOf": [
												{
													"type": "number",
													"minimum": 0,
													"maximum": 1
												},
												{
													"type": "null"
												}
											],
											"description": "Fraction of pixels with partial alpha (soft shadows, glows, fades, glass), excluding fully transparent and fully opaque pixels. `null` when unknown."
										},
										"createdAt": {
											"type": "string",
											"format": "date-time"
										}
									},
									"required": [
										"id",
										"title",
										"origin",
										"filename",
										"mimeType",
										"byteSize",
										"description",
										"lineCount",
										"colors",
										"transparentShare",
										"translucentShare",
										"createdAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteKnowledge.create",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/websiteKnowledge/{resourceId}": {
			"get": {
				"operationId": "websiteKnowledge.get",
				"summary": "Get project context entry",
				"description": "Returns one context entry with its full text, or `null` text for images. Agents should prefer search and ranged reads.",
				"tags": [
					"Project Context"
				],
				"parameters": [
					{
						"name": "resourceId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "The entry and its text.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string"
										},
										"title": {
											"type": "string"
										},
										"origin": {
											"type": "string",
											"enum": [
												"upload",
												"text"
											]
										},
										"filename": {
											"type": "string"
										},
										"mimeType": {
											"type": "string",
											"enum": [
												"application/pdf",
												"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
												"text/plain",
												"text/markdown",
												"image/png",
												"image/jpeg",
												"image/webp",
												"image/svg+xml"
											]
										},
										"byteSize": {
											"type": "integer"
										},
										"description": {
											"type": "string"
										},
										"lineCount": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											]
										},
										"colors": {
											"anyOf": [
												{
													"maxItems": 8,
													"type": "array",
													"items": {
														"type": "object",
														"properties": {
															"hex": {
																"type": "string",
																"description": "Uppercase sRGB hex, exact for flat colors."
															},
															"share": {
																"anyOf": [
																	{
																		"type": "number",
																		"minimum": 0,
																		"maximum": 1
																	},
																	{
																		"type": "null"
																	}
																],
																"description": "Fraction of opaque pixels this color and its anti-aliasing cover. `null` for SVG, whose colors are read from the source instead of rendered."
															},
															"accent": {
																"type": "boolean",
																"description": "Saturated enough to matter as an accent even at a small share."
															}
														},
														"required": [
															"hex",
															"share",
															"accent"
														],
														"additionalProperties": false
													},
													"description": "Up to 8 representative colors sorted by share. `null` for files and for images that could not be analyzed."
												},
												{
													"type": "null"
												}
											]
										},
										"transparentShare": {
											"anyOf": [
												{
													"type": "number",
													"minimum": 0,
													"maximum": 1
												},
												{
													"type": "null"
												}
											],
											"description": "Fraction of the backdrop that shows through, weighting each pixel by its transparency: 0 for an opaque image, roughly the background area for a cutout, 0.5 for a uniform 50% overlay. `null` when unknown."
										},
										"translucentShare": {
											"anyOf": [
												{
													"type": "number",
													"minimum": 0,
													"maximum": 1
												},
												{
													"type": "null"
												}
											],
											"description": "Fraction of pixels with partial alpha (soft shadows, glows, fades, glass), excluding fully transparent and fully opaque pixels. `null` when unknown."
										},
										"createdAt": {
											"type": "string",
											"format": "date-time"
										},
										"text": {
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"id",
										"title",
										"origin",
										"filename",
										"mimeType",
										"byteSize",
										"description",
										"lineCount",
										"colors",
										"transparentShare",
										"translucentShare",
										"createdAt",
										"text"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				}
			},
			"patch": {
				"operationId": "websiteKnowledge.update",
				"summary": "Update project context entry",
				"description": "Renames a context entry or replaces the text the agent reads. Replacing text does not change the downloadable original; images have no text.",
				"tags": [
					"Project Context"
				],
				"parameters": [
					{
						"name": "resourceId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": false,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"title": {
										"type": "string",
										"minLength": 1,
										"maxLength": 120
									},
									"text": {
										"description": "Replaces the text the agent reads; images have none.",
										"type": "string",
										"maxLength": 1000000
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The updated entry.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string"
										},
										"title": {
											"type": "string"
										},
										"origin": {
											"type": "string",
											"enum": [
												"upload",
												"text"
											]
										},
										"filename": {
											"type": "string"
										},
										"mimeType": {
											"type": "string",
											"enum": [
												"application/pdf",
												"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
												"text/plain",
												"text/markdown",
												"image/png",
												"image/jpeg",
												"image/webp",
												"image/svg+xml"
											]
										},
										"byteSize": {
											"type": "integer"
										},
										"description": {
											"type": "string"
										},
										"lineCount": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											]
										},
										"colors": {
											"anyOf": [
												{
													"maxItems": 8,
													"type": "array",
													"items": {
														"type": "object",
														"properties": {
															"hex": {
																"type": "string",
																"description": "Uppercase sRGB hex, exact for flat colors."
															},
															"share": {
																"anyOf": [
																	{
																		"type": "number",
																		"minimum": 0,
																		"maximum": 1
																	},
																	{
																		"type": "null"
																	}
																],
																"description": "Fraction of opaque pixels this color and its anti-aliasing cover. `null` for SVG, whose colors are read from the source instead of rendered."
															},
															"accent": {
																"type": "boolean",
																"description": "Saturated enough to matter as an accent even at a small share."
															}
														},
														"required": [
															"hex",
															"share",
															"accent"
														],
														"additionalProperties": false
													},
													"description": "Up to 8 representative colors sorted by share. `null` for files and for images that could not be analyzed."
												},
												{
													"type": "null"
												}
											]
										},
										"transparentShare": {
											"anyOf": [
												{
													"type": "number",
													"minimum": 0,
													"maximum": 1
												},
												{
													"type": "null"
												}
											],
											"description": "Fraction of the backdrop that shows through, weighting each pixel by its transparency: 0 for an opaque image, roughly the background area for a cutout, 0.5 for a uniform 50% overlay. `null` when unknown."
										},
										"translucentShare": {
											"anyOf": [
												{
													"type": "number",
													"minimum": 0,
													"maximum": 1
												},
												{
													"type": "null"
												}
											],
											"description": "Fraction of pixels with partial alpha (soft shadows, glows, fades, glass), excluding fully transparent and fully opaque pixels. `null` when unknown."
										},
										"createdAt": {
											"type": "string",
											"format": "date-time"
										}
									},
									"required": [
										"id",
										"title",
										"origin",
										"filename",
										"mimeType",
										"byteSize",
										"description",
										"lineCount",
										"colors",
										"transparentShare",
										"translucentShare",
										"createdAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteKnowledge.update",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			},
			"delete": {
				"operationId": "websiteKnowledge.delete",
				"summary": "Delete project context resource",
				"description": "Immediately removes a context resource from the website and agent index and schedules its private assets for retained cleanup.",
				"tags": [
					"Project Context"
				],
				"parameters": [
					{
						"name": "resourceId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Deletion confirmation.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"deleted": {
											"type": "boolean",
											"const": true
										}
									},
									"required": [
										"deleted"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				}
			}
		},
		"/websiteKnowledge/{resourceId}/finalize": {
			"post": {
				"operationId": "websiteKnowledge.finalize",
				"summary": "Finalize project context upload",
				"description": "Verifies the uploaded bytes, normalizes the file in isolation, and generates its index description.",
				"tags": [
					"Project Context"
				],
				"parameters": [
					{
						"name": "resourceId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "The completed resource.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string"
										},
										"title": {
											"type": "string"
										},
										"origin": {
											"type": "string",
											"enum": [
												"upload",
												"text"
											]
										},
										"filename": {
											"type": "string"
										},
										"mimeType": {
											"type": "string",
											"enum": [
												"application/pdf",
												"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
												"text/plain",
												"text/markdown",
												"image/png",
												"image/jpeg",
												"image/webp",
												"image/svg+xml"
											]
										},
										"byteSize": {
											"type": "integer"
										},
										"description": {
											"type": "string"
										},
										"lineCount": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											]
										},
										"colors": {
											"anyOf": [
												{
													"maxItems": 8,
													"type": "array",
													"items": {
														"type": "object",
														"properties": {
															"hex": {
																"type": "string",
																"description": "Uppercase sRGB hex, exact for flat colors."
															},
															"share": {
																"anyOf": [
																	{
																		"type": "number",
																		"minimum": 0,
																		"maximum": 1
																	},
																	{
																		"type": "null"
																	}
																],
																"description": "Fraction of opaque pixels this color and its anti-aliasing cover. `null` for SVG, whose colors are read from the source instead of rendered."
															},
															"accent": {
																"type": "boolean",
																"description": "Saturated enough to matter as an accent even at a small share."
															}
														},
														"required": [
															"hex",
															"share",
															"accent"
														],
														"additionalProperties": false
													},
													"description": "Up to 8 representative colors sorted by share. `null` for files and for images that could not be analyzed."
												},
												{
													"type": "null"
												}
											]
										},
										"transparentShare": {
											"anyOf": [
												{
													"type": "number",
													"minimum": 0,
													"maximum": 1
												},
												{
													"type": "null"
												}
											],
											"description": "Fraction of the backdrop that shows through, weighting each pixel by its transparency: 0 for an opaque image, roughly the background area for a cutout, 0.5 for a uniform 50% overlay. `null` when unknown."
										},
										"translucentShare": {
											"anyOf": [
												{
													"type": "number",
													"minimum": 0,
													"maximum": 1
												},
												{
													"type": "null"
												}
											],
											"description": "Fraction of pixels with partial alpha (soft shadows, glows, fades, glass), excluding fully transparent and fully opaque pixels. `null` when unknown."
										},
										"createdAt": {
											"type": "string",
											"format": "date-time"
										}
									},
									"required": [
										"id",
										"title",
										"origin",
										"filename",
										"mimeType",
										"byteSize",
										"description",
										"lineCount",
										"colors",
										"transparentShare",
										"translucentShare",
										"createdAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				}
			}
		},
		"/websiteKnowledge/{resourceId}/download": {
			"get": {
				"operationId": "websiteKnowledge.download",
				"summary": "Download project context resource",
				"description": "Returns a short-lived private URL for downloading one context resource in its original format.",
				"tags": [
					"Project Context"
				],
				"parameters": [
					{
						"name": "resourceId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Private download URL and expiration.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"url": {
											"type": "string"
										},
										"expiresAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											]
										}
									},
									"required": [
										"url",
										"expiresAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				}
			}
		},
		"/websiteKnowledge/{resourceId}/read": {
			"get": {
				"operationId": "websiteKnowledge.read",
				"summary": "Read project context resource",
				"description": "Read a bounded line range from a project context document. Read short relevant documents directly; search large documents first. Do not reread sufficient search passages.",
				"tags": [
					"Project Context"
				],
				"parameters": [
					{
						"name": "resourceId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "startLine",
						"in": "query",
						"required": false,
						"schema": {
							"type": "integer",
							"exclusiveMinimum": 0,
							"maximum": 9007199254740991
						}
					},
					{
						"name": "endLine",
						"in": "query",
						"required": false,
						"schema": {
							"type": "integer",
							"exclusiveMinimum": 0,
							"maximum": 9007199254740991
						}
					}
				],
				"responses": {
					"200": {
						"description": "The resolved line range and content.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"resourceId": {
											"type": "string"
										},
										"filename": {
											"type": "string"
										},
										"startLine": {
											"type": "integer"
										},
										"endLine": {
											"type": "integer"
										},
										"totalLines": {
											"type": "integer"
										},
										"content": {
											"type": "string"
										},
										"truncated": {
											"type": "boolean"
										}
									},
									"required": [
										"resourceId",
										"filename",
										"startLine",
										"endLine",
										"totalLines",
										"content",
										"truncated"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteKnowledge.read",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websiteKnowledge/search": {
			"post": {
				"operationId": "websiteKnowledge.search",
				"summary": "Search project context documents",
				"description": "Find evidence across selected project context documents. Batch independent queries; use passages directly when sufficient. Results merge overlapping text and include source ranges and zero-based queryIndexes into queries. matched reports a hit; truncated means evidence may be omitted, clipped means partial lines, and unavailableResourceIds were not searched. Resolve missing evidence before concluding a fact is absent.",
				"tags": [
					"Project Context"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"resourceIds": {
										"minItems": 1,
										"maxItems": 25,
										"type": "array",
										"items": {
											"type": "string",
											"minLength": 1
										},
										"description": "Selected text resource ids from the project context index, in relevance order."
									},
									"queries": {
										"minItems": 1,
										"maxItems": 8,
										"type": "array",
										"items": {
											"type": "string",
											"minLength": 1,
											"maxLength": 500,
											"pattern": "^[^\\r\\n]+$"
										},
										"description": "Independent case-insensitive literal terms matched within a line, not questions or regular expressions."
									},
									"contextLines": {
										"default": 2,
										"type": "integer",
										"minimum": 0,
										"maximum": 5
									},
									"maxCharacters": {
										"default": 8000,
										"description": "Shared passage character budget across all documents and queries.",
										"type": "integer",
										"minimum": 4000,
										"maximum": 16000
									}
								},
								"required": [
									"websiteId",
									"resourceIds",
									"queries"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Deduplicated passages, query coverage, and unavailable resource ids.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"queries": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"query": {
														"type": "string"
													},
													"matched": {
														"type": "boolean"
													},
													"truncated": {
														"type": "boolean"
													}
												},
												"required": [
													"query",
													"matched",
													"truncated"
												],
												"additionalProperties": false
											}
										},
										"passages": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"resourceId": {
														"type": "string"
													},
													"filename": {
														"type": "string"
													},
													"startLine": {
														"type": "integer"
													},
													"endLine": {
														"type": "integer"
													},
													"content": {
														"type": "string"
													},
													"clipped": {
														"type": "boolean"
													},
													"queryIndexes": {
														"type": "array",
														"items": {
															"type": "integer"
														}
													}
												},
												"required": [
													"resourceId",
													"filename",
													"startLine",
													"endLine",
													"content",
													"clipped",
													"queryIndexes"
												],
												"additionalProperties": false
											}
										},
										"unavailableResourceIds": {
											"type": "array",
											"items": {
												"type": "string"
											}
										}
									},
									"required": [
										"queries",
										"passages",
										"unavailableResourceIds"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteKnowledge.search",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websiteKnowledge/{resourceId}/view": {
			"get": {
				"operationId": "websiteKnowledge.view",
				"summary": "View project context image",
				"description": "Returns one private PNG, JPEG, or WebP context resource.",
				"tags": [
					"Project Context"
				],
				"parameters": [
					{
						"name": "resourceId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "An image visual.",
						"content": {
							"image/*": {
								"schema": {
									"type": "string",
									"format": "binary"
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteKnowledge.view",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websites/{websiteId}/favicon": {
			"put": {
				"operationId": "websiteFavicons.set",
				"summary": "Set or clear the website favicon",
				"description": "Sets the website’s current favicon to a ready PNG, SVG, or ICO image from the same website asset library. Send `assetId: null` to clear it. The change applies immediately to every site host and is independent of source drafts and publishing. Read the current favicon from the `faviconUrl` field on `GET /websites` and `GET /websites/find`.",
				"tags": [
					"Websites"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"assetId": {
										"anyOf": [
											{
												"type": "string",
												"minLength": 1
											},
											{
												"type": "null"
											}
										]
									}
								},
								"required": [
									"assetId"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The current website favicon after the update.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"websiteId": {
											"type": "string",
											"description": "Id of the website."
										},
										"favicon": {
											"anyOf": [
												{
													"type": "object",
													"properties": {
														"assetId": {
															"type": "string",
															"description": "Id of the website asset used as the favicon."
														},
														"filename": {
															"type": "string",
															"description": "Original filename of the asset."
														},
														"mimeType": {
															"type": "string",
															"description": "MIME type of the asset (PNG, SVG, or ICO)."
														},
														"url": {
															"type": "string",
															"description": "Stable public CDN URL serving the favicon."
														},
														"fallbackUrl": {
															"description": "For an SVG favicon, the stable public CDN URL of its rasterized PNG, served to browsers that ignore SVG icons and as the Apple touch icon. `null` otherwise.",
															"type": [
																"string",
																"null"
															]
														}
													},
													"required": [
														"assetId",
														"filename",
														"mimeType",
														"url",
														"fallbackUrl"
													],
													"additionalProperties": false
												},
												{
													"type": "null"
												}
											],
											"description": "The current favicon, or `null` when no available favicon is configured."
										}
									},
									"required": [
										"websiteId",
										"favicon"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteFavicons.set",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": true
			}
		},
		"/websiteSourceCode/summary": {
			"get": {
				"operationId": "websiteSourceCode.summary.get",
				"summary": "Get source version summary",
				"description": "Returns `headVersion`, `publishedVersion`, and whether the website has unpublished source changes.",
				"tags": [
					"Source code"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Draft and published version summary for the website.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"websiteId": {
											"type": "string",
											"description": "Id of the website."
										},
										"headVersion": {
											"type": "integer",
											"description": "Current draft source version. Starts at 1 and increments with every content-changing edit."
										},
										"publishedVersion": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											],
											"description": "Source version currently published. `null` when the website has never been published."
										},
										"hasUnpublishedChanges": {
											"type": "boolean",
											"description": "Whether head differs from the published version (always `true` before the first publish)."
										}
									},
									"required": [
										"websiteId",
										"headVersion",
										"publishedVersion",
										"hasUnpublishedChanges"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.summary.get",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websiteSourceCode/files": {
			"get": {
				"operationId": "websiteSourceCode.files.list",
				"summary": "List source files",
				"description": "Lists file identities, sizes, and line counts without contents, sorted by type then name with cursor pagination (default 20, maximum 100). Filter by name with `nameGlob`. Defaults to head; pass `target=published` or an explicit `version`, and pin continuation requests to the resolved response version.",
				"tags": [
					"Source code"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "target",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Version alias to read: `head` (default) or `published`.",
							"type": "string",
							"enum": [
								"head",
								"published"
							]
						}
					},
					{
						"name": "version",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Explicit source version. Takes precedence over `target`.",
							"type": "integer",
							"exclusiveMinimum": 0,
							"maximum": 9007199254740991
						}
					},
					{
						"name": "nameGlob",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Only list files whose name matches this glob. `*` and `?` stop at a `/` segment boundary; `**` crosses them, for example `blog/**`.",
							"type": "string",
							"minLength": 1,
							"maxLength": 255
						}
					},
					{
						"name": "cursor",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Opaque cursor from the previous page. Continue with the returned resolved version to keep the listing pinned.",
							"type": "string"
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"default": 20,
							"description": "Maximum files to return. Defaults to 20 and cannot exceed 100.",
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					}
				],
				"responses": {
					"200": {
						"description": "The file listing for the requested source version.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"type": {
														"type": "string",
														"enum": [
															"page",
															"component",
															"module",
															"server_function",
															"global_css",
															"not_found",
															"website_config",
															"custom_head_start",
															"custom_head_end",
															"custom_body_start",
															"custom_body_end"
														],
														"description": "File type. `page`, `component`, and `module` files are named; every other type is a singleton whose name equals the type."
													},
													"name": {
														"type": "string",
														"description": "File name to pass to websiteSourceCode.files.read."
													},
													"bytes": {
														"type": "integer",
														"description": "Content size in bytes."
													},
													"lines": {
														"type": "integer",
														"description": "Number of lines in the content."
													}
												},
												"required": [
													"type",
													"name",
													"bytes",
													"lines"
												],
												"additionalProperties": false
											}
										},
										"nextCursor": {
											"description": "Opaque cursor for the next page. Pass it as the `cursor` parameter on the next request. `null` when this is the last page.",
											"type": [
												"string",
												"null"
											]
										},
										"websiteId": {
											"type": "string",
											"description": "Id of the website."
										},
										"version": {
											"type": "integer",
											"description": "Source version that was listed. Pass it as `version` when continuing with `nextCursor`."
										}
									},
									"required": [
										"items",
										"nextCursor",
										"websiteId",
										"version"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.files.list",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			},
			"put": {
				"operationId": "websiteSourceCode.files.upsert",
				"summary": "Create or update a source file",
				"description": "Creates or replaces one head file. Pages, components, and modules require `name`; singleton types take only `type`. Takes the edit lease when no `leaseToken` is sent and returns `changed: false` for identical content.",
				"tags": [
					"Source code"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"file": {
										"anyOf": [
											{
												"type": "object",
												"properties": {
													"type": {
														"type": "string",
														"const": "page"
													},
													"name": {
														"type": "string",
														"minLength": 1,
														"maxLength": 255
													}
												},
												"required": [
													"type",
													"name"
												]
											},
											{
												"type": "object",
												"properties": {
													"type": {
														"type": "string",
														"const": "component"
													},
													"name": {
														"type": "string",
														"minLength": 1,
														"maxLength": 255
													}
												},
												"required": [
													"type",
													"name"
												]
											},
											{
												"type": "object",
												"properties": {
													"type": {
														"type": "string",
														"const": "module"
													},
													"name": {
														"type": "string",
														"minLength": 1,
														"maxLength": 255
													}
												},
												"required": [
													"type",
													"name"
												]
											},
											{
												"type": "object",
												"properties": {
													"type": {
														"type": "string",
														"const": "server_function"
													},
													"name": {
														"type": "string",
														"pattern": "^[A-Za-z][A-Za-z0-9_-]{0,63}$"
													}
												},
												"required": [
													"type",
													"name"
												]
											},
											{
												"type": "object",
												"properties": {
													"type": {
														"type": "string",
														"enum": [
															"global_css",
															"not_found",
															"website_config",
															"custom_head_start",
															"custom_head_end",
															"custom_body_start",
															"custom_body_end"
														]
													}
												},
												"required": [
													"type"
												]
											}
										]
									},
									"content": {
										"type": "string"
									},
									"leaseToken": {
										"description": "Omit to have Cactal take the edit lease for you; pass a token from websiteSourceCode.lease.acquire to take over from another editor.",
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"websiteId",
									"file",
									"content"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The new head version after the edit.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"version": {
											"type": "integer",
											"description": "Head version after the edit: the previous head plus one, or the unchanged head when `changed` is `false`."
										},
										"changed": {
											"type": "boolean",
											"description": "`false` when the edit was a no-op and no new version was created."
										}
									},
									"required": [
										"version",
										"changed"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.files.upsert",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			},
			"delete": {
				"operationId": "websiteSourceCode.files.delete",
				"summary": "Delete a source file",
				"description": "Deletes one head file while preserving history. Address named files by `type` and `name`, and singletons by `type`; takes the edit lease when no `leaseToken` is sent.",
				"tags": [
					"Source code"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"file": {
										"anyOf": [
											{
												"type": "object",
												"properties": {
													"type": {
														"type": "string",
														"const": "page"
													},
													"name": {
														"type": "string",
														"minLength": 1,
														"maxLength": 255
													}
												},
												"required": [
													"type",
													"name"
												]
											},
											{
												"type": "object",
												"properties": {
													"type": {
														"type": "string",
														"const": "component"
													},
													"name": {
														"type": "string",
														"minLength": 1,
														"maxLength": 255
													}
												},
												"required": [
													"type",
													"name"
												]
											},
											{
												"type": "object",
												"properties": {
													"type": {
														"type": "string",
														"const": "module"
													},
													"name": {
														"type": "string",
														"minLength": 1,
														"maxLength": 255
													}
												},
												"required": [
													"type",
													"name"
												]
											},
											{
												"type": "object",
												"properties": {
													"type": {
														"type": "string",
														"const": "server_function"
													},
													"name": {
														"type": "string",
														"pattern": "^[A-Za-z][A-Za-z0-9_-]{0,63}$"
													}
												},
												"required": [
													"type",
													"name"
												]
											},
											{
												"type": "object",
												"properties": {
													"type": {
														"type": "string",
														"enum": [
															"global_css",
															"not_found",
															"website_config",
															"custom_head_start",
															"custom_head_end",
															"custom_body_start",
															"custom_body_end"
														]
													}
												},
												"required": [
													"type"
												]
											}
										]
									},
									"leaseToken": {
										"description": "Omit to have Cactal take the edit lease for you; pass a token from websiteSourceCode.lease.acquire to take over from another editor.",
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"websiteId",
									"file"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The new head version after the deletion.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"version": {
											"type": "integer",
											"description": "Head version after the edit: the previous head plus one, or the unchanged head when `changed` is `false`."
										},
										"changed": {
											"type": "boolean",
											"description": "`false` when the edit was a no-op and no new version was created."
										}
									},
									"required": [
										"version",
										"changed"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.files.delete",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/websiteSourceCode/files/read": {
			"post": {
				"operationId": "websiteSourceCode.files.read",
				"summary": "Read source files",
				"description": "Reads up to 50 files by name from head, published, or an explicit version. A single-file read supports line and character ranges; continue truncated reads with `nextStartCharacter` and the resolved `version`.",
				"tags": [
					"Source code"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"target": {
										"description": "Version alias to read: `head` (default) or `published`.",
										"type": "string",
										"enum": [
											"head",
											"published"
										]
									},
									"version": {
										"description": "Explicit source version. Takes precedence over `target`.",
										"type": "integer",
										"exclusiveMinimum": 0,
										"maximum": 9007199254740991
									},
									"names": {
										"minItems": 1,
										"maxItems": 50,
										"type": "array",
										"items": {
											"type": "string",
											"minLength": 1
										},
										"description": "File names to read (up to 50). Singleton files are addressed by their type name, for example `global_css`."
									},
									"startLine": {
										"description": "First line to return (1-based, inclusive). Only valid when reading a single file.",
										"type": "integer",
										"exclusiveMinimum": 0,
										"maximum": 9007199254740991
									},
									"endLine": {
										"description": "Last line to return (1-based, inclusive). Only valid when reading a single file.",
										"type": "integer",
										"exclusiveMinimum": 0,
										"maximum": 9007199254740991
									},
									"startCharacter": {
										"description": "First character to return from the selected line range (1-based, inclusive). Only valid when reading a single file.",
										"type": "integer",
										"exclusiveMinimum": 0,
										"maximum": 9007199254740991
									},
									"endCharacter": {
										"description": "Last character to return from the selected line range (1-based, inclusive). Only valid when reading a single file.",
										"type": "integer",
										"exclusiveMinimum": 0,
										"maximum": 9007199254740991
									}
								},
								"required": [
									"websiteId",
									"names"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The requested files with their contents.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"websiteId": {
											"type": "string",
											"description": "Id of the website."
										},
										"version": {
											"type": "integer",
											"description": "Source version that was read. Pass it as `version` when continuing truncated content."
										},
										"files": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"type": {
														"type": "string",
														"enum": [
															"page",
															"component",
															"module",
															"server_function",
															"global_css",
															"not_found",
															"website_config",
															"custom_head_start",
															"custom_head_end",
															"custom_body_start",
															"custom_body_end"
														],
														"description": "File type."
													},
													"name": {
														"type": "string",
														"description": "File name."
													},
													"content": {
														"type": "string",
														"description": "Source text: the whole file, or the requested line range."
													},
													"totalLines": {
														"type": "integer",
														"description": "Total number of lines in the full file."
													},
													"truncated": {
														"description": "`true` when content was cut at the size cap. Continue with `nextStartCharacter` or request a narrower line range.",
														"type": "boolean"
													},
													"nextStartCharacter": {
														"description": "Pass as `startCharacter` with the same line range and the response `version` to continue truncated content.",
														"type": "integer"
													}
												},
												"required": [
													"type",
													"name",
													"content",
													"totalLines"
												],
												"additionalProperties": false
											},
											"description": "The requested files in request order."
										}
									},
									"required": [
										"websiteId",
										"version",
										"files"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.files.read",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websiteSourceCode/files/search": {
			"post": {
				"operationId": "websiteSourceCode.files.search",
				"summary": "Search source files",
				"description": "Finds text across every source file and returns one entry per matching line with its file name, line, and column. Call this first to locate code, then read only the files it names with websiteSourceCode.files.read. `pattern` is literal text unless `isRegex` is set, and matching is line by line, so a pattern cannot span a newline. Narrow with `nameGlob` and `type`, ask for surrounding lines with `contextLines`, or set `filesOnly` for the cheapest orientation. Results are cursor-paginated over matches.",
				"tags": [
					"Source code"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"target": {
										"description": "Version alias to read: `head` (default) or `published`.",
										"type": "string",
										"enum": [
											"head",
											"published"
										]
									},
									"version": {
										"description": "Explicit source version. Takes precedence over `target`.",
										"type": "integer",
										"exclusiveMinimum": 0,
										"maximum": 9007199254740991
									},
									"pattern": {
										"type": "string",
										"minLength": 1,
										"maxLength": 500,
										"description": "Text to find. Literal unless `isRegex` is set. Matched line by line, so `^` and `$` anchor to line boundaries and a pattern cannot span a newline."
									},
									"isRegex": {
										"description": "Read `pattern` as a regular expression. JavaScript syntax works, including `\\b`, `\\d`, lookahead, and lazy quantifiers; named capture groups are not supported.",
										"type": "boolean"
									},
									"caseSensitive": {
										"description": "Match case exactly. Defaults to `true`.",
										"type": "boolean"
									},
									"nameGlob": {
										"description": "Only search names matching this glob. `*` and `?` stop at a `/`; `**` crosses them, for example `blog/**`.",
										"type": "string",
										"minLength": 1,
										"maxLength": 255
									},
									"type": {
										"description": "Only search files of this type.",
										"type": "string",
										"enum": [
											"page",
											"component",
											"module",
											"server_function",
											"global_css",
											"not_found",
											"website_config",
											"custom_head_start",
											"custom_head_end",
											"custom_body_start",
											"custom_body_end"
										]
									},
									"filesOnly": {
										"description": "Return one entry per matching file, with no line detail.",
										"type": "boolean"
									},
									"contextLines": {
										"description": "Surrounding lines to return on each side of a match (0 to 3). Defaults to 0.",
										"type": "integer",
										"minimum": 0,
										"maximum": 3
									},
									"maxMatchesPerFile": {
										"description": "Maximum matches from any one file (up to 50). Defaults to 10.",
										"type": "integer",
										"minimum": 1,
										"maximum": 50
									},
									"cursor": {
										"description": "Opaque cursor from the previous page. Pass the returned `version` too to keep the search pinned.",
										"type": "string"
									},
									"limit": {
										"default": 20,
										"description": "Maximum matches to return per page. Defaults to 20, maximum 100.",
										"type": "integer",
										"minimum": 1,
										"maximum": 100
									}
								},
								"required": [
									"websiteId",
									"pattern"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "A page of matches for the requested source version.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"type": {
														"type": "string",
														"enum": [
															"page",
															"component",
															"module",
															"server_function",
															"global_css",
															"not_found",
															"website_config",
															"custom_head_start",
															"custom_head_end",
															"custom_body_start",
															"custom_body_end"
														],
														"description": "File type."
													},
													"name": {
														"type": "string",
														"description": "File name to pass to websiteSourceCode.files.read."
													},
													"line": {
														"description": "1-based number of the matching line. Omitted when `filesOnly` is `true`.",
														"type": "integer"
													},
													"column": {
														"description": "1-based character offset of the first match on the line. Omitted when `filesOnly` is `true`.",
														"type": "integer"
													},
													"match": {
														"description": "The matching line, cut at 300 characters. Omitted when `filesOnly` is `true`.",
														"type": "string"
													},
													"before": {
														"description": "Lines immediately before the match. Present only when `contextLines` is set.",
														"type": "array",
														"items": {
															"type": "string"
														}
													},
													"after": {
														"description": "Lines immediately after the match. Present only when `contextLines` is set.",
														"type": "array",
														"items": {
															"type": "string"
														}
													}
												},
												"required": [
													"type",
													"name"
												],
												"additionalProperties": false
											}
										},
										"nextCursor": {
											"description": "Opaque cursor for the next page. Pass it as the `cursor` parameter on the next request. `null` when this is the last page.",
											"type": [
												"string",
												"null"
											]
										},
										"websiteId": {
											"type": "string",
											"description": "Id of the website."
										},
										"version": {
											"type": "integer",
											"description": "Source version that was searched. Pass it as `version` when continuing."
										},
										"truncated": {
											"description": "`true` when a file on this page had more matches than `maxMatchesPerFile`. Raise it, or narrow the pattern, to see the rest.",
											"type": "boolean"
										}
									},
									"required": [
										"items",
										"nextCursor",
										"websiteId",
										"version"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.files.search",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websiteSourceCode/changes": {
			"get": {
				"operationId": "websiteSourceCode.changes.list",
				"summary": "List changed source files",
				"description": "Lists added, removed, and modified file identities between two version refs, defaulting from published to head. Results use cursor pagination; pin continuation requests to the resolved version numbers.",
				"tags": [
					"Source code"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "from",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Base side of the diff: a version number, `head`, or `published`. Defaults to `published`.",
							"type": "string",
							"pattern": "^(head|published|[1-9][0-9]*)$"
						}
					},
					{
						"name": "to",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Target side of the diff: a version number, `head`, or `published`. Defaults to `head`.",
							"type": "string",
							"pattern": "^(head|published|[1-9][0-9]*)$"
						}
					},
					{
						"name": "cursor",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Opaque cursor from the previous page. Continue with the returned resolved versions to keep the comparison pinned.",
							"type": "string"
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"default": 20,
							"description": "Maximum candidate files to compare. Defaults to 20 and cannot exceed 20.",
							"type": "integer",
							"minimum": 1,
							"maximum": 20
						}
					}
				],
				"responses": {
					"200": {
						"description": "A page of changed file identities.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"type": {
														"type": "string",
														"enum": [
															"page",
															"component",
															"module",
															"server_function",
															"global_css",
															"not_found",
															"website_config",
															"custom_head_start",
															"custom_head_end",
															"custom_body_start",
															"custom_body_end"
														],
														"description": "File type."
													},
													"name": {
														"type": "string",
														"description": "File name."
													},
													"status": {
														"type": "string",
														"enum": [
															"added",
															"removed",
															"modified"
														],
														"description": "How the file changed from `fromVersion` to `toVersion`."
													}
												},
												"required": [
													"type",
													"name",
													"status"
												],
												"additionalProperties": false
											}
										},
										"nextCursor": {
											"description": "Opaque cursor for the next page. Pass it as the `cursor` parameter on the next request. `null` when this is the last page.",
											"type": [
												"string",
												"null"
											]
										},
										"websiteId": {
											"type": "string",
											"description": "Id of the website."
										},
										"fromVersion": {
											"type": "integer",
											"description": "Resolved base version. Pass it as `from` when continuing."
										},
										"toVersion": {
											"type": "integer",
											"description": "Resolved target version. Pass it as `to` when continuing."
										}
									},
									"required": [
										"items",
										"nextCursor",
										"websiteId",
										"fromVersion",
										"toVersion"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.changes.list",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websiteSourceCode/files/diff": {
			"get": {
				"operationId": "websiteSourceCode.files.diff",
				"summary": "Diff one source file",
				"description": "Returns a bounded unified diff for one file name between two version refs, defaulting from published to head.",
				"tags": [
					"Source code"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "from",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Base side of the diff: a version number, `head`, or `published`. Defaults to `published`.",
							"type": "string",
							"pattern": "^(head|published|[1-9][0-9]*)$"
						}
					},
					{
						"name": "to",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Target side of the diff: a version number, `head`, or `published`. Defaults to `head`.",
							"type": "string",
							"pattern": "^(head|published|[1-9][0-9]*)$"
						}
					},
					{
						"name": "name",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1,
							"description": "File name to compare."
						}
					}
				],
				"responses": {
					"200": {
						"description": "The selected file comparison and unified diff.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"websiteId": {
											"type": "string",
											"description": "Id of the website."
										},
										"name": {
											"type": "string",
											"description": "Compared file name."
										},
										"fromVersion": {
											"type": "integer",
											"description": "Resolved base version."
										},
										"toVersion": {
											"type": "integer",
											"description": "Resolved target version."
										},
										"fromType": {
											"description": "File type at the base version, when present.",
											"type": "string",
											"enum": [
												"page",
												"component",
												"module",
												"server_function",
												"global_css",
												"not_found",
												"website_config",
												"custom_head_start",
												"custom_head_end",
												"custom_body_start",
												"custom_body_end"
											]
										},
										"toType": {
											"description": "File type at the target version, when present.",
											"type": "string",
											"enum": [
												"page",
												"component",
												"module",
												"server_function",
												"global_css",
												"not_found",
												"website_config",
												"custom_head_start",
												"custom_head_end",
												"custom_body_start",
												"custom_body_end"
											]
										},
										"status": {
											"type": "string",
											"enum": [
												"added",
												"removed",
												"modified",
												"unchanged"
											],
											"description": "How the file differs between the two versions."
										},
										"diff": {
											"type": "string",
											"description": "Unified diff hunks."
										},
										"truncated": {
											"description": "`true` when diff computation or output reached its bound. Read both versions for the full content.",
											"type": "boolean"
										}
									},
									"required": [
										"websiteId",
										"name",
										"fromVersion",
										"toVersion",
										"status",
										"diff"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.files.diff",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websiteSourceCode/lease/acquire": {
			"post": {
				"operationId": "websiteSourceCode.lease.acquire",
				"summary": "Acquire the edit lease",
				"description": "Acquires and replaces the website's single edit lease, taking over from any other editor. Writes that pass the returned principal-bound `leaseToken` keep working until someone else takes the lease; writes without a token take the lease automatically unless another editor is still working: the Cactal agent while it runs, or another caller that wrote in the last 5 minutes.",
				"tags": [
					"Source code"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"websiteId"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The new lease token and current head version.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"leaseToken": {
											"type": "string",
											"description": "Token to send with every source mutation. Valid until another lease is acquired for this website."
										},
										"headVersion": {
											"type": "integer",
											"description": "Draft source version at acquisition."
										}
									},
									"required": [
										"leaseToken",
										"headVersion"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.lease.acquire",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/websiteSourceCode/files/str-replace": {
			"post": {
				"operationId": "websiteSourceCode.files.strReplace",
				"summary": "Edit source files with string replacement",
				"description": "Atomically applies ordered exact-string edits to up to 50 files and 200 edits in total as one head version. Each `oldStr` must match once unless `replaceAll` is true, and any failing edit aborts the whole call. Set `dryRun` to validate and count replacements without committing. Takes the edit lease when no `leaseToken` is sent.",
				"tags": [
					"Source code"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"files": {
										"minItems": 1,
										"maxItems": 50,
										"type": "array",
										"items": {
											"type": "object",
											"properties": {
												"name": {
													"type": "string",
													"minLength": 1,
													"description": "Name of the file to edit. Singleton files are addressed by their type name, for example `global_css`."
												},
												"edits": {
													"minItems": 1,
													"type": "array",
													"items": {
														"type": "object",
														"properties": {
															"oldStr": {
																"type": "string",
																"minLength": 1,
																"description": "Exact text to find. Must match exactly once unless `replaceAll` is `true`."
															},
															"newStr": {
																"type": "string",
																"description": "Replacement text."
															},
															"replaceAll": {
																"description": "Replace every occurrence instead of requiring a unique match.",
																"type": "boolean"
															}
														},
														"required": [
															"oldStr",
															"newStr"
														]
													},
													"description": "Edits applied in order against the evolving content of this file."
												}
											},
											"required": [
												"name",
												"edits"
											]
										},
										"description": "Files to edit (up to 50, and 200 edits in total). Every edit across every file commits as one version, or none of them do."
									},
									"dryRun": {
										"description": "Validate every edit and report the replacement counts without committing.",
										"type": "boolean"
									},
									"leaseToken": {
										"description": "Omit to have Cactal take the edit lease for you; pass a token from websiteSourceCode.lease.acquire to take over from another editor.",
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"websiteId",
									"files"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The new head version plus per-file replacement counts.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"version": {
											"type": "integer",
											"description": "Head version after the edit: the previous head plus one, or the unchanged head when `changed` is `false`."
										},
										"changed": {
											"type": "boolean",
											"description": "`false` when the edit was a no-op and no new version was created."
										},
										"files": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"name": {
														"type": "string",
														"description": "Name of the edited file."
													},
													"replacements": {
														"type": "integer",
														"description": "Occurrences replaced in this file."
													}
												},
												"required": [
													"name",
													"replacements"
												],
												"additionalProperties": false
											},
											"description": "Per-file replacement counts, in request order. Reported for `dryRun` calls too, which commit nothing."
										}
									},
									"required": [
										"version",
										"changed",
										"files"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.files.strReplace",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/websiteSourceCode/pages/route": {
			"patch": {
				"operationId": "websiteSourceCode.pages.editRoute",
				"summary": "Edit a page route",
				"description": "Rewrites and normalizes a page's `definePage` route literal. Takes the edit lease when no `leaseToken` is sent.",
				"tags": [
					"Source code"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"name": {
										"type": "string",
										"minLength": 1,
										"maxLength": 255
									},
									"route": {
										"type": "string"
									},
									"leaseToken": {
										"description": "Omit to have Cactal take the edit lease for you; pass a token from websiteSourceCode.lease.acquire to take over from another editor.",
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"websiteId",
									"name",
									"route"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The committed edit and the normalized route.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"version": {
											"type": "integer",
											"description": "Head version after the edit: the previous head plus one, or the unchanged head when `changed` is `false`."
										},
										"changed": {
											"type": "boolean",
											"description": "`false` when the edit was a no-op and no new version was created."
										},
										"route": {
											"type": "string",
											"description": "Normalized route literal now in the page source."
										},
										"routeChanged": {
											"type": "boolean",
											"description": "Whether the route differs from its previous value."
										}
									},
									"required": [
										"version",
										"changed",
										"route",
										"routeChanged"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.pages.editRoute",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/websiteSourceCode/metadata": {
			"patch": {
				"operationId": "websiteSourceCode.metadata.edit",
				"summary": "Edit page or site metadata",
				"description": "Replaces a page's complete metadata object when `page` is sent, otherwise the site defaults in `website_config`. Takes the edit lease when no `leaseToken` is sent.",
				"tags": [
					"Source code"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"page": {
										"description": "Page to edit. Omit to edit the site-wide default metadata in `website_config`.",
										"type": "string",
										"minLength": 1,
										"maxLength": 255
									},
									"metadata": {},
									"leaseToken": {
										"description": "Omit to have Cactal take the edit lease for you; pass a token from websiteSourceCode.lease.acquire to take over from another editor.",
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"websiteId",
									"metadata"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The committed edit and the effective metadata.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"version": {
											"type": "integer",
											"description": "Head version after the edit: the previous head plus one, or the unchanged head when `changed` is `false`."
										},
										"changed": {
											"type": "boolean",
											"description": "`false` when the edit was a no-op and no new version was created."
										},
										"metadata": {
											"description": "Metadata now in the source: the page metadata when `page` was sent, otherwise the site-wide defaults that page metadata is layered onto."
										},
										"metadataChanged": {
											"type": "boolean",
											"description": "Whether the metadata differs from its previous value."
										}
									},
									"required": [
										"version",
										"changed",
										"metadata",
										"metadataChanged"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.metadata.edit",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/websiteSourceCode/head/check": {
			"post": {
				"operationId": "websiteSourceCode.head.check",
				"summary": "Validate the head source",
				"description": "Runs the publish-time framework quality gate without changing source: format, typecheck, lint, build, and a server-render of every page using placeholder content synthesized from the CMS schema. Failures return `ok: false` with diagnostics, and pages that compiled but threw while rendering in `renderFailures`. No lease is required and no CMS content is read, so the outcome depends only on source and schema.",
				"tags": [
					"Publishing"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"websiteId"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The quality-gate outcome for the checked head version.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"version": {
											"type": "integer",
											"description": "Source version that was checked."
										},
										"ok": {
											"type": "boolean",
											"description": "Whether the source passed validation and compiled."
										},
										"message": {
											"description": "Failure summary. Present only when `ok` is `false`.",
											"type": "string"
										},
										"details": {
											"description": "Framework diagnostics such as per-page errors. Present only for failures that carry details.",
											"type": "object",
											"propertyNames": {
												"type": "string"
											},
											"additionalProperties": {}
										},
										"notes": {
											"description": "Non-blocking observations about the draft, such as a form that emails nobody. Present only when there are any.",
											"type": "array",
											"items": {
												"type": "string"
											}
										},
										"renderFailures": {
											"description": "Pages that compiled but threw while server-rendering. Present only when rendering is what failed.",
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"pageId": {
														"type": "string",
														"description": "Id of the page that failed."
													},
													"route": {
														"type": "string",
														"description": "Route the failing page serves."
													},
													"message": {
														"type": "string",
														"description": "Error thrown while server-rendering the page."
													}
												},
												"required": [
													"pageId",
													"route",
													"message"
												],
												"additionalProperties": false
											}
										}
									},
									"required": [
										"version",
										"ok"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.head.check",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websiteSourceCode/deployment/status": {
			"post": {
				"operationId": "websiteSourceCode.deployment.status",
				"summary": "Get deployment build status",
				"description": "Returns the preview revision, available page routes, publish availability, and build state for the selected head or published source version. Fails with 404 when `target=published` is requested for a never-published website.",
				"tags": [
					"Publishing"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"target": {
										"type": "string",
										"enum": [
											"head",
											"published"
										]
									}
								},
								"required": [
									"websiteId",
									"target"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Deployment state for the resolved source version.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"websiteId": {
											"type": "string",
											"description": "Id of the website."
										},
										"sourceVersion": {
											"type": "integer",
											"description": "Source version resolved from `target`."
										},
										"previewRevision": {
											"type": "string",
											"description": "Opaque value that changes when the preview should be refreshed."
										},
										"hasPages": {
											"type": "boolean",
											"description": "Whether the resolved source version contains at least one page."
										},
										"pages": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"pageId": {
														"type": "string"
													},
													"route": {
														"type": "string"
													},
													"draftOnly": {
														"type": "boolean",
														"description": "Whether the page is served only on the draft preview and returns 404 on published hosts."
													}
												},
												"required": [
													"pageId",
													"route",
													"draftOnly"
												],
												"additionalProperties": false
											},
											"description": "Statically extractable page routes in the resolved source version."
										},
										"canPublish": {
											"type": "boolean",
											"description": "Whether this preview is ready and has not already been published."
										},
										"deployment": {
											"anyOf": [
												{
													"type": "object",
													"properties": {
														"id": {
															"type": "string",
															"description": "Unique id of the deployment."
														},
														"status": {
															"type": "string",
															"enum": [
																"building",
																"ready",
																"failed"
															],
															"description": "Build state of the deployment."
														},
														"failureMessage": {
															"description": "Human-readable build failure summary. `null` unless `status` is `failed`.",
															"type": [
																"string",
																"null"
															]
														},
														"failureKind": {
															"description": "Failure category. `null` unless `status` is `failed`.",
															"anyOf": [
																{
																	"type": "string",
																	"enum": [
																		"transient",
																		"permanent",
																		"unknown"
																	]
																},
																{
																	"type": "null"
																}
															]
														}
													},
													"required": [
														"id",
														"status"
													],
													"additionalProperties": false
												},
												{
													"type": "null"
												}
											],
											"description": "The deployment for the resolved source version, or `null` when none has been built."
										}
									},
									"required": [
										"websiteId",
										"sourceVersion",
										"previewRevision",
										"hasPages",
										"pages",
										"canPublish",
										"deployment"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.deployment.status",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websiteSourceCode/deployment/ensure": {
			"post": {
				"operationId": "websiteSourceCode.deployment.ensure",
				"summary": "Ensure a deployment build",
				"description": "Returns an existing non-failed deployment for the selected version or builds one. Poll status when another request is already building it.",
				"tags": [
					"Publishing"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"target": {
										"type": "string",
										"enum": [
											"head",
											"published"
										]
									}
								},
								"required": [
									"websiteId",
									"target"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The ensured deployment for the resolved source version.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"websiteId": {
											"type": "string",
											"description": "Id of the website."
										},
										"sourceVersion": {
											"type": "integer",
											"description": "Source version resolved from `target`."
										},
										"deployment": {
											"type": "object",
											"properties": {
												"id": {
													"type": "string",
													"description": "Unique id of the deployment."
												},
												"status": {
													"type": "string",
													"enum": [
														"building",
														"ready",
														"failed"
													],
													"description": "Build state of the deployment."
												},
												"failureMessage": {
													"description": "Human-readable build failure summary. `null` unless `status` is `failed`.",
													"type": [
														"string",
														"null"
													]
												},
												"failureKind": {
													"description": "Failure category. `null` unless `status` is `failed`.",
													"anyOf": [
														{
															"type": "string",
															"enum": [
																"transient",
																"permanent",
																"unknown"
															]
														},
														{
															"type": "null"
														}
													]
												}
											},
											"required": [
												"id",
												"status"
											],
											"additionalProperties": false,
											"description": "The reused or newly built deployment."
										}
									},
									"required": [
										"websiteId",
										"sourceVersion",
										"deployment"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.deployment.ensure",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/websiteSourceCode/head/publish": {
			"post": {
				"operationId": "websiteSourceCode.head.publish",
				"summary": "Publish the head version",
				"description": "Validates, builds, and publishes head. Canonical formatting may create a new head first. Pass the `version` the check returned to refuse publishing a draft that changed since. Takes the edit lease when no `leaseToken` is sent.",
				"tags": [
					"Publishing"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"leaseToken": {
										"description": "Omit to have Cactal take the edit lease for you; pass a token from websiteSourceCode.lease.acquire to take over from another editor.",
										"type": "string",
										"minLength": 1
									},
									"version": {
										"description": "Version returned by websiteSourceCode.head.check. Publishing refuses when the draft changed after that check.",
										"type": "integer",
										"exclusiveMinimum": 0,
										"maximum": 9007199254740991
									}
								},
								"required": [
									"websiteId"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The published version now live and its serving deployment.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"websiteId": {
											"type": "string",
											"description": "Id of the website."
										},
										"publishedVersion": {
											"type": "integer",
											"description": "Source version now live."
										},
										"formattingApplied": {
											"type": "boolean",
											"description": "`true` when canonical formatting was committed as a new head version before publishing; re-read affected files before further edits."
										},
										"deployment": {
											"description": "The ready deployment now serving the site.",
											"type": "object",
											"properties": {
												"id": {
													"type": "string",
													"description": "Unique id of the deployment."
												},
												"status": {
													"type": "string",
													"enum": [
														"building",
														"ready",
														"failed"
													],
													"description": "Build state of the deployment."
												},
												"failureMessage": {
													"description": "Human-readable build failure summary. `null` unless `status` is `failed`.",
													"type": [
														"string",
														"null"
													]
												},
												"failureKind": {
													"description": "Failure category. `null` unless `status` is `failed`.",
													"anyOf": [
														{
															"type": "string",
															"enum": [
																"transient",
																"permanent",
																"unknown"
															]
														},
														{
															"type": "null"
														}
													]
												}
											},
											"required": [
												"id",
												"status"
											],
											"additionalProperties": false
										}
									},
									"required": [
										"websiteId",
										"publishedVersion",
										"formattingApplied"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.head.publish",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/websiteSourceCode/head/restore": {
			"post": {
				"operationId": "websiteSourceCode.head.restore",
				"summary": "Restore head to a previous state",
				"description": "Copies an earlier version or `published` into a new head without publishing or rewriting history. Takes the edit lease when no `leaseToken` is sent.",
				"tags": [
					"Publishing"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"to": {
										"type": "string",
										"pattern": "^(published|[1-9][0-9]*)$",
										"description": "State to restore to head: a source version number or `published`."
									},
									"leaseToken": {
										"description": "Omit to have Cactal take the edit lease for you; pass a token from websiteSourceCode.lease.acquire to take over from another editor.",
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"websiteId",
									"to"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The new head version matching the restored source.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"version": {
											"type": "integer",
											"description": "Head version after the edit: the previous head plus one, or the unchanged head when `changed` is `false`."
										},
										"changed": {
											"type": "boolean",
											"description": "`false` when the edit was a no-op and no new version was created."
										}
									},
									"required": [
										"version",
										"changed"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.head.restore",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/websiteSourceCode/published/unpublish": {
			"post": {
				"operationId": "websiteSourceCode.published.unpublish",
				"summary": "Unpublish the website",
				"description": "Takes the website offline without changing its draft source or version history. The unchanged head can be published again later. No edit lease is required.",
				"tags": [
					"Publishing"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									}
								},
								"required": [
									"websiteId"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Confirmation that the website is no longer published.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"websiteId": {
											"type": "string",
											"description": "Id of the website."
										},
										"publishedVersion": {
											"type": "null",
											"description": "Always `null` after the site is unpublished."
										}
									},
									"required": [
										"websiteId",
										"publishedVersion"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.published.unpublish",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/websiteSourceCode/published/rollback-version": {
			"post": {
				"operationId": "websiteSourceCode.published.rollbackToVersion",
				"summary": "Roll back to a previous version",
				"description": "Validates and republishes an earlier version as a new head and published version, preserving history. Takes the edit lease when no `leaseToken` is sent.",
				"tags": [
					"Publishing"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"leaseToken": {
										"description": "Omit to have Cactal take the edit lease for you; pass a token from websiteSourceCode.lease.acquire to take over from another editor.",
										"type": "string",
										"minLength": 1
									},
									"version": {
										"type": "integer",
										"exclusiveMinimum": 0,
										"maximum": 9007199254740991
									}
								},
								"required": [
									"websiteId",
									"version"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The re-published version now live and its serving deployment.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"websiteId": {
											"type": "string",
											"description": "Id of the website."
										},
										"publishedVersion": {
											"type": "integer",
											"description": "Source version now live: a new head version whose files match the requested version."
										},
										"deployment": {
											"description": "The ready deployment now serving the site.",
											"type": "object",
											"properties": {
												"id": {
													"type": "string",
													"description": "Unique id of the deployment."
												},
												"status": {
													"type": "string",
													"enum": [
														"building",
														"ready",
														"failed"
													],
													"description": "Build state of the deployment."
												},
												"failureMessage": {
													"description": "Human-readable build failure summary. `null` unless `status` is `failed`.",
													"type": [
														"string",
														"null"
													]
												},
												"failureKind": {
													"description": "Failure category. `null` unless `status` is `failed`.",
													"anyOf": [
														{
															"type": "string",
															"enum": [
																"transient",
																"permanent",
																"unknown"
															]
														},
														{
															"type": "null"
														}
													]
												}
											},
											"required": [
												"id",
												"status"
											],
											"additionalProperties": false
										}
									},
									"required": [
										"websiteId",
										"publishedVersion"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSourceCode.published.rollbackToVersion",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/websitePreview/capture": {
			"post": {
				"operationId": "websitePreview.capture",
				"summary": "Capture a page screenshot",
				"description": "Renders one page of the site in a real browser and returns the image, so you can check layout, spacing, and responsive behaviour that source-level checks cannot see. `path` is site-relative and may include a query string, so dynamic routes are captured with real values rather than placeholders. Focus the capture with `selector` (plus optional `padding` for surrounding context) or a `clip` rectangle in CSS pixels from the top-left of the page; the region is scrolled into view and captured within the configured viewport, so it must fit inside it, and these options exclude each other and `fullPage`. Fonts, images, and (for `fullPage`) lazy content are awaited before the capture, and CSS animations are fast-forwarded to their end state by default. Fails with 409 when the draft build is still in progress or has failed; poll `websiteSourceCode.deployment.status` and retry.",
				"tags": [
					"Publishing"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"path": {
										"default": "/",
										"description": "Site-relative path to render, including any query string, e.g. `/blog/my-post`.",
										"type": "string",
										"maxLength": 2048,
										"pattern": "^\\/([^/\\\\\\s][^\\\\\\s]*)?$"
									},
									"target": {
										"default": "head",
										"type": "string",
										"enum": [
											"head",
											"published"
										],
										"description": "`head` renders the current draft; `published` renders the live site."
									},
									"viewport": {
										"default": "desktop",
										"description": "Device class to render at.",
										"type": "string",
										"enum": [
											"desktop",
											"tablet",
											"mobile"
										]
									},
									"colorScheme": {
										"description": "Browser color preference to emulate. Omit it to use the renderer default.",
										"type": "string",
										"enum": [
											"dark",
											"light"
										]
									},
									"fullPage": {
										"default": false,
										"type": "boolean",
										"description": "Capture the whole scrollable page instead of just the viewport. Excludes `selector` and `clip`."
									},
									"selector": {
										"description": "Crop to the first element matching this CSS selector, scrolled into view. Preferred: it follows the layout at every viewport.",
										"type": "string",
										"minLength": 1
									},
									"padding": {
										"description": "CSS pixels of surrounding page to include on every side of the `selector` match.",
										"type": "integer",
										"minimum": 0,
										"maximum": 500
									},
									"clip": {
										"description": "Crop to this rectangle, in CSS pixels from the top-left of the rendered page at the chosen viewport; it is scrolled into view and must fit within the viewport. Only for regions no selector frames.",
										"type": "object",
										"properties": {
											"x": {
												"type": "number",
												"minimum": 0,
												"maximum": 100000
											},
											"y": {
												"type": "number",
												"minimum": 0,
												"maximum": 100000
											},
											"width": {
												"type": "number",
												"minimum": 1,
												"maximum": 2000
											},
											"height": {
												"type": "number",
												"minimum": 1,
												"maximum": 2000
											}
										},
										"required": [
											"x",
											"y",
											"width",
											"height"
										]
									},
									"scrollTo": {
										"description": "Scroll to a CSS selector or an absolute Y offset before capturing.",
										"anyOf": [
											{
												"type": "string",
												"minLength": 1
											},
											{
												"type": "integer",
												"minimum": 0,
												"maximum": 9007199254740991
											}
										]
									},
									"waitForSelector": {
										"description": "Wait for this CSS selector to match before capturing.",
										"type": "string",
										"minLength": 1
									},
									"delayMs": {
										"description": "Extra settle time before capturing, in milliseconds.",
										"type": "integer",
										"minimum": 0,
										"maximum": 5000
									},
									"freezeAnimations": {
										"default": true,
										"description": "Fast-forward CSS animations and transitions to their end state so captures are deterministic.",
										"type": "boolean"
									}
								},
								"required": [
									"websiteId"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Rendered page image.",
						"content": {
							"image/jpeg": {
								"schema": {
									"type": "string",
									"format": "binary"
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websitePreview.capture",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/cms/schema": {
			"get": {
				"operationId": "cms.schema.snapshot",
				"summary": "Get the CMS schema snapshot",
				"description": "Returns every active collection and field of a website in one call — the full content model. Collections are ordered newest first; fields are grouped by collection in display order. Use this to discover the schema before reading or writing items.",
				"tags": [
					"CMS collections"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "The full content model for the website.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"collections": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string",
														"description": "Unique collection id."
													},
													"slug": {
														"type": "string",
														"description": "URL-shaped identifier, unique among the website’s active collections. Lowercase letters, digits, and hyphens, starting with a letter; max 64 characters."
													},
													"displayName": {
														"type": "string",
														"description": "Human-readable name, 1–200 characters."
													},
													"description": {
														"description": "Optional description, up to 2,000 characters.",
														"type": [
															"string",
															"null"
														]
													}
												},
												"required": [
													"id",
													"slug",
													"displayName",
													"description"
												],
												"additionalProperties": false
											},
											"description": "Active collections, newest first."
										},
										"fields": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string",
														"description": "Unique field id."
													},
													"collectionId": {
														"type": "string",
														"description": "Id of the owning collection."
													},
													"key": {
														"type": "string",
														"description": "JSON key under item `data`. Lowercase snake_case, max 64 characters, unique per collection. Changed only via `key` on the update-field endpoint, which migrates stored data."
													},
													"displayName": {
														"type": "string",
														"description": "Human-readable name, 1–200 characters."
													},
													"description": {
														"description": "Optional description, up to 2,000 characters.",
														"type": [
															"string",
															"null"
														]
													},
													"type": {
														"type": "string",
														"enum": [
															"plain_text",
															"markdown",
															"number",
															"boolean",
															"date",
															"link",
															"email",
															"color",
															"option",
															"reference",
															"multi_reference",
															"image",
															"gallery",
															"file"
														],
														"description": "Value type. Immutable after creation."
													},
													"required": {
														"type": "boolean",
														"description": "Whether every item must carry a non-null value for this field. Enforced on write."
													},
													"isUnique": {
														"type": "boolean",
														"description": "Whether values must be unique across the collection’s active items. Not supported for `plain_text`, `markdown`, `multi_reference`, `image`, `gallery`, or `file` fields."
													},
													"example": {
														"description": "Optional example value used as a design-phase placeholder. Any JSON value; `null` when unset."
													},
													"config": {
														"anyOf": [
															{
																"type": "object",
																"properties": {
																	"choices": {
																		"type": "array",
																		"items": {
																			"type": "object",
																			"properties": {
																				"id": {
																					"type": "string",
																					"description": "Stable choice id stored as the field value."
																				},
																				"label": {
																					"type": "string",
																					"description": "Human-readable choice label."
																				}
																			},
																			"required": [
																				"id",
																				"label"
																			],
																			"additionalProperties": false
																		},
																		"description": "Selectable choices, 1–1,000."
																	},
																	"multiple": {
																		"type": "boolean",
																		"description": "Whether items may select multiple choices."
																	}
																},
																"required": [
																	"choices",
																	"multiple"
																],
																"additionalProperties": false
															},
															{
																"type": "null"
															}
														],
														"description": "Type-specific configuration. Present for `option` fields only; `null` for all other types."
													},
													"targetCollectionId": {
														"description": "For `reference` and `multi_reference` fields, the id of the collection referenced items must belong to (same website). `null` otherwise. Immutable.",
														"type": [
															"string",
															"null"
														]
													}
												},
												"required": [
													"id",
													"collectionId",
													"key",
													"displayName",
													"description",
													"type",
													"required",
													"isUnique",
													"example",
													"config",
													"targetCollectionId"
												],
												"additionalProperties": false
											},
											"description": "All fields across those collections, grouped by collection in display order."
										}
									},
									"required": [
										"collections",
										"fields"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "cms.schema.snapshot",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/cms/collections": {
			"get": {
				"operationId": "cms.collections.list",
				"summary": "List collections",
				"description": "Returns the website’s active collections, newest first, with keyset pagination. `limit` defaults to 20 (max 100); pass the returned `nextCursor` as `cursor` to fetch the next page.",
				"tags": [
					"CMS collections"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "cursor",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"default": 20,
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					}
				],
				"responses": {
					"200": {
						"description": "One page of collections.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string",
														"description": "Unique collection id."
													},
													"websiteId": {
														"type": "string",
														"description": "Id of the owning website."
													},
													"slug": {
														"type": "string",
														"description": "URL-shaped identifier, unique among the website’s active collections. Lowercase letters, digits, and hyphens, starting with a letter; max 64 characters."
													},
													"displayName": {
														"type": "string",
														"description": "Human-readable name, 1–200 characters."
													},
													"description": {
														"description": "Optional description, up to 2,000 characters.",
														"type": [
															"string",
															"null"
														]
													},
													"createdAt": {
														"description": "Creation timestamp.",
														"type": "string",
														"format": "date-time"
													},
													"updatedAt": {
														"description": "Last-modification timestamp.",
														"type": "string",
														"format": "date-time"
													},
													"itemCount": {
														"type": "integer",
														"minimum": 0,
														"description": "Number of active items in the collection."
													}
												},
												"required": [
													"id",
													"websiteId",
													"slug",
													"displayName",
													"description",
													"createdAt",
													"updatedAt",
													"itemCount"
												],
												"additionalProperties": false
											}
										},
										"nextCursor": {
											"description": "Opaque cursor for the next page. Pass it as the `cursor` parameter on the next request. `null` when this is the last page.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"items",
										"nextCursor"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "cms.collections.list",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			},
			"post": {
				"operationId": "cms.collections.create",
				"summary": "Create a collection",
				"description": "Creates a collection in the website’s CMS. `slug` uses lowercase letters, digits, and hyphens (max 64 characters, starting with a letter) and must be unique among the website’s active collections; `displayName` is 1–200 characters and `description` up to 2,000. Optionally pass `fields` (up to 100, same shape as the create-field body) to define the schema in the same call. A website can hold at most 100 active collections; the limit is enforced with a conflict error.",
				"tags": [
					"CMS collections"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1
									},
									"slug": {
										"type": "string",
										"pattern": "^[a-z][a-z0-9-]{0,63}$"
									},
									"displayName": {
										"type": "string",
										"minLength": 1,
										"maxLength": 200
									},
									"description": {
										"anyOf": [
											{
												"type": "string",
												"maxLength": 2000
											},
											{
												"type": "null"
											}
										]
									},
									"fields": {
										"maxItems": 100,
										"type": "array",
										"items": {
											"type": "object",
											"properties": {
												"key": {
													"type": "string",
													"pattern": "^[a-z][a-z0-9_]{0,63}$"
												},
												"displayName": {
													"type": "string",
													"minLength": 1,
													"maxLength": 200
												},
												"description": {
													"anyOf": [
														{
															"type": "string",
															"maxLength": 2000
														},
														{
															"type": "null"
														}
													]
												},
												"type": {
													"type": "string",
													"enum": [
														"plain_text",
														"markdown",
														"number",
														"boolean",
														"date",
														"link",
														"email",
														"color",
														"option",
														"reference",
														"multi_reference",
														"image",
														"gallery",
														"file"
													]
												},
												"required": {
													"type": "boolean"
												},
												"isUnique": {
													"type": "boolean"
												},
												"example": {},
												"targetCollectionId": {
													"type": "string",
													"minLength": 1
												},
												"config": {
													"type": "object",
													"properties": {
														"choices": {
															"minItems": 1,
															"maxItems": 1000,
															"type": "array",
															"items": {
																"type": "object",
																"properties": {
																	"id": {
																		"type": "string",
																		"minLength": 1,
																		"maxLength": 100
																	},
																	"label": {
																		"type": "string",
																		"minLength": 1,
																		"maxLength": 200
																	}
																},
																"required": [
																	"id",
																	"label"
																]
															}
														},
														"multiple": {
															"type": "boolean"
														}
													},
													"required": [
														"choices"
													]
												}
											},
											"required": [
												"key",
												"displayName",
												"type"
											]
										}
									}
								},
								"required": [
									"websiteId",
									"slug",
									"displayName"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The created collection and its fields.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"collection": {
											"type": "object",
											"properties": {
												"id": {
													"type": "string",
													"description": "Unique collection id."
												},
												"websiteId": {
													"type": "string",
													"description": "Id of the owning website."
												},
												"slug": {
													"type": "string",
													"description": "URL-shaped identifier, unique among the website’s active collections. Lowercase letters, digits, and hyphens, starting with a letter; max 64 characters."
												},
												"displayName": {
													"type": "string",
													"description": "Human-readable name, 1–200 characters."
												},
												"description": {
													"description": "Optional description, up to 2,000 characters.",
													"type": [
														"string",
														"null"
													]
												},
												"createdAt": {
													"description": "Creation timestamp.",
													"type": "string",
													"format": "date-time"
												},
												"updatedAt": {
													"description": "Last-modification timestamp.",
													"type": "string",
													"format": "date-time"
												}
											},
											"required": [
												"id",
												"websiteId",
												"slug",
												"displayName",
												"description",
												"createdAt",
												"updatedAt"
											],
											"additionalProperties": false
										},
										"fields": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string",
														"description": "Unique field id."
													},
													"collectionId": {
														"type": "string",
														"description": "Id of the owning collection."
													},
													"key": {
														"type": "string",
														"description": "JSON key under item `data`. Lowercase snake_case, max 64 characters, unique per collection. Changed only via `key` on the update-field endpoint, which migrates stored data."
													},
													"displayName": {
														"type": "string",
														"description": "Human-readable name, 1–200 characters."
													},
													"description": {
														"description": "Optional description, up to 2,000 characters.",
														"type": [
															"string",
															"null"
														]
													},
													"type": {
														"type": "string",
														"enum": [
															"plain_text",
															"markdown",
															"number",
															"boolean",
															"date",
															"link",
															"email",
															"color",
															"option",
															"reference",
															"multi_reference",
															"image",
															"gallery",
															"file"
														],
														"description": "Value type. Immutable after creation."
													},
													"required": {
														"type": "boolean",
														"description": "Whether every item must carry a non-null value for this field. Enforced on write."
													},
													"isUnique": {
														"type": "boolean",
														"description": "Whether values must be unique across the collection’s active items. Not supported for `plain_text`, `markdown`, `multi_reference`, `image`, `gallery`, or `file` fields."
													},
													"position": {
														"type": "string",
														"description": "Fractional ordering key; fields sort lexicographically by this value."
													},
													"example": {
														"description": "Optional example value used as a design-phase placeholder. Any JSON value; `null` when unset."
													},
													"config": {
														"anyOf": [
															{
																"type": "object",
																"properties": {
																	"choices": {
																		"type": "array",
																		"items": {
																			"type": "object",
																			"properties": {
																				"id": {
																					"type": "string",
																					"description": "Stable choice id stored as the field value."
																				},
																				"label": {
																					"type": "string",
																					"description": "Human-readable choice label."
																				}
																			},
																			"required": [
																				"id",
																				"label"
																			],
																			"additionalProperties": false
																		},
																		"description": "Selectable choices, 1–1,000."
																	},
																	"multiple": {
																		"type": "boolean",
																		"description": "Whether items may select multiple choices."
																	}
																},
																"required": [
																	"choices",
																	"multiple"
																],
																"additionalProperties": false
															},
															{
																"type": "null"
															}
														],
														"description": "Type-specific configuration. Present for `option` fields only; `null` for all other types."
													},
													"targetCollectionId": {
														"description": "For `reference` and `multi_reference` fields, the id of the collection referenced items must belong to (same website). `null` otherwise. Immutable.",
														"type": [
															"string",
															"null"
														]
													}
												},
												"required": [
													"id",
													"collectionId",
													"key",
													"displayName",
													"description",
													"type",
													"required",
													"isUnique",
													"position",
													"example",
													"config",
													"targetCollectionId"
												],
												"additionalProperties": false
											},
											"description": "The fields defining the shape of every item in the collection, in display order."
										}
									},
									"required": [
										"collection",
										"fields"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "cms.collections.create",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/cms/collections/{collectionId}": {
			"get": {
				"operationId": "cms.collections.get",
				"summary": "Get a collection",
				"description": "Returns a collection together with its full field list, which defines the shape of every item in it.",
				"tags": [
					"CMS collections"
				],
				"parameters": [
					{
						"name": "collectionId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "The collection and its fields.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"collection": {
											"type": "object",
											"properties": {
												"id": {
													"type": "string",
													"description": "Unique collection id."
												},
												"websiteId": {
													"type": "string",
													"description": "Id of the owning website."
												},
												"slug": {
													"type": "string",
													"description": "URL-shaped identifier, unique among the website’s active collections. Lowercase letters, digits, and hyphens, starting with a letter; max 64 characters."
												},
												"displayName": {
													"type": "string",
													"description": "Human-readable name, 1–200 characters."
												},
												"description": {
													"description": "Optional description, up to 2,000 characters.",
													"type": [
														"string",
														"null"
													]
												},
												"createdAt": {
													"description": "Creation timestamp.",
													"type": "string",
													"format": "date-time"
												},
												"updatedAt": {
													"description": "Last-modification timestamp.",
													"type": "string",
													"format": "date-time"
												}
											},
											"required": [
												"id",
												"websiteId",
												"slug",
												"displayName",
												"description",
												"createdAt",
												"updatedAt"
											],
											"additionalProperties": false
										},
										"fields": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string",
														"description": "Unique field id."
													},
													"collectionId": {
														"type": "string",
														"description": "Id of the owning collection."
													},
													"key": {
														"type": "string",
														"description": "JSON key under item `data`. Lowercase snake_case, max 64 characters, unique per collection. Changed only via `key` on the update-field endpoint, which migrates stored data."
													},
													"displayName": {
														"type": "string",
														"description": "Human-readable name, 1–200 characters."
													},
													"description": {
														"description": "Optional description, up to 2,000 characters.",
														"type": [
															"string",
															"null"
														]
													},
													"type": {
														"type": "string",
														"enum": [
															"plain_text",
															"markdown",
															"number",
															"boolean",
															"date",
															"link",
															"email",
															"color",
															"option",
															"reference",
															"multi_reference",
															"image",
															"gallery",
															"file"
														],
														"description": "Value type. Immutable after creation."
													},
													"required": {
														"type": "boolean",
														"description": "Whether every item must carry a non-null value for this field. Enforced on write."
													},
													"isUnique": {
														"type": "boolean",
														"description": "Whether values must be unique across the collection’s active items. Not supported for `plain_text`, `markdown`, `multi_reference`, `image`, `gallery`, or `file` fields."
													},
													"position": {
														"type": "string",
														"description": "Fractional ordering key; fields sort lexicographically by this value."
													},
													"example": {
														"description": "Optional example value used as a design-phase placeholder. Any JSON value; `null` when unset."
													},
													"config": {
														"anyOf": [
															{
																"type": "object",
																"properties": {
																	"choices": {
																		"type": "array",
																		"items": {
																			"type": "object",
																			"properties": {
																				"id": {
																					"type": "string",
																					"description": "Stable choice id stored as the field value."
																				},
																				"label": {
																					"type": "string",
																					"description": "Human-readable choice label."
																				}
																			},
																			"required": [
																				"id",
																				"label"
																			],
																			"additionalProperties": false
																		},
																		"description": "Selectable choices, 1–1,000."
																	},
																	"multiple": {
																		"type": "boolean",
																		"description": "Whether items may select multiple choices."
																	}
																},
																"required": [
																	"choices",
																	"multiple"
																],
																"additionalProperties": false
															},
															{
																"type": "null"
															}
														],
														"description": "Type-specific configuration. Present for `option` fields only; `null` for all other types."
													},
													"targetCollectionId": {
														"description": "For `reference` and `multi_reference` fields, the id of the collection referenced items must belong to (same website). `null` otherwise. Immutable.",
														"type": [
															"string",
															"null"
														]
													}
												},
												"required": [
													"id",
													"collectionId",
													"key",
													"displayName",
													"description",
													"type",
													"required",
													"isUnique",
													"position",
													"example",
													"config",
													"targetCollectionId"
												],
												"additionalProperties": false
											},
											"description": "The fields defining the shape of every item in the collection, in display order."
										}
									},
									"required": [
										"collection",
										"fields"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "cms.collections.get",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			},
			"patch": {
				"operationId": "cms.collections.update",
				"summary": "Update a collection",
				"description": "Updates `displayName` (1–200 characters), `description` (up to 2,000 characters; pass `null` to clear), and/or `slug` (a rename). A slug change fails with a conflict when another active collection in the website already uses it, and when the website has a published version the rename is validated against the published site’s content queries and rejected when it would break them.",
				"tags": [
					"CMS collections"
				],
				"parameters": [
					{
						"name": "collectionId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": false,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"displayName": {
										"type": "string",
										"minLength": 1,
										"maxLength": 200
									},
									"description": {
										"anyOf": [
											{
												"type": "string",
												"maxLength": 2000
											},
											{
												"type": "null"
											}
										]
									},
									"slug": {
										"description": "New slug for the collection — a rename. Omit to keep the current slug. Must be unique among the website’s active collections; when the website has a published version the rename is validated against the published site’s content queries.",
										"type": "string",
										"pattern": "^[a-z][a-z0-9-]{0,63}$"
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The updated collection.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique collection id."
										},
										"websiteId": {
											"type": "string",
											"description": "Id of the owning website."
										},
										"slug": {
											"type": "string",
											"description": "URL-shaped identifier, unique among the website’s active collections. Lowercase letters, digits, and hyphens, starting with a letter; max 64 characters."
										},
										"displayName": {
											"type": "string",
											"description": "Human-readable name, 1–200 characters."
										},
										"description": {
											"description": "Optional description, up to 2,000 characters.",
											"type": [
												"string",
												"null"
											]
										},
										"createdAt": {
											"description": "Creation timestamp.",
											"type": "string",
											"format": "date-time"
										},
										"updatedAt": {
											"description": "Last-modification timestamp.",
											"type": "string",
											"format": "date-time"
										}
									},
									"required": [
										"id",
										"websiteId",
										"slug",
										"displayName",
										"description",
										"createdAt",
										"updatedAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "cms.collections.update",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			},
			"delete": {
				"operationId": "cms.collections.delete",
				"summary": "Delete a collection",
				"description": "Soft-deletes the collection, making it and all of its items unreachable through the API; the slug becomes reusable. Refused with a conflict while any reference field in another collection targets this collection — delete those fields first. If the website has a published version, the deletion is validated against the published site’s content queries and rejected when the site still depends on the collection.",
				"tags": [
					"CMS collections"
				],
				"parameters": [
					{
						"name": "collectionId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Deletion acknowledgement.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"deleted": {
											"type": "boolean",
											"const": true,
											"description": "Always `true` once the collection is deleted."
										}
									},
									"required": [
										"deleted"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "cms.collections.delete",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/cms/collections/{collectionId}/fields": {
			"post": {
				"operationId": "cms.fields.create",
				"summary": "Add a field to a collection",
				"description": "Adds a typed field at the end of the collection’s field order. `key` is lowercase snake_case (max 64 characters), must be unique in the collection, and cannot use the reserved metadata names `id`, `slug`, `publishedAt`, `publishAt`, `createdAt`, `updatedAt`, or `position`; `type` is immutable after creation. `reference` and `multi_reference` fields require a `targetCollectionId` in the same website, and `option` fields require `config.choices` (1–1,000 choices). A collection can hold at most 100 fields, and `required: true` is rejected while the collection has active items — add the field as optional, backfill every item, then mark it required.",
				"tags": [
					"CMS fields"
				],
				"parameters": [
					{
						"name": "collectionId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"key": {
										"type": "string",
										"pattern": "^[a-z][a-z0-9_]{0,63}$"
									},
									"displayName": {
										"type": "string",
										"minLength": 1,
										"maxLength": 200
									},
									"description": {
										"anyOf": [
											{
												"type": "string",
												"maxLength": 2000
											},
											{
												"type": "null"
											}
										]
									},
									"type": {
										"type": "string",
										"enum": [
											"plain_text",
											"markdown",
											"number",
											"boolean",
											"date",
											"link",
											"email",
											"color",
											"option",
											"reference",
											"multi_reference",
											"image",
											"gallery",
											"file"
										]
									},
									"required": {
										"type": "boolean"
									},
									"isUnique": {
										"type": "boolean"
									},
									"example": {},
									"targetCollectionId": {
										"type": "string",
										"minLength": 1
									},
									"config": {
										"type": "object",
										"properties": {
											"choices": {
												"minItems": 1,
												"maxItems": 1000,
												"type": "array",
												"items": {
													"type": "object",
													"properties": {
														"id": {
															"type": "string",
															"minLength": 1,
															"maxLength": 100
														},
														"label": {
															"type": "string",
															"minLength": 1,
															"maxLength": 200
														}
													},
													"required": [
														"id",
														"label"
													]
												}
											},
											"multiple": {
												"type": "boolean"
											}
										},
										"required": [
											"choices"
										]
									}
								},
								"required": [
									"key",
									"displayName",
									"type"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The created field.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique field id."
										},
										"collectionId": {
											"type": "string",
											"description": "Id of the owning collection."
										},
										"key": {
											"type": "string",
											"description": "JSON key under item `data`. Lowercase snake_case, max 64 characters, unique per collection. Changed only via `key` on the update-field endpoint, which migrates stored data."
										},
										"displayName": {
											"type": "string",
											"description": "Human-readable name, 1–200 characters."
										},
										"description": {
											"description": "Optional description, up to 2,000 characters.",
											"type": [
												"string",
												"null"
											]
										},
										"type": {
											"type": "string",
											"enum": [
												"plain_text",
												"markdown",
												"number",
												"boolean",
												"date",
												"link",
												"email",
												"color",
												"option",
												"reference",
												"multi_reference",
												"image",
												"gallery",
												"file"
											],
											"description": "Value type. Immutable after creation."
										},
										"required": {
											"type": "boolean",
											"description": "Whether every item must carry a non-null value for this field. Enforced on write."
										},
										"isUnique": {
											"type": "boolean",
											"description": "Whether values must be unique across the collection’s active items. Not supported for `plain_text`, `markdown`, `multi_reference`, `image`, `gallery`, or `file` fields."
										},
										"position": {
											"type": "string",
											"description": "Fractional ordering key; fields sort lexicographically by this value."
										},
										"example": {
											"description": "Optional example value used as a design-phase placeholder. Any JSON value; `null` when unset."
										},
										"config": {
											"anyOf": [
												{
													"type": "object",
													"properties": {
														"choices": {
															"type": "array",
															"items": {
																"type": "object",
																"properties": {
																	"id": {
																		"type": "string",
																		"description": "Stable choice id stored as the field value."
																	},
																	"label": {
																		"type": "string",
																		"description": "Human-readable choice label."
																	}
																},
																"required": [
																	"id",
																	"label"
																],
																"additionalProperties": false
															},
															"description": "Selectable choices, 1–1,000."
														},
														"multiple": {
															"type": "boolean",
															"description": "Whether items may select multiple choices."
														}
													},
													"required": [
														"choices",
														"multiple"
													],
													"additionalProperties": false
												},
												{
													"type": "null"
												}
											],
											"description": "Type-specific configuration. Present for `option` fields only; `null` for all other types."
										},
										"targetCollectionId": {
											"description": "For `reference` and `multi_reference` fields, the id of the collection referenced items must belong to (same website). `null` otherwise. Immutable.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"id",
										"collectionId",
										"key",
										"displayName",
										"description",
										"type",
										"required",
										"isUnique",
										"position",
										"example",
										"config",
										"targetCollectionId"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "cms.fields.create",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/cms/fields/{fieldId}": {
			"patch": {
				"operationId": "cms.fields.update",
				"summary": "Update a field",
				"description": "Updates `displayName`, `description`, `required`, `isUnique`, `example`, and (for `option` fields only) `config`; `type` and `targetCollectionId` are immutable. Setting `required: true` is rejected until every active item has a valid value for the field. Setting `isUnique: true` enforces uniqueness on all future writes and fails with a conflict when existing values already collide; unique is not supported for `plain_text`, `markdown`, `multi_reference`, `image`, `gallery`, or `file` fields. Pass `key` to rename the field’s key: the key is atomically migrated in every stored item’s `data` in the same transaction (no values are lost), a conflict is returned when the new key is already used in the collection, and when the website has a published version the rename is validated against the published site’s content queries. Pass `position: \"first\"` or `\"last\"`, or `\"before\"` / `\"after\"` together with `anchorFieldId`, to move the field within the collection’s display order.",
				"tags": [
					"CMS fields"
				],
				"parameters": [
					{
						"name": "fieldId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": false,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"displayName": {
										"type": "string",
										"minLength": 1,
										"maxLength": 200
									},
									"description": {
										"anyOf": [
											{
												"type": "string",
												"maxLength": 2000
											},
											{
												"type": "null"
											}
										]
									},
									"required": {
										"type": "boolean"
									},
									"isUnique": {
										"type": "boolean"
									},
									"example": {},
									"config": {
										"type": "object",
										"properties": {
											"choices": {
												"minItems": 1,
												"maxItems": 1000,
												"type": "array",
												"items": {
													"type": "object",
													"properties": {
														"id": {
															"type": "string",
															"minLength": 1,
															"maxLength": 100
														},
														"label": {
															"type": "string",
															"minLength": 1,
															"maxLength": 200
														}
													},
													"required": [
														"id",
														"label"
													]
												}
											},
											"multiple": {
												"type": "boolean"
											}
										},
										"required": [
											"choices"
										]
									},
									"key": {
										"description": "New key for the field — a rename. Omit to keep the current key. Atomically migrates the key in every stored item’s `data`, so no values are lost.",
										"type": "string",
										"pattern": "^[a-z][a-z0-9_]{0,63}$"
									},
									"position": {
										"description": "Moves the field within the collection’s display order. Omit to leave the order unchanged. `\"before\"` / `\"after\"` require `anchorFieldId`.",
										"type": "string",
										"enum": [
											"before",
											"after",
											"first",
											"last"
										]
									},
									"anchorFieldId": {
										"description": "Sibling field to move against when `position` is `\"before\"` or `\"after\"`.",
										"type": "string",
										"minLength": 1
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The updated field.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique field id."
										},
										"collectionId": {
											"type": "string",
											"description": "Id of the owning collection."
										},
										"key": {
											"type": "string",
											"description": "JSON key under item `data`. Lowercase snake_case, max 64 characters, unique per collection. Changed only via `key` on the update-field endpoint, which migrates stored data."
										},
										"displayName": {
											"type": "string",
											"description": "Human-readable name, 1–200 characters."
										},
										"description": {
											"description": "Optional description, up to 2,000 characters.",
											"type": [
												"string",
												"null"
											]
										},
										"type": {
											"type": "string",
											"enum": [
												"plain_text",
												"markdown",
												"number",
												"boolean",
												"date",
												"link",
												"email",
												"color",
												"option",
												"reference",
												"multi_reference",
												"image",
												"gallery",
												"file"
											],
											"description": "Value type. Immutable after creation."
										},
										"required": {
											"type": "boolean",
											"description": "Whether every item must carry a non-null value for this field. Enforced on write."
										},
										"isUnique": {
											"type": "boolean",
											"description": "Whether values must be unique across the collection’s active items. Not supported for `plain_text`, `markdown`, `multi_reference`, `image`, `gallery`, or `file` fields."
										},
										"position": {
											"type": "string",
											"description": "Fractional ordering key; fields sort lexicographically by this value."
										},
										"example": {
											"description": "Optional example value used as a design-phase placeholder. Any JSON value; `null` when unset."
										},
										"config": {
											"anyOf": [
												{
													"type": "object",
													"properties": {
														"choices": {
															"type": "array",
															"items": {
																"type": "object",
																"properties": {
																	"id": {
																		"type": "string",
																		"description": "Stable choice id stored as the field value."
																	},
																	"label": {
																		"type": "string",
																		"description": "Human-readable choice label."
																	}
																},
																"required": [
																	"id",
																	"label"
																],
																"additionalProperties": false
															},
															"description": "Selectable choices, 1–1,000."
														},
														"multiple": {
															"type": "boolean",
															"description": "Whether items may select multiple choices."
														}
													},
													"required": [
														"choices",
														"multiple"
													],
													"additionalProperties": false
												},
												{
													"type": "null"
												}
											],
											"description": "Type-specific configuration. Present for `option` fields only; `null` for all other types."
										},
										"targetCollectionId": {
											"description": "For `reference` and `multi_reference` fields, the id of the collection referenced items must belong to (same website). `null` otherwise. Immutable.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"id",
										"collectionId",
										"key",
										"displayName",
										"description",
										"type",
										"required",
										"isUnique",
										"position",
										"example",
										"config",
										"targetCollectionId"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "cms.fields.update",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			},
			"delete": {
				"operationId": "cms.fields.delete",
				"summary": "Delete a field",
				"description": "Permanently deletes the field and its stored values: the key is stripped from every item’s `data`, and reference or asset attachments made through the field are removed. This cannot be undone. If the website has a published version, the deletion is validated against the published site’s content queries and rejected when the site still depends on the field.",
				"tags": [
					"CMS fields"
				],
				"parameters": [
					{
						"name": "fieldId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Deletion acknowledgement.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"deleted": {
											"type": "boolean",
											"const": true,
											"description": "Always `true` once the field and its stored values are removed."
										}
									},
									"required": [
										"deleted"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "cms.fields.delete",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/cms/collections/{collectionId}/items": {
			"get": {
				"operationId": "cms.items.list",
				"summary": "List items in a collection",
				"description": "Returns the collection’s active items in manual (drag) order by default, 20 per page (max 100); paginate with `cursor` or with 1-based `page` offsets, not both. Filter with `published` (`true` / `false`) and a JSON-encoded `filter` map of `{ fieldKey: { operator: value } }` — operators `eq`, `neq`, `in`, `nin`, `gt`, `gte`, `lt`, `lte`, `contains`, `startsWith`, `endsWith`, and `exists`, plus `slug` equality on reference fields; metadata keys are `id`, `slug`, `publishedAt`, `publishAt`, `createdAt`, and `updatedAt`. Combine clauses with `filterLogic` (`and` by default, or `or`). `search` accepts a query string (1–200 characters) or JSON `{ \"query\", \"fields\" }` to restrict which text fields are searched; `sort` accepts JSON `{ \"kind\": \"field\" | \"meta\", \"field\", \"direction\": \"asc\" | \"desc\" }`, where sortable metadata fields are `id`, `slug`, `publishedAt`, `publishAt`, `createdAt`, `updatedAt`, and `position`. `depth` (0–3, default 0) embeds referenced items in place of their ids, one level per unit.",
				"tags": [
					"CMS items"
				],
				"parameters": [
					{
						"name": "collectionId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "cursor",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "page",
						"in": "query",
						"required": false,
						"schema": {
							"type": "integer",
							"minimum": 1,
							"maximum": 9007199254740991
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"default": 20,
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					},
					{
						"name": "depth",
						"in": "query",
						"required": false,
						"schema": {
							"default": 0,
							"type": "integer",
							"minimum": 0,
							"maximum": 3
						}
					},
					{
						"name": "published",
						"in": "query",
						"required": false,
						"schema": {
							"anyOf": [
								{
									"type": "string",
									"const": "true"
								},
								{
									"type": "string",
									"const": "false"
								}
							]
						}
					},
					{
						"name": "filter",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "filterLogic",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string",
							"enum": [
								"and",
								"or"
							]
						}
					},
					{
						"name": "search",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "sort",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					}
				],
				"responses": {
					"200": {
						"description": "One page of items.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string",
														"description": "Unique item id."
													},
													"collectionId": {
														"type": "string",
														"description": "Id of the owning collection."
													},
													"slug": {
														"type": "string",
														"description": "URL-shaped identifier, unique among the collection’s active items. Lowercase letters, digits, and hyphens; max 256 characters."
													},
													"publishedAt": {
														"anyOf": [
															{
																"type": "string",
																"format": "date-time"
															},
															{
																"type": "null"
															}
														],
														"description": "`null` while the item is a draft or scheduled; the actual publish timestamp otherwise."
													},
													"publishAt": {
														"anyOf": [
															{
																"type": "string",
																"format": "date-time"
															},
															{
																"type": "null"
															}
														],
														"description": "Future publication time while the item is scheduled; `null` for drafts and published items."
													},
													"createdAt": {
														"description": "Creation timestamp.",
														"type": "string",
														"format": "date-time"
													},
													"updatedAt": {
														"description": "Last-modification timestamp.",
														"type": "string",
														"format": "date-time"
													},
													"position": {
														"type": "string",
														"description": "Fractional ordering key; the default list order sorts by this value."
													},
													"data": {
														"type": "object",
														"propertyNames": {
															"type": "string"
														},
														"additionalProperties": {},
														"description": "One entry per field of the collection, keyed by field key; optional fields with no value read as `null`. Scalar fields use plain JSON values: `date` as an ISO 8601 string, `color` as normalized hex, `option` as a choice id (or array of ids when multi-select). `reference` values are the referenced item id at depth 0 or an embedded item object (this same item shape) at depth ≥ 1; `multi_reference` values are ordered arrays of the same. `image` and `gallery` values resolve to image objects carrying `id`, delivery `url`, `originalUrl`, `srcSet`, `variants`, dimensions, `filename`, `mimeType`, `byteSize`, and `altText`; `file` values resolve to `{ id, url, filename, mimeType, byteSize }`. Unset asset fields read as `null` (or `[]` for galleries)."
													}
												},
												"required": [
													"id",
													"collectionId",
													"slug",
													"publishedAt",
													"publishAt",
													"createdAt",
													"updatedAt",
													"position",
													"data"
												],
												"additionalProperties": false
											}
										},
										"nextCursor": {
											"description": "Opaque cursor for the next page when paginating with `cursor`. `null` on the last page and when paginating with `page`.",
											"type": [
												"string",
												"null"
											]
										},
										"nextPage": {
											"anyOf": [
												{
													"type": "integer"
												},
												{
													"type": "null"
												}
											],
											"description": "Next page number when paginating with `page`. `null` on the last page and when paginating with `cursor`."
										}
									},
									"required": [
										"items",
										"nextCursor",
										"nextPage"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "cms.items.list",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			},
			"post": {
				"operationId": "cms.items.create",
				"summary": "Create an item",
				"description": "Creates an item at the end of the collection’s manual order. Scalar values go in `data` keyed by field key and are validated against the field types — unknown keys are rejected, required fields must be present, `plain_text` and `markdown` are capped at 1,048,576 bytes (markdown must not contain raw HTML), `date` values are ISO 8601 with timezone, and `color` values are normalized to hex. Relationship and asset values go in the side maps: `references` (`reference`: an item id; `multi_reference`: an ordered array of up to 1,000 ids), `images` (`image`: an asset id or `{ assetId, altText }`; `gallery`: an ordered array of up to 25 of those), and `files` (`file`: an asset id or `{ assetId }`). `slug` is optional — it is derived from `data.title` or `data.name` when omitted. Set a non-future `publishedAt` to create an already-published item, or a future `publishAt` to schedule it; the two fields are mutually exclusive.",
				"tags": [
					"CMS items"
				],
				"parameters": [
					{
						"name": "collectionId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": false,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"slug": {
										"type": "string",
										"pattern": "^[a-z0-9][a-z0-9-]{0,255}$"
									},
									"publishedAt": {
										"description": "Initial publication timestamp. Must not be in the future. Omit for a draft, or use `publishAt` to schedule publication.",
										"anyOf": [
											{},
											{
												"type": "null"
											}
										]
									},
									"publishAt": {
										"description": "Future absolute timestamp for scheduled publication. Cannot be combined with `publishedAt`.",
										"anyOf": [
											{},
											{
												"type": "null"
											}
										]
									},
									"data": {
										"default": {},
										"type": "object",
										"propertyNames": {
											"type": "string"
										},
										"additionalProperties": {}
									},
									"references": {
										"type": "object",
										"propertyNames": {
											"type": "string"
										},
										"additionalProperties": {
											"anyOf": [
												{
													"type": "string",
													"minLength": 1
												},
												{
													"maxItems": 1000,
													"type": "array",
													"items": {
														"type": "string",
														"minLength": 1
													}
												},
												{
													"type": "null"
												}
											]
										}
									},
									"images": {
										"type": "object",
										"propertyNames": {
											"type": "string"
										},
										"additionalProperties": {
											"anyOf": [
												{
													"anyOf": [
														{
															"type": "string",
															"minLength": 1
														},
														{
															"type": "object",
															"properties": {
																"assetId": {
																	"type": "string",
																	"minLength": 1
																},
																"altText": {
																	"anyOf": [
																		{
																			"type": "string",
																			"maxLength": 2000
																		},
																		{
																			"type": "null"
																		}
																	]
																}
															},
															"required": [
																"assetId"
															],
															"additionalProperties": false
														}
													]
												},
												{
													"maxItems": 25,
													"type": "array",
													"items": {
														"anyOf": [
															{
																"type": "string",
																"minLength": 1
															},
															{
																"type": "object",
																"properties": {
																	"assetId": {
																		"type": "string",
																		"minLength": 1
																	},
																	"altText": {
																		"anyOf": [
																			{
																				"type": "string",
																				"maxLength": 2000
																			},
																			{
																				"type": "null"
																			}
																		]
																	}
																},
																"required": [
																	"assetId"
																],
																"additionalProperties": false
															}
														]
													}
												},
												{
													"type": "null"
												}
											]
										}
									},
									"files": {
										"type": "object",
										"propertyNames": {
											"type": "string"
										},
										"additionalProperties": {
											"anyOf": [
												{
													"anyOf": [
														{
															"type": "string",
															"minLength": 1
														},
														{
															"type": "object",
															"properties": {
																"assetId": {
																	"type": "string",
																	"minLength": 1
																}
															},
															"required": [
																"assetId"
															]
														}
													]
												},
												{
													"type": "null"
												}
											]
										}
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Identity of the created item. Read the full item back with the get-item endpoint.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Id of the created item."
										},
										"slug": {
											"type": "string",
											"description": "Slug of the created item, derived from `data.title` or `data.name` when not provided."
										},
										"publishedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											],
											"description": "`null` when the item was created as a draft; the publish timestamp otherwise."
										},
										"publishAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											],
											"description": "Future publication time when the item was created as scheduled; `null` otherwise."
										}
									},
									"required": [
										"id",
										"slug",
										"publishedAt",
										"publishAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "cms.items.create",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/cms/items/find": {
			"get": {
				"operationId": "cms.items.get",
				"summary": "Get an item",
				"description": "Returns one item, looked up by `itemId` or by the `collectionId` + `slug` pair (provide exactly one of the two). The `data` object is complete and schema-shaped: every currently-defined field is present and missing optional values read as `null`. `depth` (0–3, default 0) embeds referenced items in place of their ids, one level per unit; referenced items that were deleted read as `null` (single reference) or are omitted from arrays (multi-reference). Set `published` to `\"true\"` or `\"false\"` to match only a published or only a draft item (a mismatch reads as not found). List items that reference it with `cms.items.backreferences.list`.",
				"tags": [
					"CMS items"
				],
				"parameters": [
					{
						"name": "itemId",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Item id. Provide either this or the `collectionId` + `slug` pair.",
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "collectionId",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Collection to look the item up in by slug. Pair with `slug`.",
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "slug",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Item slug within the collection. Pair with `collectionId`.",
							"type": "string",
							"pattern": "^[a-z0-9][a-z0-9-]{0,255}$"
						}
					},
					{
						"name": "depth",
						"in": "query",
						"required": false,
						"schema": {
							"default": 0,
							"type": "integer",
							"minimum": 0,
							"maximum": 3
						}
					},
					{
						"name": "published",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Only match a published (`\"true\"`) or draft (`\"false\"`) item; a mismatch reads as not found. Omit to match either.",
							"anyOf": [
								{
									"type": "string",
									"const": "true"
								},
								{
									"type": "string",
									"const": "false"
								}
							]
						}
					}
				],
				"responses": {
					"200": {
						"description": "The item, hydrated to the requested depth.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Unique item id."
										},
										"collectionId": {
											"type": "string",
											"description": "Id of the owning collection."
										},
										"slug": {
											"type": "string",
											"description": "URL-shaped identifier, unique among the collection’s active items. Lowercase letters, digits, and hyphens; max 256 characters."
										},
										"publishedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											],
											"description": "`null` while the item is a draft or scheduled; the actual publish timestamp otherwise."
										},
										"publishAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											],
											"description": "Future publication time while the item is scheduled; `null` for drafts and published items."
										},
										"createdAt": {
											"description": "Creation timestamp.",
											"type": "string",
											"format": "date-time"
										},
										"updatedAt": {
											"description": "Last-modification timestamp.",
											"type": "string",
											"format": "date-time"
										},
										"position": {
											"type": "string",
											"description": "Fractional ordering key; the default list order sorts by this value."
										},
										"data": {
											"type": "object",
											"propertyNames": {
												"type": "string"
											},
											"additionalProperties": {},
											"description": "One entry per field of the collection, keyed by field key; optional fields with no value read as `null`. Scalar fields use plain JSON values: `date` as an ISO 8601 string, `color` as normalized hex, `option` as a choice id (or array of ids when multi-select). `reference` values are the referenced item id at depth 0 or an embedded item object (this same item shape) at depth ≥ 1; `multi_reference` values are ordered arrays of the same. `image` and `gallery` values resolve to image objects carrying `id`, delivery `url`, `originalUrl`, `srcSet`, `variants`, dimensions, `filename`, `mimeType`, `byteSize`, and `altText`; `file` values resolve to `{ id, url, filename, mimeType, byteSize }`. Unset asset fields read as `null` (or `[]` for galleries)."
										}
									},
									"required": [
										"id",
										"collectionId",
										"slug",
										"publishedAt",
										"publishAt",
										"createdAt",
										"updatedAt",
										"position",
										"data"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "cms.items.get",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/cms/items/{itemId}/backreferences": {
			"get": {
				"operationId": "cms.items.backreferences.list",
				"summary": "List item backreferences",
				"description": "Lists active items that reference the target item through a `reference` or `multi_reference` field, newest first, with cursor pagination (default 20 per page, maximum 100). Each source item appears once even when multiple fields point to the target. `depth` (0–3, default 0) controls reference hydration in the returned source items.",
				"tags": [
					"CMS items"
				],
				"parameters": [
					{
						"name": "itemId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "cursor",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"default": 20,
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					},
					{
						"name": "depth",
						"in": "query",
						"required": false,
						"schema": {
							"default": 0,
							"type": "integer",
							"minimum": 0,
							"maximum": 3
						}
					}
				],
				"responses": {
					"200": {
						"description": "A page of active items that reference the target item.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string",
														"description": "Unique item id."
													},
													"collectionId": {
														"type": "string",
														"description": "Id of the owning collection."
													},
													"slug": {
														"type": "string",
														"description": "URL-shaped identifier, unique among the collection’s active items. Lowercase letters, digits, and hyphens; max 256 characters."
													},
													"publishedAt": {
														"anyOf": [
															{
																"type": "string",
																"format": "date-time"
															},
															{
																"type": "null"
															}
														],
														"description": "`null` while the item is a draft or scheduled; the actual publish timestamp otherwise."
													},
													"publishAt": {
														"anyOf": [
															{
																"type": "string",
																"format": "date-time"
															},
															{
																"type": "null"
															}
														],
														"description": "Future publication time while the item is scheduled; `null` for drafts and published items."
													},
													"createdAt": {
														"description": "Creation timestamp.",
														"type": "string",
														"format": "date-time"
													},
													"updatedAt": {
														"description": "Last-modification timestamp.",
														"type": "string",
														"format": "date-time"
													},
													"position": {
														"type": "string",
														"description": "Fractional ordering key; the default list order sorts by this value."
													},
													"data": {
														"type": "object",
														"propertyNames": {
															"type": "string"
														},
														"additionalProperties": {},
														"description": "One entry per field of the collection, keyed by field key; optional fields with no value read as `null`. Scalar fields use plain JSON values: `date` as an ISO 8601 string, `color` as normalized hex, `option` as a choice id (or array of ids when multi-select). `reference` values are the referenced item id at depth 0 or an embedded item object (this same item shape) at depth ≥ 1; `multi_reference` values are ordered arrays of the same. `image` and `gallery` values resolve to image objects carrying `id`, delivery `url`, `originalUrl`, `srcSet`, `variants`, dimensions, `filename`, `mimeType`, `byteSize`, and `altText`; `file` values resolve to `{ id, url, filename, mimeType, byteSize }`. Unset asset fields read as `null` (or `[]` for galleries)."
													}
												},
												"required": [
													"id",
													"collectionId",
													"slug",
													"publishedAt",
													"publishAt",
													"createdAt",
													"updatedAt",
													"position",
													"data"
												],
												"additionalProperties": false
											}
										},
										"nextCursor": {
											"description": "Opaque cursor for the next page. Pass it as the `cursor` parameter on the next request. `null` when this is the last page.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"items",
										"nextCursor"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "cms.items.get",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/cms/items/{itemId}": {
			"patch": {
				"operationId": "cms.items.update",
				"summary": "Update an item",
				"description": "Patches an item; omitted fields are left untouched. Keys in `data` are merged into the existing values and validated the same way as on create, while each key provided in `references`, `images`, or `files` replaces that field’s value wholesale — pass `null` (or `[]` for multi-valued fields) to clear an optional field. `slug` renames the item’s slug; slug and unique-field collisions fail with a conflict. Pass `position: \"first\"` or `\"last\"`, or `\"before\"` / `\"after\"` together with `anchorItemId`, to move the item within the collection’s manual order — the default sort for item lists.",
				"tags": [
					"CMS items"
				],
				"parameters": [
					{
						"name": "itemId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": false,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"slug": {
										"description": "New slug for the item — a rename. Omit to keep the current slug. Must be unique among the collection’s active items.",
										"type": "string",
										"pattern": "^[a-z0-9][a-z0-9-]{0,255}$"
									},
									"data": {
										"type": "object",
										"propertyNames": {
											"type": "string"
										},
										"additionalProperties": {}
									},
									"references": {
										"type": "object",
										"propertyNames": {
											"type": "string"
										},
										"additionalProperties": {
											"anyOf": [
												{
													"type": "string",
													"minLength": 1
												},
												{
													"maxItems": 1000,
													"type": "array",
													"items": {
														"type": "string",
														"minLength": 1
													}
												},
												{
													"type": "null"
												}
											]
										}
									},
									"images": {
										"type": "object",
										"propertyNames": {
											"type": "string"
										},
										"additionalProperties": {
											"anyOf": [
												{
													"anyOf": [
														{
															"type": "string",
															"minLength": 1
														},
														{
															"type": "object",
															"properties": {
																"assetId": {
																	"type": "string",
																	"minLength": 1
																},
																"altText": {
																	"anyOf": [
																		{
																			"type": "string",
																			"maxLength": 2000
																		},
																		{
																			"type": "null"
																		}
																	]
																}
															},
															"required": [
																"assetId"
															],
															"additionalProperties": false
														}
													]
												},
												{
													"maxItems": 25,
													"type": "array",
													"items": {
														"anyOf": [
															{
																"type": "string",
																"minLength": 1
															},
															{
																"type": "object",
																"properties": {
																	"assetId": {
																		"type": "string",
																		"minLength": 1
																	},
																	"altText": {
																		"anyOf": [
																			{
																				"type": "string",
																				"maxLength": 2000
																			},
																			{
																				"type": "null"
																			}
																		]
																	}
																},
																"required": [
																	"assetId"
																],
																"additionalProperties": false
															}
														]
													}
												},
												{
													"type": "null"
												}
											]
										}
									},
									"files": {
										"type": "object",
										"propertyNames": {
											"type": "string"
										},
										"additionalProperties": {
											"anyOf": [
												{
													"anyOf": [
														{
															"type": "string",
															"minLength": 1
														},
														{
															"type": "object",
															"properties": {
																"assetId": {
																	"type": "string",
																	"minLength": 1
																}
															},
															"required": [
																"assetId"
															]
														}
													]
												},
												{
													"type": "null"
												}
											]
										}
									},
									"position": {
										"description": "Moves the item within the collection’s manual order — the default sort for item lists. Omit to leave the order unchanged. `\"before\"` / `\"after\"` require `anchorItemId`.",
										"type": "string",
										"enum": [
											"before",
											"after",
											"first",
											"last"
										]
									},
									"anchorItemId": {
										"description": "Sibling item to move against when `position` is `\"before\"` or `\"after\"`.",
										"type": "string",
										"minLength": 1
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Identity and ordering key of the updated item. Read the full item back with the get-item endpoint.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Id of the updated item."
										},
										"slug": {
											"type": "string",
											"description": "Slug of the item after the update."
										},
										"position": {
											"type": "string",
											"description": "The item’s fractional ordering key after the update; changes when `position` is used. The default list order sorts by this value."
										}
									},
									"required": [
										"id",
										"slug",
										"position"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"409": {
						"description": "The request conflicts with the current state of the resource.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "cms.items.update",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			},
			"delete": {
				"operationId": "cms.items.delete",
				"summary": "Delete an item",
				"description": "Soft-deletes the item: it disappears from reads, list results, and backreferences, and fields referencing it read `null` (single reference) or shrink (multi-reference). Its slug becomes reusable, and incoming references are preserved so the item can be restored.",
				"tags": [
					"CMS items"
				],
				"parameters": [
					{
						"name": "itemId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Deletion acknowledgement.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"deleted": {
											"type": "boolean",
											"const": true,
											"description": "Always `true` once the item is soft-deleted."
										}
									},
									"required": [
										"deleted"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "cms.items.delete",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/cms/items/{itemId}/set-published": {
			"post": {
				"operationId": "cms.items.setPublished",
				"summary": "Publish, schedule, or unpublish an item",
				"description": "Use exactly one publication action: `published: true` publishes immediately and sets `publishedAt` to now; a future `publishAt` schedules a draft for automatic publication; `published: false` unpublishes an item or cancels its schedule. A published item must be unpublished before it can be scheduled because CMS items do not have revision snapshots. Published websites read only items with an actual `publishedAt`, so immediate and scheduled promotion become visible without a site republish. Repeating the same action is idempotent.",
				"tags": [
					"CMS items"
				],
				"parameters": [
					{
						"name": "itemId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": false,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"published": {
										"description": "`true` publishes immediately; `false` unpublishes or cancels a schedule. Use `publishAt` instead to schedule publication.",
										"type": "boolean"
									},
									"publishAt": {
										"description": "Future absolute timestamp for scheduling. Omit `published`; scheduled items remain hidden until the promotion job runs."
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The item id plus its actual `publishedAt` and pending `publishAt` after the change.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Id of the item."
										},
										"publishedAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											],
											"description": "Actual publish timestamp after the change. Set when published immediately or by the scheduler; `null` for drafts and scheduled items."
										},
										"publishAt": {
											"anyOf": [
												{
													"type": "string",
													"format": "date-time"
												},
												{
													"type": "null"
												}
											],
											"description": "Future publication time after the change; `null` for drafts and published items."
										}
									},
									"required": [
										"id",
										"publishedAt",
										"publishAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "cms.items.setPublished",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/cms/items/{itemId}/restore": {
			"post": {
				"operationId": "cms.items.restore",
				"summary": "Restore a deleted item",
				"description": "Restores a soft-deleted item, making it readable again and re-exposing references to and from it. Returns 404 when the item is not currently deleted.",
				"tags": [
					"CMS items"
				],
				"parameters": [
					{
						"name": "itemId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Restore acknowledgement.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"restored": {
											"type": "boolean",
											"const": true,
											"description": "Always `true` once the item is live again."
										}
									},
									"required": [
										"restored"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "cms.items.restore",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		},
		"/websites/{websiteId}/analytics/query": {
			"get": {
				"operationId": "websiteAnalytics.query.get",
				"summary": "Query analytics totals and breakdowns",
				"description": "Returns aggregate engagement totals — page views, distinct visitors, visits, bounces, and total time in seconds — for a date range, optionally broken down by one dimension. Use it to answer \"how much traffic did the site get\" and \"what are the top pages/referrers/countries\" style questions; use the timeseries endpoint when you need values over time instead. `startAt` and `endAt` are Unix epoch milliseconds; optional filter parameters (path, referrer, country, event, UTM fields, ...) narrow the data. Without `dimension` the response carries the totals alone. With `dimension` (e.g. `path` for top pages, `referrer` for top referrers, `country` for top countries) the response also carries `rows`: up to `limit` (default 25, max 1000) values of that dimension ranked by page views, each with its own engagement totals. Requires a plan that includes analytics (403 otherwise); all-zero totals until the website is published and has traffic.",
				"tags": [
					"Analytics"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "startAt",
						"in": "query",
						"required": true,
						"schema": {
							"type": "integer",
							"minimum": 0,
							"maximum": 9007199254740991
						}
					},
					{
						"name": "endAt",
						"in": "query",
						"required": true,
						"schema": {
							"type": "integer",
							"minimum": 0,
							"maximum": 9007199254740991
						}
					},
					{
						"name": "path",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "referrer",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "title",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "query",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "browser",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "os",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "device",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "country",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "region",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "city",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "language",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "hostname",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "tag",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "event",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "distinctId",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "utmSource",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "utmMedium",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "utmCampaign",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "utmContent",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "utmTerm",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "segment",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string",
							"format": "uuid",
							"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
						}
					},
					{
						"name": "cohort",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string",
							"format": "uuid",
							"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
						}
					},
					{
						"name": "dimension",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Break the range down by this dimension, e.g. `path` for top pages, `referrer` for top referrers, `country` for top countries. Omit for totals only.",
							"type": "string",
							"enum": [
								"path",
								"entry",
								"exit",
								"title",
								"query",
								"referrer",
								"channel",
								"domain",
								"country",
								"region",
								"city",
								"browser",
								"os",
								"device",
								"language",
								"screen",
								"event",
								"hostname",
								"tag",
								"distinctId"
							]
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"default": 25,
							"description": "Maximum number of dimension rows to return (default 25, max 1000). Only used when `dimension` is set.",
							"type": "integer",
							"minimum": 1,
							"maximum": 1000
						}
					}
				],
				"responses": {
					"200": {
						"description": "Engagement totals for the range, plus ranked `rows` when a `dimension` was requested.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"pageviews": {
											"type": "number",
											"description": "Total page views."
										},
										"visitors": {
											"type": "number",
											"description": "Distinct visitors."
										},
										"visits": {
											"type": "number",
											"description": "Visits (sessions)."
										},
										"bounces": {
											"type": "number",
											"description": "Single-page visits."
										},
										"totaltime": {
											"type": "number",
											"description": "Total time spent in seconds, summed across visits."
										},
										"comparison": {
											"description": "Totals for the comparison period, present when the provider computes one.",
											"type": "object",
											"properties": {
												"pageviews": {
													"type": "number",
													"description": "Total page views."
												},
												"visitors": {
													"type": "number",
													"description": "Distinct visitors."
												},
												"visits": {
													"type": "number",
													"description": "Visits (sessions)."
												},
												"bounces": {
													"type": "number",
													"description": "Single-page visits."
												},
												"totaltime": {
													"type": "number",
													"description": "Total time spent in seconds, summed across visits."
												}
											},
											"required": [
												"pageviews",
												"visitors",
												"visits",
												"bounces",
												"totaltime"
											],
											"additionalProperties": false
										},
										"rows": {
											"description": "Values of the requested `dimension` ranked by page views, each with its own engagement totals. Present only when `dimension` is set.",
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"name": {
														"type": "string",
														"description": "Dimension value, e.g. the URL path, referrer domain, or country code."
													},
													"pageviews": {
														"type": "number",
														"description": "Total page views."
													},
													"visitors": {
														"type": "number",
														"description": "Distinct visitors."
													},
													"visits": {
														"type": "number",
														"description": "Visits (sessions)."
													},
													"bounces": {
														"type": "number",
														"description": "Single-page visits."
													},
													"totaltime": {
														"type": "number",
														"description": "Total time spent in seconds, summed across visits."
													}
												},
												"required": [
													"name",
													"pageviews",
													"visitors",
													"visits",
													"bounces",
													"totaltime"
												],
												"additionalProperties": false
											}
										}
									},
									"required": [
										"pageviews",
										"visitors",
										"visits",
										"bounces",
										"totaltime"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteAnalytics.view",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websites/{websiteId}/analytics/timeseries": {
			"get": {
				"operationId": "websiteAnalytics.timeseries.get",
				"summary": "Get an analytics time series",
				"description": "Returns one traffic time series bucketed by `unit` (`year`, `month`, `day`, `hour`, or `minute`). `series` selects which: `pageviews` returns parallel `pageviews` and `sessions` arrays with one point per time bucket (and accepts `compare` — `prev` or `yoy` — to request comparison data for the previous period or year), while `events` returns tracked custom-event counts as one `events` point per event name per time bucket. Only the requested series' fields are present in the response. `startAt` and `endAt` are Unix epoch milliseconds; `timezone` is an IANA name (e.g. `America/New_York`) that controls bucket boundaries; optional filter parameters narrow the data. The series is capped to the most recent `maxPoints` time buckets (default 30, max 500); a `truncated` field reports when the cap was applied. Requires a plan that includes analytics (403 otherwise); empty until the website is published and has traffic.",
				"tags": [
					"Analytics"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "startAt",
						"in": "query",
						"required": true,
						"schema": {
							"type": "integer",
							"minimum": 0,
							"maximum": 9007199254740991
						}
					},
					{
						"name": "endAt",
						"in": "query",
						"required": true,
						"schema": {
							"type": "integer",
							"minimum": 0,
							"maximum": 9007199254740991
						}
					},
					{
						"name": "path",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "referrer",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "title",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "query",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "browser",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "os",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "device",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "country",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "region",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "city",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "language",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "hostname",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "tag",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "event",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "distinctId",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "utmSource",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "utmMedium",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "utmCampaign",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "utmContent",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "utmTerm",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "segment",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string",
							"format": "uuid",
							"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
						}
					},
					{
						"name": "cohort",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string",
							"format": "uuid",
							"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
						}
					},
					{
						"name": "unit",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string",
							"enum": [
								"year",
								"month",
								"day",
								"hour",
								"minute"
							]
						}
					},
					{
						"name": "timezone",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "maxPoints",
						"in": "query",
						"required": false,
						"schema": {
							"default": 30,
							"type": "integer",
							"minimum": 1,
							"maximum": 500
						}
					},
					{
						"name": "series",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string",
							"enum": [
								"pageviews",
								"events"
							],
							"description": "Which time series to return: `pageviews` for page views and sessions per bucket, `events` for tracked custom-event counts per event name per bucket."
						}
					},
					{
						"name": "compare",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Request comparison data alongside the primary series: `prev` for the previous period, `yoy` for the same range one year earlier. Only valid when `series` is `pageviews`.",
							"type": "string",
							"enum": [
								"prev",
								"yoy"
							]
						}
					}
				],
				"responses": {
					"200": {
						"description": "The requested time series: `pageviews` and `sessions` per bucket, or `events` per event name per bucket.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"pageviews": {
											"description": "Page views per time bucket. Present only when `series` is `pageviews`.",
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"x": {
														"type": "string",
														"description": "Start of the time bucket."
													},
													"y": {
														"type": "number",
														"description": "Count in the bucket."
													}
												},
												"required": [
													"x",
													"y"
												],
												"additionalProperties": false
											}
										},
										"sessions": {
											"description": "Distinct sessions per time bucket. Present only when `series` is `pageviews`.",
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"x": {
														"type": "string",
														"description": "Start of the time bucket."
													},
													"y": {
														"type": "number",
														"description": "Count in the bucket."
													}
												},
												"required": [
													"x",
													"y"
												],
												"additionalProperties": false
											}
										},
										"events": {
											"description": "One point per event name per time bucket. Present only when `series` is `events`.",
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"x": {
														"type": "string",
														"description": "Event name."
													},
													"t": {
														"type": "string",
														"description": "Start of the time bucket."
													},
													"y": {
														"type": "number",
														"description": "Number of events in the bucket."
													}
												},
												"required": [
													"x",
													"t",
													"y"
												],
												"additionalProperties": false
											}
										},
										"truncated": {
											"description": "Present only when the series exceeded `maxPoints` and was capped to the most recent time buckets.",
											"type": "object",
											"properties": {
												"shown": {
													"type": "integer",
													"description": "Time buckets returned."
												},
												"total": {
													"type": "integer",
													"description": "Time buckets the range produced."
												},
												"hint": {
													"type": "string",
													"description": "How to retrieve the rest."
												}
											},
											"required": [
												"shown",
												"total",
												"hint"
											],
											"additionalProperties": false
										}
									},
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteAnalytics.view",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websites/{websiteId}/analytics/realtime": {
			"get": {
				"operationId": "websiteAnalytics.realtime.get",
				"summary": "Get a realtime traffic snapshot",
				"description": "Returns a compact snapshot of traffic over Umami's trailing 30-minute realtime window: total page views, distinct visitors, tracked custom events, and distinct countries, plus the snapshot timestamp. The visitor total is not directly comparable to the removed `active` endpoint's five-minute count. Use it to see recent activity; use the query endpoint for historical ranges. Requires a plan that includes analytics (403 otherwise); all-zero totals until the website is published and has traffic.",
				"tags": [
					"Analytics"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Traffic totals for the trailing 30-minute window.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"views": {
											"type": "number",
											"description": "Page views in the trailing 30-minute window."
										},
										"visitors": {
											"type": "number",
											"description": "Distinct visitors in the trailing 30-minute window."
										},
										"events": {
											"type": "number",
											"description": "Tracked custom events in the trailing 30-minute window."
										},
										"countries": {
											"type": "number",
											"description": "Distinct countries in the trailing 30-minute window."
										},
										"timestamp": {
											"type": "string",
											"description": "Snapshot time as an ISO 8601 timestamp."
										}
									},
									"required": [
										"views",
										"visitors",
										"events",
										"countries",
										"timestamp"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteAnalytics.view",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websites/{websiteId}/forms": {
			"get": {
				"operationId": "websiteForms.list",
				"summary": "List forms",
				"description": "Returns every form with inbox and spam counts, whether the published site still references it, who it notifies, the number of inbox submissions since the caller last opened the Forms tab, and monthly usage against the Free plan limit (`limit` is null on paid plans). Forms come from `<form method=\"post\" action=\"/_cactal/forms/<name>\">` in the website source and appear here as soon as the draft or published build contains them, before any submission.",
				"tags": [
					"Forms"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Forms, unseen count, and monthly usage.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"forms": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string"
													},
													"name": {
														"type": "string"
													},
													"displayName": {
														"type": "string"
													},
													"recipients": {
														"type": "array",
														"items": {
															"type": "object",
															"properties": {
																"id": {
																	"type": "string"
																},
																"email": {
																	"type": "string"
																},
																"status": {
																	"type": "string",
																	"enum": [
																		"member",
																		"pending",
																		"confirmed",
																		"failing",
																		"stopped"
																	]
																},
																"createdAt": {
																	"type": "string"
																}
															},
															"required": [
																"id",
																"email",
																"status",
																"createdAt"
															],
															"additionalProperties": false
														},
														"description": "Who is emailed for this form. New forms start with the organization owners."
													},
													"onSite": {
														"type": "boolean",
														"description": "Whether the published site still references this form."
													},
													"inboxCount": {
														"type": "integer"
													},
													"spamCount": {
														"type": "integer"
													},
													"lastSubmissionAt": {
														"type": [
															"string",
															"null"
														]
													}
												},
												"required": [
													"id",
													"name",
													"displayName",
													"recipients",
													"onSite",
													"inboxCount",
													"spamCount",
													"lastSubmissionAt"
												],
												"additionalProperties": false
											}
										},
										"unseenCount": {
											"type": "integer",
											"description": "Published-site inbox submissions since the caller last opened Forms; test submissions never count."
										},
										"usage": {
											"type": "object",
											"properties": {
												"used": {
													"type": "integer",
													"description": "Accepted published submissions this month."
												},
												"limit": {
													"anyOf": [
														{
															"type": "integer"
														},
														{
															"type": "null"
														}
													],
													"description": "Monthly limit on the Free plan; null when unlimited."
												}
											},
											"required": [
												"used",
												"limit"
											],
											"additionalProperties": false
										}
									},
									"required": [
										"forms",
										"unseenCount",
										"usage"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteForms.view",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websites/{websiteId}/forms/submissions": {
			"get": {
				"operationId": "websiteForms.submissions.list",
				"summary": "List form submissions",
				"description": "Returns submissions newest first as a cursor-paginated page. Filter by `form` name, `status` (`inbox` by default, or `spam`), and `source` (`published` for real visitors, `draft` for test submissions from the draft preview). Items carry the fields in submitted order, the inferred submitter email and name, page, spam signals, classifier verdict, and notification state. Submission data is personal information: never render it on the public website.",
				"tags": [
					"Forms"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "form",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Only submissions of this form name.",
							"type": "string"
						}
					},
					{
						"name": "status",
						"in": "query",
						"required": false,
						"schema": {
							"description": "`inbox` (default) or `spam`.",
							"type": "string",
							"enum": [
								"inbox",
								"spam"
							]
						}
					},
					{
						"name": "source",
						"in": "query",
						"required": false,
						"schema": {
							"description": "`published` for real visitors, `draft` for test submissions from the draft preview.",
							"type": "string",
							"enum": [
								"published",
								"draft"
							]
						}
					},
					{
						"name": "cursor",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"default": 20,
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					}
				],
				"responses": {
					"200": {
						"description": "A page of submissions and the next cursor.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string"
													},
													"formName": {
														"type": "string"
													},
													"status": {
														"type": "string",
														"enum": [
															"inbox",
															"spam"
														]
													},
													"source": {
														"type": "string",
														"enum": [
															"published",
															"draft"
														]
													},
													"data": {
														"type": "array",
														"items": {
															"type": "object",
															"properties": {
																"name": {
																	"type": "string"
																},
																"value": {
																	"anyOf": [
																		{
																			"type": "string"
																		},
																		{
																			"type": "array",
																			"items": {
																				"type": "string"
																			}
																		}
																	]
																}
															},
															"required": [
																"name",
																"value"
															],
															"additionalProperties": false
														},
														"description": "Fields in submitted order."
													},
													"submitterEmail": {
														"type": [
															"string",
															"null"
														]
													},
													"submitterName": {
														"type": [
															"string",
															"null"
														]
													},
													"pagePath": {
														"type": [
															"string",
															"null"
														]
													},
													"country": {
														"type": [
															"string",
															"null"
														]
													},
													"flags": {
														"type": "array",
														"items": {
															"type": "string"
														}
													},
													"classification": {
														"anyOf": [
															{
																"type": "object",
																"properties": {
																	"verdict": {
																		"type": "string",
																		"enum": [
																			"lead",
																			"spam"
																		]
																	},
																	"confidence": {
																		"type": "number"
																	},
																	"model": {
																		"type": "string"
																	},
																	"at": {
																		"type": "string"
																	}
																},
																"required": [
																	"verdict",
																	"confidence",
																	"model",
																	"at"
																],
																"additionalProperties": false
															},
															{
																"type": "null"
															}
														]
													},
													"notifiedAt": {
														"type": [
															"string",
															"null"
														]
													},
													"notificationError": {
														"type": [
															"string",
															"null"
														]
													},
													"createdAt": {
														"type": "string"
													}
												},
												"required": [
													"id",
													"formName",
													"status",
													"source",
													"data",
													"submitterEmail",
													"submitterName",
													"pagePath",
													"country",
													"flags",
													"classification",
													"notifiedAt",
													"notificationError",
													"createdAt"
												],
												"additionalProperties": false
											}
										},
										"nextCursor": {
											"description": "Opaque cursor for the next page. Pass it as the `cursor` parameter on the next request. `null` when this is the last page.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"items",
										"nextCursor"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteForms.view",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websites/{websiteId}/forms/submissions/{submissionId}": {
			"get": {
				"operationId": "websiteForms.submissions.get",
				"summary": "Get a form submission",
				"description": "Returns one submission with its fields in submitted order.",
				"tags": [
					"Forms"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "submissionId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "The submission.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string"
										},
										"formName": {
											"type": "string"
										},
										"status": {
											"type": "string",
											"enum": [
												"inbox",
												"spam"
											]
										},
										"source": {
											"type": "string",
											"enum": [
												"published",
												"draft"
											]
										},
										"data": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"name": {
														"type": "string"
													},
													"value": {
														"anyOf": [
															{
																"type": "string"
															},
															{
																"type": "array",
																"items": {
																	"type": "string"
																}
															}
														]
													}
												},
												"required": [
													"name",
													"value"
												],
												"additionalProperties": false
											},
											"description": "Fields in submitted order."
										},
										"submitterEmail": {
											"type": [
												"string",
												"null"
											]
										},
										"submitterName": {
											"type": [
												"string",
												"null"
											]
										},
										"pagePath": {
											"type": [
												"string",
												"null"
											]
										},
										"country": {
											"type": [
												"string",
												"null"
											]
										},
										"flags": {
											"type": "array",
											"items": {
												"type": "string"
											}
										},
										"classification": {
											"anyOf": [
												{
													"type": "object",
													"properties": {
														"verdict": {
															"type": "string",
															"enum": [
																"lead",
																"spam"
															]
														},
														"confidence": {
															"type": "number"
														},
														"model": {
															"type": "string"
														},
														"at": {
															"type": "string"
														}
													},
													"required": [
														"verdict",
														"confidence",
														"model",
														"at"
													],
													"additionalProperties": false
												},
												{
													"type": "null"
												}
											]
										},
										"notifiedAt": {
											"type": [
												"string",
												"null"
											]
										},
										"notificationError": {
											"type": [
												"string",
												"null"
											]
										},
										"createdAt": {
											"type": "string"
										}
									},
									"required": [
										"id",
										"formName",
										"status",
										"source",
										"data",
										"submitterEmail",
										"submitterName",
										"pagePath",
										"country",
										"flags",
										"classification",
										"notifiedAt",
										"notificationError",
										"createdAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteForms.view",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			},
			"patch": {
				"operationId": "websiteForms.submissions.setStatus",
				"summary": "Mark a submission as spam or not spam",
				"description": "Moves a submission between the inbox and the spam folder. Restoring to `inbox` sends the notification email if it was never sent. Spam is deleted automatically after 30 days.",
				"tags": [
					"Forms"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "submissionId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"status": {
										"type": "string",
										"enum": [
											"inbox",
											"spam"
										],
										"description": "`spam` hides the submission in the spam folder; `inbox` restores it and sends any missed notification."
									}
								},
								"required": [
									"status"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The updated submission.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string"
										},
										"formName": {
											"type": "string"
										},
										"status": {
											"type": "string",
											"enum": [
												"inbox",
												"spam"
											]
										},
										"source": {
											"type": "string",
											"enum": [
												"published",
												"draft"
											]
										},
										"data": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"name": {
														"type": "string"
													},
													"value": {
														"anyOf": [
															{
																"type": "string"
															},
															{
																"type": "array",
																"items": {
																	"type": "string"
																}
															}
														]
													}
												},
												"required": [
													"name",
													"value"
												],
												"additionalProperties": false
											},
											"description": "Fields in submitted order."
										},
										"submitterEmail": {
											"type": [
												"string",
												"null"
											]
										},
										"submitterName": {
											"type": [
												"string",
												"null"
											]
										},
										"pagePath": {
											"type": [
												"string",
												"null"
											]
										},
										"country": {
											"type": [
												"string",
												"null"
											]
										},
										"flags": {
											"type": "array",
											"items": {
												"type": "string"
											}
										},
										"classification": {
											"anyOf": [
												{
													"type": "object",
													"properties": {
														"verdict": {
															"type": "string",
															"enum": [
																"lead",
																"spam"
															]
														},
														"confidence": {
															"type": "number"
														},
														"model": {
															"type": "string"
														},
														"at": {
															"type": "string"
														}
													},
													"required": [
														"verdict",
														"confidence",
														"model",
														"at"
													],
													"additionalProperties": false
												},
												{
													"type": "null"
												}
											]
										},
										"notifiedAt": {
											"type": [
												"string",
												"null"
											]
										},
										"notificationError": {
											"type": [
												"string",
												"null"
											]
										},
										"createdAt": {
											"type": "string"
										}
									},
									"required": [
										"id",
										"formName",
										"status",
										"source",
										"data",
										"submitterEmail",
										"submitterName",
										"pagePath",
										"country",
										"flags",
										"classification",
										"notifiedAt",
										"notificationError",
										"createdAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteForms.view",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			},
			"delete": {
				"operationId": "websiteForms.submissions.delete",
				"summary": "Delete a form submission",
				"description": "Permanently deletes one submission.",
				"tags": [
					"Forms"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "submissionId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Confirmation.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"deleted": {
											"type": "boolean"
										}
									},
									"required": [
										"deleted"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteForms.view",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": true
			}
		},
		"/websites/{websiteId}/forms/{formName}/recipients": {
			"post": {
				"operationId": "websiteForms.recipients.add",
				"summary": "Add a notification recipient to a form",
				"description": "Adds an email address to one form, identified by its name from the action path. New forms notify the organization owners until changed. Organization members and website editors receive submissions immediately; other addresses get a confirmation email (link valid 7 days). `member` and `confirmed` addresses receive email; `pending` addresses must confirm by link; `failing` addresses bounced or complained; `stopped` addresses opted out. Works before the first submission; at most 20 addresses per website.",
				"tags": [
					"Forms"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "formName",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"description": "The form name from the action path."
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"email": {
										"type": "string",
										"maxLength": 320,
										"format": "email",
										"pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
										"description": "Address to notify."
									}
								},
								"required": [
									"email"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The recipient.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string"
										},
										"email": {
											"type": "string"
										},
										"status": {
											"type": "string",
											"enum": [
												"member",
												"pending",
												"confirmed",
												"failing",
												"stopped"
											]
										},
										"createdAt": {
											"type": "string"
										}
									},
									"required": [
										"id",
										"email",
										"status",
										"createdAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteForms.manage",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websites/{websiteId}/forms/{formName}/recipients/{recipientId}": {
			"delete": {
				"operationId": "websiteForms.recipients.remove",
				"summary": "Remove a notification recipient from a form",
				"description": "Stops emailing an address for one form. A form with no recipients emails nobody; its submissions still reach the Forms inbox.",
				"tags": [
					"Forms"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "formName",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "recipientId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Confirmation.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"removed": {
											"type": "boolean"
										}
									},
									"required": [
										"removed"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteForms.manage",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": true
			}
		},
		"/websites/{websiteId}/forms/recipients/{recipientId}/resend": {
			"post": {
				"operationId": "websiteForms.recipients.resendConfirmation",
				"summary": "Resend a recipient confirmation",
				"description": "Issues a fresh confirmation link to a pending, failing, or stopped address.",
				"tags": [
					"Forms"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "recipientId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "The recipient.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string"
										},
										"email": {
											"type": "string"
										},
										"status": {
											"type": "string",
											"enum": [
												"member",
												"pending",
												"confirmed",
												"failing",
												"stopped"
											]
										},
										"createdAt": {
											"type": "string"
										}
									},
									"required": [
										"id",
										"email",
										"status",
										"createdAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteForms.manage",
				"x-cactal-read-only": false,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": false
			}
		},
		"/docs/search": {
			"get": {
				"operationId": "docs.search",
				"summary": "Search the Cactal documentation",
				"description": "Search the Cactal product documentation: concepts, guides, agent workflows, platform limits, and the API reference. Returns matching Markdown excerpts with page paths; read a full page with the docs read operation. Consult the docs before non-trivial or unfamiliar work such as source-code editing, publishing, or CMS schema changes.",
				"tags": [
					"Documentation"
				],
				"parameters": [
					{
						"name": "query",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1,
							"maxLength": 500,
							"description": "Plain-language question or keywords, for example \"how does publishing work\"."
						}
					}
				],
				"responses": {
					"200": {
						"description": "Matching documentation excerpts in Markdown.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"content": {
											"type": "string",
											"description": "Up to 5 matching documentation excerpts formatted as Markdown, each naming the page path to pass to the docs read operation for the full page."
										}
									},
									"required": [
										"content"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": null,
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/docs/read": {
			"get": {
				"operationId": "docs.read",
				"summary": "Read a Cactal documentation page",
				"description": "Read one Cactal documentation page as Markdown. Pass a page path from docs search results, for example \"concepts/publishing\" or \"agents/agent-safe-publishing\". Pass \"llms.txt\" for an index of every page with titles and descriptions.",
				"tags": [
					"Documentation"
				],
				"parameters": [
					{
						"name": "path",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1,
							"maxLength": 500,
							"description": "Docs page path, for example \"concepts/publishing\". Pass \"llms.txt\" for the index of every page."
						}
					}
				],
				"responses": {
					"200": {
						"description": "The requested documentation page in Markdown.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"content": {
											"type": "string",
											"description": "The documentation page formatted as Markdown."
										}
									},
									"required": [
										"content"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": null,
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/skills/{skillId}": {
			"get": {
				"operationId": "skills.read",
				"summary": "Read a Cactal agent skill",
				"description": "Read the matching Cactal skill before acting: source-editing-workflow before source edits, design-direction for design, copywriting for words. Required skills are included.\n\n- analytics: Traffic, visitors, referrers, and other analytics reports\n- assets-and-media: Generate, import, or place images, video, fonts, favicons\n- cms-content-editing: Create, edit, publish, schedule, or delete CMS items\n- cms-data-in-pages: Show CMS content on pages with definePage queries\n- cms-item-queries: Filter, search, and sort CMS item lists\n- cms-modeling: Create or change CMS collections and fields\n- copywriting: Write or rewrite website copy\n- design-direction: New visual design or restyling of palette, type, and layout\n- forms: Forms, submissions, and notification recipients\n- image-reforming: Turn found or imperfect photos into better images\n- imports-and-modules: Imports, components, and shared modules\n- logo-design: Design or redraw a logo, brand mark, icon, or favicon\n- npm-packages: Add, upgrade, or debug npm packages\n- page-metadata-and-seo: Page metadata, SEO, social cards, and sitemaps\n- pages-and-routes: Create pages, routes, URL params, and a custom 404\n- publishing-and-recovery: Publish, roll back, restore, or unpublish the website\n- rendering-and-browser-apis: Browser-only APIs, Intl, and server render failures\n- server-functions: Server functions and website secrets\n- shaders: Build GPU effects with Paper Shaders or custom GLSL\n- source-editing-workflow: Read before any page, component, CSS, or config edit\n- source-files-and-naming: Create, delete, or name source files\n- styling-and-css: Global CSS, Tailwind theme, and fonts\n- validate-and-preview: Checks, diagnostics, screenshots, and deployments\n- web-research: Public web facts and studying reference sites\n- website-config: Redirects, response headers, and website_config\n- website-settings-and-domains: Rename the website or slug, hosts, custom domains, DNS",
				"tags": [
					"Documentation"
				],
				"parameters": [
					{
						"name": "skillId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"enum": [
								"analytics",
								"assets-and-media",
								"cms-content-editing",
								"cms-data-in-pages",
								"cms-item-queries",
								"cms-modeling",
								"copywriting",
								"design-direction",
								"forms",
								"image-reforming",
								"imports-and-modules",
								"logo-design",
								"npm-packages",
								"page-metadata-and-seo",
								"pages-and-routes",
								"publishing-and-recovery",
								"rendering-and-browser-apis",
								"server-functions",
								"shaders",
								"source-editing-workflow",
								"source-files-and-naming",
								"styling-and-css",
								"validate-and-preview",
								"web-research",
								"website-config",
								"website-settings-and-domains"
							],
							"description": "Skill to read; the skills it requires come with it."
						}
					}
				],
				"responses": {
					"200": {
						"description": "The skill and the skills it requires, formatted as Markdown.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"content": {
											"type": "string",
											"description": "The skill and every skill it requires, dependencies first, as Markdown."
										}
									},
									"required": [
										"content"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": null,
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/profile": {
			"get": {
				"operationId": "profile.get",
				"summary": "Get your profile",
				"description": "Returns who this credential acts for (a stable `id`, plus `name` and `email` for a person) and the organizations it can work in. An organization lists `websites` only when access is limited to those websites. Use an organization `id` to list or create websites.",
				"tags": [
					"Organizations"
				],
				"responses": {
					"200": {
						"description": "The caller and the organizations it can work in.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string",
											"description": "Stable id of the person or credential calling Cactal."
										},
										"name": {
											"type": "string"
										},
										"email": {
											"type": "string"
										},
										"organizations": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string"
													},
													"name": {
														"type": "string"
													},
													"websites": {
														"description": "The only websites this caller can use here; absent when it can use every website in the organization.",
														"type": "array",
														"items": {
															"type": "object",
															"properties": {
																"id": {
																	"type": "string"
																},
																"name": {
																	"type": "string"
																}
															},
															"required": [
																"id",
																"name"
															],
															"additionalProperties": false
														}
													}
												},
												"required": [
													"id",
													"name"
												],
												"additionalProperties": false
											}
										}
									},
									"required": [
										"id",
										"organizations"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": null,
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/webResearch/screenshot": {
			"post": {
				"operationId": "webResearch.screenshot",
				"summary": "Capture an external page",
				"description": "Renders an external public page in a real browser and returns the image, to study a reference site or the user's existing site: its layout, hierarchy, density, and feel. Consent banners and overlays are dismissed first. Focus with `selector` (plus `padding`) or `clip` and page down with `scrollTo`; external pages render in their own color scheme and never full page. Capture your own website with `websitePreview.capture`. Charged in AI credits to the website's organization.",
				"tags": [
					"Web research"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1,
										"description": "A website you can edit; its organization pays for the call in AI credits."
									},
									"url": {
										"type": "string",
										"maxLength": 2048,
										"format": "uri",
										"description": "Absolute public http(s) URL of the page to capture."
									},
									"viewport": {
										"default": "desktop",
										"description": "Device class to render at.",
										"type": "string",
										"enum": [
											"desktop",
											"tablet",
											"mobile"
										]
									},
									"selector": {
										"description": "Crop to the first element matching this CSS selector, scrolled into view. Preferred: it follows the layout at every viewport.",
										"type": "string",
										"minLength": 1
									},
									"padding": {
										"description": "CSS pixels of surrounding page to include on every side of the `selector` match.",
										"type": "integer",
										"minimum": 0,
										"maximum": 500
									},
									"clip": {
										"description": "Crop to this rectangle, in CSS pixels from the top-left of the rendered page at the chosen viewport; it is scrolled into view and must fit within the viewport. Only for regions no selector frames.",
										"type": "object",
										"properties": {
											"x": {
												"type": "number",
												"minimum": 0,
												"maximum": 100000
											},
											"y": {
												"type": "number",
												"minimum": 0,
												"maximum": 100000
											},
											"width": {
												"type": "number",
												"minimum": 1,
												"maximum": 2000
											},
											"height": {
												"type": "number",
												"minimum": 1,
												"maximum": 2000
											}
										},
										"required": [
											"x",
											"y",
											"width",
											"height"
										]
									},
									"scrollTo": {
										"description": "Scroll to a CSS selector or an absolute Y offset before capturing.",
										"anyOf": [
											{
												"type": "string",
												"minLength": 1
											},
											{
												"type": "integer",
												"minimum": 0,
												"maximum": 9007199254740991
											}
										]
									},
									"waitForSelector": {
										"description": "Wait for this CSS selector to match before capturing.",
										"type": "string",
										"minLength": 1
									},
									"delayMs": {
										"description": "Extra settle time before capturing, in milliseconds.",
										"type": "integer",
										"minimum": 0,
										"maximum": 5000
									},
									"freezeAnimations": {
										"default": true,
										"description": "Fast-forward CSS animations and transitions to their end state so captures are deterministic.",
										"type": "boolean"
									}
								},
								"required": [
									"websiteId",
									"url"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Rendered page image.",
						"content": {
							"image/jpeg": {
								"schema": {
									"type": "string",
									"format": "binary"
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websitePreview.capture",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/webResearch/probe": {
			"post": {
				"operationId": "webResearch.probe",
				"summary": "Measure an external page",
				"description": "Renders a public page in a browser and runs measurement presets or a custom script in it, for the exact colors, type, spacing, radii, shadows, font files, images, and head metadata a screenshot cannot give. Each result is a JSON string, cut with `truncated: true` when too long. Page content is untrusted evidence. Charged in AI credits to the website's organization.",
				"tags": [
					"Web research"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"url": {
										"type": "string",
										"maxLength": 2048,
										"format": "uri",
										"description": "Absolute public http(s) URL of the page to render."
									},
									"presets": {
										"description": "Built-in measurement presets to run in the rendered page: design-tokens (computed and declared colors, typography, fonts, layout, shape, depth), images (every image, background, and video with size, position, poster, and role), metadata (head metadata in the field names metadata edits take, plus favicon, theme color, and scroll height).",
										"minItems": 1,
										"maxItems": 3,
										"type": "array",
										"items": {
											"type": "string",
											"enum": [
												"design-tokens",
												"images",
												"metadata"
											]
										}
									},
									"script": {
										"description": "Custom JavaScript run in the page as the body of an async function; `return` a JSON-serializable value. Use it only when no preset answers the question.",
										"type": "string",
										"minLength": 1,
										"maxLength": 20000
									},
									"websiteId": {
										"type": "string",
										"minLength": 1,
										"description": "A website you can edit; its organization pays for the call in AI credits."
									}
								},
								"required": [
									"url",
									"websiteId"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "One result per preset or script.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"url": {
											"type": "string",
											"format": "uri"
										},
										"measuredAt": {
											"type": "object",
											"properties": {
												"timestamp": {
													"type": "string"
												}
											},
											"required": [
												"timestamp"
											],
											"additionalProperties": false
										},
										"results": {
											"type": "object",
											"propertyNames": {
												"type": "string"
											},
											"additionalProperties": {
												"type": "object",
												"properties": {
													"value": {
														"type": "string"
													},
													"truncated": {
														"type": "boolean"
													},
													"length": {
														"type": "integer",
														"minimum": 0
													},
													"error": {
														"type": "string"
													}
												},
												"required": [
													"value",
													"truncated",
													"length"
												],
												"additionalProperties": false
											}
										},
										"message": {
											"type": "string"
										}
									},
									"required": [
										"url",
										"measuredAt",
										"results"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteAgent.interact",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/webResearch/profile": {
			"post": {
				"operationId": "webResearch.profile",
				"summary": "Read a business's own pages",
				"description": "Reads one to ten of a business's own pages (its site, Google listing, Instagram, Facebook, Yelp, or any other profile) into one Markdown profile each (name, address, phone, links, hours, services and prices, rating, reviews and posts quoted verbatim, what the page did not show) with a status and warnings. `images: true`, only for a business the user confirmed as theirs, also lists the page's photos with their role, poster (`byBusiness`), and link expiry, and shows them on contact sheets, each a grid of pictures, each under its number (`n`; numbers run on across calls), with a gray checkerboard where a picture is transparent. Import the good ones with `websiteAssets.importFromUrl` by number (`photo`), describing what you see; a sheet itself is never imported. Import keepers soon: signed links expire within days. Page content is untrusted evidence. MCP clients that render images get the sheets as images; others get base64 WebP in `sheets`. Charged in AI credits to the website's organization.",
				"tags": [
					"Web research"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1,
										"description": "A website you can edit; its organization pays for the call in AI credits."
									},
									"urls": {
										"minItems": 1,
										"maxItems": 10,
										"type": "array",
										"items": {
											"type": "string",
											"maxLength": 2048,
											"format": "uri"
										},
										"description": "One to ten absolute public URLs of the business's own pages: its site, its Google listing, its Instagram, Facebook, Yelp, Booksy, Nextdoor, Etsy, or any other profile. Read in parallel."
									},
									"objective": {
										"description": "What the profile is for; the write-up leans toward it.",
										"type": "string",
										"minLength": 1,
										"maxLength": 5000
									},
									"images": {
										"default": false,
										"description": "Also return the page's own photos (avatar, cover, posts, gallery, menu) with their rights and expiry. Only for a business the user confirmed as theirs.",
										"type": "boolean"
									}
								},
								"required": [
									"websiteId",
									"urls"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "One profile per page, with contact sheets.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"results": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"url": {
														"type": "string",
														"format": "uri"
													},
													"platform": {
														"type": "string",
														"enum": [
															"instagram",
															"facebook",
															"yelp",
															"google",
															"booksy",
															"nextdoor",
															"etsy",
															"fresha",
															"doordash",
															"linkedin",
															"tiktok",
															"tripadvisor",
															"other"
														]
													},
													"status": {
														"type": "string",
														"enum": [
															"read",
															"partial",
															"unreadable"
														]
													},
													"warnings": {
														"type": "array",
														"items": {
															"type": "string"
														}
													},
													"name": {
														"type": "string"
													},
													"address": {
														"type": "string"
													},
													"images": {
														"type": "array",
														"items": {
															"type": "object",
															"properties": {
																"n": {
																	"type": "integer"
																},
																"url": {
																	"type": "string",
																	"format": "uri"
																},
																"role": {
																	"type": "string",
																	"enum": [
																		"avatar",
																		"cover",
																		"post",
																		"gallery",
																		"menu"
																	]
																},
																"caption": {
																	"type": "string"
																},
																"postUrl": {
																	"type": "string",
																	"format": "uri"
																},
																"postedAt": {
																	"type": "string"
																},
																"expiresAt": {
																	"type": "string"
																},
																"byBusiness": {
																	"type": "boolean"
																}
															},
															"required": [
																"url",
																"role",
																"byBusiness"
															],
															"additionalProperties": false
														}
													},
													"sourceKind": {
														"type": "string",
														"enum": [
															"structured-data",
															"rendered-page",
															"text-fallback"
														]
													},
													"fetchedAt": {
														"type": "string"
													},
													"content": {
														"type": "string"
													}
												},
												"required": [
													"url",
													"platform",
													"status",
													"warnings",
													"images",
													"fetchedAt",
													"content"
												],
												"additionalProperties": false
											}
										},
										"sheets": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"data": {
														"type": "string"
													},
													"mediaType": {
														"type": "string",
														"const": "image/webp"
													}
												},
												"required": [
													"data",
													"mediaType"
												],
												"additionalProperties": false
											}
										}
									},
									"required": [
										"results"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteAgent.interact",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/webResearch/view-image": {
			"post": {
				"operationId": "webResearch.viewImage",
				"summary": "View public images",
				"description": "Fetches 1 to 48 public image URLs and returns the pictures; nothing is stored. Up to 4 come back full size (an SVG also returns the start of its source); more come back on contact sheets, each a grid of pictures, each under its number (`n`; numbers run on across calls), with a gray checkerboard where a picture is transparent. Import the good ones with `websiteAssets.importFromUrl` by number (`photo`), describing what you see; a sheet itself is never imported. MCP clients that render images get the pictures as images; others get base64 WebP in `pictures`. Not billed to credits.",
				"tags": [
					"Web research"
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"websiteId": {
										"type": "string",
										"minLength": 1,
										"description": "A website you can edit; its organization pays for the call in AI credits."
									},
									"urls": {
										"minItems": 1,
										"maxItems": 48,
										"type": "array",
										"items": {
											"type": "string",
											"maxLength": 2048,
											"format": "uri"
										},
										"description": "Absolute public image URLs to look at: up to 4 come back full size, more come back on numbered contact sheets."
									}
								},
								"required": [
									"websiteId",
									"urls"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The pictures, with what was seen and what failed.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"message": {
											"type": "string"
										},
										"images": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"n": {
														"description": "The number over the picture on the contact sheets; import it by this number (`photo`).",
														"type": "integer"
													},
													"url": {
														"type": "string"
													},
													"contentType": {
														"type": "string"
													},
													"byteSize": {
														"type": "integer"
													},
													"width": {
														"type": [
															"number",
															"null"
														]
													},
													"height": {
														"type": [
															"number",
															"null"
														]
													},
													"source": {
														"description": "The start of an SVG image, as text.",
														"type": "string"
													}
												},
												"required": [
													"url",
													"width",
													"height"
												],
												"additionalProperties": false
											}
										},
										"errors": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"n": {
														"type": "integer"
													},
													"url": {
														"type": "string"
													},
													"type": {
														"type": "string"
													},
													"message": {
														"type": "string"
													}
												},
												"required": [
													"url",
													"type",
													"message"
												],
												"additionalProperties": false
											}
										},
										"pictures": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"data": {
														"type": "string"
													},
													"mediaType": {
														"type": "string"
													},
													"filename": {
														"type": "string"
													}
												},
												"required": [
													"data",
													"mediaType",
													"filename"
												],
												"additionalProperties": false
											},
											"description": "Base64 WebP: each image full size for up to 4 URLs, otherwise the numbered contact sheets."
										}
									},
									"required": [
										"images",
										"errors",
										"pictures"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteAssets.get",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websites/{websiteId}/secrets": {
			"get": {
				"operationId": "websiteSecrets.list",
				"summary": "List secrets",
				"description": "Returns the names of a website's secrets with when each was created and last changed. Values are write-only and never returned. Server functions read secrets from `env` by name.",
				"tags": [
					"Secrets"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "Secret names and timestamps.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"name": {
														"type": "string"
													},
													"createdAt": {
														"type": "string"
													},
													"updatedAt": {
														"type": "string"
													}
												},
												"required": [
													"name",
													"createdAt",
													"updatedAt"
												],
												"additionalProperties": false
											}
										},
										"manageUrl": {
											"type": "string",
											"description": "The website's settings, where a person adds or replaces secret values."
										}
									},
									"required": [
										"items",
										"manageUrl"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteSecrets.manage",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websites/{websiteId}/secrets/{name}": {
			"put": {
				"operationId": "websiteSecrets.set",
				"summary": "Set a secret",
				"description": "Creates or replaces the secret with this name. Names start with a letter or underscore and contain only letters, digits, and underscores. Values are up to 16,384 characters; they are stored encrypted, redacted from function output when 8 or more characters, and never returned. At most 100 secrets per website.",
				"tags": [
					"Secrets"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "name",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"pattern": "^[A-Za-z_][A-Za-z0-9_]{0,127}$"
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"value": {
										"type": "string",
										"minLength": 1,
										"maxLength": 16384,
										"description": "The secret value. It is stored encrypted and never returned."
									}
								},
								"required": [
									"value"
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The secret name and timestamps.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"name": {
											"type": "string"
										},
										"createdAt": {
											"type": "string"
										},
										"updatedAt": {
											"type": "string"
										}
									},
									"required": [
										"name",
										"createdAt",
										"updatedAt"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				}
			},
			"delete": {
				"operationId": "websiteSecrets.delete",
				"summary": "Delete a secret",
				"description": "Permanently deletes the secret with this name. `removed` is false when no such secret existed.",
				"tags": [
					"Secrets"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "name",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"pattern": "^[A-Za-z_][A-Za-z0-9_]{0,127}$"
						}
					}
				],
				"responses": {
					"200": {
						"description": "Whether a secret was removed.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"removed": {
											"type": "boolean"
										}
									},
									"required": [
										"removed"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				}
			}
		},
		"/websites/{websiteId}/functions/runs": {
			"get": {
				"operationId": "websiteFunctions.runs.list",
				"summary": "List server function runs",
				"description": "Lists a website's server function runs, newest first, for `http` calls and form submissions alike. Secret values are redacted. Runs are kept for 30 days.",
				"tags": [
					"Server functions"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "name",
						"in": "query",
						"required": false,
						"schema": {
							"description": "Only runs of this function.",
							"type": "string"
						}
					},
					{
						"name": "cursor",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"name": "limit",
						"in": "query",
						"required": false,
						"schema": {
							"default": 20,
							"type": "integer",
							"minimum": 1,
							"maximum": 100
						}
					}
				],
				"responses": {
					"200": {
						"description": "A page of runs.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"items": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"id": {
														"type": "string"
													},
													"functionName": {
														"type": "string"
													},
													"trigger": {
														"type": "string",
														"enum": [
															"http",
															"form"
														]
													},
													"source": {
														"type": "string",
														"enum": [
															"draft",
															"published"
														]
													},
													"triggerId": {
														"description": "The form submission id a `form` run handled.",
														"type": [
															"string",
															"null"
														]
													},
													"status": {
														"type": "string",
														"enum": [
															"succeeded",
															"failed"
														]
													},
													"errorCode": {
														"anyOf": [
															{
																"type": "string",
																"enum": [
																	"not_found",
																	"function_error",
																	"timeout",
																	"limit_exceeded",
																	"unavailable"
																]
															},
															{
																"type": "null"
															}
														]
													},
													"errorMessage": {
														"type": [
															"string",
															"null"
														]
													},
													"cpuMs": {
														"type": "number"
													},
													"wallMs": {
														"type": "number"
													},
													"queueMs": {
														"type": "number"
													},
													"memoryBytes": {
														"type": "number"
													},
													"createdAt": {
														"type": "string"
													}
												},
												"required": [
													"id",
													"functionName",
													"trigger",
													"source",
													"triggerId",
													"status",
													"errorCode",
													"errorMessage",
													"cpuMs",
													"wallMs",
													"queueMs",
													"memoryBytes",
													"createdAt"
												],
												"additionalProperties": false
											}
										},
										"nextCursor": {
											"description": "Opaque cursor for the next page. Pass it as the `cursor` parameter on the next request. `null` when this is the last page.",
											"type": [
												"string",
												"null"
											]
										}
									},
									"required": [
										"items",
										"nextCursor"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteFunctions.view",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websites/{websiteId}/functions/runs/{runId}": {
			"get": {
				"operationId": "websiteFunctions.runs.get",
				"summary": "Get a server function run",
				"description": "Returns one run with its input, output, console output and outgoing requests. Secret values, in plain and common encoded forms, are redacted.",
				"tags": [
					"Server functions"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "runId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					}
				],
				"responses": {
					"200": {
						"description": "The run.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"id": {
											"type": "string"
										},
										"functionName": {
											"type": "string"
										},
										"trigger": {
											"type": "string",
											"enum": [
												"http",
												"form"
											]
										},
										"source": {
											"type": "string",
											"enum": [
												"draft",
												"published"
											]
										},
										"triggerId": {
											"description": "The form submission id a `form` run handled.",
											"type": [
												"string",
												"null"
											]
										},
										"status": {
											"type": "string",
											"enum": [
												"succeeded",
												"failed"
											]
										},
										"errorCode": {
											"anyOf": [
												{
													"type": "string",
													"enum": [
														"not_found",
														"function_error",
														"timeout",
														"limit_exceeded",
														"unavailable"
													]
												},
												{
													"type": "null"
												}
											]
										},
										"errorMessage": {
											"type": [
												"string",
												"null"
											]
										},
										"cpuMs": {
											"type": "number"
										},
										"wallMs": {
											"type": "number"
										},
										"queueMs": {
											"type": "number"
										},
										"memoryBytes": {
											"type": "number"
										},
										"createdAt": {
											"type": "string"
										},
										"input": {
											"description": "The payload as JSON text.",
											"type": [
												"string",
												"null"
											]
										},
										"output": {
											"description": "The returned data as JSON text, when the run succeeded.",
											"type": [
												"string",
												"null"
											]
										},
										"logs": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"level": {
														"type": "string"
													},
													"message": {
														"type": "string"
													}
												},
												"required": [
													"level",
													"message"
												],
												"additionalProperties": false
											}
										},
										"requests": {
											"type": "array",
											"items": {
												"type": "object",
												"properties": {
													"method": {
														"type": "string"
													},
													"url": {
														"type": "string"
													},
													"status": {
														"type": "number"
													},
													"error": {
														"type": "string"
													},
													"durationMs": {
														"type": "number"
													}
												},
												"required": [
													"method",
													"url",
													"durationMs"
												],
												"additionalProperties": false
											}
										}
									},
									"required": [
										"id",
										"functionName",
										"trigger",
										"source",
										"triggerId",
										"status",
										"errorCode",
										"errorMessage",
										"cpuMs",
										"wallMs",
										"queueMs",
										"memoryBytes",
										"createdAt",
										"input",
										"output",
										"logs",
										"requests"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteFunctions.view",
				"x-cactal-read-only": true,
				"x-cactal-destructive": false,
				"x-cactal-idempotent": true
			}
		},
		"/websites/{websiteId}/functions/{name}/invoke": {
			"post": {
				"operationId": "websiteFunctions.invoke",
				"summary": "Run a server function on the draft",
				"description": "Runs one of the draft site's `http` server functions with the website's secrets and returns its result, exactly as page code on the draft would receive it. It makes the function's real outgoing requests. Counts toward the plan's monthly calls.",
				"tags": [
					"Server functions"
				],
				"parameters": [
					{
						"name": "websiteId",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1
						}
					},
					{
						"name": "name",
						"in": "path",
						"required": true,
						"schema": {
							"type": "string",
							"minLength": 1,
							"description": "An `http` server function."
						}
					}
				],
				"requestBody": {
					"required": false,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"payload": {
										"description": "The handler's first argument, as JSON."
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The run id and the result: `{ ok: true, data }` or `{ ok: false, error }`.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"runId": {
											"type": "string"
										},
										"result": {
											"anyOf": [
												{
													"type": "object",
													"properties": {
														"ok": {
															"type": "boolean",
															"const": true
														},
														"data": {}
													},
													"required": [
														"ok",
														"data"
													],
													"additionalProperties": false
												},
												{
													"type": "object",
													"properties": {
														"ok": {
															"type": "boolean",
															"const": false
														},
														"error": {
															"type": "object",
															"properties": {
																"code": {
																	"type": "string",
																	"enum": [
																		"not_found",
																		"function_error",
																		"timeout",
																		"limit_exceeded",
																		"unavailable"
																	]
																},
																"message": {
																	"type": "string"
																}
															},
															"required": [
																"code",
																"message"
															],
															"additionalProperties": false
														}
													},
													"required": [
														"ok",
														"error"
													],
													"additionalProperties": false
												}
											]
										}
									},
									"required": [
										"runId",
										"result"
									],
									"additionalProperties": false
								}
							}
						}
					},
					"400": {
						"description": "Validation failed. The response `message` names the first invalid field.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"401": {
						"description": "Missing, invalid, expired, or revoked API key.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"403": {
						"description": "The authenticated principal lacks the required capability.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"404": {
						"description": "The resource does not exist or is outside the principal’s access scope.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ServiceError"
								}
							}
						}
					}
				},
				"x-cactal-required-capability": "websiteFunctions.invoke",
				"x-cactal-read-only": false,
				"x-cactal-destructive": true,
				"x-cactal-idempotent": false
			}
		}
	},
	"components": {
		"securitySchemes": {
			"apiKey": {
				"type": "http",
				"scheme": "bearer",
				"description": "Cactal API key. Create one in the dashboard or via `POST /apiKeys`. The plaintext key is shown once at creation."
			}
		},
		"schemas": {
			"ServiceError": {
				"type": "object",
				"required": [
					"kind",
					"message"
				],
				"description": "Canonical error body returned by every non-2xx response. Extra fields carry error-specific details.",
				"properties": {
					"kind": {
						"type": "string",
						"enum": [
							"validation",
							"unauthorized",
							"forbidden",
							"not_found",
							"conflict",
							"rate_limited",
							"internal"
						],
						"description": "Stable, machine-readable error category."
					},
					"message": {
						"type": "string",
						"description": "Human-readable explanation of the failure."
					},
					"suggestion": {
						"type": "string",
						"description": "What to do next when the failure has a known fix; may name the exact operation to call."
					},
					"validValues": {
						"type": "array",
						"items": {
							"type": "string"
						},
						"description": "The acceptable values for the failing field, when the set is closed."
					}
				},
				"additionalProperties": true
			}
		}
	}
}
