Inbox
REST endpoints for the Inbox: the people waiting for an answer, your team chats and what arrived for you, in one ordered list with Waiting, New and All tabs, the rail's count, marking a conversation as needing no reply, dismissing a row, and replying to someone new.
Overview
The Inbox is composed on the server for the person asking. Every row comes from a record that already exists (a message, a lead, a meeting answer, a brochure view, a request from another agency, a chat you are in); nothing is stored for the Inbox itself except two marks: that a conversation needs no reply, and that someone dismissed a row.
A row leaves because something happened on its record: you replied on any channel, called, booked, linked, marked it handled or dismissed it. Only two rows expire on their own: a new conversation after 14 days, and an opened brochure after 48 hours.
The endpoints on this page use the dashboard's Clerk bearer token and resolve the organization from the request. Reading is open to every member. Marking a row handled needs an active plan, exactly as on the CRM endpoints.
Endpoints
| Method & path | Who | Purpose |
|---|---|---|
GET /inbox | Any member | One page of the Inbox, in order |
GET /inbox/count | Any member | How many people are waiting for you |
POST /inbox/items/:ref/handled | Anyone who can open the lead, or who has the row | Mark a conversation as needing no reply, or dismiss a row |
DELETE /inbox/items/:ref/handled | The same | Undo that mark or dismissal |
GET /inbox/faces/:kind/:id | Anyone who has the row | The person's profile picture, the path in faceUrl |
POST /new-conversations/:key/reply | The owner of the inbox it came in on | Reply to someone who is not a lead yet |
What is in it
kind | A row when | It leaves when |
|---|---|---|
awaiting_reply | The newest message on a lead, on any channel, is theirs | Anyone replies on any channel, a call connects, or the row is marked handled |
new_conversation | A first message from someone who is not a lead yet reached one of your connected inboxes | Add as lead, link to a lead, Not a lead, or 14 days |
new_lead | A lead assigned to you nobody has called, emailed or messaged | Any call, sent email or outbound message |
unassigned_lead | A new lead with nobody on it (admins, scope agency) | Someone is assigned |
invite_declined | A lead declined, or answered maybe to, an upcoming meeting you organised | The meeting is moved (everyone is asked again), cancelled or accepted |
brochure_opened | A brochure you made for one lead was opened by someone outside your agency | Any contact with the lead after the view, or 48 hours |
reconnect | One of your connected inboxes needs reconnecting | It is connected again |
co_listing_invitation | Another agency invited you (or your agency, for admins) to co-list a home | Accepted or declined |
partnership_request | Another agency asked to partner (admins) | Accepted or declined |
open_house_tomorrow | Your open house is tomorrow and agents said they are going | The day passes |
agent_request | An agent at another agency asked to message you | Accepted or declined |
request_reply | Someone answered your open buyer request in a chat and you have not replied | You reply in that thread, or the request closes |
post_draft | A listing post on your own Instagram or LinkedIn waits for you: a draft (from a new listing, a price change, the Post action or Ask), a post that failed, or one whose account needs reconnecting. reason is { code: "post_draft", postId, channel, postKind, status }, ref the listing, automatic true when a trigger drafted it | It is published, scheduled or discarded |
With a view (see below), your own chats join the list:
kind | A row when | It leaves when |
|---|---|---|
team_dm | A direct message with a colleague or an agent at another agency whose newest message is theirs and unread by you | You read it or reply |
team_mention | An unread message in a channel or group mentions you | You read it |
team_channel | A channel or group that does not wait on you (the all view only) | Never waits |
lead_thread | A lead's conversation that waits on nobody (the all view only) | Never waits |
A chat is only ever yours: you see a direct message, a channel or a group only
as a member of it, at every scope. An admin's members or agency scope
changes which lead conversations show, never whose chats. Group chatter never
waits; only a direct message to you or a mention of you does. Your own last
message never waits.
Overdue tasks are not rows: they live on the calendar.
Order
Rows come back in one total order:
reconnect, when a broken inbox blocks a reply on this page;- the people waiting (
awaiting_reply,new_conversation,new_lead,unassigned_lead) together, longest waiting first; - the team (
group: "team"):agent_requestfirst, thenteam_dm,team_mentionandrequest_reply, longest waiting first. A lead waiting always comes before your team; invite_declined(and areconnectthat blocks nothing);brochure_opened;- the network rows;
post_draft.
Inside a group, Ask may lift a row (someone asking to be left alone first, then
a specific request such as a viewing) or sink it (an automatic reply). Ask never
removes a row: with Ask's reading missing or not yet available in the message's
language, every message still appears with the reason wrote.
A lead appears once, with its strongest reason. additionalCount says how many
other reasons it has.
GET /inbox
| Query | Type | Notes |
|---|---|---|
scope | me | members | agency | Default me. Members always get me. |
assigneeIds | string, repeatable | User ids for scope=members (admins). |
channel | email | whatsapp | linkedin | instagram | telegram | Only rows on this channel. |
q | string | Only rows whose person or agency name contains it. |
cursor | string | nextCursor from the previous page. |
limit | 1 to 50 | Default 30. |
timeZone | IANA name | For "tomorrow" and the note's "overnight". Default UTC. |
view | waiting | new | all | The view (the dashboard's filter). Without it you get the list as it was before the tabs, with no chats, no views and no note. |
via | a connected inbox id | team | agencies | Only rows from that inbox, your colleagues' chats, or other agencies (their chats and their requests). |
curl 'https://api.fondaro.com/inbox?view=waiting&scope=me&limit=30&timeZone=Europe/Madrid' \
-H 'Authorization: Bearer <clerk-session-token>'{
"asOf": "2026-09-25T10:00:00.000Z",
"scope": "me",
"items": [
{
"id": "message:6b0c9d0e-5d7a-4e0a-9d59-0f3c1e2a7b11",
"kind": "awaiting_reply",
"group": "people",
"ref": { "kind": "lead", "id": "1042" },
"conversationKey": null,
"messageRef": { "kind": "message", "id": "6b0c9d0e-5d7a-4e0a-9d59-0f3c1e2a7b11" },
"connectedAccountId": "3f0c6f8e-1c1a-4a7e-9e7b-2d0c7f6a1b11",
"occurredAt": "2026-09-25T08:58:12.000Z",
"lead": {
"id": 1042,
"firstName": "Lead",
"lastName": "Example",
"language": "en",
"assigneeIds": ["user_2abc"]
},
"title": "Lead Example",
"channel": "whatsapp",
"reason": { "code": "wrote", "channel": "whatsapp" },
"replyOwed": true,
"automatic": false,
"fromAnAgency": false,
"handleable": true,
"additionalCount": 0,
"offers": [],
"preview": "Hi! Is Saturday 11 still ok?",
"chat": null,
"faceUrl": "/inbox/faces/message/6b0c9d0e-5d7a-4e0a-9d59-0f3c1e2a7b11"
},
{
"id": "chat:0d1e2f3a-4b5c-4d6e-8f90-a1b2c3d4e5f6",
"kind": "team_dm",
"group": "team",
"ref": { "kind": "conversation", "id": "0d1e2f3a-4b5c-4d6e-8f90-a1b2c3d4e5f6" },
"conversationKey": null,
"messageRef": null,
"connectedAccountId": null,
"occurredAt": "2026-09-25T09:40:00.000Z",
"lead": null,
"title": "Ana Soler",
"channel": null,
"reason": { "code": "team", "waits": "dm" },
"replyOwed": true,
"automatic": false,
"fromAnAgency": false,
"handleable": false,
"additionalCount": 0,
"offers": [],
"preview": "Can you take the viewing on Saturday?",
"chat": {
"conversationId": "0d1e2f3a-4b5c-4d6e-8f90-a1b2c3d4e5f6",
"type": "dm",
"scope": "internal",
"status": "active",
"waits": "dm",
"unreadCount": 1,
"mentionCount": 0,
"person": { "userId": "user_2ana", "avatarUrl": null },
"last": { "fromViewer": false, "senderFirstName": "Ana", "attachment": null },
"mentionedBy": null
},
"faceUrl": null
}
],
"nextCursor": null,
"groups": { "reconnect": 0, "people": 1, "team": 1, "invites": 0, "brochures": 0, "network": 0, "posts": 0 },
"counts": { "awaiting_reply": 1, "team_dm": 1, "new_conversation": 0, "...": 0 },
"waitingCount": 2,
"hasConnectedAccount": true,
"view": "waiting",
"views": { "waiting": 2, "new": 0 },
"note": {
"sentences": [
{
"shape": "wrote",
"people": [{ "rowId": "message:6b0c9d0e-5d7a-4e0a-9d59-0f3c1e2a7b11", "ref": { "kind": "lead", "id": "1042" }, "name": "Lead Example" }],
"others": 0,
"questionId": null
},
{
"shape": "team",
"person": { "rowId": "chat:0d1e2f3a-4b5c-4d6e-8f90-a1b2c3d4e5f6", "ref": { "kind": "conversation", "id": "0d1e2f3a-4b5c-4d6e-8f90-a1b2c3d4e5f6" }, "name": "Ana Soler" },
"mention": false,
"channelName": null
}
]
}
}refis what the row opens: the lead, acalendar_event, abrochure, alisting, anorganization, anopen_house, or a chatconversation(agent_request,request_replyand the team rows). It isnullfornew_conversation(open it byconversationKeywith the new conversation endpoints) and forreconnect(useconnectedAccountId).reasonis a code with parameters, for the client to put into words:ask(questionId, Ask's reading),wrote(channel),new_conversation(withaskReason, why they wrote, when Ask read it),enquiry(why a new lead enquired, when Ask read it),origin(where a new lead came from),invite_declined,brochure_opened,reconnect,co_listing_invitation,partnership_request,open_house_tomorrow,agent_request,request_reply,team(a chat;waitsisdm,mentionornull) andthread(a lead's conversation inall: the channel of the newest message and whether it was yours).previewis the person's own words, at most about 140 characters: a message's first line, an email's subject, a new conversation's newest message, a chat's last message. It isnullwhen there are no words (a new lead, a voice note:chat.last.attachmentthen saysvoice,giforcard).chatcarries a team row's conversation: direct message or channel,internal(colleagues) orexternal(another agency), why it waits, the unread and mention counts, the other person of a direct message, and who sent the last message. A person is always a name, never an email.faceUrlis the path of the person's own profile picture on WhatsApp, Instagram, LinkedIn or Telegram, for a lead's or a new conversation's row on those channels, andnullotherwise. Fetch it with your bearer token: it answers the image (cached privately for a day, with anETag), or 404 when they have none or you cannot see the row.connectedAccountIdis the broken inbox onreconnect, and the inbox a message arrived on for a message row (whatviafilters on).offersare Ask's suggestions for the message, the same ones the lead page shows. Accepting one always opens a proposal for you to approve.groups,counts,waitingCountandviewscover the whole Inbox, not the page and not theq,channelorviafilter.- Paging is by position in the order, so a row that clears between two pages never shifts the next one.
Tabs
view | What it holds |
|---|---|
waiting | Every row above that waits, your waiting chats included, in the order above |
new | new_lead, new_conversation, unassigned_lead and agent_request, in the same order |
all | Every conversation, newest activity first: one row per lead with a message or email, per new conversation, and per chat you are in. Requests to you are pinned above the first page. A lead or chat that waits keeps its waiting row. |
views counts the rows of waiting and new (all has no count). With a
view, q also matches the last words (preview), not only the name. Each
view has its own cursor; a cursor from another view starts from the top.
The note
note is on the first page of every view (null on later pages, and when
there is nothing to say): one or two sentences chosen by the server, as parts
your client puts into words, so every client says the same thing. The first
shape that fits wins, in this order: wrote (people who wrote), new_people
(new leads from one source, with overnight before noon for leads since 18:00
the day before in timeZone), invite, brochure, then team (a direct
message or a mention, never ahead of a lead sentence). A sentence names at most
two people; others counts only the people beyond them. It never carries a
total. Each name's rowId is the row it focuses. The dashboard no longer draws
the note (2026-09-27); it stays on the wire for other clients.
GET /inbox/count
The rail's number: people waiting for you (awaiting_reply,
new_conversation, new_lead), one per lead, plus the chats that owe you an
answer (team_dm, team_mention) and requests to you (agent_request),
always in your own scope.
curl 'https://api.fondaro.com/inbox/count' \
-H 'Authorization: Bearer <clerk-session-token>'{ "waitingCount": 3 }Handled
"No reply needed" is kept on the message it clears, so it holds on the web and the phone alike. The next message from the lead is a new row, so the lead comes back by itself.
:ref is the row's messageRef as a key (email:<uuid> or message:<uuid>,
URL-encoded), or lead:<id> for the lead's newest message, which must be
theirs. Only a message on a lead you can open can be marked. A row with no
message is dismissed instead (below). New conversations are decided with the new
conversation endpoints, and chats clear when you read them.
curl -X POST 'https://api.fondaro.com/inbox/items/message%3A6b0c9d0e-5d7a-4e0a-9d59-0f3c1e2a7b11/handled' \
-H 'Authorization: Bearer <clerk-session-token>'{
"messageRef": "message:6b0c9d0e-5d7a-4e0a-9d59-0f3c1e2a7b11",
"handled": true,
"handledAt": "2026-09-25T10:02:41.000Z"
}Marking a message that is already marked keeps the first mark. DELETE on the
same path removes it and returns "handled": false.
| Status | When |
|---|---|
400 | :ref is not an email, a message, a lead or a row that can be dismissed |
403 | Your organization has no active plan |
404 | No inbound message on a lead you can open, or no such row in your Inbox |
Dismiss
A row with no message of its own (new_lead, unassigned_lead,
invite_declined, brochure_opened, reconnect, co_listing_invitation,
partnership_request, open_house_tomorrow, agent_request,
request_reply, post_draft) is dismissed on the same path, with the row's
id as :ref. With a view, these rows come back with handleable: true;
without one, handleable still means a message to mark.
The dismissal holds for everyone in your organization, on the web and the
phone, until something newer happens on the row: another view of the brochure,
a new answer to the invite, a newer reply to your buyer request, the inbox
breaking again. Then the row comes back and can be dismissed again. You can
dismiss only a row your own Inbox shows you right now. timeZone (an IANA
name, default UTC) finds an open house "tomorrow" where you are.
curl -X POST 'https://api.fondaro.com/inbox/items/new_lead%3Alead%3A1042/handled?timeZone=Europe/Madrid' \
-H 'Authorization: Bearer <clerk-session-token>'{
"messageRef": "new_lead:lead:1042",
"handled": true,
"handledAt": "2026-09-25T10:02:41.000Z"
}DELETE on the same path is Undo: the row is back on the next read.
Read
Every row carries unread, the dot: something new since you last opened this
conversation. It is yours alone, on the web and the phone; a colleague opening
the same lead does not clear it for you. It says nothing about owing an answer:
a row you have opened still waits in Waiting and still counts in
waitingCount until you answer or mark it handled.
| Row | unread is true while |
|---|---|
A lead (awaiting_reply, lead_thread, new_lead, unassigned_lead) | Their newest message, or the enquiry of a new lead, is newer than your last open. A conversation whose newest message is yours is read. |
new_conversation | Their newest message is newer than your last open, until you reply |
A chat (team_dm, team_mention, team_channel) | It has messages you have not read. Chats are read by reading them, not here. |
| Anything else | You have not opened it since it happened (another view of the brochure, a new answer to the invite) |
POST /inbox/read marks a conversation opened now. key names it: lead:<id>
for any row of a lead, conversation:<conversationKey> for a new conversation,
the row's id for anything else. You can read only what your own Inbox could
show you: a lead you can open, a new conversation in one of your inboxes, a row
your Inbox holds right now. timeZone (an IANA name, default UTC) finds an
open house "tomorrow" where you are. Reading needs no active plan.
curl -X POST 'https://api.fondaro.com/inbox/read' \
-H 'Authorization: Bearer <clerk-session-token>' \
-H 'Content-Type: application/json' \
-d '{ "key": "lead:1042" }'{ "key": "lead:1042", "readAt": "2026-09-25T10:02:41.000Z" }Reading again moves readAt. Afterwards inbox:changed goes to your own
sockets only, so your other tabs and devices refetch.
| Status | When |
|---|---|
400 | key is not a lead, a new conversation or a row that reads by its id (a chat, or a lead's row by its own id) |
404 | No such lead you can open, conversation in your inboxes, or row in your Inbox |
Reply to someone new
POST /new-conversations/:key/reply answers a new_conversation row from the
inbox they wrote to, before you decide whether they are a lead. :key is the
row's conversationKey, URL-encoded. Only the owner of that inbox can reply,
exactly as only they can open the conversation.
| Body | Type | Notes |
|---|---|---|
body | string, 1 to 20,000 characters | Plain text. A WhatsApp, Instagram, LinkedIn or Telegram message is at most 4,000. |
subject | string | Email only. Default: Re: and their newest subject. |
cc | string[] | Email only. Up to 10 addresses; one already in To is dropped, and each is kept once. |
bcc | string[] | Email only. Up to 10 addresses; one already in To or Cc is dropped. |
bodyHtml | string, at most 100,000 characters | Email only. The formatted body, sent instead of body'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). body stays the plain version. |
A chat reply ignores cc, bcc and bodyHtml. The From is always the inbox they wrote to, so the reply stays in their thread.
An email goes out from that mailbox as a reply to their newest message, so it
lands in the same thread for them. A chat message goes into the chat they
opened, a few seconds after your last message from that account, like every
message you send. The reply stays with the conversation: it moves to the lead
when you add them as a lead or link them, and it goes with the conversation on
Not a lead or after 14 days. The row stays in your Inbox until you decide,
without its dot (replyOwed: false), and the conversation's newest message is
yours (answered: true).
curl -X POST 'https://api.fondaro.com/new-conversations/email.3f0c6f8e-1c1a-4a7e-9e7b-2d0c7f6a1b11.YnV5ZXJAZXhhbXBsZS5jb20/reply' \
-H 'Authorization: Bearer <clerk-session-token>' \
-H 'Content-Type: application/json' \
-d '{ "body": "Hello, the villa is free to view on Saturday at 11." }'With formatting and a copy:
curl -X POST 'https://api.fondaro.com/new-conversations/email.3f0c6f8e-1c1a-4a7e-9e7b-2d0c7f6a1b11.YnV5ZXJAZXhhbXBsZS5jb20/reply' \
-H 'Authorization: Bearer <clerk-session-token>' \
-H 'Content-Type: application/json' \
-d '{
"body": "Hello, the villa is free to view on Saturday at 11.",
"bodyHtml": "<p>Hello, the villa is free to view on <strong>Saturday at 11</strong>.</p>",
"cc": ["partner@example.com"]
}'{
"message": {
"id": "9a1b2c3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d",
"kind": "email",
"direction": "outbound",
"subject": "Re: Villa in Mijas",
"body": "Hello, the villa is free to view on Saturday at 11.",
"messageAt": "2026-09-25T10:04:00.000Z",
"attachments": []
}
}The conversation's messages (GET /new-conversations/:key) now carry
direction, inbound or outbound.
| Status | When |
|---|---|
400 | An empty body, or a chat message over 4,000 characters |
403 | A sandbox organization (nothing is sent from one), or no active plan |
404 | No such conversation in your inboxes |
409 | The inbox cannot send: code is mailbox_needs_reconnect or message_needs_reconnect (reconnect it), message_account_restricted, or mailbox_not_connected / message_not_connected (connect it again). integration names the connection page |
429 | Sending is paused: message_paced, message_rate_limited or mailbox_rate_limited |
502 | The provider did not take it; try again |
Live updates
After a message reaches a lead, a new conversation arrives, a reply goes to
one, or a row is marked, dismissed or brought back, the dashboard's chat socket
sends inbox:changed with an empty payload to your organization. Refetch GET /inbox. A client should also
refetch every 60 seconds, which covers a dropped socket.
MCP
list_inbox on the MCP server (scope crm:read) returns the
list as it was before the tabs, without offers, with preview on lead rows.
It never returns a chat: team rows and chat words stay out of the assistant
and MCP. There is no tool to mark a row handled.
Related Articles
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.
Calendar
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.
MCP server
Connect an MCP client with Fondaro OAuth or a scoped fdr_mcp_ API key.
API Overview
Introduction to the Fondaro API: base URL, authentication, scopes, OpenAPI, response format, errors, pagination and rate limits.