{"openapi":"3.1.0","info":{"title":"Mondovo Public API","version":"1.0.0","x-spec-updated":"2026-07-24","summary":"Create, refine, and publish Mondovo websites from your own tooling.","description":"REST API for headless automation over Mondovo sites: create a site from a structured brief, poll generation, apply natural-language refinements, upload assets, and publish.\n\n## Access\nAll endpoints require a static API key (`mk_live_...`) sent as a Bearer token. API keys are part of the **Agency add-on** and are created at **Dashboard → Settings → API Keys** by an organization owner or admin (max 10 active keys per organization). The add-on is checked on every request — if it lapses, existing keys return `403 addon_required`.\n\nIf you just want to use Mondovo from an AI assistant (Claude, ChatGPT, Claude Desktop), you do **not** need an API key: connect via the Mondovo connector at `https://app.mondovo.com/api/mcp/sites` — included on every plan. See https://app.mondovo.com/developers.\n\n## Rate limits\nEach key is limited per minute and per hour (defaults: 10/minute, 60/hour). Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers for the currently binding window.\n\n## Errors\nEvery non-2xx response uses one envelope: `{ \"error\": \"<code>\", \"message\": \"...\", \"suggestion\": \"...\" }` plus code-specific fields. Notable codes: `addon_required` (403), `plan_limit_reached` (402), `billing_confirmation_required` (402 — publishing a site starts a monthly per-site charge that must be confirmed once in the Mondovo editor; the API cannot consent to new recurring billing), `contact_info_needs_confirmation` (422), `rate_limit_exceeded` (429).\n\n## Scope of responses\nThe API returns site metadata and status only — it never returns site source code or files.","contact":{"name":"Mondovo Support","email":"support@mondovo.com","url":"https://app.mondovo.com/developers"}},"servers":[{"url":"https://app.mondovo.com","description":"Production"}],"security":[{"apiKey":[]}],"tags":[{"name":"Sites","description":"Create, list, inspect, and delete sites."},{"name":"Editing","description":"Natural-language refinement."},{"name":"Publishing","description":"Take a site live."},{"name":"Assets","description":"Store media permanently for a site."}],"paths":{"/api/v1/sites":{"post":{"tags":["Sites"],"operationId":"createSite","summary":"Create a site from a structured brief","description":"Creates a site and starts generation in the background (typically ~60s). Returns `202` immediately; poll `GET /api/v1/sites/{id}` until `status` is `ready`. Requires scope `sites:create`.","security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSiteInput"}}}},"responses":{"200":{"description":"Idempotent replay: a previous call with the same `idempotency_key` already created this site — that site is returned instead of a duplicate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Site"}}}},"202":{"description":"Site created; generation started.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the binding window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["site_id","status","poll_url"],"properties":{"site_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["generating"]},"estimated_seconds":{"type":"integer","description":"Rough generation ETA."},"poll_url":{"type":"string","description":"Path to poll for status."}}},"example":{"site_id":"5f8a1c2e-1234-4b6f-9d3e-0a1b2c3d4e5f","status":"generating","estimated_seconds":60,"poll_url":"/api/v1/sites/5f8a1c2e-1234-4b6f-9d3e-0a1b2c3d4e5f"}}}},"400":{"description":"Error codes: `invalid_json`, `validation_error` (with `details[]`), `invalid_brand`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication failed. Error codes: `invalid_api_key` (missing/unknown key), `key_revoked`, `key_expired`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"`plan_limit_reached` with `limit_type: \"draft_sites\"` — the workspace is at its draft-site limit. Includes `current`, `maximum`, `upgrade_url`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not allowed. Error codes: `addon_required` (the API key’s workspace no longer has the Agency add-on — checked on every request, not just at key creation) and `missing_scope` (the key lacks the scope this endpoint needs).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Error codes: `duplicate_request` (a create with this `idempotency_key` is still running) or `generation_conflict` (generation already in progress).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (`rate_limit_exceeded`). The body includes `retry_after_seconds`, `limit`, and `window` (\"minute\" or \"hour\"); a `Retry-After` header is also set.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the binding window resets.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Sites"],"operationId":"listSites","summary":"List sites","description":"Lists the organization’s sites, most recent first, capped at 50. Requires scope `sites:read`.","responses":{"200":{"description":"Sites in the organization.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the binding window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["sites","total"],"properties":{"sites":{"type":"array","items":{"$ref":"#/components/schemas/Site"}},"total":{"type":"integer"}}}}}},"401":{"description":"Authentication failed. Error codes: `invalid_api_key` (missing/unknown key), `key_revoked`, `key_expired`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not allowed. Error codes: `addon_required` (the API key’s workspace no longer has the Agency add-on — checked on every request, not just at key creation) and `missing_scope` (the key lacks the scope this endpoint needs).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (`rate_limit_exceeded`). The body includes `retry_after_seconds`, `limit`, and `window` (\"minute\" or \"hour\"); a `Retry-After` header is also set.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the binding window resets.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/sites/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Site ID."}],"get":{"tags":["Sites"],"operationId":"getSite","summary":"Get site status and details","description":"Returns the site’s status, progress, preview/editor links, live URL, and page slugs. Use this to poll while `status` is `generating` or `publishing`. Requires scope `sites:read`.","responses":{"200":{"description":"The site.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the binding window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Site"},"example":{"site_id":"5f8a1c2e-1234-4b6f-9d3e-0a1b2c3d4e5f","name":"Bright Path Physio","status":"ready","preview_url":"https://app.mondovo.com/preview/abc123","editor_url":"https://app.mondovo.com/client/.../site/...","pages":["/","/about","/contact"],"created_at":"2026-07-24T10:00:00Z","updated_at":"2026-07-24T10:01:12Z"}}}},"401":{"description":"Authentication failed. Error codes: `invalid_api_key` (missing/unknown key), `key_revoked`, `key_expired`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not allowed. Error codes: `addon_required` (the API key’s workspace no longer has the Agency add-on — checked on every request, not just at key creation) and `missing_scope` (the key lacks the scope this endpoint needs).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`site_not_found` — no such site in this organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (`rate_limit_exceeded`). The body includes `retry_after_seconds`, `limit`, and `window` (\"minute\" or \"hour\"); a `Retry-After` header is also set.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the binding window resets.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Sites"],"operationId":"deleteSite","summary":"Delete a site","description":"Soft-deletes a draft site. Published sites cannot be deleted via the API — unpublish first from the Mondovo dashboard. Requires scope `sites:delete`.","responses":{"200":{"description":"Deleted.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the binding window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["deleted","site_id"],"properties":{"deleted":{"type":"boolean"},"site_id":{"type":"string","format":"uuid"}}},"example":{"deleted":true,"site_id":"5f8a1c2e-1234-4b6f-9d3e-0a1b2c3d4e5f"}}}},"400":{"description":"`site_published` — the site is live; unpublish before deleting.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication failed. Error codes: `invalid_api_key` (missing/unknown key), `key_revoked`, `key_expired`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not allowed. Error codes: `addon_required` (the API key’s workspace no longer has the Agency add-on — checked on every request, not just at key creation) and `missing_scope` (the key lacks the scope this endpoint needs).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`site_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (`rate_limit_exceeded`). The body includes `retry_after_seconds`, `limit`, and `window` (\"minute\" or \"hour\"); a `Retry-After` header is also set.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the binding window resets.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error (`internal_error`). Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/sites/{id}/refine":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Site ID."}],"post":{"tags":["Editing"],"operationId":"refineSite","summary":"Refine a site with a natural-language instruction","description":"Applies an AI edit to one page (homepage by default). Counts against the workspace’s monthly AI-refinement allowance. Requires scope `sites:refine`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefineSiteInput"}}}},"responses":{"200":{"description":"Refinement applied and saved.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the binding window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["site_id","status"],"properties":{"site_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["refined"]},"message":{"type":"string"},"poll_url":{"type":"string"}}},"example":{"site_id":"5f8a1c2e-1234-4b6f-9d3e-0a1b2c3d4e5f","status":"refined","message":"Site has been refined successfully.","poll_url":"/api/v1/sites/5f8a1c2e-1234-4b6f-9d3e-0a1b2c3d4e5f"}}}},"400":{"description":"Error codes: `invalid_json`, `validation_error`, `no_code` (the site has no generated code yet — generate it first, or the `page` slug does not exist).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication failed. Error codes: `invalid_api_key` (missing/unknown key), `key_revoked`, `key_expired`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"`plan_limit_reached` with `limit_type: \"ai_refinements\"` — the monthly refinement allowance is used up. Includes `resets_at` and `upgrade_url`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not allowed. Error codes: `addon_required` (the API key’s workspace no longer has the Agency add-on — checked on every request, not just at key creation) and `missing_scope` (the key lacks the scope this endpoint needs).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`site_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`site_busy` — generation is in progress; poll and retry once idle.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (`rate_limit_exceeded`). The body includes `retry_after_seconds`, `limit`, and `window` (\"minute\" or \"hour\"); a `Retry-After` header is also set.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the binding window resets.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Error codes: `refinement_failed`, `refinement_not_saved` (the edit was produced but could not be saved — no refinement credit is consumed), `configuration_error`, `internal_error`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/sites/{id}/publish":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Site ID."}],"post":{"tags":["Publishing"],"operationId":"publishSite","summary":"Publish a site to the web","description":"Compiles and deploys the site to `https://<subdomain>.mondovo.site` (custom domains are attached in the dashboard). Requires scope `sites:publish`.\n\n**Billing:** taking a site live adds the standard per-site monthly charge to the subscription. The account owner must confirm that charge **once, in the Mondovo editor** — the API cannot consent to new recurring billing, so the first publish of a billable site returns `402 billing_confirmation_required`. After the charge is confirmed (or on plans/sites where no new charge applies), API publishes go straight through.","requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishSiteInput"}}}},"responses":{"200":{"description":"Published.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the binding window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["site_id","status","deployment_url"],"properties":{"site_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["published"]},"deployment_url":{"type":"string","format":"uri"},"message":{"type":"string"}}},"example":{"site_id":"5f8a1c2e-1234-4b6f-9d3e-0a1b2c3d4e5f","status":"published","deployment_url":"https://bright-path-physio.mondovo.site","message":"Site published successfully."}}}},"400":{"description":"Error codes: `validation_error` (bad subdomain), `no_content` (site not generated yet), `too_many_pages_in_site` (abuse guard — not a plan limit).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication failed. Error codes: `invalid_api_key` (missing/unknown key), `key_revoked`, `key_expired`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Two distinct billing errors:\n- `billing_confirmation_required` — publishing starts a monthly per-site charge; includes `price_per_site_monthly`. The owner must publish once from the Mondovo editor to confirm billing. The API cannot approve new recurring charges.\n- `plan_limit_reached` with `limit_type: \"published_sites\"` — the plan’s live-site limit is reached; includes `current`, `maximum`, `upgrade_url`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"billing_confirmation_required":{"value":{"error":"billing_confirmation_required","message":"Publishing this site adds $20/month to the subscription. Billing must be confirmed in the Mondovo editor before the site can go live.","price_per_site_monthly":20,"suggestion":"Open the site in the Mondovo editor and publish from there — the editor shows a one-time billing confirmation for the monthly live-site charge."}},"plan_limit_reached":{"value":{"error":"plan_limit_reached","limit_type":"published_sites","current":3,"maximum":3,"message":"Published site limit reached.","upgrade_url":"https://app.mondovo.com/dashboard/billing"}}}}}},"403":{"description":"Authenticated but not allowed. Error codes: `addon_required` (the API key’s workspace no longer has the Agency add-on — checked on every request, not just at key creation) and `missing_scope` (the key lacks the scope this endpoint needs).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`site_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Error codes: `site_busy` (still generating) or `subdomain_taken`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`contact_info_needs_confirmation` — the site still carries AI-generated placeholder contact details (may include `placeholders` and `missing_fields`). Fix them via refine, or resend with `confirm_contact_info: true` to acknowledge and publish. Retrying unchanged never clears this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (`rate_limit_exceeded`). The body includes `retry_after_seconds`, `limit`, and `window` (\"minute\" or \"hour\"); a `Retry-After` header is also set.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the binding window resets.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Error codes: `publish_failed`, `configuration_error`, `internal_error`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/sites/{id}/assets":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Site ID."}],"post":{"tags":["Assets"],"operationId":"uploadAsset","summary":"Store an image or video permanently for a site","description":"Fetch-by-URL rehosting: pass an HTTPS URL (including expiring signed URLs) and Mondovo downloads the file, stores it permanently on its CDN, registers it in the site’s asset library, and returns a stable URL. Allowed types: images (JPG, PNG, WebP, GIF; max 20 MB) and video (MP4, WebM, MOV; max 80 MB — videos are optimized for the web after upload). Requires scope `assets:write` or `sites:refine`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadAssetInput"}}}},"responses":{"200":{"description":"Stored. Use `url` — it never expires.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the binding window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["site_id","url","type","mime_type","bytes"],"properties":{"site_id":{"type":"string","format":"uuid"},"asset_id":{"type":"string","format":"uuid","description":"Asset-library record ID (may be omitted)."},"url":{"type":"string","format":"uri","description":"Permanent CDN URL."},"type":{"type":"string","enum":["image","video"]},"mime_type":{"type":"string"},"bytes":{"type":"integer","description":"Stored size in bytes."},"message":{"type":"string"}}}}}},"400":{"description":"Error codes: `invalid_json`, `validation_error`, `invalid_input`, `invalid_url` (non-HTTPS or blocked host), `fetch_failed` (source unreachable/expired), `unsupported_type`, `empty_asset`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication failed. Error codes: `invalid_api_key` (missing/unknown key), `key_revoked`, `key_expired`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not allowed. Error codes: `addon_required` (the API key’s workspace no longer has the Agency add-on — checked on every request, not just at key creation) and `missing_scope` (the key lacks the scope this endpoint needs).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`site_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`file_too_large` — over the 20 MB (image) / 80 MB (video) cap.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (`rate_limit_exceeded`). The body includes `retry_after_seconds`, `limit`, and `window` (\"minute\" or \"hour\"); a `Retry-After` header is also set.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the currently binding window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the binding window resets.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Error codes: `upload_failed`, `internal_error`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"mk_live_...","description":"Static Mondovo API key sent as `Authorization: Bearer mk_live_...`. Created at Dashboard → Settings → API Keys (organization owners/admins; Agency add-on required). Keys are organization-scoped. Default scopes: `sites:create`, `sites:read`, `sites:refine`, `sites:publish`, `sites:delete` (`sites:refine` also covers asset uploads)."}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","description":"Stable machine-readable error code (e.g. \"addon_required\", \"plan_limit_reached\")."},"message":{"type":"string","description":"Human-readable explanation."},"suggestion":{"type":"string","description":"Actionable next step, phrased for an AI agent relaying the error to a user."}},"additionalProperties":true},"Site":{"type":"object","required":["site_id","name","status"],"properties":{"site_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string","enum":["generating","refining","ready","publishing","published","error"]},"generation_progress":{"type":"number","description":"0-100 while status is \"generating\"."},"generation_progress_message":{"type":"string","description":"Human-readable progress note while generating."},"preview_url":{"type":"string","format":"uri","description":"Shareable public preview link (no login required). Omitted when previews are disabled."},"editor_url":{"type":"string","format":"uri","description":"Deep link into the Mondovo editor (requires login). Omitted while generating or errored."},"deployment_url":{"type":"string","format":"uri","description":"Live URL once published."},"pages":{"type":"array","items":{"type":"string"},"description":"Slugs of the site’s pages (e.g. \"/\", \"/about\"). Omitted before generation completes."},"error_message":{"type":"string","description":"Present only when status is \"error\"."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"CreateSiteInput":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"Business / site name."},"brand_id":{"type":"string","format":"uuid","description":"Optional existing Brand Studio brand to link. Must belong to your organization. Omit to create a normal unlinked site."},"business":{"type":"object","additionalProperties":false,"properties":{"industry":{"type":"string","maxLength":100},"location":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":5000},"services":{"type":"array","maxItems":50,"items":{"type":"string","maxLength":500}},"target_audience":{"type":"string","maxLength":500},"unique_selling_points":{"type":"array","maxItems":20,"items":{"type":"string","maxLength":500}},"tone_of_voice":{"type":"string","maxLength":100},"contact_email":{"type":"string","format":"email"},"contact_phone":{"type":"string","maxLength":50},"social_links":{"type":"object","additionalProperties":{"type":"string","maxLength":500},"description":"Map of platform name to profile URL."}}},"design":{"type":"object","description":"Design direction. Unknown extra keys (e.g. \"vibe\", \"typography\") are accepted and folded into the creative brief.","properties":{"style":{"type":"string","maxLength":100},"color_palette":{"type":"object","additionalProperties":false,"properties":{"primary":{"type":"string","maxLength":20},"secondary":{"type":"string","maxLength":20},"accent":{"type":"string","maxLength":20}}},"mood":{"type":"string","maxLength":500},"sections":{"type":"array","maxItems":30,"items":{"type":"string","maxLength":100},"description":"Homepage sections to include, e.g. [\"hero\",\"services\",\"testimonials\",\"contact\"]."},"references":{"type":"string","maxLength":1000,"description":"Free-text style references."},"logo_url":{"type":"string","format":"uri","maxLength":2000},"reference_images":{"type":"array","maxItems":6,"items":{"type":"string","format":"uri","maxLength":2000},"description":"HTTPS screenshots used as DESIGN references (palette/typography/rhythm), not content to render."},"reference_fidelity":{"type":"string","enum":["inspire","replicate"],"description":"How faithfully to honor reference_images: \"replicate\" recreates the reference’s structure; \"inspire\" (default) adapts it."},"source_url":{"type":"string","format":"uri","maxLength":2000,"description":"HTTPS URL of a live site to capture and use as the design source. Non-HTTPS and private hosts are rejected."},"source_copy":{"type":"string","enum":["rewrite","preserve"],"description":"With source_url: \"rewrite\" (default) writes fresh copy from your business details; \"preserve\" keeps the source wording."}},"additionalProperties":true},"content":{"type":"object","description":"Seed content. Unknown extra keys are passed through to the site content.","properties":{"headline":{"type":"string","maxLength":500},"tagline":{"type":"string","maxLength":500},"about":{"type":"string","maxLength":5000},"testimonials":{"type":"array","maxItems":30,"items":{"type":"object","required":["text"],"properties":{"name":{"type":"string","maxLength":200},"text":{"type":"string","maxLength":2000},"rating":{"type":"number","minimum":1,"maximum":5},"role":{"type":"string","maxLength":200}}}},"services":{"type":"array","maxItems":50,"items":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":2000},"price":{"type":"string","maxLength":100},"icon":{"type":"string","maxLength":100}}}},"team":{"type":"array","maxItems":30,"items":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":200},"role":{"type":"string","maxLength":200},"bio":{"type":"string","maxLength":2000},"image_url":{"type":"string","format":"uri","maxLength":2000}}}},"faq":{"type":"array","maxItems":50,"items":{"type":"object","required":["question","answer"],"properties":{"question":{"type":"string","maxLength":500},"answer":{"type":"string","maxLength":5000}}}}},"additionalProperties":true},"pages":{"type":"array","maxItems":20,"items":{"type":"string","maxLength":50},"description":"Page names to create, e.g. [\"home\",\"about\",\"contact\"]. Omit to let Mondovo plan the site structure."},"goal":{"type":"string","maxLength":300,"description":"The site’s primary conversion objective (e.g. \"book a consultation\"). Drives hero CTA and section selection. Omitted → inferred."},"idempotency_key":{"type":"string","minLength":1,"maxLength":100,"description":"Stable key for this creation attempt. Retrying a timed-out create with the SAME value returns the site the first attempt made instead of creating a duplicate. Use a fresh value (e.g. a UUID) per new site."}}},"RefineSiteInput":{"type":"object","required":["instruction"],"properties":{"instruction":{"type":"string","minLength":1,"maxLength":5000,"description":"Natural-language edit, e.g. \"Make the hero headline more confident and add a pricing section\"."},"page":{"type":"string","maxLength":50,"description":"Slug of the page to refine (e.g. \"/about\"). Omit to refine the homepage."}}},"PublishSiteInput":{"type":"object","properties":{"subdomain":{"type":"string","minLength":3,"maxLength":63,"pattern":"^[a-z0-9][a-z0-9-]*[a-z0-9]$","description":"Desired subdomain (site goes live at https://<subdomain>.mondovo.site). Only applied if the site has no subdomain yet; omitted → auto-derived from the site name."},"confirm_contact_info":{"type":"boolean","description":"Publishing is paused (422 contact_info_needs_confirmation) while the site still carries AI-generated placeholder contact details. Set true to acknowledge the warning and publish anyway — retrying without it never clears the guard."}}},"UploadAssetInput":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","maxLength":2000,"description":"HTTPS URL of the image or video to store permanently (a generated image, stock URL, or expiring signed URL). Mondovo downloads it and returns a stable CDN URL."},"alt_text":{"type":"string","maxLength":500,"description":"Optional label / alt text kept on the asset."},"filename":{"type":"string","maxLength":200,"description":"Optional display name for the asset library. The stored file name is always derived from the validated content type."}}}}}}