# Connected inboxes

> Connect, reconnect and disconnect your inboxes, choose their calendars, and email or message leads from them.

A connected inbox (in the API, a **connected account**) is a mailbox, WhatsApp, Instagram, LinkedIn or Telegram account a member connected to Fondaro. Use these endpoints to manage inboxes and to email or message a lead from them; every email and message goes out from the person's own account.

These endpoints take a signed-in member's session token (`Authorization: Bearer <session-token>`); API keys do not open them. See [Authentication](https://www.fondaro.com/docs/api/authentication.md).

Base URL: `https://api.fondaro.com`. Each member connects at most one inbox of each kind. Connecting is a browser round trip: you get a link, the person signs in with the provider, and Fondaro never receives a password.

## The connected account object

| Field | Type | Description |
|---|---|---|
| `id` | uuid | |
| `kind` | string | `mailbox`, `shared_mailbox`, `whatsapp`, `instagram`, `linkedin` or `telegram` |
| `provider` | string | `google`, `microsoft`, `imap` (other mailbox), or the chat channel's own name |
| `address` | string or null | The email address, number or handle, once known |
| `status` | string | `connecting`, `ok`, `needs_reconnect`, `error` or `disconnected` |
| `statusReason` | string or null | Why the status last changed |
| `statusChangedAt` | string | ISO 8601 |
| `capabilities` | string[] | What works now: `email`, `calendar`, `messaging` |
| `connectedAt` | string or null | ISO 8601 |
| `disconnectedAt` | string or null | Always `null` in lists |
| `ownerUserId` | string | The member who connected it |
| `assignedUserIds` | string[] | Other people an admin gave this inbox to |
| `calendars` | object[] | Only with the `calendar` capability. See [Choose which calendars show](#choose-which-calendars-show) |

`needs_reconnect` means the provider stopped accepting the connection (a changed password, or access removed). [Reconnecting](#reconnect-an-inbox) restores it with the same `id`.

## List your inboxes

`GET /connected-accounts`

Returns your live inboxes and the colleagues' inboxes an admin gave you. `available` is `false` when connecting is not available; the lists are then empty.

```bash
curl "https://api.fondaro.com/connected-accounts" \
  -H "Authorization: Bearer $SESSION_TOKEN"
```

```json
{
  "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",
      "assignedUserIds": [],
      "calendars": [
        { "id": "agent@example.com", "name": "agent@example.com", "primary": true, "readOnly": false, "selected": true }
      ]
    }
  ],
  "assigned": []
}
```

## List the organization's inboxes

`GET /connected-accounts/organization`

Admins only. Returns every member's live inboxes as an array. Each item adds `owner` (`name`, `email`, `imageUrl`) and `included` (`true` when it is one of the plan's included inboxes, the oldest ones; `false` when it is one of the extra inboxes bought).

```bash
curl "https://api.fondaro.com/connected-accounts/organization" \
  -H "Authorization: Bearer $SESSION_TOKEN"
```

```json
[
  {
    "id": "3c9e2f41-7b1d-4c8a-9e53-0a6d2b7f1e84",
    "kind": "mailbox",
    "provider": "microsoft",
    "address": "agent@example.com",
    "status": "needs_reconnect",
    "ownerUserId": "user_2b",
    "assignedUserIds": [],
    "owner": { "name": "Agent Name", "email": "agent@example.com", "imageUrl": null },
    "included": true
  }
]
```

## Check the price of the next inbox

`GET /connected-accounts/price`

Returns whether the next inbox fits in your organization's inboxes, how many are free, the price of one extra inbox, and whether you may add one to the plan yourself.

```bash
curl "https://api.fondaro.com/connected-accounts/price" \
  -H "Authorization: Bearer $SESSION_TOKEN"
```

```json
{
  "included": false,
  "room": 0,
  "amountCents": 1000,
  "currency": "EUR",
  "canAddPaid": false,
  "requests": [
    { "id": "6f0c1d2e-8a3b-4c5d-9e7f-0a1b2c3d4e5f", "kind": "whatsapp", "provider": "whatsapp", "status": "pending", "askedAt": "2026-09-30T09:12:00.000Z", "answeredAt": null }
  ]
}
```

| Field | Description |
|---|---|
| `included` | `true` while fewer inboxes are connected than your organization has (included plus bought), so anyone on the team can connect the next one |
| `room` | How many inboxes are still free. Absent for an organization with no limit |
| `amountCents`, `currency` | The monthly price of one extra inbox, in minor units |
| `canAddPaid` | `true` for an admin, who can add an inbox to the plan ([Change your inboxes](#change-your-inboxes)); `false` for a member, who asks an admin |
| `requests` | Your own open requests (see [Ask for an inbox](#ask-for-an-inbox)), `pending` or `allowed` |

`GET /billing/plan-usage` returns the plan's people and inbox totals:

```json
{
  "inboxes": {
    "included": 5,
    "bonus": 0,
    "bought": 2,
    "capacity": 7,
    "inUse": 6,
    "room": 1,
    "pricePerInbox": 1000,
    "monthlyBought": 2000
  }
}
```

`included` is one inbox per member plus any `bonus`; `capacity` is `included + bought`; `room` is `capacity - inUse`, never below 0. `monthlyBought` is `null` for a member.

## Change your inboxes

Admins only. Each extra inbox is a monthly charge on your plan, like a person. `inboxes` is always the total after the change (included plus bought).

`GET /billing/inboxes/preview?inboxes=7` previews the change without making it:

```bash
curl "https://api.fondaro.com/billing/inboxes/preview?inboxes=7" \
  -H "Authorization: Bearer $SESSION_TOKEN"
```

```json
{
  "currency": "EUR",
  "dueToday": 640,
  "lines": [{ "description": "Remaining time on 1 × Extra inboxes", "amount": 640 }],
  "nextAmount": 2000,
  "nextDate": "2026-11-01T00:00:00.000Z",
  "inboxes": { "from": 6, "to": 7, "included": 5, "bonus": 0, "boughtFrom": 1, "boughtTo": 2, "inUse": 6, "pricePerInbox": 1000 }
}
```

`PATCH /billing/inboxes` makes it. Send a fresh `requestId` per change and the same one when you retry it, so a retry never adds twice.

```bash
curl -X PATCH "https://api.fondaro.com/billing/inboxes" \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"inboxes": 7, "requestId": "3a1f2b4c-5d6e-4f70-8a9b-0c1d2e3f4a5b"}'
```

```json
{ "inboxes": 7, "bought": 2, "inUse": 6 }
```

Adding inboxes is charged now for the rest of the billing period, and lands only once that payment succeeds (`402 PAYMENT_ACTION_REQUIRED` otherwise). Removing inboxes credits the unused days on the next invoice. During a trial nothing is charged until it ends.

| Status | Code | When |
|---|---|---|
| `400` | `below_included` | Fewer than the inboxes your plan includes |
| `409` | `inboxes_in_use` | Fewer than the inboxes connected now. The body lists the newest ones in `connected` (`id`, `ownerName`, `kind`, `address`): disconnect one first |
| `409` | `BILLING_STATE_CONFLICT` | No active plan, or an organization with no limit |
| `402` | `PAYMENT_ACTION_REQUIRED` | The card was not charged; nothing changed |

## Connect an inbox

`POST /connected-accounts/connect`

Starts a connection and returns a `url` to open in the person's browser. They return to the matching Integrations page with `?connected=<provider>` on success or `?connect_error=<reason>` on failure.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `provider` | Body | string | Yes | `google`, `microsoft`, `imap`, `whatsapp`, `instagram`, `linkedin` or `telegram` |
| `kind` | Body | string | No | Defaults to `mailbox` for an email provider, else the provider. `shared_mailbox` (the agency's shared inbox) is for admins only |

```bash
curl -X POST "https://api.fondaro.com/connected-accounts/connect" \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"provider": "google"}'
```

```json
{ "url": "https://..." }
```

The inbox appears in `GET /connected-accounts` once the provider confirms it; poll until `status` is `ok`.

When every inbox is in use, connecting is refused with the facts in the body (`capacity`, `inUse`, `pricePerInbox`): an admin gets `409 inboxes_full` (add an inbox first with [`PATCH /billing/inboxes`](#change-your-inboxes)), a member `403 admin_approval_required` ([ask for one](#ask-for-an-inbox)). Connecting itself never charges anything.

## Ask for an inbox

`POST /connected-accounts/requests`

When every inbox is in use, a member asks the admins instead (body as for connect). While one is free, asking returns `409 not_needed`: connect instead. Admins see the request in their Inbox and get a notification. Asking again for the same kind returns the open request.

```bash
curl -X POST "https://api.fondaro.com/connected-accounts/requests" \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"provider": "whatsapp"}'
```

```json
{ "id": "6f0c1d2e-8a3b-4c5d-9e7f-0a1b2c3d4e5f", "kind": "whatsapp", "provider": "whatsapp", "status": "pending", "askedAt": "2026-09-30T09:12:00.000Z", "answeredAt": null }
```

Admins list open requests with `GET /connected-accounts/requests` (each adds `userId`, `requester` and the monthly `amountCents` and `currency`), and answer with `POST /connected-accounts/requests/:id/allow` or `POST /connected-accounts/requests/:id/decline`. Allowing tells the member they can connect. They connect while an inbox is free, so when every inbox is in use, add one with [`PATCH /billing/inboxes`](#change-your-inboxes) before allowing. Answering a request that is no longer open returns `409 request_closed`.

## Give an inbox to other people

`PUT /connected-accounts/:id/assignees`

Admins only. Sets everyone besides the owner who uses this inbox (such as the agency's `info@`). They see its new conversations and can send from it. Replaces the whole list.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | Path | uuid | Yes | The connected account |
| `userIds` | Body | string[] | Yes | Up to 200 member user ids. `[]` removes everyone |

```bash
curl -X PUT "https://api.fondaro.com/connected-accounts/8d2a61f0-5c3e-4b7a-a1d9-6e4f0b2c7a15/assignees" \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"userIds": ["user_2b", "user_2c"]}'
```

```json
{ "id": "8d2a61f0-5c3e-4b7a-a1d9-6e4f0b2c7a15", "kind": "shared_mailbox", "address": "info@example.com", "assignedUserIds": ["user_2b", "user_2c"] }
```

## Reconnect an inbox

`POST /connected-accounts/:id/reconnect`

Returns a `url`, as for connect. Only the member who connected the inbox can reconnect it. It keeps its `id` and uses no extra inbox.

```bash
curl -X POST "https://api.fondaro.com/connected-accounts/3c9e2f41-7b1d-4c8a-9e53-0a6d2b7f1e84/reconnect" \
  -H "Authorization: Bearer $SESSION_TOKEN"
```

```json
{ "url": "https://..." }
```

## Disconnect an inbox

`DELETE /connected-accounts/:id`

Disconnects at once and answers `204` with no body. The owner or any admin can disconnect. Conversations already on leads stay. There is no refund for the current month.

```bash
curl -X DELETE "https://api.fondaro.com/connected-accounts/3c9e2f41-7b1d-4c8a-9e53-0a6d2b7f1e84" \
  -H "Authorization: Bearer $SESSION_TOKEN"
```

## Choose which calendars show

A Google or Microsoft inbox with the `calendar` capability lists its calendars in `calendars`. A calendar someone else owns and shared into the inbox is listed only when the inbox's owner is an organization admin; for a member it is left out and its events leave their calendar.

| Field | Type | Description |
|---|---|---|
| `id` | string | The provider's calendar id. URL-encode it in a path |
| `name` | string or null | As named in Google or Outlook |
| `primary` | boolean | The main calendar. It always shows |
| `readOnly` | boolean | Shared for reading only. New events never go to it |
| `selected` | boolean | Its events show on the person's [calendar](https://www.fondaro.com/docs/api/calendar.md). A new calendar starts hidden, except the primary |

`PUT /connected-accounts/:id/calendars/:calendarId` with `{ "selected": true | false }` shows or hides one calendar. `POST /connected-accounts/:id/calendars/refresh` (no body) lists the calendars again, for one made after connecting. Both act on your own inbox only and answer `{ "calendars": [...] }`.

```bash
curl -X PUT "https://api.fondaro.com/connected-accounts/3c9e2f41-7b1d-4c8a-9e53-0a6d2b7f1e84/calendars/viewings%40group.calendar.google.com" \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"selected": false}'
```

```json
{
  "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 }
  ]
}
```

## Email a lead

### See which address you send from

`GET /crm/email-sender`

Returns how an email you send now would leave, so a composer can show the From address or a Connect step.

```bash
curl "https://api.fondaro.com/crm/email-sender" \
  -H "Authorization: Bearer $SESSION_TOKEN"
```

```json
{
  "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" }
  ]
}
```

`via` is `mailbox` or `none` (nothing to send from). An inbox that needs reconnecting still answers `mailbox` with its `status`. `options` lists every inbox you may pick as From whose status is `ok`: your own, then those an admin gave you.

### Send an email

`POST /crm/leads/:id/emails`

Sends from your connected mailbox. It lands in your Sent folder, and the lead's reply comes back to your inbox and onto the lead.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | Path | integer | Yes | The lead |
| `subject` | Body | string | Yes | Up to 500 characters |
| `bodyText` | Body | string | Yes | The plain version, always stored |
| `bodyHtml` | Body | string | No | Formatted body, up to 100,000 characters, cleaned to basic text tags and http, https or mailto links |
| `fromAccountId` | Body | uuid | No | One of your `options`. Default: your own mailbox |
| `cc`, `bcc` | Body | string[] | No | Up to 10 addresses each |
| `signatureHtml` | Body | string | No | Your signature, cleaned before sending |
| `replyToEmailId` | Body | uuid | No | An email on the lead's timeline to reply to, so it threads |
| `documentIds` | Body | uuid[] | No | Up to 10 uploaded [Documents](https://www.fondaro.com/docs/api/documents.md) to attach, 20 MB together |

```bash
curl -X POST "https://api.fondaro.com/crm/leads/4821/emails" \
  -H "Authorization: Bearer $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"}'
```

The response is the email as it now sits on the lead's timeline, with `toAddresses`, `ccAddresses` and `bccAddresses`.

### Find past emails

`POST /crm/leads/:id/find-past-emails`

Searches your connected inboxes for the lead's email address over the last 12 months and adds what it finds to the lead within a minute or two. Use it for a lead created after you connected (connecting brings in the last 90 days once). Safe to repeat. Answers `202`.

```bash
curl -X POST "https://api.fondaro.com/crm/leads/4821/find-past-emails" \
  -H "Authorization: Bearer $SESSION_TOKEN"
```

```json
{ "mailboxes": 1, "found": 6, "enqueued": 4 }
```

## Message a lead

### List the lead's channels

`GET /crm/leads/:id/channels`

Lists the channels you can message this lead on: you have a live WhatsApp, Instagram, LinkedIn or Telegram connection and the lead is reachable there (on WhatsApp by their number, elsewhere because they wrote to you or a member linked their profile). An empty list means no message channel.

```bash
curl "https://api.fondaro.com/crm/leads/4821/channels" \
  -H "Authorization: Bearer $SESSION_TOKEN"
```

```json
{
  "channels": [
    {
      "channel": "whatsapp",
      "canSend": true,
      "block": null,
      "integration": "whatsapp",
      "accountAddress": "+34600000001",
      "counterpart": "+34600000002",
      "newChat": true,
      "manyNewChats": false
    }
  ]
}
```

`block` says why `canSend` is `false`: `needs_reconnect` or `restricted` (the provider restricted the account). `newChat` is `true` when the next message opens a chat with someone new. `manyNewChats` is `true` when it does and the account has already started many new chats in the last 24 hours: advice to spread them out, never a block.

### Send a message

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

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | Path | integer | Yes | The lead |
| `channel` | Body | string | Yes | `whatsapp`, `instagram`, `linkedin` or `telegram` |
| `body` | Body | string | Yes | Up to 4,000 characters. May be empty when a file goes alone |
| `attachments` | Body | uuid[] | No | Up to 3 uploaded Documents ids, 10 MB together |

```bash
curl -X POST "https://api.fondaro.com/crm/leads/4821/messages" \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channel": "whatsapp", "body": "Saturday at 11 works for me."}'
```

```json
{
  "id": "6a1f0c3e-2b7d-4e59-8c14-9d3a5e7b2f60",
  "leadId": 4821,
  "channel": "whatsapp",
  "kind": "text",
  "direction": "outbound",
  "status": "sent",
  "body": "Saturday at 11 works for me.",
  "attachments": [],
  "messageAt": "2026-09-25T10:04:00.000Z"
}
```

To protect your account, messages from one account go at least 15 seconds apart (Instagram 10 seconds, and at most 10 an hour); a send inside that gap waits up to 30 seconds. A first message to someone new is never held back.

`GET /crm/leads/:id/messages/:messageId/attachments/:index` returns a short-lived download link (`url`, `filename`, `contentType`) for a stored file on a message.

### Link a lead's channel

Link a WhatsApp number, Instagram, LinkedIn or Telegram identity, or an extra email address, to a lead so messages there land on it.

| Method and path | Purpose |
|---|---|
| `GET /crm/leads/:id/channel-identities` | The identities linked to the lead |
| `POST /crm/leads/:id/channel-identities/lookup` | Who that is on the channel, before linking. Writes nothing |
| `POST /crm/leads/:id/channel-identities` | Link it, and attach what they already wrote |
| `DELETE /crm/leads/:id/channel-identities/:identityId` | Unlink it (`204`) |
| `POST /crm/leads/:id/find-past-messages` | Read the last year of the lead's chats on your own accounts |

```bash
curl -X POST "https://api.fondaro.com/crm/leads/4821/channel-identities" \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channel": "whatsapp", "value": "+34600000002"}'
```

```json
{
  "identity": { "id": "1d2e3f40-5a6b-4c7d-8e9f-0a1b2c3d4e5f", "channel": "whatsapp", "display": "+34600000002", "linkedBy": "user_2a", "createdAt": "2026-09-25T10:00:00.000Z" },
  "attachedMessages": 3,
  "attachedEmails": 0
}
```

### 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. It changes nothing on the lead. LinkedIn allows about 80 look ups a day per account.

```bash
curl -X POST "https://api.fondaro.com/crm/leads/4821/lookup-profile" \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channel": "linkedin"}'
```

```json
{
  "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"
}
```

## Errors

| Status | Code | When |
|---|---|---|
| `400` | `invalid_kind` | The `kind` cannot use that `provider` |
| `400` | `not_a_member` | An assignee is not in your organization |
| `400` | `sender_not_allowed` | `fromAccountId` is not one of your `options` |
| `400` | `lead_has_no_email` | Find past emails on a lead with no email address |
| `402` | `not_entitled` | Connecting needs an active plan or trial |
| `403` | `ORG_ADMIN_REQUIRED` | An admin-only action, or the shared mailbox |
| `403` | `admin_approval_required` | A member connecting when every inbox is in use. [Ask for one](#ask-for-an-inbox) |
| `403` | `not_owner` | Reconnecting or disconnecting someone else's inbox |
| `403` | `sandbox`, `SANDBOX_OUTBOUND_DISABLED` | Sandbox organizations cannot connect inboxes or send |
| `403` | `mailbox_not_connected`, `mailbox_needs_reconnect` | Sending an email without a working mailbox |
| `403` | `message_not_connected`, `message_needs_reconnect`, `message_account_restricted` | Sending a message without a working connection |
| `403` | `SUBSCRIPTION_REQUIRED` | A lead write without an active plan |
| `404` | `CONNECTED_ACCOUNT_NOT_FOUND`, `CALENDAR_NOT_FOUND`, `lookup_not_found` | Not found, or not yours |
| `409` | `already_connected` | You already have a live inbox of this kind |
| `409` | `inboxes_full` | An admin connecting when every inbox is in use. [Add one first](#change-your-inboxes) |
| `409` | `not_needed`, `request_closed` | No request is needed, or it is no longer open |
| `409` | `CALENDAR_PRIMARY_LOCKED`, `CONNECTED_ACCOUNT_NEEDS_RECONNECT` | Hiding the primary calendar, or refreshing an inbox that needs reconnecting |
| `409` | `mailbox_not_connected`, `mailbox_needs_reconnect` | Find past emails without a working mailbox |
| `409` | `channel_linked_elsewhere` | The identity is linked to another lead |
| `422` | `message_not_on_channel`, `channel_invalid`, `lookup_not_linked` | The lead is not reachable there, the value is not valid, or no profile is linked |
| `429` | `message_paced`, `message_rate_limited`, `mailbox_rate_limited`, `lookup_limited` | Try again after `retryAfterSeconds` |
| `502` | `message_send_failed`, `mailbox_send_failed`, `provider_error` | The provider did not take it; try again |
| `503` | `not_available` | Connecting is not available |

Errors use the standard body `{ "statusCode", "message", "error" }`, plus `code` and any fields listed above.

Source: https://www.fondaro.com/docs/api/connected-accounts
