Documents

Store, organize, share and attach your agency's documents and files: written documents, uploads, folders, the bin and share links.

The Documents API is your organization's drive: written (Markdown) documents, uploaded files, folders and a bin, plus share links and attachments to leads and listings.

These endpoints take a signed-in member's session token (Authorization: Bearer <session-token>); API keys do not open them. See Authentication. The two /public/documents routes need no credential.

Every request is scoped to the caller's organization, which is never a parameter. Two rules apply throughout:

  • Reading follows visibility. A member sees organization and public documents plus their own private ones; an organization admin sees every document. A document you cannot see answers 404.
  • Editing follows visibility. Anyone who can see a Team document can edit its title and body; a private document only its creator and organization admins. Sharing, revoking, moving and binning stay with the creator and organization admins. Attaching only needs read access to the document and access to the lead or listing.

Documents need an active subscription. Without one, every route answers 403 with code: "SUBSCRIPTION_REQUIRED" and share links answer 404. Nothing is deleted; subscribing again restores the library and its links.

The document object

List endpoints return the summary fields. Single-document responses add the detail fields.

FieldTypeDescription
idstringUUID
titlestringUp to 200 characters
kindstringmarkdown, whiteboard, pdf or file. Tolerate unknown values
visibilitystringprivate, organization or public
sizeBytesnumber or nullUploaded documents only
createdBystringUser id of the creator
assigneeIdsstring[]User ids, up to 50
shareUrlstring or nullSet only while a share link is live
attachedLeadCountnumber
attachedListingCountnumber
pinnedAtstring or nullPinned for the whole organization
folderIdstring or nullnull is the top level
contentTypestring or nullUploads only: application/pdf, an image/* type, or application/octet-stream
fileExtensionstring or nullUploads only, lower case
deletedAtstring or nullSet only on rows read from the bin
createdAt, updatedAtstringISO 8601
bodystring or nullDetail only. Markdown source of a markdown document; the Excalidraw scene JSON of a whiteboard (the public API, MCP and Ask Fondaro return a plain-text outline instead)
shareRevokedAtstring or nullDetail only
shareExpiresAtstring or nullDetail only. null means the link never expires
attachedLeadIdsnumber[]Detail only
attachedListingIdsstring[]Detail only

List documents

GET /documents

Lists the documents you can see, most recently edited first by default.

ParameterTypeRequiredDescription
kindQuery, stringNomarkdown, whiteboard, pdf or file
folderIdQuery, stringNoA folder id, or root for the top level. Omit to list every document wherever it lives
scopeQuery, stringNoshared (created by someone else) or pinned
visibilityQuery, stringNoNarrows what you can already see; never widens it
assigneeIdQuery, stringNoDocuments with this assignee
mineQuery, booleanNotrue for documents you created
createdByQuery, string[]NoCreator user ids, up to 50. Repeat the parameter
qQuery, stringNoTitle search, up to 128 characters
sortQuery, stringNoupdated (default), title (A to Z) or size (largest first)
offsetQuery, integerNoDefault 0
limitQuery, integerNo1 to 50, default 20
bash
curl -G https://api.fondaro.com/documents \  -H "Authorization: Bearer $SESSION_TOKEN" \  --data-urlencode "kind=pdf" \  --data-urlencode "q=guide"
json
{  "items": [    {      "id": "3f1c0d0e-0000-4000-8000-000000000000",      "title": "Buyer guide",      "kind": "pdf",      "visibility": "public",      "sizeBytes": 2214592,      "createdBy": "user_2abc",      "assigneeIds": [],      "shareUrl": "https://www.fondaro.com/d/9pQ2rB7wKx1vT0sN4mH6cA",      "attachedLeadCount": 3,      "attachedListingCount": 0,      "pinnedAt": null,      "folderId": null,      "contentType": "application/pdf",      "fileExtension": "pdf",      "deletedAt": null,      "createdAt": "2026-08-01T09:12:00.000Z",      "updatedAt": "2026-08-20T14:03:00.000Z"    }  ],  "total": 1,  "offset": 0,  "limit": 20}

Create a written document

POST /documents

Creates a Markdown document. It starts as private.

ParameterTypeRequiredDescription
titleBody, stringYes1 to 200 characters
bodyBody, stringNoGitHub-flavoured Markdown, up to 1,000,000 bytes
assigneeIdsBody, string[]NoUp to 50 user ids
folderIdBody, stringNoFolder to create it in; omit for the top level
bash
curl -X POST https://api.fondaro.com/documents \  -H "Authorization: Bearer $SESSION_TOKEN" \  -H "Content-Type: application/json" \  -d '{"title": "Buyer guide", "body": "# Buyer guide\n\nWhat buyers ask us most."}'
json
{  "id": "3f1c0d0e-0000-4000-8000-000000000000",  "title": "Buyer guide",  "kind": "markdown",  "visibility": "private",  "body": "# Buyer guide\n\nWhat buyers ask us most.",  "shareUrl": null,  "attachedLeadIds": [],  "attachedListingIds": []}

Upload a file

POST /documents/upload

Uploads any file as multipart/form-data. The file's bytes decide its kind: a PDF becomes pdf, anything else file. Only PDFs and images (JPEG, PNG, WebP, GIF) are ever shown inline; other files only download.

ParameterTypeRequiredDescription
fileForm, fileYesA PDF up to 25 MB, any other file up to 50 MB
titleForm, stringNoDefaults to the file name
folderIdForm, stringNoFolder to upload into
assigneeIdsForm, string[]NoRepeat the field or send a JSON array
bash
curl -X POST https://api.fondaro.com/documents/upload \  -H "Authorization: Bearer $SESSION_TOKEN" \  -F "file=@price-list.xlsx" \  -F "folderId=6f1c0b8e-2a1d-4f7e-9a51-0c9d3c2b7e11"
json
{  "id": "8a2d4c6e-1b3f-4a5c-9e7d-0f1a2b3c4d5e",  "title": "price-list.xlsx",  "kind": "file",  "contentType": "application/octet-stream",  "fileExtension": "xlsx",  "sizeBytes": 48213}

Each organization has 10 GB of storage, bin included. An upload that would go over it answers 400 with code: "DOCUMENT_STORAGE_FULL" and stores nothing.

Read, update and delete a document

GET /documents/:id returns the detail object.

PATCH /documents/:id changes any of these fields:

ParameterTypeRequiredDescription
titleBody, stringNo1 to 200 characters
bodyBody, stringNomarkdown and whiteboard documents only. Replaces the whole body. A whiteboard body must be an Excalidraw scene (type: "excalidraw", an elements array, files with metadata only and no dataURL), up to 8 MB; a written document is capped at 1,000,000 bytes. The public API, MCP and Ask Fondaro refuse a whiteboard body with DOCUMENT_KIND_READ_ONLY
baseUpdatedAtBody, stringNoThe updatedAt your edit started from. With title or body, a document changed since answers 409 with code: "DOCUMENT_CHANGED_ELSEWHERE"
assigneeIdsBody, string[]NoReplaces the list. Up to 50
teamIdsBody, string[]NoUp to 20 teams; their current members join the assignees. See Teams
visibilityBody, stringNoprivate or organization. Use share for public
pinnedBody, booleanNoPin or unpin for the organization
folderIdBody, string or nullNoMove to a folder; null is the top level
bash
curl -X PATCH https://api.fondaro.com/documents/$DOC_ID \  -H "Authorization: Bearer $SESSION_TOKEN" \  -H "Content-Type: application/json" \  -d '{"visibility": "organization", "pinned": true}'
json
{ "id": "3f1c0d0e-0000-4000-8000-000000000000", "visibility": "organization", "pinnedAt": "2026-10-02T09:00:00.000Z" }

A public document cannot change visibility with PATCH: revoke its link first.

DELETE /documents/:id moves the document to the bin and answers { "deleted": true }. It disappears from every list and its share link answers 404 until it is restored.

Whiteboards

POST /documents/whiteboards creates an empty private whiteboard. Body: optional title and folderId. It answers 400 with code: "DOCUMENT_STORAGE_FULL" when the organization's storage is full.

POST /documents/:id/inline-images also accepts a whiteboard: image bytes are stored with the board and counted toward storage, and the scene's files hold only their metadata.

PUT /documents/:id/preview stores the board's preview (multipart file, a PNG up to 2 MB, edit rights). It is not counted toward storage. GET /documents/:id/preview answers 302 to a five-minute link, or 404 when there is no preview. GET /public/documents/:slug/preview is the keyless twin for a shared board.

Version history

Anyone who can edit a document can read its history. A version is the body as it was before a change: one per working session for a person typing, always before Ask Fondaro, MCP or the public API replaces a body, and always before a restore. Fondaro keeps the newest 50 per document and none older than 90 days. Versions do not count toward storage.

EndpointAnswers
GET /documents/:id/versions{ items }, newest first, at most 50: id, createdAt, createdBy (null for a live-editing save), source (person, ask, api or restore), sizeBytes
GET /documents/:id/versions/:versionIdOne version with its title, kind and body
POST /documents/:id/versions/:versionId/restoreThe document, with that body written back. The body it replaced is saved as a new version first, and an open editor receives the restored text

Download or view a file

GET /documents/:id/download and GET /documents/:id/view

Both answer 302 to a link that works for five minutes. download saves the file under a name based on the title; view opens it inline and works for PDFs and images only. A markdown or whiteboard document has no file and answers 400.

bash
curl -L -o guide.pdf https://api.fondaro.com/documents/$DOC_ID/download \  -H "Authorization: Bearer $SESSION_TOKEN"

Share a document

POST /documents/:id/share

Makes the document public behind an unguessable link and returns the detail object with shareUrl set. Sharing again while the link is live returns the same link.

ParameterTypeRequiredDescription
expiresAtBody, stringNoISO 8601 time in the future. Omit for a link that never expires
bash
curl -X POST https://api.fondaro.com/documents/$DOC_ID/share \  -H "Authorization: Bearer $SESSION_TOKEN" \  -H "Content-Type: application/json" \  -d '{}'
json
{  "id": "3f1c0d0e-0000-4000-8000-000000000000",  "visibility": "public",  "shareUrl": "https://www.fondaro.com/d/9pQ2rB7wKx1vT0sN4mH6cA",  "shareExpiresAt": null,  "shareRevokedAt": null}

POST /documents/:id/revoke takes the link down and sets visibility back to organization. Sharing after a revoke or an expiry creates a new link, so addresses already sent stay dead.

Attach to leads and listings

EndpointBodyPurpose
POST /documents/:id/leads{ "leadIds": number[] }, 1 to 50Attach to leads you can open
DELETE /documents/:id/leads/:leadIdDetach one lead
POST /documents/:id/property-listings{ "listingIds": string[] }, 1 to 50Attach to your organization's listings
DELETE /documents/:id/property-listings/:listingIdDetach one listing

All four return the document detail object. If any lead or listing is out of reach, the request answers 404 and nothing is attached. Attaching again is harmless.

bash
curl -X POST https://api.fondaro.com/documents/$DOC_ID/leads \  -H "Authorization: Bearer $SESSION_TOKEN" \  -H "Content-Type: application/json" \  -d '{"leadIds": [4821, 4822]}'
json
{ "id": "3f1c0d0e-0000-4000-8000-000000000000", "attachedLeadIds": [4821, 4822], "attachedLeadCount": 2 }

List a lead's or listing's documents

GET /crm/leads/:id/documents and GET /documents/by-listing/:listingId

Return { "documents": [...] }, most recently attached first. Attaching is not a permission: a private document someone else attached stays hidden from you.

bash
curl https://api.fondaro.com/crm/leads/4821/documents \  -H "Authorization: Bearer $SESSION_TOKEN"
json
{  "documents": [    {      "id": "3f1c0d0e-0000-4000-8000-000000000000",      "title": "Buyer guide",      "kind": "pdf",      "visibility": "public",      "sizeBytes": 2214592,      "createdBy": "user_2abc",      "attachedBy": "user_2def",      "attachedAt": "2026-08-20T14:05:00.000Z",      "updatedAt": "2026-08-20T14:03:00.000Z"    }  ]}

Count documents for many listings

POST /documents/by-listing/counts

Counts the documents you can see on each listing in one request. Every requested id is in the answer, 0 included; an id outside your organization also counts 0.

ParameterTypeRequiredDescription
listingIdsBody, string[]Yes1 to 100 listing UUIDs
bash
curl -X POST https://api.fondaro.com/documents/by-listing/counts \  -H "Authorization: Bearer $SESSION_TOKEN" \  -H "Content-Type: application/json" \  -d '{"listingIds": ["7b0e6c1a-1d2e-4f3a-9b4c-5d6e7f8a9b0c"]}'
json
{ "7b0e6c1a-1d2e-4f3a-9b4c-5d6e7f8a9b0c": 3 }

Save a lead's attachment to Documents

POST /crm/leads/:id/emails/:emailId/attachments/:attachmentId/save-to-documents saves a file from a lead's email, and POST /crm/leads/:id/messages/:messageId/attachments/:index/save-to-documents saves one from a lead's message. Both answer 201 with the new document, already attached to the lead.

bash
curl -X POST https://api.fondaro.com/crm/leads/4821/emails/$EMAIL_ID/attachments/$ATTACHMENT_ID/save-to-documents \  -H "Authorization: Bearer $SESSION_TOKEN"
json
{ "id": "5c7e9a1b-2d4f-4b6a-8c0e-1f3a5b7d9e2c", "kind": "pdf", "attachedLeadIds": [4821] }

Send a document in a lead email

POST /crm/leads/:id/emails accepts documentIds: up to ten uploaded documents (a PDF or any other file), sent as real attachments, 20 MB in total. To attach a file from a computer, upload it first with POST /documents/upload and send its id. A written document, or one with no stored file, answers 422 with code: "CRM_EMAIL_DOCUMENT_NOT_ATTACHABLE"; going over the size cap answers 422 with code: "CRM_EMAIL_ATTACHMENTS_TOO_LARGE". To send a written document, put its shareUrl in the message.

bash
curl -X POST https://api.fondaro.com/crm/leads/4821/emails \  -H "Authorization: Bearer $SESSION_TOKEN" \  -H "Content-Type: application/json" \  -d '{"subject": "The guide I mentioned", "bodyText": "Here it is.", "documentIds": ["3f1c0d0e-0000-4000-8000-000000000000"]}'

See Connected inboxes for the rest of this endpoint.

Folders

EndpointPurpose
GET /documents/foldersEvery folder, as { "items": [...] }
POST /documents/foldersCreate a folder: name (1 to 200 characters), optional parentId
PATCH /documents/folders/:folderIdChange name, parentId (null for the top level) or pinned
DELETE /documents/folders/:folderIdMove the folder and everything in it to the bin

Every member sees every folder and may create one; changing a folder is limited to its creator and admins. A folder's itemCount and sizeBytes count only what you can see inside it.

bash
curl -X POST https://api.fondaro.com/documents/folders \  -H "Authorization: Bearer $SESSION_TOKEN" \  -H "Content-Type: application/json" \  -d '{"name": "Marbella listings"}'
json
{  "id": "6f1c0b8e-2a1d-4f7e-9a51-0c9d3c2b7e11",  "name": "Marbella listings",  "parentId": null,  "createdBy": "user_2abc",  "pinnedAt": null,  "itemCount": 0,  "sizeBytes": 0,  "deletedAt": null,  "createdAt": "2026-10-02T09:00:00.000Z",  "updatedAt": "2026-10-02T09:00:00.000Z"}

Move items and use the bin

These endpoints take documentIds and folderIds (each up to 100; at least one item in total). Every item must be one you may change, or the whole request is refused.

EndpointBodyAnswer
POST /documents/moveItems plus targetFolderId (null for the top level){ "moved": n }
POST /documents/binItems{ "binned": n }
GET /documents/bin{ "folders", "documents", "retentionDays" }: what you may restore (admins see the whole organization's bin)
POST /documents/bin/restoreItems{ "restored": n }
POST /documents/bin/purgeItems{ "purged": n }, deleted for good
POST /documents/bin/empty{ "documents": n, "folders": n }, everything you could restore, deleted for good
GET /documents/storage{ "usedBytes", "quotaBytes" } for the organization

Binning a folder takes everything inside it, and restoring the folder brings back exactly that set. Items stay in the bin for 30 days, then are deleted for good.

bash
curl -X POST https://api.fondaro.com/documents/bin/restore \  -H "Authorization: Bearer $SESSION_TOKEN" \  -H "Content-Type: application/json" \  -d '{"documentIds": ["2b9c1c55-6d7e-4f3a-8e21-9a0b1c2d3e4f"]}'
json
{ "restored": 1 }

GET /public/documents/:slug

Needs no credential: the slug is the access. Answers the document without anything about the organization or its author.

bash
curl https://api.fondaro.com/public/documents/9pQ2rB7wKx1vT0sN4mH6cA
json
{  "slug": "9pQ2rB7wKx1vT0sN4mH6cA",  "title": "Buyer guide",  "kind": "pdf",  "body": null,  "viewUrl": "https://…",  "contentType": "application/pdf",  "fileExtension": "pdf",  "sizeBytes": 2214592,  "updatedAt": "2026-08-20T14:03:00.000Z"}

body holds the Markdown of a written document. For a whiteboard it holds the scene with every object that is not a property replaced by a hidden placeholder, so no lead, deal or person id leaves the server. viewUrl is a five-minute link for a PDF or image; use it at once and never store it. The lasting address is https://www.fondaro.com/d/{slug}.

GET /public/documents/:slug/download answers 302 to a five-minute download of an uploaded file, checking the link again on every request.

Errors and limits

StatusWhen
400Invalid input, a folder moved into itself, an empty bulk request, a body on a document that has none, an invalid whiteboard scene (DOCUMENT_WHITEBOARD_INVALID), DOCUMENT_KIND_READ_ONLY, view on a file that is not a PDF or image, or DOCUMENT_STORAGE_FULL
403SUBSCRIPTION_REQUIRED, or changing a document or folder you neither created nor administer
404A document, lead or listing you cannot see; an unknown share slug, or one whose organization is disabled or not subscribed
409DOCUMENT_CHANGED_ELSEWHERE on an edit sent with baseUpdatedAt
410A share link that was revoked or expired; code is revoked or expired

The /public/documents routes allow 60 requests a minute per IP address. The same library is available through the MCP server with the documents:read and documents:write scopes; uploading and binning stay on this API.