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 & pathWhoPurpose
GET /connected-accountsAny memberYour own connected inboxes
GET /connected-accounts/organizationAdminEvery member's connected inboxes
GET /connected-accounts/priceAny memberWhat the next inbox costs your organization, and whether you may add a paid one
POST /connected-accounts/connectAny memberStart connecting an inbox; returns a link
POST /connected-accounts/requestsMemberAsk your admins for an inbox your plan does not include
GET /connected-accounts/requestsAdminThe members' open inbox requests
POST /connected-accounts/requests/:id/allowAdminAllow a member one paid inbox of that kind
POST /connected-accounts/requests/:id/declineAdminDecline a request
POST /connected-accounts/:id/reconnectOwnerStart reconnecting an inbox; returns a link
DELETE /connected-accounts/:idOwner or adminDisconnect an inbox
PUT /connected-accounts/:id/calendars/:calendarIdOwnerShow or hide one of the inbox's calendars
POST /connected-accounts/:id/calendars/refreshOwnerList the inbox's calendars again
GET /crm/email-senderAny memberWhich address your CRM email goes out from
POST /crm/leads/:id/emailsAdmin or lead assigneeSend an email to a lead from your inbox
POST /crm/leads/:id/find-past-emailsAdmin or lead assigneeSearch your inbox for a lead's older emails and add them to the lead
GET /crm/leads/:id/channelsAdmin or lead assigneeWhich message channels you can use for this lead
POST /crm/leads/:id/messagesAdmin or lead assigneeSend a WhatsApp, Instagram, LinkedIn or Telegram message to a lead
GET /crm/leads/:id/messages/:messageId/attachments/:indexAdmin or lead assigneeA short-lived link to a file on a message
POST /crm/leads/:id/lookup-profileAdmin or lead assigneeRead a lead's linked LinkedIn or Instagram profile once

The connected account object

FieldTypeNotes
idstringUUID
kindstringmailbox today; shared_mailbox, whatsapp, linkedin, instagram and telegram are reserved for later channels
providerstringgoogle, microsoft or imap (Other mailbox)
addressstring or nullThe connected email address, once known
statusstringconnecting, ok, needs_reconnect, error or disconnected
statusReasonstring or nullWhy the status last changed, when there is a reason
statusChangedAtstringISO-8601
capabilitiesstring[]What currently works through this inbox: email, calendar, messaging
connectedAtstring or nullISO-8601
disconnectedAtstring or nullISO-8601; always null in list responses
ownerUserIdstringClerk user id of the member who connected it
calendarsobject[]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 fieldTypeNotes
providerstringgoogle, microsoft or imap
kindstring, optionalDefaults 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.

StatusCodeMeaning
409already_connectedYou already have a live inbox of this kind
403sandboxSandbox organizations cannot connect inboxes
403admin_approval_requiredYou are a member, your plan's included inboxes are in use, and no admin allowed you one of this kind. Ask for one
402not_entitledYour organization has no active plan or trial
503not_availableConnecting 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
}
StatusCodeMeaning
409not_neededYou are an admin, or your plan still includes your next inbox: connect it directly
409already_connectedYou already have a live inbox of this kind
403ORG_ADMIN_REQUIREDThe shared mailbox is connected by an admin
403sandboxSandbox organizations cannot connect inboxes
402not_entitledYour 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:

FieldTypeNotes
idstringThe calendar's id as the provider lists it. URL-encode it in a path: it may contain @ or #
namestring or nullAs named in Google or Outlook
primarybooleanThe account's main calendar. It always shows and cannot be hidden
readOnlybooleanShared with the person for reading only. It can show, but new events never go to it
selectedbooleanIts 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>'
StatusCodeMeaning
400selected is missing or not a boolean
404CONNECTED_ACCOUNT_NOT_FOUNDNot your inbox, disconnected, or without calendar
404CALENDAR_NOT_FOUNDThe inbox does not list that calendar (try List the calendars again)
409CALENDAR_PRIMARY_LOCKEDHiding the primary calendar
409CONNECTED_ACCOUNT_NEEDS_RECONNECTRefresh 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"
    }
  ]
}
FieldTypeMeaning
via"mailbox" | "none"mailbox: your connected inbox sends. none: there is nothing to send from
addressstring | nullThe From address: your inbox's address, or null without an inbox
statusstring | nullYour inbox's status (connecting, ok, needs_reconnect, error), or null without an inbox
accountIdstring | nullThe connected account id, or null without an inbox
optionsobject[]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.

FieldTypeRequiredNotes
subjectstringYes
bodyTextstringYesThe plain version. Always stored, and sent as paragraphs when there is no bodyHtml
bodyHtmlstringNoThe 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)
fromAccountIdstringNoThe accountId of one of your options from GET /crm/email-sender. Without it the email goes out from your default
ccstring[]NoUp to 10 email addresses. An address already in To is dropped, and each address is kept once
bccstring[]NoUp to 10 email addresses. An address already in To or Cc is dropped
signatureHtmlstringNoYour signature. Cleaned before sending: layout tags, links and https images stay; style and other attributes go
replyToEmailIdstringNoThe 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
documentIdsstring[]NoSee 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.

StatusCodeMeaning
400sender_not_allowedfromAccountId is not one of your options
403mailbox_not_connectedYou have no connected inbox. Connect an inbox first
403mailbox_needs_reconnectYour inbox needs reconnecting. Use POST /connected-accounts/:id/reconnect
403SANDBOX_OUTBOUND_DISABLEDNothing 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 }
StatusCodeMeaning
400lead_has_no_emailThe lead has no email address to search for
403You are not an admin or an assignee of this lead
404No such lead in your organization
409mailbox_not_connectedYou have no connected inbox. Connect one first
409mailbox_needs_reconnectYour 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.

FieldTypeRequiredNotes
channelstringYeswhatsapp, instagram, linkedin or telegram
bodystringYesUp to 4,000 characters. May be empty when a file goes alone
attachmentsstring[]NoUp 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.

StatusCodeMeaning
403message_not_connectedYou have no connection of that channel. integration names the setup page
403message_needs_reconnectYour connection needs reconnecting
403message_account_restrictedThe provider restricted the account; nothing more is sent from it
403message_new_chats_closedNew chats open at opensAt
403SANDBOX_OUTBOUND_DISABLEDNothing is sent from a sandbox organization
404No such lead, or you are not an admin or an assignee of it
422message_not_on_channelThe lead is not on WhatsApp, or has no linked profile on that channel
429message_paced / message_rate_limitedTry again after retryAfterSeconds
502message_send_failedThe 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"
}
StatusCodeMeaning
422lookup_not_linkedLink the lead on that channel first
404lookup_not_foundThe profile could not be found
429lookup_limitedTry again after retryAfterSeconds