Overview
The FunBIM API gives read-only access to the data of all your company's projects:
- projects, their configuration (trades, statuses, types, priorities, companies) and spatial structure;
- members and their permissions;
- issues and forms, with their answers, comments, sub-tasks and signatories;
- document management files, coded documents and their approval workflow;
- photos, progress records and equipment;
- a full export of a project as a ZIP archive.
The API is included in the FunBIM 3D company subscription. Keys created in FunBIM are read-only: an integration cannot alter your projects.
Quick start
1. Create a key in FunBIM. You need to be an administrator of the company, on a FunBIM 3D subscription:
- in My Projects, open your organization's card ("Organization settings, applied to all your projects");
- in the left-hand menu, choose API;
- click Create a key, give it a name (for example "Power BI") and a validity period.
The key is shown only once: copy it straight away and keep it like a password. The same screen lists your keys and their last use, and lets you revoke one.
2. List your company's projects:
curl https://api.funbim.com/v1/projects \
-H "Authorization: Bearer fbk_live_XXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
3. You get the list of your projects:
{
"data": [
{
"id": "-NLKzINUsK6CcZspR6B6",
"name": "Résidence Les Tilleuls",
"customId": "2026-014",
"status": "active",
"address": "12 rue des Tilleuls, 69003 Lyon",
"createdAt": "2026-01-09T10:04:37.722Z",
"updatedAt": "2026-10-14T16:52:35.162Z",
"createdBy": { "visId": "-MUiZYD9AXG_hj7GjYyr", "name": "Claire Martin" },
"spatialLabels": { "buildings": "Bâtiments" }
}
],
"nextCursor": null
}
Authentication
Every request carries the key in the Authorization: Bearer fbk_live_… header. A key belongs to the company, not to a person: it gives access to all the company's projects, and to them only.
- A company can have up to 10 active keys, one per use (BI, ERP…).
- Each key has a validity period (1 year at most) and can be revoked at any time; revocation takes effect within a minute.
- Use the key server-side only. Never embed it in a web page or a mobile app.
A missing, invalid, revoked or expired key always gets the same 401 invalid_key response, whatever the cause.
Security and isolation
The API is built so that a key can never read anything but its own company's data.
- Isolation per company: every request is tied to the key's company, and the scope is checked before any read. A project of another company answers
404, exactly like a missing project: you cannot even tell it exists. - Read-only: a key created in FunBIM never changes your data. Starting an export is the only accepted action.
- Protected keys: FunBIM never stores your key in clear text, only a fingerprint. It is shown once, when created; if you lose it, revoke it and create another one.
- Files: binaries are only served through short-lived signed links (15 minutes, 24 hours for an export archive), and only for files of the requested project.
- Minimal personal data: people are given by a public identifier and their name, never by their email or login identifier.
- Plain errors: error responses carry no internal technical detail.
Good practices:
- one key per tool (BI, ERP…), so you can revoke one without cutting the others;
- keep it in a secrets manager, never in source code or a shared spreadsheet;
- a key sees every project of the company: keep it for trusted tools.
These guarantees are checked by automated tests, including cross-company access attempts run against the deployed API.
Conventions
- Responses are UTF-8 JSON; a resource is in
data, a list in data + nextCursor. - Dates are ISO 8601 UTC (
2026-10-14T08:30:00.000Z); a missing value is null. - People are given as
{ visId, name }, configuration items (status, type, trade…) as { id, label }. The full lists are in /projects/{projectId}/config. name is null when a person's name is not set, or is only an email address: the API never publishes emails. The visId still identifies the person.- Form answers are in
formData, a label → value object ready to use. - Deleted items are excluded, unless you pass
includeDeleted=true. - New fields may appear: ignore the ones you don't know.
Location on drawings and models
An issue or a form can be located in three ways, which can be combined: on a PDF drawing (planLocation), in the model (modelLocation) and in the project structure (spatialLocation). A field is null when the item isn't located that way. fileId gives the drawing or model concerned.
On a PDF drawing — the position is given on the page as displayed, rotation included, from the top-left corner:
"planLocation": {
"fileId": "-NLLAA7v50nyjO5a2FEm",
"page": 1,
"x": 0.521,
"y": 0.512,
"rotation": 270,
"xPt": 1350.43,
"yPt": 884.62,
"pageWidthPt": 2592,
"pageHeightPt": 1728
}
x, y: position as a fraction of the page (0 to 1). To place the pin on an image of the drawing: x × image width, y × image height.xPt, yPt: the same position in PDF points; pageWidthPt, pageHeightPt: the size of the displayed page, in PDF points.page starts at 1; rotation is the page rotation (0, 90, 180 or 270).- The position stays right from one revision of the drawing to the next, even if the page is rotated differently.
In the model — kind tells which kind of point it is:
"modelLocation": {
"kind": "3d",
"fileId": "-O7_mttUaADaWQEW9EMM",
"viewableGuid": "fe9402fe-49f5-4c05-9873-8601750db635-001e911a",
"sheetName": null,
"linkedFileId": null,
"elementGuid": "ad3aac00-f6ca-4a67-9ecb-94800e6c7479-0013a9a2",
"x": 94.99,
"y": -79.56,
"z": 20.53
}
kind: "3d": a point in the model; x, y, z are model coordinates, elementGuid is the element pointed at.kind: "sheet": a point on a 2D sheet of the model, named by sheetName; x, y are coordinates on that sheet and z is 0.viewableGuid is the model view where the point was placed; linkedFileId the linked model, for a federated model.
In the project structure — building, level, area and unit, with their label:
"spatialLocation": {
"buildingId": "-NQUhGd5NcOGBgfj8u8N",
"levelId": null,
"areaId": null,
"housingId": null,
"building": { "id": "-NQUhGd5NcOGBgfj8u8N", "label": "Building A" },
"level": null,
"area": null,
"housing": null
}
The plain identifiers (buildingId…) are still there. The full structure is in /projects/{projectId}/spatial.
Files and photos
Binaries are never in JSON responses. Each file and photo carries a downloadPath: call it with your key and the API answers 302 to a signed download URL, valid for 15 minutes.
curl -L -o drawing.pdf \
-H "Authorization: Bearer $FUNBIM_API_KEY" \
https://api.funbim.com/v1/projects/PROJECT_ID/files/FILE_ID/download
For a photo, ?size=thumb returns the thumbnail when there is one.
ZIP export of a project
To archive a whole project or migrate your data, start an export: FunBIM builds a ZIP archive with all the data in the API format, an Excel sheet of issues and forms, and, if requested, the files, photos and signatures.
1. Start the exportcurl -X POST https://api.funbim.com/v1/projects/PROJECT_ID/exports \
-H "Authorization: Bearer $FUNBIM_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "includeMedia": true }'
# 202 Accepted
{ "data": { "id": "EXPORT_ID", "status": "pending", … } }
2. Poll the export, then downloadcurl https://api.funbim.com/v1/projects/PROJECT_ID/exports/EXPORT_ID \
-H "Authorization: Bearer $FUNBIM_API_KEY"
# once "status": "done"
{ "data": { "status": "done", "sizeBytes": 371160353,
"downloadUrl": "https://storage.googleapis.com/…", … } }
- The archive is ready within minutes; poll the export every 10 to 30 seconds.
- The
downloadUrl link is valid for 24 hours and can be requested again; the archive is kept for 7 days. - Above 5 GB of files, the archive is built without them: download them afterwards through their
downloadPath. - One export at a time per project, 10 exports per day per company.
Limits
| Limit | Value |
|---|
| Rate | 120 requests per minute per key |
| Quota | 50,000 requests per day per company (reset at midnight UTC) |
| Exports | 1 running per project, 10 per day per company |
Beyond that, the API answers 429 with a Retry-After header giving the wait in seconds.
Errors
An error always returns an HTTP status and an error object:
HTTP/2 404
{ "error": { "code": "not_found", "message": "Resource not found." } }
| HTTP | Code | Meaning |
|---|
| 400 | invalid_request | Invalid or repeated parameter |
| 401 | invalid_key | Missing, invalid, revoked or expired key |
| 403 | plan_required | The company's subscription doesn't include the API |
| 404 | not_found | Resource missing or outside your company |
| 405 | invalid_request | Method not allowed (the API is read-only) |
| 409 | export_in_progress | An export of this project is already running |
| 429 | rate_limited / quota_exceeded | Rate or daily quota exceeded |
| 500 | internal | Internal error, try again |
Versioning
Version 1 (/v1) only receives additions: new fields, new endpoints. A breaking change will go through a version 2, announced at least 12 months ahead.
Reference
Every version 1 endpoint, generated from the API contract. Paths are relative to the base URL https://api.funbim.com/v1.
Projects
GET/projects→ 200
Your company's projects
Response
| Field | Type |
|---|
id | string |
name | string |
customId | string | null |
status | "active" | "archived" |
address | string | null |
createdAt | date-time |
updatedAt | date-time |
createdBy | object |
createdBy.visId | string |
createdBy.name | string | null |
spatialLabels | object |
spatialLabels.buildings · optional | string |
spatialLabels.levels · optional | string |
spatialLabels.areas · optional | string |
spatialLabels.housings · optional | string |
GET/projects/{projectId}→ 200
Project details
Parameters
| Field | Type |
|---|
projectId · path | string |
Response
| Field | Type |
|---|
id | string |
name | string |
customId | string | null |
status | "active" | "archived" |
address | string | null |
createdAt | date-time |
updatedAt | date-time |
createdBy | object |
createdBy.visId | string |
createdBy.name | string | null |
spatialLabels | object |
spatialLabels.buildings · optional | string |
spatialLabels.levels · optional | string |
spatialLabels.areas · optional | string |
spatialLabels.housings · optional | string |
GET/projects/{projectId}/config→ 200
Trades, statuses, types, priorities, roles, fields, companies
Parameters
| Field | Type |
|---|
projectId · path | string |
Response
| Field | Type |
|---|
trades | object[] |
statuses | object[] |
types | object[] |
priorities | object[] |
roles | object[] |
fields | object[] |
companies | object[] |
GET/projects/{projectId}/spatial→ 200
Buildings, levels, areas, housing units
Parameters
| Field | Type |
|---|
projectId · path | string |
Response
| Field | Type |
|---|
buildings | object[] |
levels | object[] |
areas | object[] |
housings | object[] |
GET/projects/{projectId}/members→ 200
Project members
Parameters
| Field | Type |
|---|
projectId · path | string |
Response
| Field | Type |
|---|
visId | string |
name | string | null |
company | string | null |
role | "owner" | "admin" | "member" | "viewer" | "custom" |
tradeIds | string[] |
permissions | object |
permissions.create | "all" | "ownTrades" | "none" |
permissions.read | "all" | "ownTrades" |
permissions.edit | "all" | "ownTrades" | "ownItemsInOwnTrades" | "commentsOnly" |
permissions.close | "all" | "ownTrades" | "none" |
permissions.delete | "all" | "ownItems" | "none" |
permissions.canImport | boolean |
permissions.canConfigure | boolean |
joinedAt | date-time | null |
Issues and forms
GET/projects/{projectId}/observations→ 200
Issues and forms
Parameters
| Field | Type |
|---|
projectId · path | string |
kind · query, optional | "observation" | "form" |
cursor · query, optional | string |
limit · query, optional | integer |
includeDeleted · query, optional | boolean |
updatedSince · query, optional | date-time |
Response
Paginated list: data is an array, nextCursor reads the next page.
| Field | Type |
|---|
id | string |
kind | "observation" | "form" |
number | integer | null |
customId | string | null |
title | string |
description | string | null |
status | object |
status.id | string |
status.label | string | null |
status.category | "opened" | "intermediate" | "closed" |
type | object | null |
type.id | string |
type.label | string | null |
priority | object | null |
priority.id | string |
priority.label | string | null |
trades | object[] |
allTrades | boolean |
assignedMembers | object[] |
assignedCompanies | object[] |
dueDate | date | null |
fileId | string | null |
planLocation | object | null |
planLocation.fileId | string |
planLocation.page | integer |
planLocation.x | number | null |
planLocation.y | number | null |
planLocation.rotation | "0" | "90" | "180" | "270" |
planLocation.xPt | number | null |
planLocation.yPt | number | null |
planLocation.pageWidthPt | number | null |
planLocation.pageHeightPt | number | null |
modelLocation | object | null |
modelLocation.kind | "3d" | "sheet" |
modelLocation.fileId | string |
modelLocation.viewableGuid | string | null |
modelLocation.sheetName | string | null |
modelLocation.linkedFileId | string | null |
modelLocation.elementGuid | string | null |
modelLocation.x | number |
modelLocation.y | number |
modelLocation.z | number |
spatialLocation | object | null |
spatialLocation.buildingId | string | null |
spatialLocation.levelId | string | null |
spatialLocation.areaId | string | null |
spatialLocation.housingId | string | null |
spatialLocation.building | object | null |
spatialLocation.level | object | null |
spatialLocation.area | object | null |
spatialLocation.housing | object | null |
createdAt | date-time |
createdBy | object |
createdBy.visId | string |
createdBy.name | string | null |
updatedAt | date-time |
updatedBy | object | null |
updatedBy.visId | string |
updatedBy.name | string | null |
closedAt | date-time | null |
closedBy | object | null |
closedBy.visId | string |
closedBy.name | string | null |
archived | boolean |
highlighted | boolean |
deleted | boolean |
photoIds | string[] |
formData | map<string, string> |
signatures | object[] |
GET/projects/{projectId}/observations/{observationId}→ 200
Details (sub-tasks and comments included)
Parameters
| Field | Type |
|---|
projectId · path | string |
observationId · path | string |
Response
| Field | Type |
|---|
id | string |
kind | "observation" | "form" |
number | integer | null |
customId | string | null |
title | string |
description | string | null |
status | object |
status.id | string |
status.label | string | null |
status.category | "opened" | "intermediate" | "closed" |
type | object | null |
type.id | string |
type.label | string | null |
priority | object | null |
priority.id | string |
priority.label | string | null |
trades | object[] |
allTrades | boolean |
assignedMembers | object[] |
assignedCompanies | object[] |
dueDate | date | null |
fileId | string | null |
planLocation | object | null |
planLocation.fileId | string |
planLocation.page | integer |
planLocation.x | number | null |
planLocation.y | number | null |
planLocation.rotation | "0" | "90" | "180" | "270" |
planLocation.xPt | number | null |
planLocation.yPt | number | null |
planLocation.pageWidthPt | number | null |
planLocation.pageHeightPt | number | null |
modelLocation | object | null |
modelLocation.kind | "3d" | "sheet" |
modelLocation.fileId | string |
modelLocation.viewableGuid | string | null |
modelLocation.sheetName | string | null |
modelLocation.linkedFileId | string | null |
modelLocation.elementGuid | string | null |
modelLocation.x | number |
modelLocation.y | number |
modelLocation.z | number |
spatialLocation | object | null |
spatialLocation.buildingId | string | null |
spatialLocation.levelId | string | null |
spatialLocation.areaId | string | null |
spatialLocation.housingId | string | null |
spatialLocation.building | object | null |
spatialLocation.level | object | null |
spatialLocation.area | object | null |
spatialLocation.housing | object | null |
createdAt | date-time |
createdBy | object |
createdBy.visId | string |
createdBy.name | string | null |
updatedAt | date-time |
updatedBy | object | null |
updatedBy.visId | string |
updatedBy.name | string | null |
closedAt | date-time | null |
closedBy | object | null |
closedBy.visId | string |
closedBy.name | string | null |
archived | boolean |
highlighted | boolean |
deleted | boolean |
photoIds | string[] |
formData | map<string, string> |
signatures | object[] |
subtaskLists | object[] |
comments | object[] |
Documents
GET/projects/{projectId}/files→ 200
Document management files and folders
Parameters
| Field | Type |
|---|
projectId · path | string |
cursor · query, optional | string |
limit · query, optional | integer |
includeDeleted · query, optional | boolean |
updatedSince · query, optional | date-time |
Response
Paginated list: data is an array, nextCursor reads the next page.
| Field | Type |
|---|
id | string |
name | string |
title | string | null |
kind | "folder" | "pdf" | "model3d" | "image" | "other" |
mimeType | string | null |
sizeBytes | integer |
parentId | string | null |
revisionRootId | string |
owner | object |
owner.visId | string |
owner.name | string | null |
createdAt | date-time |
updatedAt | date-time |
deleted | boolean |
downloadPath | string | null |
GET/projects/{projectId}/files/{fileId}/download→ 302
Download a file
Parameters
| Field | Type |
|---|
projectId · path | string |
fileId · path | string |
Response
Redirects to a signed download URL, valid for 15 minutes.
GET/projects/{projectId}/documents→ 200
Coded documents (drawing lists) and approval workflow
Parameters
| Field | Type |
|---|
projectId · path | string |
cursor · query, optional | string |
limit · query, optional | integer |
includeDeleted · query, optional | boolean |
updatedSince · query, optional | date-time |
Response
Paginated list: data is an array, nextCursor reads the next page.
| Field | Type |
|---|
id | string |
fileId | string | null |
fileName | string |
title | string | null |
status | object |
status.id | string |
status.label | string | null |
codification | map<string, string> |
dueDate | date | null |
previousFileIds | string[] |
visaCount | integer |
responseCount | integer |
transmittalNumbers | integer[] |
createdAt | date-time |
updatedAt | date-time |
deleted | boolean |
Photos
GET/projects/{projectId}/photos→ 200
Photos
Parameters
| Field | Type |
|---|
projectId · path | string |
cursor · query, optional | string |
limit · query, optional | integer |
includeDeleted · query, optional | boolean |
updatedSince · query, optional | date-time |
Response
Paginated list: data is an array, nextCursor reads the next page.
| Field | Type |
|---|
id | string |
attachedTo | object |
attachedTo.kind | "observation" | "form" | "comment" | "other" |
attachedTo.id | string | null |
owner | object |
owner.visId | string |
owner.name | string | null |
contentType | string | null |
sizeBytes | integer |
createdAt | date-time |
deleted | boolean |
downloadPath | string |
thumbnailPath | string | null |
GET/projects/{projectId}/photos/{photoId}/download→ 302
Download a photo
Parameters
| Field | Type |
|---|
projectId · path | string |
photoId · path | string |
size · query, optional | "full" | "thumb" |
Response
Redirects to a signed download URL, valid for 15 minutes.
Progress and equipment
GET/projects/{projectId}/progress→ 200
Progress records
Parameters
| Field | Type |
|---|
projectId · path | string |
cursor · query, optional | string |
limit · query, optional | integer |
includeDeleted · query, optional | boolean |
updatedSince · query, optional | date-time |
Response
Paginated list: data is an array, nextCursor reads the next page.
| Field | Type |
|---|
id | string |
type | object |
type.id | string |
type.label | string | null |
status | object | null |
status.id | string |
status.label | string | null |
fileId | string |
measure | number |
unit | string |
author | object |
author.visId | string |
author.name | string | null |
createdAt | date-time |
updatedAt | date-time | null |
deleted | boolean |
xfdf | string | null |
GET/projects/{projectId}/equipments→ 200
Equipment
Parameters
| Field | Type |
|---|
projectId · path | string |
cursor · query, optional | string |
limit · query, optional | integer |
includeDeleted · query, optional | boolean |
updatedSince · query, optional | date-time |
Response
Paginated list: data is an array, nextCursor reads the next page.
| Field | Type |
|---|
id | string |
name | string |
category | string | null |
customId | string | null |
type | object | null |
type.id | string |
type.label | string | null |
status | object | null |
status.id | string |
status.label | string | null |
fileId | string | null |
linkedFileId | string | null |
externalId | string | null |
measure | number | null |
measureUnit | "m" | "m2" | "unit" | null |
author | object |
author.visId | string |
author.name | string | null |
createdAt | date-time |
updatedAt | date-time |
closedAt | date-time | null |
closedBy | object | null |
closedBy.visId | string |
closedBy.name | string | null |
stateHistory | object[] |
deleted | boolean |
ZIP export
POST/projects/{projectId}/exports→ 202
Start a ZIP export of the project
Parameters
| Field | Type |
|---|
projectId · path | string |
Request body
| Field | Type |
|---|
includeMedia · optional | boolean |
Response
| Field | Type |
|---|
id | string |
projectId | string |
status | "pending" | "processing" | "done" | "failed" |
includeMedia | boolean |
createdAt | date-time |
finishedAt | date-time | null |
sizeBytes | integer | null |
downloadUrl | string | null |
downloadUrlExpiresAt | date-time | null |
expiresAt | date-time | null |
error | string | null |
GET/projects/{projectId}/exports/{exportId}→ 200
Export status (and ZIP link once ready)
Parameters
| Field | Type |
|---|
projectId · path | string |
exportId · path | string |
Response
| Field | Type |
|---|
id | string |
projectId | string |
status | "pending" | "processing" | "done" | "failed" |
includeMedia | boolean |
createdAt | date-time |
finishedAt | date-time | null |
sizeBytes | integer | null |
downloadUrl | string | null |
downloadUrlExpiresAt | date-time | null |
expiresAt | date-time | null |
error | string | null |
Connect FunBIM to your tools
The API is included in the FunBIM 3D company subscription. Let's talk about your integration.
Contact us