← Back to Home

Build with Mondovo

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).

Connect your AI assistant

Included on every plan

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.

Connector URL

https://app.mondovo.com/api/mcp/sites

claude.ai

Settings → Connectors → Add custom connector → paste the URL → sign in and approve.

ChatGPT

Settings → Connectors (or Apps) → Add connector → paste the URL → sign in and approve.

Claude Desktop

Settings → Connectors → Add custom connector → paste the URL. Same OAuth sign-in — no key needed.

What your assistant can do (24 tools)
  • create_site — Create a new site from a structured brief
  • get_site — Check status and get preview/live URLs
  • list_sites — Find your existing sites
  • refine_site — Edit a site in natural language
  • publish_site — Publish a site to the web
  • add_page — Add one inner page, generated to match the theme
  • add_pages — Add up to 10 inner pages in one call
  • get_intake_questions — Fetch the brief questions Mondovo asks
  • validate_design — Validate an externally-authored design payload
  • create_site_from_design — Create a site from an authored design
  • import_design_variation — Import an authored design as a new variation
  • generate_design_variation — Generate an additional design variation
  • list_variations — List a site’s design variations
  • switch_variation — Switch the active design variation
  • list_boards — List design boards
  • pin_design_to_board — Pin a design to a board
  • share_board — Share a board with a client
  • upload_asset — Store an image or video permanently
  • list_contacts — List the contacts captured by a site
  • get_contact — Read one contact in full
  • list_contact_fields — List custom contact fields and pipeline stages
  • upsert_contacts — Import or update contacts in bulk
  • tag_contacts — Add or remove tags across contacts
  • define_contact_field — Define a custom contact attribute
  • Your assistant can drive the builder, but it cannot read or export your site’s code — tools return status and links only.
  • All plan allowances apply normally: draft limits, monthly AI edits, and publish billing are the same as in the app.
  • Connections are rate-limited to 30 requests/minute and 300/hour.
  • Disconnect any app at any time from Dashboard → Settings → Connected Apps.

Step-by-step walkthrough with screenshots: mondovo.com/connectors

REST API

Agency add-on

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.

Authentication

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.

Endpoints

POST/api/v1/sitesscope: sites:create

Create a site

Creates 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"
}
GET/api/v1/sitesscope: sites:read

List sites

Lists 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
}
GET/api/v1/sites/{id}scope: sites:read

Get site status

Returns 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_..."
POST/api/v1/sites/{id}/refinescope: sites:refine

Refine with natural language

Applies 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"
}
POST/api/v1/sites/{id}/publishscope: sites:publish

Publish to the web

Deploys 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."
}
POST/api/v1/sites/{id}/assetsscope: assets:write or sites:refine

Store an asset permanently

Pass 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."
}
DELETE/api/v1/sites/{id}scope: sites:delete

Delete a draft site

Soft-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" }

CRM

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.

POST/api/v1/sites/{id}/contactsscope: crm:write

Import or update contacts

Create-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.

GET/api/v1/sites/{id}/contactsscope: crm:read

List contacts

Returns 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"
    }
  ]
}
GET/api/v1/sites/{id}/contacts/{contactId}scope: crm:read

Get one contact

Returns a single contact with every stored attribute.

curl https://app.mondovo.com/api/v1/sites/5f8a1c2e-.../contacts/9c2d4e6f-... \
  -H "Authorization: Bearer mk_live_..."
POST/api/v1/sites/{id}/contacts/tagsscope: crm:write

Add or remove tags in bulk

Applies 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"] }'
GET/api/v1/sites/{id}/contacts/fieldsscope: crm:read

List custom fields and pipeline stages

Returns 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"
}
POST/api/v1/sites/{id}/contacts/fieldsscope: crm:write

Define a custom field

Declare 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"] }'

Using a key with Claude Desktop (automation)

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_..." }
    }
  }
}

Rate limits

  • API keys: 10 requests/minute and 60/hour per key by default.
  • AI-assistant connections: 30 requests/minute and 300/hour per connection.

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: 1784102460

Errors

Every 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. ..."
}
StatusCodeMeaning
401invalid_api_keyMissing or unrecognized key. Send Authorization: Bearer mk_live_...
401key_revoked / key_expiredThe key was revoked or has passed its expiry — create a new one.
403addon_requiredThe workspace no longer has the Agency add-on. Checked on every request, not just at key creation.
403missing_scopeThe key lacks the scope this endpoint needs.
402plan_limit_reachedA plan allowance is used up (draft sites, Free-plan AI edits, or published sites). Includes limit_type and upgrade_url.
402billing_confirmation_requiredPublishing this site starts a monthly per-site charge that must be confirmed once in the Mondovo editor. The API cannot approve new recurring billing.
404site_not_foundNo site with that ID in your organization.
404contact_not_foundNo contact with that ID on that site.
400missing_match_keyA contact in the import batch is missing the field named by match_on.
400unknown_stageA status value is not a pipeline stage on that site. The response lists valid_stages.
403demo_site_readonlyContacts on a demo site cannot be modified.
503stages_unreadablePipeline stages could not be read, so status could not be validated. Retry, or omit status.
409site_busy / generation_conflictGeneration is in progress — poll GET /sites/{id} and retry once idle.
409subdomain_takenChoose a different subdomain.
409duplicate_requestA create with this idempotency_key is still running.
409contact_conflictA concurrent write created one of the contacts in an import batch and the retry still conflicted. The batch is idempotent — send it again.
422contact_info_needs_confirmationThe site still carries AI-generated placeholder contact details. Fix them via refine, or resend publish with confirm_contact_info: true.
413file_too_largeAsset over the 20 MB (image) / 80 MB (video) cap.
429rate_limit_exceededToo many requests. Body includes retry_after_seconds; a Retry-After header is also set.
500internal_errorUnexpected failure — safe to retry.

Billing notes

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.

OpenAPI specification

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

View the spec →