Developers
Flusia API Reference
Read and write the data of your Flusia CRM workspace from your own code or from automation platforms, and receive signed webhooks when something changes.
Overview
The Flusia API is an HTTPS + JSON API. Every request is authenticated with an API token that belongs to one organization (workspace) and carries its own permissions. All data you read or write is always scoped to that organization.
There are two families of endpoints behind the same host and the same token:
- REST resources under
/api/api-*: create, read, update and delete leads and clients, deals, tasks, sales tasks, projects, pipelines, services, plus read-only invoices and documents. - API v1 under
/crm/v1: outgoing webhooks (REST hooks), lookup lists for dropdowns, exact lead search, lead tags, custom fields and notes.
Requests and responses use Content-Type: application/json and UTF-8. Timestamps are ISO 8601 strings in UTC. Identifiers are UUIDs. All example ids, names and values on this page are fictitious.
Base URLs
| Family | Base URL |
|---|---|
| REST resources | https://api.flusia.it/api (for example https://api.flusia.it/api/api-leads) |
| API v1 | https://api.flusia.it/crm/v1 |
| Token check | https://api.flusia.it/me |
Only HTTPS is supported.
Authentication
Create an API token in Flusia under Settings > API (app.flusia.it/settings?tab=api-tokens) with New token. Tokens start with flusia_. When you create a token you choose what it can read, write and delete, and optionally an expiry date and a list of allowed IP addresses.
Send the token in the Authorization header of every request:
Authorization: Bearer flusia_your_token_here
curl https://api.flusia.it/me \
-H "Authorization: Bearer flusia_your_token_here"
401 when the token is missing, malformed, unknown, disabled or expired.IP allowlist (optional). If the token has a list of allowed IP addresses, requests from any other address are rejected with 403. Leave the list empty to allow any address.
Permissions
Each token has a permission set per resource with three actions: read, write (create and update) and delete. A request without the required permission is rejected with 403. Example of a token's permissions, as returned by GET /me:
{
"leads": { "read": true, "write": true, "delete": false },
"deals": { "read": true, "write": false, "delete": false },
"tasks": { "read": true, "write": true, "delete": true }
}
The permission required by each endpoint is shown next to it on this page (for example leads.read).
Errors and status codes
Errors are returned as JSON with an error message and, when available, extra details:
{
"error": "Insufficient permissions: leads.write required",
"details": "optional extra information"
}
| Status | Meaning |
|---|---|
200 | Success. |
201 | Resource created. |
400 | Invalid request: missing or invalid parameters or body fields. The message says which. |
401 | Missing, malformed, unknown, disabled or expired API token. |
403 | The token lacks the required permission, the caller IP is not allowed for the token, or the endpoint is not public. |
404 | The resource does not exist in your organization, or the route does not exist. |
405 | HTTP method not supported on this path. |
409 | Conflict, for example a duplicate (see each endpoint). |
429 | Rate limit exceeded (see Rate limits). |
500 / 502 | Server error or upstream temporarily unavailable. Retry later with backoff. |
Rate limits
- REST resources (
/api/*): 100 requests per minute per token and caller IP. - API v1 (
/crm/v1/*): 100 requests per minute per token.
Responses include the standard RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers. When the limit is exceeded the API answers 429:
{ "error": "Too many requests, please try again later.", "retryAfter": 60 }
Wait the number of seconds in retryAfter before retrying.
Pagination
List endpoints of the REST resources (leads, deals, sales tasks, tasks, projects, services, invoices, documents) are paginated with two query parameters:
| Parameter | Default | Description |
|---|---|---|
page | 1 | Page number, starting from 1. |
limit | 50 | Items per page, maximum 100. |
The response carries a pagination object; keep requesting the next page until page equals total_pages:
{
"data": [ ... ],
"pagination": { "page": 1, "limit": 50, "total": 128, "total_pages": 3 }
}
Pipelines, lookups, webhook subscriptions and the sub-lists of a record (comments, checklist, members...) are returned whole, without pagination.
Terminology
| Term | Meaning |
|---|---|
| Lead / client | Both are records of the same resource (/api/api-leads). Whether a record is a lead or a client depends on its state (stato_cliente, see states lookup, field is_lead). |
| Deal | A quote or offer sent to a lead or client (/api/api-deals). Statuses: draft, sent, accepted, rejected. |
| Sales task | A commercial activity such as a call, meeting or visit scheduled on a lead, client or deal (/api/api-activities). Shown as "Sales Task" in the app. |
| Task | A to-do inside a project (/api/api-tasks). |
| Pipeline / stage | The sales pipelines of leads and their stages (/api/api-pipelines). |
Endpoint index
Account: /me
Use this endpoint to check that a token is valid and to read the organization it belongs to.
Returns the organization and the token's permissions. Any valid token can call it.
curl https://api.flusia.it/me -H "Authorization: Bearer flusia_your_token_here"
{
"organization": { "id": "0b7c1e52-3f1a-4c55-9d1e-6a1f2b3c4d5e", "name": "Example Srl" },
"token": {
"id": "5d2f0c1a-8b7e-4c3d-9a10-2b3c4d5e6f70",
"name": "My integration",
"permissions": { "leads": { "read": true, "write": true, "delete": false } }
},
"authenticated": true
}
Same check on the API v1 host path. Response:
{
"organization_id": "0b7c1e52-3f1a-4c55-9d1e-6a1f2b3c4d5e",
"token_id": "5d2f0c1a-8b7e-4c3d-9a10-2b3c4d5e6f70",
"permissions": { "leads": { "read": true, "write": true, "delete": false } }
}
Lookups
Short lists (id and name, plus a few fields) used to fill dropdowns and to map values such as owners, tags, pipelines and stages. Always filtered to your organization.
Response: { "data": [ ... ] }. Unknown kinds return 404; a token without the permission listed below gets 403.
| kind | Permission | Fields of each item |
|---|---|---|
users | leads.read | id, name, email (active users of the organization) |
tags | leads.read | id, name, color (lead/client tags) |
lead-sources | leads.read | id, name, category |
states | leads.read | id, name, is_lead (true = lead state, false = client state) |
interests | leads.read | id, name, parent_id, category_type |
pipelines | leads.read | id, name, is_default |
stages | leads.read | id, name, pipeline_id, pipeline_name, is_won, is_lost. Optional query pipeline_id (UUID) to filter. |
custom-fields | leads.read | id, key, name, type, options, is_required, help_text (definitions of lead custom fields) |
services | services.read | id, name |
projects | tasks.read | id, name (non-archived projects) |
deal-statuses | deals.read | id, name: draft, sent, accepted, rejected |
curl "https://api.flusia.it/crm/v1/lookups/stages?pipeline_id=7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d" \
-H "Authorization: Bearer flusia_your_token_here"
{
"data": [
{ "id": "c3d4e5f6-0000-4000-8000-000000000001", "name": "New", "pipeline_id": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d", "pipeline_name": "Sales", "is_won": false, "is_lost": false },
{ "id": "c3d4e5f6-0000-4000-8000-000000000002", "name": "Won", "pipeline_id": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d", "pipeline_name": "Sales", "is_won": true, "is_lost": false }
]
}
Lead search, tags, custom fields and notes
These API v1 endpoints complement the Leads REST resource. They return a lead or client in the public lead shape, the same shape sent by lead webhooks. Unknown or deleted ids return 404 with {"error": "Lead not found"}.
Public lead shape
All non-internal fields of the lead or client record (same names as in /api/api-leads, for example id, business_name, email, phone, stato_cliente, pipeline_id, pipeline_stage_id, created_at, updated_at), plus these readable fields:
| Field | Description |
|---|---|
is_lead | true if the state is a lead state, false for a client state, null if no state. |
address | { street, city, province, postal_code, country } |
pipeline | { id, name } or null |
stage | { id, name, is_won, is_lost } or null |
state | { id, name } or null |
owner | { id, name, email } of the assigned user, or null |
lead_source, lead_source_id | Source name and, when the source is a configured one, its id. |
opportunity_value | Number or null. |
tags, interests | Arrays of names. |
custom_fields | Object keyed by field key: { "budget": { "label": "Budget", "type": "number", "value": "1500" } } |
contacts | Contact people linked to a company: [{ id, name, email, phone, whatsapp_number, role, area, is_primary, is_referente }] |
url | Link to the record in the Flusia app. |
{
"id": "2f516d26-bc55-4e8e-a1d6-959bff44cffe",
"business_name": "Mario Rossi",
"email": "mario.rossi@example.com",
"phone": "+39 333 000 0000",
"entity_type": "individual",
"stato_cliente": "a1b2c3d4-0000-4000-8000-000000000010",
"pipeline_id": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"pipeline_stage_id": "c3d4e5f6-0000-4000-8000-000000000001",
"created_at": "2026-10-15T08:30:00.000Z",
"updated_at": "2026-10-18T14:02:11.000Z",
"is_lead": true,
"address": { "street": "Via Example 1", "city": "Milano", "province": "MI", "postal_code": "20100", "country": "IT" },
"pipeline": { "id": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d", "name": "Sales" },
"stage": { "id": "c3d4e5f6-0000-4000-8000-000000000001", "name": "New", "is_won": false, "is_lost": false },
"state": { "id": "a1b2c3d4-0000-4000-8000-000000000010", "name": "New lead" },
"owner": { "id": "9e8d7c6b-0000-4000-8000-000000000020", "name": "Anna Bianchi", "email": "anna@example.com" },
"lead_source": "Website",
"lead_source_id": "b2c3d4e5-0000-4000-8000-000000000030",
"opportunity_value": 2500,
"tags": ["VIP"],
"interests": ["Consulting"],
"custom_fields": { "budget": { "label": "Budget", "type": "number", "value": "1500" } },
"contacts": [],
"url": "https://app.flusia.it/leads/2f516d26-bc55-4e8e-a1d6-959bff44cffe"
}
Exact search by email and/or phone, for "find or create" flows. Email matching is case-insensitive. Phone matching compares the last 9 digits against both phone and whatsapp_number (at least 6 digits required). Returns up to 5 matches, email matches first, then most recently updated.
Query parameters
| Name | Type | Description |
|---|---|---|
email | string | Email to match exactly. |
phone | string | Phone number in any format. |
At least one is required, otherwise 400 {"error": "Provide email and/or phone"}. Response: { "data": [ lead, ... ] } (empty array when nothing matches).
curl "https://api.flusia.it/crm/v1/leads/find?email=mario.rossi%40example.com" \
-H "Authorization: Bearer flusia_your_token_here"
Returns one lead or client in the public shape: { "data": lead }.
Adds tags to a lead or client, by tag id or by name (case-insensitive), without removing existing tags. Adding a tag starts the "tag added" automations configured in Flusia.
Body
| Field | Type | Description |
|---|---|---|
tags | string[] (required) | 1 to 50 tag ids or names (max 100 characters each). |
create_missing | boolean | Default false. When true, names that do not exist yet are created as new tags. |
If some tags do not exist (and create_missing is false, or an unknown id is given) the answer is 400 with error, missing (list) and a hint. On success: { "data": lead } with the updated tags.
curl -X POST https://api.flusia.it/crm/v1/leads/2f516d26-bc55-4e8e-a1d6-959bff44cffe/tags \
-H "Authorization: Bearer flusia_your_token_here" \
-H "Content-Type: application/json" \
-d '{"tags": ["VIP", "Trade fair 2026"], "create_missing": true}'
Removes one tag (id or name, URL-encoded) from the lead or client. Response: { "removed": true }, or { "removed": false } if the tag was not assigned.
Sets custom field values, addressed by field key or definition id (see the custom-fields lookup). Values are validated by type: number fields need a number, date fields a date (YYYY-MM-DD), select fields one of the allowed options (case-insensitive). Other types are stored as text; arrays are joined with commas. null or an empty string clears the value. Fields not listed are left unchanged.
Body
{ "values": { "budget": 1500, "sector": "Construction", "visit_date": "2026-11-03" } }
Unknown fields or invalid values return 400 with {"error": "Invalid custom fields", "details": "..."} and nothing is written. On success: { "data": lead }.
The latest 100 notes of the lead or client, newest first.
{
"data": [
{
"id": "d4e5f6a7-0000-4000-8000-000000000040",
"content": "Called back, interested in the premium plan",
"created_at": "2026-10-18T09:12:00.000Z",
"updated_at": "2026-10-18T09:12:00.000Z",
"author_id": "9e8d7c6b-0000-4000-8000-000000000020",
"author_name": "Anna Bianchi"
}
]
}
Adds a note to the lead or client timeline. Body: { "content": "..." } (1 to 20,000 characters). Response 201:
{ "data": { "id": "d4e5f6a7-0000-4000-8000-000000000041", "content": "Sent brochure", "created_at": "2026-10-19T10:00:00.000Z" } }
Webhooks
Subscribe an HTTPS URL to an event and Flusia will POST a signed JSON payload to it every time the event happens. Subscriptions are created through the API (REST hooks), so automation platforms can create and remove them on their own.
Events
| Event | When | Permission |
|---|---|---|
lead.created | A lead or client is created. | leads.read |
lead.updated | A field visible to users changes (name, contacts, address, owner, pipeline, stage, state, source, value, consents). | leads.read |
lead.stage_changed | The pipeline or pipeline stage changes. | leads.read |
lead.state_changed | The lead/client state changes. | leads.read |
lead.tag_added | A tag is added to a lead or client. | leads.read |
deal.created | A deal is created. | deals.read |
deal.status_changed | The deal status changes (draft, sent, accepted, rejected). | deals.read |
task.created | A task is created in a project. | tasks.read |
task.completed | A task is marked as completed. | tasks.read |
The event catalog, with allowed telling whether this token has the permission needed to subscribe.
{
"events": [
{ "event": "lead.created", "entity": "lead", "resource": "leads", "label": "New lead or client", "description": "A lead or client is created", "allowed": true }
]
}
Subscribes a URL to one event. The token needs read on the event's resource (see the table above).
Body
| Field | Type | Description |
|---|---|---|
event | string (required) | One of the events above. |
target_url | string (required) | Public https URL, max 2000 characters. Private or internal addresses, plain http and URLs with credentials are refused (400). |
source | string | Optional label of who created the subscription (max 40 characters). |
Response 201. The signing secret is returned only here: store it to verify signatures. An organization can have at most 200 active subscriptions (429 beyond that).
curl -X POST https://api.flusia.it/crm/v1/hooks \
-H "Authorization: Bearer flusia_your_token_here" \
-H "Content-Type: application/json" \
-d '{"event": "lead.stage_changed", "target_url": "https://example.com/flusia-webhook"}'
{
"id": "e5f6a7b8-0000-4000-8000-000000000050",
"event": "lead.stage_changed",
"target_url": "https://example.com/flusia-webhook",
"source": null,
"is_active": true,
"created_at": "2026-10-19T10:00:00.000Z",
"secret": "whsec_EXAMPLEexampleEXAMPLEexample00"
}
Lists the subscriptions created with this token (a token never sees subscriptions created by other tokens), newest first.
{
"data": [
{
"id": "e5f6a7b8-0000-4000-8000-000000000050",
"event": "lead.stage_changed",
"target_url": "https://example.com/flusia-webhook",
"source": null,
"is_active": true,
"consecutive_failures": 0,
"disabled_reason": null,
"last_delivery_at": "2026-10-19T10:05:12.000Z",
"created_at": "2026-10-19T10:00:00.000Z"
}
]
}
Deletes a subscription created with this token. Response: { "deleted": true, "id": "..." }; 404 if it does not exist or belongs to another token.
Returns the most recent records in exactly the payload shape of a webhook, to test an integration without waiting for a real event. Query: event (required), limit (1 to 25, default 3). The id of each sample is sample-<record id>.
curl "https://api.flusia.it/crm/v1/hooks/sample?event=deal.status_changed&limit=3" \
-H "Authorization: Bearer flusia_your_token_here"
Payload
Each delivery is a POST with a JSON body:
| Field | Description |
|---|---|
id | Delivery id (same as the X-Flusia-Delivery header). Use it to ignore duplicates. |
event | Event name. |
occurred_at | When the event happened (ISO 8601). |
organization_id | Your organization id. |
data | The current state of the record when the delivery is sent: the public lead shape, the public deal shape or the public task shape. |
details | Present for some events: lead.stage_changed → previous_pipeline, previous_stage ({id, name}); lead.state_changed → previous_state; lead.tag_added → tag; deal.status_changed → previous_status. |
{
"id": "184467",
"event": "lead.stage_changed",
"occurred_at": "2026-10-19T10:05:10.000Z",
"organization_id": "0b7c1e52-3f1a-4c55-9d1e-6a1f2b3c4d5e",
"data": { "id": "2f516d26-bc55-4e8e-a1d6-959bff44cffe", "business_name": "Mario Rossi", "stage": { "id": "c3d4e5f6-0000-4000-8000-000000000002", "name": "Won", "is_won": true, "is_lost": false }, "...": "..." },
"details": {
"previous_pipeline": { "id": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d", "name": "Sales" },
"previous_stage": { "id": "c3d4e5f6-0000-4000-8000-000000000001", "name": "New" }
}
}
Public deal shape
All non-internal fields of the deal (as in /api/api-deals) plus: amount (number), client ({ id, name, email, phone }), owner ({ id, name, email }), services ([{ service_id, name, description, quantity, unit_price, amount, discounted_amount, vat_rate }]) and url.
Public task shape
All non-internal fields of the task (as in /api/api-tasks) plus: project ({ id, name, url }) and owner ({ id, name, email }).
Headers
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | Flusia-Webhooks/1.0 |
X-Flusia-Event | Event name. |
X-Flusia-Delivery | Delivery id. |
X-Flusia-Timestamp | Unix time in seconds when the request was signed. |
X-Flusia-Signature | sha256= followed by the hex HMAC-SHA256 of <timestamp>.<raw body>, keyed with the subscription secret. |
Verifying the signature
Compute the HMAC over the raw request body (before JSON parsing), compare in constant time, and reject old timestamps (for example older than 5 minutes) to block replays. Example with Node.js and Express:
const crypto = require('node:crypto');
const express = require('express');
const app = express();
const SECRET = process.env.FLUSIA_WEBHOOK_SECRET; // the "secret" returned by POST /crm/v1/hooks
app.post('/flusia-webhook', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.get('X-Flusia-Timestamp');
const signature = req.get('X-Flusia-Signature') || '';
const body = req.body.toString('utf8');
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!timestamp || !Number.isFinite(age) || age > 300) return res.status(400).send('stale');
const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(`${timestamp}.${body}`, 'utf8').digest('hex');
const ok = signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!ok) return res.status(401).send('bad signature');
const event = JSON.parse(body);
console.log(event.event, event.data.id);
res.sendStatus(200); // answer quickly, do slow work afterwards
});
app.listen(3000);
Delivery and retries
- Answer with any
2xxstatus within 5 seconds. Redirects are not followed. - On any other status, a timeout or a network error the delivery is retried after 1, 2, 5, 10, 30 and 60 minutes (7 attempts in total), then marked as failed.
- Answer
410 Goneto close the subscription: it is disabled and receives nothing more. - A subscription that keeps failing for more than 72 hours is disabled automatically. It is also disabled when the token that created it is deactivated, expires or loses the required permission.
- Deliveries may arrive more than once or out of order: use
idto deduplicate andoccurred_atto order. The payload carries the current state of the record when it is sent.
REST resources: conventions
REST resources live under https://api.flusia.it/api/<resource>. They share these conventions:
- A single record is returned as
{ "data": { ... } }, a list as{ "data": [ ... ], "pagination": { ... } }(see Pagination), a deletion as{ "message": "... deleted successfully" }. - Create and update bodies are JSON. Updates (
PUT) are partial: only the fields you send change. - System fields are ignored when sent:
id,organization_id,created_at,updated_at,created_by,deleted_at. - Records created through the API are attributed to the user who created the API token.
- A reference to another record (for example
pipeline_idorclient_id) must be the id of a record of your organization, otherwise the request is rejected with400or404and a message naming the field. - Deletions are permanent.
Leads and clients
Leads and clients are records of the same resource; a record's state (stato_cliente) tells whether it is a lead or a client. Contact people linked to a company are not listed here; they appear in the contacts of the public lead shape.
Lists leads and clients.
Query parameters
| Name | Type | Description |
|---|---|---|
page, limit | integer | See Pagination. |
search | string | Case-insensitive "contains" match on business_name, email, phone, vat_number. |
sort_by | string | created_at (default), updated_at, business_name, email, last_activity_date, lead_received_date, opportunity_value, lead_score. Other values return 400. |
sort_order | string | asc or desc (default). |
curl "https://api.flusia.it/api/api-leads?search=rossi&limit=20&sort_by=updated_at" \
-H "Authorization: Bearer flusia_your_token_here"
{
"data": [
{
"id": "2f516d26-bc55-4e8e-a1d6-959bff44cffe",
"business_name": "Mario Rossi",
"entity_type": "individual",
"email": "mario.rossi@example.com",
"phone": "+39 333 000 0000",
"stato_cliente": "a1b2c3d4-0000-4000-8000-000000000010",
"pipeline_id": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"pipeline_stage_id": "c3d4e5f6-0000-4000-8000-000000000001",
"assigned_to": "9e8d7c6b-0000-4000-8000-000000000020",
"lead_source": "Website",
"opportunity_value": 2500,
"created_at": "2026-10-15T08:30:00.000Z",
"updated_at": "2026-10-18T14:02:11.000Z"
}
],
"pagination": { "page": 1, "limit": 20, "total": 1, "total_pages": 1 }
}
Returns one record with all its fields: { "data": { ... } }. 404 {"error": "Lead not found"} if it does not exist. For a version with readable pipeline, stage, state, owner, tags and custom fields use GET /crm/v1/leads/{id}.
Creates a lead. No field is strictly required. Response 201 with { "data": { ... } }. Creating a lead starts the "lead created" automations configured in Flusia, and an automatic owner is assigned when assigned_to is omitted and assignment rules exist.
Main body fields
| Field | Type | Description |
|---|---|---|
business_name | string | Person or company name. Defaults to "No Name". |
entity_type | string | individual or company. |
email, phone, whatsapp_number, pec, website | string | Contact details. |
vat_number, tax_code, client_code, trade_name, sdi_code | string | Business identifiers. |
street_address, street_number, postal_code, city, province, region, country, country_code | string | Address. |
birth_date | date | YYYY-MM-DD. |
stato_cliente | uuid | A lead state id (see the states lookup, is_lead: true). When omitted or not a lead state, the organization's first lead state is used. |
pipeline_id | uuid | Pipeline. Defaults to the organization's default pipeline. Invalid ids return 400. |
pipeline_stage_id | uuid | Stage of that pipeline. Defaults to its first stage. A stage of another pipeline returns 400. |
assigned_to | uuid | Owner (see the users lookup). |
lead_source, lead_source_details, lead_source_url | string | Where the lead comes from. |
lead_received_date | date-time | When the lead arrived. |
lead_score | integer | 0 to 100. |
opportunity_value | number | Estimated value. |
newsletter_subscribed, marketing_consent_email, marketing_consent_sms, marketing_consent_phone | boolean | Consents. |
general_comments, interest_notes | string | Free text. |
selected_tags | uuid[] | Tag ids to assign (see the tags lookup). To add tags by name use POST /crm/v1/leads/{id}/tags. |
selected_interest_categories | uuid[] | Interest ids (see the interests lookup). |
contacts | object[] | Contact people of a company: [{ "name", "role", "email", "phone", "whatsapp_number", "is_primary" }]. |
dedup_mode | string | create_new or update. See Duplicates below. Defaults to the organization setting (normally create_new). |
Other fields of the lead record can be sent too (for example sales_rep_id, default_payment_method_id, operational_address); unknown fields are rejected. To set custom fields use PUT /crm/v1/leads/{id}/custom-fields.
Duplicates
With dedup_mode: "create_new" a new lead is always created. With dedup_mode: "update" Flusia first looks for an existing lead with the same email (case-insensitive) or, failing that, the same phone (last 9 digits of phone or whatsapp_number). If one is found, only its empty fields are filled in, the new tags and interests are added, a note with the submitted data is added to its timeline and its owner is notified. Owner, pipeline, stage, state, source, score and value are not changed. The answer is then 200 with "deduplicated": true:
{ "data": { "id": "2f516d26-bc55-4e8e-a1d6-959bff44cffe", "business_name": "Mario Rossi", "...": "..." }, "deduplicated": true }
curl -X POST https://api.flusia.it/api/api-leads \
-H "Authorization: Bearer flusia_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"business_name": "Mario Rossi",
"entity_type": "individual",
"email": "mario.rossi@example.com",
"phone": "+39 333 000 0000",
"lead_source": "Website",
"pipeline_id": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"selected_tags": ["f1e2d3c4-0000-4000-8000-000000000060"],
"dedup_mode": "update"
}'
Partial update with the same fields as create (except dedup_mode). Note that selected_tags, selected_interest_categories and contacts, when present, replace the current lists. Changing pipeline_stage_id or stato_cliente starts the "state changed" automations. Response: { "data": { ... } }; 404 if the record does not exist.
curl -X PUT https://api.flusia.it/api/api-leads/2f516d26-bc55-4e8e-a1d6-959bff44cffe \
-H "Authorization: Bearer flusia_your_token_here" \
-H "Content-Type: application/json" \
-d '{"pipeline_id": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d", "pipeline_stage_id": "c3d4e5f6-0000-4000-8000-000000000002"}'
Permanently deletes the record. Response: { "message": "Lead deleted successfully" }; 404 if it does not exist.
Deals (quotes)
A deal is a quote or offer for a lead or client, with optional service lines. Statuses: draft, sent, accepted, rejected.
Query parameters
| Name | Type | Description |
|---|---|---|
page, limit | integer | See Pagination. |
status | string | Exact status. |
client_id, lead_id | uuid | Deals of a lead or client. A deal can be linked through either field, so query both to get all deals of a record. |
assigned_to | uuid | Owner. |
sort_by | string | created_at (default), updated_at, title, amount, status, closing_probability, signed_at, closed_at. Other values return 400. |
sort_order | string | asc or desc (default). |
Each item has all deal fields plus client ({ id, business_name, email, phone }), assigned_user ({ id, full_name, email }) and deal_services (service lines).
{
"data": [
{
"id": "9b1c7a3e-1d2f-4f7a-8f1c-1a2b3c4d5e6f",
"title": "Website redesign",
"status": "sent",
"amount": 4800,
"closing_probability": 60,
"client_id": "2f516d26-bc55-4e8e-a1d6-959bff44cffe",
"assigned_to": "9e8d7c6b-0000-4000-8000-000000000020",
"created_at": "2026-10-16T10:00:00.000Z",
"client": { "id": "2f516d26-bc55-4e8e-a1d6-959bff44cffe", "business_name": "Mario Rossi", "email": "mario.rossi@example.com", "phone": "+39 333 000 0000" },
"assigned_user": { "id": "9e8d7c6b-0000-4000-8000-000000000020", "full_name": "Anna Bianchi", "email": "anna@example.com" },
"deal_services": [ { "service_id": "aa11bb22-0000-4000-8000-000000000070", "amount": 4800, "quantity": 1 } ]
}
],
"pagination": { "page": 1, "limit": 50, "total": 1, "total_pages": 1 }
}
One deal with client (including vat_number), assigned_user, payment_method ({ id, name, payment_type }) and deal_services, each with its service ({ id, title, description }). 404 {"error": "Deal not found"}.
Body
| Field | Type | Description |
|---|---|---|
client_id | uuid (required) | The lead or client the deal is for. |
title | string (required) | Deal title. |
description | string | Description and particular conditions. |
amount | number | Total amount. Default 0. |
closing_probability | integer | 0 to 100. |
status | string | draft (default), sent, accepted, rejected. |
assigned_to | uuid | Owner (an active user of the organization; otherwise ignored). |
lead_id | uuid | Optional lead link. |
payment_method_id | uuid | Active payment method of the organization (otherwise ignored). |
offer_reference | string | Your reference. |
send_notifications | boolean | Whether Flusia sends notifications for this deal. |
services | object[] | Service lines: service_id (a service of your catalog), amount, and optionally quantity, unit_price, discounted_amount, vat_rate, description, custom_service_name, monthly_duration, payment_method_id. Unknown services return 400 and the deal is not created. |
Response 201 { "data": { ..., "client": {...}, "deal_services": [...] } }. Unknown client_id: 400.
curl -X POST https://api.flusia.it/api/api-deals \
-H "Authorization: Bearer flusia_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"client_id": "2f516d26-bc55-4e8e-a1d6-959bff44cffe",
"title": "Website redesign",
"amount": 4800,
"status": "draft",
"services": [ { "service_id": "aa11bb22-0000-4000-8000-000000000070", "amount": 4800, "quantity": 1 } ]
}'
Partial update with the same fields as create. When services is present it replaces all service lines (an empty array removes them). Response: { "data": { ... } }; 404 if the deal (or a new client_id) does not exist. To change only the status prefer the endpoint below.
Changes the status. Body: { "status": "accepted" }, one of draft, sent, accepted, rejected (otherwise 400). accepted and rejected also set closed_at. Response: { "data": { ... } }.
curl -X PATCH https://api.flusia.it/api/api-deals/9b1c7a3e-1d2f-4f7a-8f1c-1a2b3c4d5e6f/status \
-H "Authorization: Bearer flusia_your_token_here" \
-H "Content-Type: application/json" \
-d '{"status": "accepted"}'
Deletes the deal and its service lines. Response: { "message": "Deal deleted successfully" }.
Sales tasks
Sales tasks are commercial activities (calls, meetings, visits...) scheduled on a lead, client or deal. Resource name in permissions: activities.
Types (type): call, videochiamata (video call), appuntamento (meeting), sopralluogo (on-site visit), email, task, note, other, plus any custom type configured by the organization. Statuses (status): pending (default), completed, overdue, cancelled.
Newest first. Query: page, limit, and exact filters client_id, lead_id, deal_id, type, status, assigned_to.
Each item: id, title, description, type, status, due_date, client_id, deal_id, lead_id, user_id, assigned_to, outcome, completed_at, created_at, updated_at, plus client, lead ({ id, business_name, email }), deal ({ id, title }) and assignee ({ id, full_name, email }).
One sales task (client and lead include phone; deal includes status and amount). 404 {"error": "Activity not found"}.
Body
| Field | Type | Description |
|---|---|---|
title | string (required) | Title. |
type | string (required) | See types above. |
due_date | date-time (required) | When it is scheduled. |
description, outcome | string | Notes and outcome. |
status | string | Default pending. |
client_id | uuid | Lead or client the sales task belongs to (set it for leads too). |
lead_id | uuid | Lead link. |
deal_id | uuid | Deal link. |
assigned_to | uuid | Assignee (an active user; otherwise left empty). |
Only these fields are stored. A client_id, lead_id or deal_id outside your organization returns 404. Response 201 { "data": { ... } }.
curl -X POST https://api.flusia.it/api/api-activities \
-H "Authorization: Bearer flusia_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"title": "Follow-up call",
"type": "call",
"due_date": "2026-10-21T09:00:00.000Z",
"client_id": "2f516d26-bc55-4e8e-a1d6-959bff44cffe",
"lead_id": "2f516d26-bc55-4e8e-a1d6-959bff44cffe"
}'
{ "data": { "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f", "title": "Follow-up call", "type": "call", "status": "pending", "due_date": "2026-10-21T09:00:00+00:00", "...": "..." } }
Partial update of title, description, type, status, due_date, client_id, deal_id, lead_id, assigned_to, outcome. Response: { "data": { ... } }.
Response: { "message": "Activity deleted successfully" }.
Tasks
Tasks always belong to a project. Statuses: todo (default), in_progress, review, completed, standby. Priorities: low, medium (default), high, urgent.
Query parameters
| Name | Type | Description |
|---|---|---|
page, limit | integer | See Pagination. |
project_id, assigned_to | uuid | Exact filters. |
status, priority, section | string | Exact filters. |
is_completed | boolean | true: only completed; false: only not completed. |
is_overdue | boolean | true: due date passed and not completed. |
is_header, is_recurring | boolean | Section headers / recurring tasks. |
due_date_from, due_date_to, created_from, created_to | date-time | Ranges (inclusive). |
search | string | Case-insensitive match on title or description. |
sort_by | string | created_at (default), updated_at, title, due_date, start_date, priority, status, completed_at. |
sort_order | string | asc or desc (default). |
Each item has all task fields plus project ({ id, name }), assigned_user, created_by_user ({ id, full_name, email }) and tags ([{ tag: { id, name, color } }]).
One task with project, assigned_user, created_by_user, comments, checklist and attachments. 404 {"error": "Task not found"}.
Body
| Field | Type | Description |
|---|---|---|
title | string (required) | Title. |
project_id | uuid (required) | Project (see the projects lookup). Unknown project: 404. |
description | string | Description. |
status, priority | string | See values above. |
assigned_to | uuid | Assignee (an active user; otherwise left empty). |
start_date, due_date, review_at | date-time | Dates. |
estimated_hours, actual_hours, completion_percentage | number | Effort and progress. |
section, order | string, integer | Section of the project and position in it (appended at the end by default). |
dependencies | uuid[] | Tasks this one depends on. |
is_recurring, recurrence_type, recurrence_interval, recurrence_unit, recurrence_end_type, recurrence_end_date, recurrence_max_count | mixed | Recurrence: type daily, weekly, biweekly, monthly, custom; unit days, weeks, months; end never, on_date, after_count. |
checklist | object[] | Checklist items to create: [{ "title", "is_required" }]. |
tag_ids | uuid[] | Project tags to assign. |
Response 201 { "data": { ... } }.
curl -X POST https://api.flusia.it/api/api-tasks \
-H "Authorization: Bearer flusia_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"project_id": "eeeeeeee-0000-4000-8000-000000000001",
"title": "Prepare kickoff meeting",
"priority": "high",
"due_date": "2026-10-24T16:00:00.000Z",
"checklist": [ { "title": "Send agenda" }, { "title": "Book room", "is_required": true } ]
}'
Partial update with the same fields as create, except checklist and tag_ids (use the sub-resources below). Moving to another project_id requires a project of your organization. Response: { "data": { ... } }.
Marks the task as completed (status completed, 100%, completed_at now). No body.
Moves the task. Body: { "order": 3, "section": "..." } (both optional).
Deletes the task with its comments, attachments and checklist. Response: { "message": "Task deleted successfully" }.
Task comments
Comments, oldest first, each with created_by_user.
Body { "content": "..." } (required). Response 201.
Body { "content": "..." }. Only comments written by the token's user can be edited (403 otherwise); system comments cannot be edited.
Same ownership rule as edit.
Task checklist
Items ordered by sort_order: id, title, description, is_completed, is_required, sort_order, ...
Body: title (required, max 255), description, is_required (default false), sort_order (default last). Response 201.
Partial update of the item.
Body { "is_completed": true }: sets the item as done (with completion time and user) or not done.
Deletes the item.
Task tags
Assigned tags: [{ id, name, color, sort_order, assignment_id, assigned_at }].
Body { "tag_ids": ["..."] }: adds tags of the task's project (see project tags); already assigned tags are skipped. Tags of other projects return 400 with invalid_tag_ids. Returns the assigned tags.
Removes the tag from the task.
Bulk operations
Body { "project_id": "...", "tasks": [ { "title": "...", ... } ] }. Supported task fields: title, description, section, priority, assigned_to, due_date, start_date, status, order, is_header, recurrence fields, checklist, tag_ids. Items without a title are skipped. Response 201 (400 if nothing was created): { "data": [...], "summary": { "created": 2, "errors": [] } }.
Body { "updates": [ { "id": "...", "data": { "status": "in_progress" } } ] }. The project of a task cannot be changed here. Response 200: { "data": [...], "summary": { "updated": 1, "errors": [] } }; check summary.errors for items that failed.
Body { "ids": ["...", "..."] }. Response: { "message": "2 tasks deleted successfully", "summary": { "deleted": 2, "errors": [] } }.
Projects
Statuses: planning, active (default), on_hold, completed, cancelled. Urgency: low, medium, high, critical.
Query parameters
| Name | Type | Description |
|---|---|---|
page, limit | integer | See Pagination. |
status, urgency | string | Exact filters. |
client_id, project_manager_id | uuid | Exact filters. |
archived | string | false (default, active projects), true (only archived) or all. |
search | string | Case-insensitive match on name or description. |
sort_by | string | created_at (default), updated_at, name, status, start_date. |
sort_order | string | asc or desc (default). |
Each item has all project fields plus client, deal, project_manager, project_assignments and latest_status_update.
One project with client, deal, project_manager, tasks, project_assignments, project_task_tags and latest_status_update. 404 {"error": "Project not found"}.
Body: name (required), description, client_id, deal_id, status, urgency, start_date, due_date, estimated_delivery_date, project_type, objectives (array), project_manager_id, assigned_user_ids (users added as members). Response 201 { "data": { ..., "project_assignments": [...] } }.
curl -X POST https://api.flusia.it/api/api-projects \
-H "Authorization: Bearer flusia_your_token_here" \
-H "Content-Type: application/json" \
-d '{"name": "Website redesign", "client_id": "2f516d26-bc55-4e8e-a1d6-959bff44cffe", "status": "planning", "assigned_user_ids": ["9e8d7c6b-0000-4000-8000-000000000020"]}'
Partial update with the same fields. assigned_user_ids, when present, replaces the members.
Body { "status": "completed" } (one of the statuses above, otherwise 400). completed also sets completed_at.
Archives or restores the project. No body.
Response: { "message": "Project deleted successfully" }.
Project members
Members: [{ id, user_id, role, assigned_at, assigned_by, user: { id, full_name, email, profile_image_url } }].
Body { "user_id": "..." }. 404 if the user is not in the organization, 400 if already a member. Response 201.
Removes the member.
Project tags
Labels defined per project and assigned to its tasks.
Tags ordered by sort_order.
Body: name and color (required, for example #2383E2), sort_order, is_active. Response 201.
Updates name, color, sort_order, is_active.
Deletes the tag and removes it from all tasks.
Project status updates
Health updates, newest first, each with creator.
Only the latest update (or null).
Body: health_status (required: on_track, at_risk, delayed), description, next_steps, completion_percentage. Response 201.
Project brief documents
Documents and links of the project brief.
Body: name and url (required), document_type (link default, or file), visible_to_client (default false). Response 201.
Partial update.
Deletes the document entry.
Project sections
Sections group the tasks of a project. A section is identified by its section key (returned when it is created), which is the value used in the tasks' section field. Sections are listed in the tasks of GET /api/api-projects/{id} as tasks with is_header: true.
Body { "name": "Design" }. Response 201 with the header row; its section field is the section key.
Body { "name": "..." }: renames the section.
Deletes the section; its tasks are kept without a section.
Pipelines and stages
Lead pipelines and their stages (the columns a lead moves through). Reading requires pipelines.read or leads.read; every change, deletions included, requires pipelines.write.
All pipelines (not paginated): id, name, description, color, is_default, is_active, sort_order, ... Query: include_stages=true to embed lead_pipeline_stages; sort_order asc (default) or desc.
{
"data": [
{ "id": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d", "name": "Sales", "color": "#2383E2", "is_default": true, "is_active": true, "sort_order": 0 }
]
}
One pipeline with its lead_pipeline_stages. 404 {"error": "Pipeline not found"}.
Body: name (required, max 100), description, color (hex, for example #6B7280), is_default, is_active, sort_order (default last). Stages are added separately. Response 201.
Partial update with the same fields.
Deletes the pipeline and its stages. It fails while leads are still in the pipeline: move them first.
Reorders all pipelines; {id} can be the id of any of your pipelines. Body { "pipelines": [ { "id": "...", "sort_order": 0 }, { "id": "...", "sort_order": 1 } ] }.
Stages of the pipeline ordered by sort_order: id, pipeline_id, name, description, color, sort_order, is_active, is_won, is_lost.
Body: name (required), description, color, sort_order (default last), is_won, is_lost (not both true), is_active. Response 201.
Partial update.
Body { "stages": [ { "id": "...", "sort_order": 0 } ] }.
Deletes the stage. It fails while leads are still in the stage.
Services
The catalog of services used in deal lines. Reading requires services.read or deals.read; create, update and delete require services.write.
Ordered by title. Query: page, limit, category_id, service_type, search (title or description). Each item has all service fields plus category ({ id, name, color }).
{
"data": [
{ "id": "aa11bb22-0000-4000-8000-000000000070", "title": "Website", "description": "Corporate website", "list_price": 4800, "service_type": "one_time", "category": { "id": "bb22cc33-0000-4000-8000-000000000080", "name": "Web", "color": "#2383E2" } }
],
"pagination": { "page": 1, "limit": 50, "total": 1, "total_pages": 1 }
}
One service. 404 {"error": "Service not found"}.
Body: title (required), description, short_description, list_price (number), service_type (one_time, monthly, annual), category_id, terms_and_conditions, auto_invoice, requires_manual_renewal, renewal_notice_days. Response 201.
Partial update.
Response: { "message": "Service deleted successfully" }.
Invoices (read-only)
Newest first. Query: page, limit, status, deal_id, client_id (invoices of that client's deals). Each item has all invoice fields plus deal ({ id, title, client_id }) and client.
One invoice with its deal and the deal's client ({ id, business_name, email, vat_number }). 404 {"error": "Invoice not found"}.
Documents
Files attached to tasks. Reading requires documents.read or tasks.read. Uploading is not available through the API.
Newest first. Query: page, limit, task_id.
One document. 404 {"error": "Document not found"}.
Removes the document entry. Response: { "message": "Document deleted successfully" }.
Flusia API Reference. Questions about the API: write to the Flusia support team from the app. Last updated: 19 October 2026.