Connected inboxes
REST endpoints to list, connect, reconnect and disconnect the inboxes your organization's members connect to Fondaro, choose which of an inbox's calendars show, read the price of the next one, and ask an admin for a paid one.
Overview
A connected inbox is one account a member of your organization has connected to Fondaro: their Google or Microsoft email with its calendar, or another mailbox. The API calls it a connected account. Each member connects at most one inbox of each kind.
The endpoints on this page use the dashboard's Clerk bearer token and resolve the organization from the request. Every member can list, connect, reconnect and disconnect their own inboxes. Listing the whole organization's inboxes, and disconnecting another member's, is limited to organization admins; a member receives 403.
Connecting is a browser round trip: you ask Fondaro for a link, send the person to it, and they come back to the dashboard once they have signed in and allowed access. The API never receives a password.
Endpoints
| Method & path | Who | Purpose |
|---|---|---|
GET /connected-accounts | Any member | Your own connected inboxes |
GET /connected-accounts/organization | Admin | Every member's connected inboxes |
GET /connected-accounts/price | Any member | What the next inbox costs your organization, and whether you may add a paid one |
POST /connected-accounts/connect | Any member | Start connecting an inbox; returns a link |
POST /connected-accounts/requests | Member | Ask your admins for an inbox your plan does not include |
GET /connected-accounts/requests | Admin | The members' open inbox requests |
POST /connected-accounts/requests/:id/allow | Admin | Allow a member one paid inbox of that kind |
POST /connected-accounts/requests/:id/decline | Admin | Decline a request |
POST /connected-accounts/:id/reconnect | Owner | Start reconnecting an inbox; returns a link |
DELETE /connected-accounts/:id | Owner or admin | Disconnect an inbox |
PUT /connected-accounts/:id/calendars/:calendarId | Owner | Show or hide one of the inbox's calendars |
POST /connected-accounts/:id/calendars/refresh | Owner | List the inbox's calendars again |
GET /crm/email-sender | Any member | Which address your CRM email goes out from |
POST /crm/leads/:id/emails | Admin or lead assignee | Send an email to a lead from your inbox |
POST /crm/leads/:id/find-past-emails | Admin or lead assignee | Search your inbox for a lead's older emails and add them to the lead |
GET /crm/leads/:id/channels | Admin or lead assignee | Which message channels you can use for this lead |
POST /crm/leads/:id/messages | Admin or lead assignee | Send a WhatsApp, Instagram, LinkedIn or Telegram message to a lead |
GET /crm/leads/:id/messages/:messageId/attachments/:index | Admin or lead assignee | A short-lived link to a file on a message |
POST /crm/leads/:id/lookup-profile | Admin or lead assignee | Read a lead's linked LinkedIn or Instagram profile once |
The connected account object
| Field | Type | Notes |
|---|---|---|
id | string | UUID |
kind | string | mailbox today; shared_mailbox, whatsapp, linkedin, instagram and telegram are reserved for later channels |
provider | string | google, microsoft or imap (Other mailbox) |
address | string or null | The connected email address, once known |
status | string | connecting, ok, needs_reconnect, error or disconnected |
statusReason | string or null | Why the status last changed, when there is a reason |
statusChangedAt | string | ISO-8601 |
capabilities | string[] | What currently works through this inbox: email, calendar, messaging |
connectedAt | string or null | ISO-8601 |
disconnectedAt | string or null | ISO-8601; always null in list responses |
ownerUserId | string | Clerk user id of the member who connected it |
calendars | object[] | Only on an inbox with the calendar capability: its calendars, the primary first, then by name. Empty until the first sync lists them. See Calendars |
needs_reconnect means the provider stopped accepting the connection (a changed password, or access removed in the Google or Microsoft account settings). Reconnect restores it without changing the id.
List your inboxes
GET /connected-accounts returns your live inboxes, the ones not disconnected. available is false when connecting inboxes is not available on this Fondaro environment yet; accounts is then empty.
curl 'https://api.fondaro.com/connected-accounts' \
-H 'Authorization: Bearer <clerk-session-token>'{
"available": true,
"accounts": [
{
"id": "3c9e2f41-7b1d-4c8a-9e53-0a6d2b7f1e84",
"kind": "mailbox",
"provider": "google",
"address": "agent@example.com",
"status": "ok",
"statusReason": null,
"statusChangedAt": "2026-09-25T09:14:03.000Z",
"capabilities": ["email", "calendar"],
"connectedAt": "2026-09-25T09:14:03.000Z",
"disconnectedAt": null,
"ownerUserId": "user_2a",
"calendars": [
{ "id": "agent@example.com", "name": "agent@example.com", "primary": true, "readOnly": false, "selected": true },
{ "id": "viewings@group.calendar.google.com", "name": "Viewings", "primary": false, "readOnly": false, "selected": true },
{ "id": "es.spain#holiday@group.v.calendar.google.com", "name": "Holidays in Spain", "primary": false, "readOnly": true, "selected": false }
]
}
]
}List the organization's inboxes
GET /connected-accounts/organization (admin only) returns every member's live inboxes as an array. Each item is a connected account object with an owner added:
curl 'https://api.fondaro.com/connected-accounts/organization' \
-H 'Authorization: Bearer <clerk-session-token>'[
{
"id": "3c9e2f41-7b1d-4c8a-9e53-0a6d2b7f1e84",
"kind": "mailbox",
"provider": "microsoft",
"address": "agent@example.com",
"status": "needs_reconnect",
"statusReason": "credentials",
"statusChangedAt": "2026-09-25T11:02:40.000Z",
"capabilities": ["email", "calendar"],
"connectedAt": "2026-09-20T08:30:00.000Z",
"disconnectedAt": null,
"ownerUserId": "user_2b",
"owner": { "name": "Agent Name", "email": "agent@example.com", "imageUrl": null }
}
]Read the price
GET /connected-accounts/price returns what the next inbox would cost your organization. included is true while your plan's included inboxes are not all in use (one per person on your plan). amountCents is the monthly price of each further inbox in your billing currency. canAddPaid is true for an admin: only an admin adds an inbox your plan does not include. A member asks instead (see Ask for an inbox), and requests lists their own open requests, at most one per kind: pending while it waits for an admin, allowed once an admin allowed it and until that inbox connects.
curl 'https://api.fondaro.com/connected-accounts/price' \
-H 'Authorization: Bearer <clerk-session-token>'{
"included": false,
"amountCents": 1000,
"currency": "EUR",
"canAddPaid": false,
"requests": [
{
"id": "6f0c1d2e-...",
"kind": "whatsapp",
"provider": "whatsapp",
"status": "pending",
"askedAt": "2026-09-30T09:12:00.000Z",
"answeredAt": null
}
]
}See Connected inboxes: Price for when an inbox is charged.
Connect an inbox
POST /connected-accounts/connect starts a connection and returns a url. Open it in the person's browser. After they sign in and allow access they return to the matching page under Organization > Integrations, with ?connected=<provider> on success or ?connect_error=<reason> on failure.
| Body field | Type | Notes |
|---|---|---|
provider | string | google, microsoft or imap |
kind | string, optional | Defaults to mailbox |
curl -X POST 'https://api.fondaro.com/connected-accounts/connect' \
-H 'Authorization: Bearer <clerk-session-token>' \
-H 'Content-Type: application/json' \
-d '{ "provider": "google" }'{ "url": "https://..." }The inbox appears in GET /connected-accounts once the provider confirms it, usually a few seconds after the person returns; poll until its status is ok. If the inbox is not your organization's included one, its first month is charged when it connects.
| Status | Code | Meaning |
|---|---|---|
409 | already_connected | You already have a live inbox of this kind |
403 | sandbox | Sandbox organizations cannot connect inboxes |
403 | admin_approval_required | You are a member, your plan's included inboxes are in use, and no admin allowed you one of this kind. Ask for one |
402 | not_entitled | Your organization has no active plan or trial |
503 | not_available | Connecting inboxes is not available on this environment yet |
Reconnecting an inbox you already have is never refused this way.
Ask for an inbox
Once your plan's included inboxes are in use, only an admin adds another one, because it is billed monthly. A member asks with POST /connected-accounts/requests, with the same body as connect. Your organization's admins see the request in their Inbox and on Inboxes, and get a notification. Asking again for the same kind returns the open request unchanged.
curl -X POST 'https://api.fondaro.com/connected-accounts/requests' \
-H 'Authorization: Bearer <clerk-session-token>' \
-H 'Content-Type: application/json' \
-d '{ "provider": "whatsapp", "kind": "whatsapp" }'{
"id": "6f0c1d2e-...",
"kind": "whatsapp",
"provider": "whatsapp",
"status": "pending",
"askedAt": "2026-09-30T09:12:00.000Z",
"answeredAt": null
}| Status | Code | Meaning |
|---|---|---|
409 | not_needed | You are an admin, or your plan still includes your next inbox: connect it directly |
409 | already_connected | You already have a live inbox of this kind |
403 | ORG_ADMIN_REQUIRED | The shared mailbox is connected by an admin |
403 | sandbox | Sandbox organizations cannot connect inboxes |
402 | not_entitled | Your organization has no active plan or trial |
An admin lists open requests with GET /connected-accounts/requests. Each row adds userId, requester (name, email, imageUrl) and the monthly price (amountCents, currency). POST /connected-accounts/requests/:id/allow lets that member connect one inbox of that kind: their POST /connected-accounts/connect then succeeds, the inbox is billed like any extra inbox, and the member gets a notification. The allowance is used once that inbox connects. POST /connected-accounts/requests/:id/decline closes the request; the member can ask again. Answering a request that is no longer open returns 409 request_closed (allowing an allowed request again returns it unchanged).
Reconnect an inbox
POST /connected-accounts/:id/reconnect returns a url the same way. Only the member who connected the inbox can reconnect it. The inbox keeps its id, and reconnecting is never charged again.
curl -X POST 'https://api.fondaro.com/connected-accounts/3c9e2f41-7b1d-4c8a-9e53-0a6d2b7f1e84/reconnect' \
-H 'Authorization: Bearer <clerk-session-token>'{ "url": "https://..." }Disconnect an inbox
DELETE /connected-accounts/:id disconnects the inbox straight away and answers 204 with no body. The member who connected it can disconnect it, and so can any organization admin. Conversations already saved to leads stay. There is no refund for the current month.
curl -X DELETE 'https://api.fondaro.com/connected-accounts/3c9e2f41-7b1d-4c8a-9e53-0a6d2b7f1e84' \
-H 'Authorization: Bearer <clerk-session-token>'Calendars
A Google or Microsoft inbox with the calendar capability carries its calendars in calendars. Each entry:
| Field | Type | Notes |
|---|---|---|
id | string | The calendar's id as the provider lists it. URL-encode it in a path: it may contain @ or # |
name | string or null | As named in Google or Outlook |
primary | boolean | The account's main calendar. It always shows and cannot be hidden |
readOnly | boolean | Shared with the person for reading only. It can show, but new events never go to it |
selected | boolean | Its events show on the person's calendar. A newly listed calendar starts hidden, except the primary |
These routes act on your own inbox only; any other inbox, or one without calendar, is 404. Both answer { "calendars": [...] }, the inbox's calendars after the change, in the same order as on the inbox. To send a new event to one of these calendars, pass connectedAccountId and calendarId to POST /calendar/events.
Show or hide a calendar
PUT /connected-accounts/:id/calendars/:calendarId with { "selected": true } shows the calendar, false hides it. Fondaro reads the inbox's calendars straight after: a calendar you show brings its events in, and one you hide takes them off the calendar.
curl -X PUT 'https://api.fondaro.com/connected-accounts/3c9e2f41-7b1d-4c8a-9e53-0a6d2b7f1e84/calendars/viewings%40group.calendar.google.com' \
-H 'Authorization: Bearer <clerk-session-token>' \
-H 'Content-Type: application/json' \
-d '{ "selected": false }'{
"calendars": [
{ "id": "agent@example.com", "name": "agent@example.com", "primary": true, "readOnly": false, "selected": true },
{ "id": "viewings@group.calendar.google.com", "name": "Viewings", "primary": false, "readOnly": false, "selected": false }
]
}List the calendars again
POST /connected-accounts/:id/calendars/refresh asks the provider for the inbox's calendars again, for one made in Google or Outlook after connecting. It takes no body.
curl -X POST 'https://api.fondaro.com/connected-accounts/3c9e2f41-7b1d-4c8a-9e53-0a6d2b7f1e84/calendars/refresh' \
-H 'Authorization: Bearer <clerk-session-token>'| Status | Code | Meaning |
|---|---|---|
400 | selected is missing or not a boolean | |
404 | CONNECTED_ACCOUNT_NOT_FOUND | Not your inbox, disconnected, or without calendar |
404 | CALENDAR_NOT_FOUND | The inbox does not list that calendar (try List the calendars again) |
409 | CALENDAR_PRIMARY_LOCKED | Hiding the primary calendar |
409 | CONNECTED_ACCOUNT_NEEDS_RECONNECT | Refresh on an inbox that needs reconnecting |
Which address your email goes out from
GET /crm/email-sender tells you how an email you send to a lead right now would leave, so a composer can show the From address or the Connect step before anyone writes.
curl 'https://api.fondaro.com/crm/email-sender' \
-H 'Authorization: Bearer <clerk-session-token>'{
"via": "mailbox",
"address": "sofia@example.com",
"status": "ok",
"accountId": "3c9e2f41-7b1d-4c8a-9e53-0a6d2b7f1e84",
"options": [
{
"accountId": "3c9e2f41-7b1d-4c8a-9e53-0a6d2b7f1e84",
"address": "sofia@example.com",
"via": "mailbox"
},
{
"accountId": "8d2a61f0-5c3e-4b7a-a1d9-6e4f0b2c7a15",
"address": "info@example.com",
"via": "shared_mailbox"
}
]
}| Field | Type | Meaning |
|---|---|---|
via | "mailbox" | "none" | mailbox: your connected inbox sends. none: there is nothing to send from |
address | string | null | The From address: your inbox's address, or null without an inbox |
status | string | null | Your inbox's status (connecting, ok, needs_reconnect, error), or null without an inbox |
accountId | string | null | The connected account id, or null without an inbox |
options | object[] | Every address you can pick as From, each { accountId, address, via }: your own inbox (via: "mailbox") and your agency's shared inbox when you are the admin who connected it (via: "shared_mailbox"). Only inboxes whose status is ok are listed; empty when nothing can send |
An inbox whose status is needs_reconnect or error answers via: "mailbox" with that status: sending waits for a reconnect.
Send an email to a lead
POST /crm/leads/:id/emails sends from your connected inbox. The From address is your inbox unless you pick another of your options with fromAccountId; senderName and senderEmail are still accepted for older clients and ignored. The message lands in your inbox's Sent folder, and the lead's reply comes back to your inbox.
| Field | Type | Required | Notes |
|---|---|---|---|
subject | string | Yes | |
bodyText | string | Yes | The plain version. Always stored, and sent as paragraphs when there is no bodyHtml |
bodyHtml | string | No | The formatted body, at most 100,000 characters. Sent instead of bodyText's paragraphs after it is cleaned to p, br, strong, b, em, i, u, s, ul, ol, li, blockquote and a (http, https or mailto links only; no other attributes) |
fromAccountId | string | No | The accountId of one of your options from GET /crm/email-sender. Without it the email goes out from your default |
cc | string[] | No | Up to 10 email addresses. An address already in To is dropped, and each address is kept once |
bcc | string[] | No | Up to 10 email addresses. An address already in To or Cc is dropped |
signatureHtml | string | No | Your signature. Cleaned before sending: layout tags, links and https images stay; style and other attributes go |
replyToEmailId | string | No | The id of an email on this lead's timeline you are answering. The message is sent as a reply to it, so it threads in the lead's inbox |
documentIds | string[] | No | See Documents |
curl -X POST 'https://api.fondaro.com/crm/leads/4821/emails' \
-H 'Authorization: Bearer <clerk-session-token>' \
-H 'Content-Type: application/json' \
-d '{
"subject": "Re: The villa in Nueva Andalucía",
"bodyText": "Saturday at 11 works for me.",
"replyToEmailId": "b7e1c5d2-4a3f-4e8b-9c61-2d0f7a9e5b13"
}'With formatting, a copy and your agency's shared inbox as From:
curl -X POST 'https://api.fondaro.com/crm/leads/4821/emails' \
-H 'Authorization: Bearer <clerk-session-token>' \
-H 'Content-Type: application/json' \
-d '{
"subject": "Three homes for Saturday",
"bodyText": "Here are the three homes:\n- Villa Azul\n- Casa Sol\n- Finca Olivo",
"bodyHtml": "<p>Here are the <strong>three homes</strong>:</p><ul><li>Villa Azul</li><li>Casa Sol</li><li>Finca Olivo</li></ul>",
"fromAccountId": "8d2a61f0-5c3e-4b7a-a1d9-6e4f0b2c7a15",
"cc": ["partner@example.com"],
"bcc": ["office@example.com"]
}'The email on the lead's timeline carries toAddresses, ccAddresses and bccAddresses.
| Status | Code | Meaning |
|---|---|---|
400 | sender_not_allowed | fromAccountId is not one of your options |
403 | mailbox_not_connected | You have no connected inbox. Connect an inbox first |
403 | mailbox_needs_reconnect | Your inbox needs reconnecting. Use POST /connected-accounts/:id/reconnect |
403 | SANDBOX_OUTBOUND_DISABLED | Nothing is sent from a sandbox organization |
Find past emails for a lead
POST /crm/leads/:id/find-past-emails searches your own connected inboxes for the lead's email address over the last 12 months and queues what it finds. The emails then go through the same checks as new mail and appear on the lead's timeline within a minute or two; an email that involves none of your leads is never stored. Use it for a lead created after you connected your inbox: connecting brings in the last 90 days once, and a lead added later does not pull older emails on its own. Collaborator leads are open to every member.
It answers 202 with a summary. found counts emails involving one of your leads, enqueued those not already on Fondaro. Calling it again is safe: emails already saved are not added twice.
curl -X POST 'https://api.fondaro.com/crm/leads/4821/find-past-emails' \
-H 'Authorization: Bearer <clerk-session-token>'{ "mailboxes": 1, "found": 6, "enqueued": 4 }| Status | Code | Meaning |
|---|---|---|
400 | lead_has_no_email | The lead has no email address to search for |
403 | You are not an admin or an assignee of this lead | |
404 | No such lead in your organization | |
409 | mailbox_not_connected | You have no connected inbox. Connect one first |
409 | mailbox_needs_reconnect | Your inbox needs reconnecting. Use POST /connected-accounts/:id/reconnect |
Message channels for a lead
GET /crm/leads/:id/channels lists the channels you can message this lead on. A channel is listed when you have a live WhatsApp, Instagram, LinkedIn or Telegram connection and the lead is reachable there: for WhatsApp, the lead has written to you or their phone number is on WhatsApp; for the others, the lead has written to you or a member linked their profile to the lead. An empty list means no message channel for this lead.
curl 'https://api.fondaro.com/crm/leads/4821/channels' \
-H 'Authorization: Bearer <clerk-session-token>'{
"channels": [
{
"channel": "whatsapp",
"canSend": false,
"block": "new_chats_closed",
"opensAt": "2026-09-26T09:14:00.000Z",
"integration": "whatsapp",
"accountAddress": "+34600000001",
"counterpart": "+34600000002",
"newChat": true
}
]
}block says why canSend is false: needs_reconnect (reconnect the account), restricted (the provider restricted the account; nothing more is sent from it), or new_chats_closed (a first message to someone new waits until opensAt; replies are never held).
Send a message to a lead
POST /crm/leads/:id/messages sends from your own connection of that channel. A person sends every message: there is no scheduled, bulk or automated sending.
| Field | Type | Required | Notes |
|---|---|---|---|
channel | string | Yes | whatsapp, instagram, linkedin or telegram |
body | string | Yes | Up to 4,000 characters. May be empty when a file goes alone |
attachments | string[] | No | Up to 3 Documents PDF ids, 10 MB together |
curl -X POST 'https://api.fondaro.com/crm/leads/4821/messages' \
-H 'Authorization: Bearer <clerk-session-token>' \
-H 'Content-Type: application/json' \
-d '{ "channel": "whatsapp", "body": "Saturday at 11 works for me." }'It answers with the message (status sent). To protect your account, messages from one account go at least 15 seconds apart (Instagram 10 seconds, and at most 10 an hour). A message sent inside that gap waits for its turn, for up to 30 seconds, and shows on the lead as pending meanwhile. A first message to someone new is held for 24 hours after you connect or reconnect, then limited to 3 a day in the first week and 20 a day after.
| Status | Code | Meaning |
|---|---|---|
403 | message_not_connected | You have no connection of that channel. integration names the setup page |
403 | message_needs_reconnect | Your connection needs reconnecting |
403 | message_account_restricted | The provider restricted the account; nothing more is sent from it |
403 | message_new_chats_closed | New chats open at opensAt |
403 | SANDBOX_OUTBOUND_DISABLED | Nothing is sent from a sandbox organization |
404 | No such lead, or you are not an admin or an assignee of it | |
422 | message_not_on_channel | The lead is not on WhatsApp, or has no linked profile on that channel |
429 | message_paced / message_rate_limited | Try again after retryAfterSeconds |
502 | message_send_failed | The message could not be sent. Try again |
Download a file on a message
GET /crm/leads/:id/messages/:messageId/attachments/:index returns a short-lived download link for the file at position index in the message's attachments. A file that was not kept (stored: false) answers 404, and so does a Documents file you sent, which you open from Documents.
curl 'https://api.fondaro.com/crm/leads/4821/messages/6a1f0c3e-2b7d-4e59-8c14-9d3a5e7b2f60/attachments/0' \
-H 'Authorization: Bearer <clerk-session-token>'{ "url": "https://…", "filename": "terrace.jpg", "contentType": "image/jpeg" }Look up a lead's profile
POST /crm/leads/:id/lookup-profile reads the lead's linked LinkedIn or Instagram profile once, through your own connection, without the lead seeing a profile visit. It changes nothing on the lead; add the details you want yourself. LinkedIn allows about 80 look ups a day per account, Instagram 10 an hour.
curl -X POST 'https://api.fondaro.com/crm/leads/4821/lookup-profile' \
-H 'Authorization: Bearer <clerk-session-token>' \
-H 'Content-Type: application/json' \
-d '{ "channel": "linkedin" }'{
"channel": "linkedin",
"identity": "sample-buyer",
"name": "Sample Buyer",
"headline": "Relocating to Marbella",
"company": "Example Ltd",
"location": "London",
"category": null,
"emails": [],
"phones": [],
"profileUrl": "https://www.linkedin.com/in/sample-buyer"
}| Status | Code | Meaning |
|---|---|---|
422 | lookup_not_linked | Link the lead on that channel first |
404 | lookup_not_found | The profile could not be found |
429 | lookup_limited | Try again after retryAfterSeconds |
Related Articles
Connected inboxes guide
Connect your email, calendar and messaging accounts from Inboxes, see what Fondaro keeps, reconnect or disconnect them, and ask your admin for an extra inbox.
API Overview
Introduction to the Fondaro API: base URL, authentication, scopes, OpenAPI, response format, errors, pagination and rate limits.
Billing
Your plan, what you used this month, your credit, billing details and invoices, all on one page.
Calendar API
REST endpoints for the calendar: one window of events, viewings, open houses and expected closes, what is not done, busy time, and creating, moving, ticking and cancelling events.