Focus Flow is an MCP server: your AI assistant can look at your real studio — clients, jobs, calendar, unpaid invoices — and act on it, with your permission. You sign in with your Focus Flow account; there are no keys to copy.
https://focusflowstudio.app/api/mcphttps://focusflowstudio.app/api/mcp — same Focus Flow sign-inYou choose per-connection which areas the assistant can touch (read CRM, read finances, send email…). Anything that leaves the studio — an email, a calendar event — first comes back as a preview to confirm: nothing is sent until you say so. You can revoke a connected assistant anytime from Settings → Integrations.
Available on every plan. On Starter, assistant actions share the monthly Flowy AI quota (50/month); Studio is unlimited.
Headless clients authenticate with a brand API key instead of the sign-in flow. Create one in Settings → Integrations → Focus Flow MCP, then point your client at the same URL with a Bearer header:
# Claude Code
claude mcp add --transport http focus-flow https://focusflowstudio.app/api/mcp \
--header "Authorization: Bearer ffs_live_..."
# Any MCP client (generic config)
{
"mcpServers": {
"focus-flow": {
"type": "http",
"url": "https://focusflowstudio.app/api/mcp",
"headers": { "Authorization": "Bearer ffs_live_..." }
}
}
}The tool list is filtered by the key’s scopes — a read-only key never even sees send_email. 24 tools are exposed: studio overview, client/lead/job search, schedule and availability, gallery stats, unpaid invoices and revenue, price list, plus guarded actions (create lead, update job status, calendar events, email with two-step confirm).
Base URL https://focusflowstudio.app/api/v1. Create an API key in Settings → Integrations → Focus Flow MCP (it is shown once — store it like a password), then:
# 1. Verify the key — who am I?
curl https://focusflowstudio.app/api/v1/me \
-H "Authorization: Bearer ffs_live_..."
# 2. List your clients
curl "https://focusflowstudio.app/api/v1/clients?limit=10" \
-H "Authorization: Bearer ffs_live_..."
# 3. Create a lead (the API intake for new inquiries)
curl -X POST https://focusflowstudio.app/api/v1/leads \
-H "Authorization: Bearer ffs_live_..." \
-H "Content-Type: application/json" \
-d '{"firstName":"Anna","lastName":"Rossi","email":"anna@example.com","phone":"+39 333 1234567"}'Every request carries Authorization: Bearer ffs_live_…. A key belongs to one brand: everything you read or write is scoped to that studio — the brandId can never be switched from the request body. Keys are stored hashed, can expire, and are revocable instantly from the panel.
| Scope | Grants |
|---|---|
crm:read | Read clients, leads, jobs |
crm:write | Create/update clients and leads, update jobs |
galleries:read | Read galleries and their public share links |
bookings:read | Read booking requests |
bookings:write | Approve / decline booking requests |
calendar:read | Read calendar events and availability |
calendar:write | Create/update calendar events |
finance:read | Read documents (quotes, invoices, contracts) and payments |
catalog:read | Read services, packages and pricing |
email:send | Send email to your clients (explicit opt-in, never default) |
Defaults at creation: all read scopes + crm:write. email:send and bookings:write are explicit opt-ins.
Every response uses the same envelope:
// success // failure
{ {
"success": true, "success": false,
"data": { ... }, "error": "human message",
"meta": { "nextCursor": "...", "count": 25 } "code": "machine_code"
} }Lists are cursor-paginated: pass ?limit= (default 25, max 100) and feed meta.nextCursor back as ?cursor= until it comes back null. Cursors are opaque — never build one by hand. Entities are JSON objects in camelCase.
| Endpoint | Scope | Notes |
|---|---|---|
GET /v1/me | — | Verify a key: brand, scopes, plan and rate limits |
GET·POST /v1/clients · GET·PATCH /v1/clients/{id} | crm:read / crm:write | CRM clients |
GET·POST /v1/leads · GET·PATCH /v1/leads/{id} | crm:read / crm:write | Leads — POST is the API intake for new inquiries |
GET /v1/jobs · GET·PATCH /v1/jobs/{id} | crm:read / crm:write | Jobs (created in-app; paidAmount is read-only) |
GET /v1/galleries · GET /v1/galleries/{id} | galleries:read | Galleries with shareUrl for published ones |
GET /v1/bookings · GET·PATCH /v1/bookings/{id} | bookings:read / bookings:write | Booking requests — PATCH approves or declines |
GET·POST /v1/calendar/events · GET·PATCH /v1/calendar/events/{id} | calendar:read / calendar:write | Calendar with non-blocking conflict check on create |
GET /v1/documents · GET /v1/documents/{id} | finance:read | Quotes, invoices, contracts (read-only) |
GET /v1/payments | finance:read | Payments (read-only) |
GET /v1/services · GET /v1/packages | catalog:read | Price list: services and packages with items |
POST /v1/emails/send | email:send | Email to your clients — max 2 recipients, GDPR consent enforced |
Deliberate boundaries: bookings cannot be created via API (they are born in your public booking page — use POST /v1/leads for intake), lead conversion and job creation stay in the app, and galleries, documents and payments are read-only in v1.
| Status | Codes | What to do |
|---|---|---|
400 | validation_error · invalid_json · invalid_cursor | Bad input; details lists field errors |
401 | invalid_key · key_revoked · key_expired | Fix or rotate the key in Settings → Integrations |
403 | invalid_scope · access_revoked · recipient_not_client · recipients_blocked | Missing scope, or email guardrails stopped the send |
404 | not_found | Resource does not exist in this brand |
409 | duplicate_email · unsupported_transition | Conflict with current state |
422 | email_not_configured | Set up your sender in Settings → Email first |
429 | rate_limited | Respect Retry-After and X-RateLimit-* headers |
5xx | internal_error · email_send_failed | Retry with backoff; contact us if it persists |
| Plan | REST API | MCP tool calls |
|---|---|---|
| Starter / Trial | 60 req/min · 5,000/day | Shares the Flowy AI monthly quota (Starter 50/month, Trial unlimited) |
| Studio | 300 req/min · 25,000/day | Unlimited |
Watch X-RateLimit-Limit / X-RateLimit-Remaining on every response; a 429 includes Retry-After.
The full machine-readable spec lives at /api/v1/openapi.json (OpenAPI 3.1) — point your generator, Postman or your LLM at it. Prefer it browsable? Open the interactive reference.
Write to us — integrations are a first-class feature, not an afterthought. If something in the API blocks you, we want to know.