Fyndol API & MCP

Fyndol is the all-in-one CRM and marketing platform for marketing agencies of any niche. Connect AI assistants and your own software to a Fyndol agency. Everything is limited to the agency (and sub-accounts) the person who connected can access.

MCP server (for AI apps): https://app.fyndol.com/mcp — Streamable HTTP, OAuth 2.1 sign-in (Dynamic Client Registration + PKCE). Add it as a custom connector in your AI app; you sign in to Fyndol and pick what it may do.
REST API: https://app.fyndol.com/api/v1 · OpenAPI 3.1: /api/v1/openapi.json

Authentication

curl https://app.fyndol.com/api/v1/contacts?limit=10 \
  -H "Authorization: Bearer <access token or API key>"

Scopes

ScopeWhat it allows
contacts.readRead contacts, tags and custom fields
conversations.readRead conversations (inbox summaries)
opportunities.readRead pipelines and opportunities
workflows.readRead workflows and their health
calendars.readRead calendars and appointments
sites.readRead funnels and websites
reports.readRead reports and KPIs
usage.readRead AI usage and estimated cost (agency owner/admin only)
changes.proposePropose changes (you approve them in Fyndol, unless a sub-account is set to “without asking”)
changes.autoAllow changes without approval (they can start your published workflows; the app still can’t send, delete, buy or see keys) (agency owner/admin only)

Changes and approvals

A change (POST /api/v1/actions or an MCP write tool) becomes a pending change with an approve_url, and the person approves it in Fyndol — unless the connection has changes.auto (owner/admin only) or the sub-account's AI changes setting is “without asking”; then it runs right away, the reply says why (reason) and the person is notified (onboarding a client, draft invoices and contracts, stopping workflow enrollments and turning on a contact's consent to be called or texted always wait). The contact, tag and opportunity endpoints below follow the same rule for OAuth tokens: they write directly when the change may run right away (otherwise 403 approval_required — use POST /api/v1/actions), and they also need contacts.read (contacts) or opportunities.read (opportunities), because they answer with the record. API keys write directly. No endpoint or tool sends texts or emails, deletes records, buys anything or reads keys — but a change such as adding a tag, creating a contact or moving a deal starts the sub-account's published workflows, which may text, email or call contacts.

Consent and Do Not Disturb

An API key or a connected app can never turn ON a contact's consent to be called or texted (custom fields like ai_call_consent, sms_consent, tcpa_consent, sms_opt_in) or turn OFF Do Not Disturb. Only the contact can give consent (a Fyndol form, booking page or their own reply), or a person sets it on the contact in Fyndol. POST /contacts still saves the contact without those values and lists them in ignored_fields and warnings (a note on the contact says what was sent); PATCH /contacts/{id} answers 403 and changes nothing; an MCP or /actions change that does it waits for a person's approval. Saying No, sending a Yes the contact already gave and turning Do Not Disturb on always work. Other fields, like terms_consent or gdpr_consent, save normally.

Pagination, limits and errors

Endpoints

EndpointParameters
GET/api/v1/sub-accounts
List sub-accounts
Sub-accounts (locations) this token can reach, with contact counts.
limit — Items per page
offset — Items to skip
GET/api/v1/conversations
List conversations
Recent conversations (inbox) with the last message preview, newest first. Scope: conversations.read.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
unread_only — Only unread conversations
limit — Items per page
offset — Items to skip
GET/api/v1/opportunities
List opportunities
Opportunities (deals), most recently updated first. Scope: opportunities.read.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
pipeline_id
stage_id
status — open | won | lost | abandoned
q — Name contains
limit — Items per page
offset — Items to skip
POST/api/v1/opportunities
Create an opportunity
Same rules as Create a contact, with opportunities.read instead of contacts.read for OAuth tokens; otherwise POST /actions (create_opportunity). With a "contact" object the contact is saved first (found by email / phone, or created): when that created or changed a contact, the reply has contact_id with contact_created or contact_updated — also in a 400 when the deal itself is refused (e.g. its stage needs a deal value), so you know the contact was saved.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
Body: pipeline_id, stage_id, contact_id, contact, name, value, status, assigned_to
GET/api/v1/tags
List tags
Tags with how many contacts have each. Scope: contacts.read.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
limit — Items per page
offset — Items to skip
GET/api/v1/custom-fields
List custom fields
Contact and opportunity custom fields. Scope: contacts.read.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
limit — Items per page
offset — Items to skip
GET/api/v1/workflows
List workflows
Workflows (automations): status, trigger, step count, contacts still inside. Scope: workflows.read.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
limit — Items per page
offset — Items to skip
GET/api/v1/workflows/{id}
Get a workflow
One workflow with its trigger and steps. Scope: workflows.read.
id*
GET/api/v1/workflow-health
Workflow health
Problems (errors/warnings) per workflow. Scope: workflows.read.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
limit — Items per page
offset — Items to skip
GET/api/v1/calendars
Calendars and upcoming appointments
Calendars plus appointments in a range (default: next 14 days). Scope: calendars.read.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
from — ISO date
to — ISO date
GET/api/v1/sites
Funnels and websites
Funnels/websites with pages, views and form submissions, plus custom domains. Scope: sites.read.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
GET/api/v1/reports/summary
KPI summary
New leads by source, opportunities created/won/lost, appointments, messages, form submissions over the last N days. Scope: reports.read.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
days — Look-back window (default 7)
GET/api/v1/reports/client
Client report
The Client Report numbers (what an agency shows its client). Scope: reports.read.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
range — this_month | last_month | last_7_days | last_30_days | …
from
to
GET/api/v1/reports/ai-setter
AI setter stats
AI call numbers: calls, answered, booked, outcomes, call length, show rate, by lead source. Scope: reports.read.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
period — this_week | last_week | last_4
include_test
GET/api/v1/invoices
List invoices
Invoices (newest first) with status, total, balance, due date and hosted link; plus outstanding / overdue totals. Scope: reports.read.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
status — draft | sent | viewed | partially_paid | paid | overdue | void | refunded
contact_id
limit — Items per page
offset — Items to skip
GET/api/v1/payments
List payments
Payments (transactions): amount, status, method, contact, invoice number; plus money collected in the last 30 days. Read-only. Scope: reports.read.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
status — succeeded | failed | refunded | partially_refunded
limit — Items per page
offset — Items to skip
GET/api/v1/contracts
List contracts
Contracts (newest first) with status, signers signed / total, total and dates; plus the contract templates. Read-only: contracts are only sent and signed inside Fyndol. Scope: contacts.read.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
status — draft | sent | viewed | partially_signed | signed | declined | expired | void
contact_id
limit — Items per page
offset — Items to skip
GET/api/v1/usage
AI usage (owner/admin)
AI calls and estimated cost for the agency or one sub-account. OAuth tokens of an owner/admin only. Scope: usage.read.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
from
to
GET/api/v1/contacts
Search contacts
Contacts, newest first. Scope: contacts.read.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
q — Name, email or phone contains
email
tag
limit — Items per page
offset — Items to skip
POST/api/v1/contacts
Create a contact
API keys with contacts.write. OAuth tokens with changes.propose (or changes.auto) and contacts.read, when the change may run without approval (the connection may make changes without approval, or the sub-account's AI changes setting is “without asking”); otherwise 403 approval_required: use POST /actions (create_contact). Consent to be called or texted (custom ai_call_consent, sms_consent, tcpa_consent, sms_opt_in…) is never turned on here: the contact is still saved without those values, they are listed in ignored_fields and warnings, and a note on the contact says what was sent.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
Body: first_name, last_name, email, phone, company, source, tags, custom, upsert
GET/api/v1/contacts/{id}
Get a contact
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
id*
PATCH/api/v1/contacts/{id}
Update a contact
Same rules as Create a contact, except that turning on consent to be called or texted (custom ai_call_consent, sms_consent, tcpa_consent…) or turning dnd off answers 403 and changes nothing. Sending No, a Yes the contact already gave, or dnd: true works. An empty body changes nothing.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
id*
Body: first_name, last_name, email, phone, company, custom, dnd, assigned_to
POST/api/v1/contacts/{id}/tags
Add tags to a contact
Same rules as Create a contact.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
id*
Body: tags
GET/api/v1/appointments
List appointments
Default range: last 7 days to next 60 days. Scope: calendars.read.
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
from
to
calendar_id
contact_id
status
GET/api/v1/pipelines
List pipelines with stages
location_id — Sub-account id (not needed for API keys, tokens limited to one sub-account, or agencies with one sub-account)
POST/api/v1/actions
Propose a change (OAuth)
Creates a change. By default it waits for the user's approval in Fyndol (Settings → Connect AI apps → Approvals) and returns 202 with approve_url; if the connection may make changes without approval, or the sub-account's AI changes setting is “without asking”, it runs right away. Tools: stop_active_runs, create_pipeline, add_pipeline_stage, rename_pipeline_stage, create_opportunity, move_opportunity, set_opportunity_status, create_contact, update_contact, tag_contacts, untag_contacts, create_tag, add_contact_note, create_task, create_workflow, update_workflow, create_workflow_draft, create_report_share_link, onboard_client, create_draft_invoice, create_draft_contract. onboard_client, stop_active_runs, create_draft_invoice and create_draft_contract always wait for approval. No tool sends messages, deletes records, bills or reveals keys, but tag, contact and opportunity changes can start the sub-account's published workflows, which may send texts or emails.
—
Body: tool, input
GET/api/v1/actions/{id}
Status of a proposed change
id*

Sub-account ids come from GET /api/v1/sub-accounts. Tokens limited to one sub-account (and API keys) don't need location_id.

Inbound webhooks (leads from website forms, Zapier, Make)

Make an inbound URL in Settings → Webhooks & API → Inbound webhooks. POST JSON (or form fields) to it; no key or login is needed (the URL is the secret). Send at least one of email, phone, first_name (or name); last_name, company and tags (only used when the URL allows them) are optional, and any other field can be used in that URL's workflows as {{webhook.<field>}}.

curl -X POST '<your inbound webhook URL>' \
  -H 'Content-Type: application/json' \
  -d '{"first_name":"Jane","email":"jane@example.com","phone":"5105550100","message":"Need a quote"}'
# → {"ok":true}