Two ways to drive Mondovo programmatically: connect your AI assistant (included on every plan), or automate headlessly with the REST API (part of the Agency add-on).
Add Mondovo as a connector in Claude, ChatGPT, or Claude Desktop and build, find, edit, and publish websites by chatting. You sign in to Mondovo and approve access — no API keys, no configuration files. The connector exposes 24 self-describing tools, so your assistant discovers what it can do on its own.
https://app.mondovo.com/api/mcp/sitesSettings → Connectors → Add custom connector → paste the URL → sign in and approve.
Settings → Connectors (or Apps) → Add connector → paste the URL → sign in and approve.
Settings → Connectors → Add custom connector → paste the URL. Same OAuth sign-in — no key needed.
create_site — Create a new site from a structured briefget_site — Check status and get preview/live URLslist_sites — Find your existing sitesrefine_site — Edit a site in natural languagepublish_site — Publish a site to the webadd_page — Add one inner page, generated to match the themeadd_pages — Add up to 10 inner pages in one callget_intake_questions — Fetch the brief questions Mondovo asksvalidate_design — Validate an externally-authored design payloadcreate_site_from_design — Create a site from an authored designimport_design_variation — Import an authored design as a new variationgenerate_design_variation — Generate an additional design variationlist_variations — List a site’s design variationsswitch_variation — Switch the active design variationlist_boards — List design boardspin_design_to_board — Pin a design to a boardshare_board — Share a board with a clientupload_asset — Store an image or video permanentlylist_contacts — List the contacts captured by a siteget_contact — Read one contact in fulllist_contact_fields — List custom contact fields and pipeline stagesupsert_contacts — Import or update contacts in bulktag_contacts — Add or remove tags across contactsdefine_contact_field — Define a custom contact attributeStep-by-step walkthrough with screenshots: mondovo.com/connectors
The v1 REST API is for headless automation — scripts, backends, and CI pipelines that create, refine, and publish sites without a human in the chat. It uses static API keys, which are part of the Agency add-on. If you just want an AI assistant to use Mondovo, use the connector above instead — it needs no key.
Create a key at Dashboard → Settings → API Keys (organization owners and admins; up to 10 active keys). The full key is shown once. Send it as a Bearer token on every request:
Authorization: Bearer mk_live_...Keys are organization-scoped with scopes sites:create sites:read sites:refine sites:publish sites:delete by default (sites:refine also covers asset uploads). The add-on is verified on every request — if it lapses, existing keys return 403 addon_required.
The CRM scopes crm:read and crm:write are not in that default set. They reach your customers’ personal data rather than your own site content, so a key only carries them if you ask for them — pass scopes when you create the key. An existing key gains nothing automatically.
/api/v1/sitesscope: sites:createCreates a site from a structured brief and starts generation in the background (~60s). Returns 202 immediately — poll the site until status is ready. Pass an idempotency_key so a retried timeout returns the original site instead of creating a duplicate.
curl -X POST https://app.mondovo.com/api/v1/sites \
-H "Authorization: Bearer mk_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Bright Path Physio",
"business": {
"industry": "physiotherapy",
"location": "Austin, TX",
"description": "Sports injury and rehab clinic."
},
"design": { "style": "clean, modern", "color_palette": { "primary": "#0f766e" } },
"goal": "book an appointment",
"idempotency_key": "3f9d2c1a-7e54-4b0e-9a1f-2b6c8d0e4f5a"
}'HTTP/1.1 202 Accepted
{
"site_id": "5f8a1c2e-1234-4b6f-9d3e-0a1b2c3d4e5f",
"status": "generating",
"estimated_seconds": 60,
"poll_url": "/api/v1/sites/5f8a1c2e-1234-4b6f-9d3e-0a1b2c3d4e5f"
}/api/v1/sitesscope: sites:readLists your organization’s sites, most recent first (up to 50).
curl https://app.mondovo.com/api/v1/sites \
-H "Authorization: Bearer mk_live_..."HTTP/1.1 200 OK
{
"sites": [
{
"site_id": "5f8a1c2e-1234-4b6f-9d3e-0a1b2c3d4e5f",
"name": "Bright Path Physio",
"status": "ready",
"preview_url": "https://app.mondovo.com/preview/abc123",
"pages": ["/", "/about", "/contact"],
"created_at": "2026-07-24T10:00:00Z"
}
],
"total": 1
}/api/v1/sites/{id}scope: sites:readReturns status, generation progress, a shareable preview_url, an editor_url, the live deployment_url once published, and page slugs. Use this to poll while status is generating or publishing.
curl https://app.mondovo.com/api/v1/sites/5f8a1c2e-... \
-H "Authorization: Bearer mk_live_..."/api/v1/sites/{id}/refinescope: sites:refineApplies an AI edit to one page (page defaults to the homepage). Counts against your monthly AI-edit allowance; if the change can’t be saved, no allowance is consumed.
curl -X POST https://app.mondovo.com/api/v1/sites/5f8a1c2e-.../refine \
-H "Authorization: Bearer mk_live_..." \
-H "Content-Type: application/json" \
-d '{ "instruction": "Make the hero headline more confident and add a FAQ section", "page": "/" }'HTTP/1.1 200 OK
{
"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"
}/api/v1/sites/{id}/publishscope: sites:publishDeploys the site to its platform address (e.g. https://<subdomain>.mondovo.com; the exact URL comes back as deployment_url). Custom domains attach in the dashboard. If the site still carries AI-generated placeholder contact details you’ll get a 422 — fix them via refine, or resend with confirm_contact_info: true. See billing notes for the first-publish 402.
curl -X POST https://app.mondovo.com/api/v1/sites/5f8a1c2e-.../publish \
-H "Authorization: Bearer mk_live_..." \
-H "Content-Type: application/json" \
-d '{ "subdomain": "bright-path-physio" }'HTTP/1.1 200 OK
{
"site_id": "5f8a1c2e-1234-4b6f-9d3e-0a1b2c3d4e5f",
"status": "published",
"deployment_url": "https://bright-path-physio.mondovo.com",
"message": "Site published successfully."
}/api/v1/sites/{id}/assetsscope: assets:write or sites:refinePass any HTTPS URL — including an expiring signed URL — and Mondovo downloads the file, stores it permanently on our CDN, adds it to the site’s asset library, and returns a stable URL. Images (JPG/PNG/WebP/GIF, max 20 MB) and video (MP4/WebM/MOV, max 80 MB; optimized for the web after upload).
curl -X POST https://app.mondovo.com/api/v1/sites/5f8a1c2e-.../assets \
-H "Authorization: Bearer mk_live_..." \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/clinic-hero.jpg", "alt_text": "Clinic reception" }'HTTP/1.1 200 OK
{
"site_id": "5f8a1c2e-1234-4b6f-9d3e-0a1b2c3d4e5f",
"asset_id": "9c2d4e6f-...",
"url": "https://site-assets.mondovo.com/sites/5f8a1c2e-.../uploads/aB3xY9....jpg",
"type": "image",
"mime_type": "image/jpeg",
"bytes": 482113,
"message": "Stored the image permanently. Use the returned url — it will not expire."
}/api/v1/sites/{id}scope: sites:deleteSoft-deletes a draft site. Published sites can’t be deleted via the API — unpublish first from the dashboard.
curl -X DELETE https://app.mondovo.com/api/v1/sites/5f8a1c2e-... \
-H "Authorization: Bearer mk_live_..."HTTP/1.1 200 OK
{ "deleted": true, "site_id": "5f8a1c2e-1234-4b6f-9d3e-0a1b2c3d4e5f" }Every Mondovo site has a built-in CRM holding the people it captures — form leads, chat leads, and anyone you import. These endpoints read and write that list, so they are the way to bring an existing customer list in from a spreadsheet or another system, tags and all. They need crm:read / crm:write, which no key carries by default.
/api/v1/sites/{id}/contactsscope: crm:writeCreate-or-update up to 500 contacts per call. Each one is matched against the site’s existing contacts on match_on (email by default, matched case-insensitively): a match is updated, everything else is created — so re-sending the same batch is safe, and paging a large import is safe.
Any attribute the CRM has no column for goes in custom_fields as flat key/value pairs. Each new key is declared as a filterable field automatically, so imported attributes are immediately usable in filters and segments. Tags merge with whatever the contact already carries unless you pass tags_mode: "replace".
Send dry_run: true first to see the counts without writing anything.
curl -X POST https://app.mondovo.com/api/v1/sites/5f8a1c2e-.../contacts \
-H "Authorization: Bearer mk_live_..." \
-H "Content-Type: application/json" \
-d '{
"match_on": "email",
"tags_mode": "merge",
"contacts": [
{
"email": "ann@example.com",
"first_name": "Ann",
"last_name": "Okafor",
"company": "Okafor Dental",
"phone": "+1 512 555 0143",
"tags": ["newsletter", "2026-import"],
"custom_fields": { "plan": "gold", "seats": 12, "renewal_date": "2027-03-01" }
},
{ "email": "bo@example.com", "first_name": "Bo", "tags": ["2026-import"] }
]
}'HTTP/1.1 200 OK
{
"site_id": "5f8a1c2e-1234-4b6f-9d3e-0a1b2c3d4e5f",
"created": 1,
"updated": 1,
"total_submitted": 2,
"duplicates_in_batch": 0,
"stage_changes_ignored": 0,
"fields_defined": ["plan", "seats", "renewal_date"],
"matched_on": "email",
"tags_mode": "merge",
"message": "Imported 1 new and updated 1 existing contact(s)."
}Two limits worth knowing before you plan a migration: status (the pipeline stage) is applied to new contacts only — an import never moves an existing contact along your pipeline, because that would fire your automations in bulk — and no endpoint here deletes a contact.
A site can hold only one contact per email address (compared without regard to case), enforced by the database rather than by a check this endpoint runs — so parallel imports cannot race each other into duplicates. If a contact appears mid-batch it is updated rather than rejected. Matching on phone has no such guarantee, because different people legitimately share a phone number; match on email when it has to be exact.
/api/v1/sites/{id}/contactsscope: crm:readReturns contacts with their tags and custom fields, newest first. Filter with status, search (name/email/company), tags (comma-separated; a contact must carry every tag listed), acquisition_source, acquisition_campaign, created_after and created_before. Paginate with limit (max 200) and offset; total counts everything matching the filter, not the page.
curl "https://app.mondovo.com/api/v1/sites/5f8a1c2e-.../contacts?tags=newsletter&limit=50" \
-H "Authorization: Bearer mk_live_..."HTTP/1.1 200 OK
{
"site_id": "5f8a1c2e-1234-4b6f-9d3e-0a1b2c3d4e5f",
"total": 412,
"limit": 50,
"offset": 0,
"contacts": [
{
"id": "9c2d4e6f-...",
"email": "ann@example.com",
"first_name": "Ann",
"company": "Okafor Dental",
"status": "qualified",
"tags": ["newsletter", "2026-import"],
"custom_fields": { "plan": "gold" },
"lead_score": 80,
"created_at": "2026-09-01T10:22:31.000Z"
}
]
}/api/v1/sites/{id}/contacts/{contactId}scope: crm:readReturns a single contact with every stored attribute.
curl https://app.mondovo.com/api/v1/sites/5f8a1c2e-.../contacts/9c2d4e6f-... \
-H "Authorization: Bearer mk_live_..."/api/v1/sites/{id}/contacts/tagsscope: crm:writeApplies tag additions and removals across a set of contacts in one call. Both are applied to each contact’s existing tags, so tags you don’t mention survive. Tags are what segments filter on, so this changes who is in a saved segment.
curl -X POST https://app.mondovo.com/api/v1/sites/5f8a1c2e-.../contacts/tags \
-H "Authorization: Bearer mk_live_..." \
-H "Content-Type: application/json" \
-d '{ "contact_ids": ["9c2d4e6f-...", "1a2b3c4d-..."], "add": ["campaign-sep"], "remove": ["cold"] }'/api/v1/sites/{id}/contacts/fieldsscope: crm:readReturns the custom attributes defined on this site’s contacts and the pipeline stage keys status will accept. Call it before an import to map your columns onto the vocabulary the site already uses instead of creating near-duplicates.
HTTP/1.1 200 OK
{
"site_id": "5f8a1c2e-1234-4b6f-9d3e-0a1b2c3d4e5f",
"fields": [
{ "field_key": "plan", "label": "Plan", "field_type": "text", "options": [] }
],
"stages": [
{ "key": "new", "label": "New" },
{ "key": "qualified", "label": "Qualified" }
],
"stages_source": "default"
}/api/v1/sites/{id}/contacts/fieldsscope: crm:writeDeclare an attribute so it gets a proper label and type instead of rendering as a raw key. Only needed when you want a specific label or a non-text type — the import creates a plain text field for any new key it sees. Defining a field never changes existing contact values.
curl -X POST https://app.mondovo.com/api/v1/sites/5f8a1c2e-.../contacts/fields \
-H "Authorization: Bearer mk_live_..." \
-H "Content-Type: application/json" \
-d '{ "field_key": "plan", "label": "Plan", "field_type": "select", "options": ["gold", "silver"] }'Agency add-on workspaces can also point MCP clients at the connector with a static key instead of OAuth — useful for shared automation accounts:
{
"mcpServers": {
"mondovo": {
"type": "streamable-http",
"url": "https://app.mondovo.com/api/mcp/sites",
"headers": { "Authorization": "Bearer mk_live_..." }
}
}
}Every API response includes the current window’s state; a 429 adds Retry-After:
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1784102460Every non-2xx response uses one envelope — a stable error code, a human-readable message, and usually a suggestion phrased so an AI agent can relay the next step:
{
"error": "addon_required",
"message": "API keys are part of the Agency add-on on this workspace",
"suggestion": "To use Mondovo from an AI assistant, connect via the Mondovo connector instead — it is included on every plan and needs no API key. ..."
}| Status | Code | Meaning |
|---|---|---|
| 401 | invalid_api_key | Missing or unrecognized key. Send Authorization: Bearer mk_live_... |
| 401 | key_revoked / key_expired | The key was revoked or has passed its expiry — create a new one. |
| 403 | addon_required | The workspace no longer has the Agency add-on. Checked on every request, not just at key creation. |
| 403 | missing_scope | The key lacks the scope this endpoint needs. |
| 402 | plan_limit_reached | A plan allowance is used up (draft sites, Free-plan AI edits, or published sites). Includes limit_type and upgrade_url. |
| 402 | billing_confirmation_required | Publishing this site starts a monthly per-site charge that must be confirmed once in the Mondovo editor. The API cannot approve new recurring billing. |
| 404 | site_not_found | No site with that ID in your organization. |
| 404 | contact_not_found | No contact with that ID on that site. |
| 400 | missing_match_key | A contact in the import batch is missing the field named by match_on. |
| 400 | unknown_stage | A status value is not a pipeline stage on that site. The response lists valid_stages. |
| 403 | demo_site_readonly | Contacts on a demo site cannot be modified. |
| 503 | stages_unreadable | Pipeline stages could not be read, so status could not be validated. Retry, or omit status. |
| 409 | site_busy / generation_conflict | Generation is in progress — poll GET /sites/{id} and retry once idle. |
| 409 | subdomain_taken | Choose a different subdomain. |
| 409 | duplicate_request | A create with this idempotency_key is still running. |
| 409 | contact_conflict | A concurrent write created one of the contacts in an import batch and the retry still conflicted. The batch is idempotent — send it again. |
| 422 | contact_info_needs_confirmation | The site still carries AI-generated placeholder contact details. Fix them via refine, or resend publish with confirm_contact_info: true. |
| 413 | file_too_large | Asset over the 20 MB (image) / 80 MB (video) cap. |
| 429 | rate_limit_exceeded | Too many requests. Body includes retry_after_seconds; a Retry-After header is also set. |
| 500 | internal_error | Unexpected failure — safe to retry. |
API publishes follow the same per-site pricing as the app: taking a site live adds $20/month to the subscription.
The API cannot consent to new recurring charges. The first publish of a billable site returns a structured 402 billing_confirmation_required (with price_per_site_monthly) — the account owner confirms the charge once by publishing from the Mondovo editor. After that, API publishes of the site go straight through.
Site creation and AI edits draw on your plan’s normal allowances — the API never bypasses them.
The full machine-readable spec (OpenAPI 3.1) — import it into your HTTP client, code generator, or a custom GPT action:
https://app.mondovo.com/api/v1/openapi.json