Sviluppatori

Documentazione API di Flusia

Leggi e scrivi i dati del tuo spazio di lavoro Flusia dal tuo codice o dalle piattaforme di automazione, e ricevi webhook firmati quando qualcosa cambia.

La documentazione tecnica qui sotto è in inglese, come è consuetudine per i riferimenti delle API.

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

FamilyBase URL
REST resourceshttps://api.flusia.it/api (for example https://api.flusia.it/api/api-leads)
API v1https://api.flusia.it/crm/v1
Token checkhttps://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"
Keep tokens secret. Never put a token in a URL or query string, in client-side code or in a public repository. If a token leaks, deactivate it in Settings > API and create a new one. Requests are rejected with 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"
}
StatusMeaning
200Success.
201Resource created.
400Invalid request: missing or invalid parameters or body fields. The message says which.
401Missing, malformed, unknown, disabled or expired API token.
403The token lacks the required permission, the caller IP is not allowed for the token, or the endpoint is not public.
404The resource does not exist in your organization, or the route does not exist.
405HTTP method not supported on this path.
409Conflict, for example a duplicate (see each endpoint).
429Rate limit exceeded (see Rate limits).
500 / 502Server 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:

ParameterDefaultDescription
page1Page number, starting from 1.
limit50Items 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

TermMeaning
Lead / clientBoth 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).
DealA quote or offer sent to a lead or client (/api/api-deals). Statuses: draft, sent, accepted, rejected.
Sales taskA 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.
TaskA to-do inside a project (/api/api-tasks).
Pipeline / stageThe sales pipelines of leads and their stages (/api/api-pipelines).

Endpoint index

SectionEndpoint
Account: /meGET /me
Account: /meGET /crm/v1/me
LookupsGET /crm/v1/lookups/{kind}
Lead search, tags, custom fields and notesGET /crm/v1/leads/find
Lead search, tags, custom fields and notesGET /crm/v1/leads/{id}
Lead search, tags, custom fields and notesPOST /crm/v1/leads/{id}/tags
Lead search, tags, custom fields and notesDELETE /crm/v1/leads/{id}/tags/{tag}
Lead search, tags, custom fields and notesPUT /crm/v1/leads/{id}/custom-fields
Lead search, tags, custom fields and notesGET /crm/v1/leads/{id}/notes
Lead search, tags, custom fields and notesPOST /crm/v1/leads/{id}/notes
WebhooksGET /crm/v1/hooks/events
WebhooksPOST /crm/v1/hooks
WebhooksGET /crm/v1/hooks
WebhooksDELETE /crm/v1/hooks/{id}
WebhooksGET /crm/v1/hooks/sample
Leads and clientsGET /api/api-leads
Leads and clientsGET /api/api-leads/{id}
Leads and clientsPOST /api/api-leads
Leads and clientsPUT /api/api-leads/{id}
Leads and clientsDELETE /api/api-leads/{id}
Deals (quotes)GET /api/api-deals
Deals (quotes)GET /api/api-deals/{id}
Deals (quotes)POST /api/api-deals
Deals (quotes)PUT /api/api-deals/{id}
Deals (quotes)PATCH /api/api-deals/{id}/status
Deals (quotes)DELETE /api/api-deals/{id}
Sales tasksGET /api/api-activities
Sales tasksGET /api/api-activities/{id}
Sales tasksPOST /api/api-activities
Sales tasksPUT /api/api-activities/{id}
Sales tasksDELETE /api/api-activities/{id}
TasksGET /api/api-tasks
TasksGET /api/api-tasks/{id}
TasksPOST /api/api-tasks
TasksPUT /api/api-tasks/{id}
TasksPATCH /api/api-tasks/{id}/complete
TasksPATCH /api/api-tasks/{id}/order
TasksDELETE /api/api-tasks/{id}
TasksGET /api/api-tasks/{id}/comments
TasksPOST /api/api-tasks/{id}/comments
TasksPUT /api/api-tasks/{id}/comments/{commentId}
TasksDELETE /api/api-tasks/{id}/comments/{commentId}
TasksGET /api/api-tasks/{id}/checklist
TasksPOST /api/api-tasks/{id}/checklist
TasksPUT /api/api-tasks/{id}/checklist/{itemId}
TasksPATCH /api/api-tasks/{id}/checklist/{itemId}/toggle
TasksDELETE /api/api-tasks/{id}/checklist/{itemId}
TasksGET /api/api-tasks/{id}/tags
TasksPOST /api/api-tasks/{id}/tags
TasksDELETE /api/api-tasks/{id}/tags/{tagId}
TasksPOST /api/api-tasks/bulk
TasksPATCH /api/api-tasks/bulk
TasksDELETE /api/api-tasks/bulk
ProjectsGET /api/api-projects
ProjectsGET /api/api-projects/{id}
ProjectsPOST /api/api-projects
ProjectsPUT /api/api-projects/{id}
ProjectsPATCH /api/api-projects/{id}/status
ProjectsPATCH /api/api-projects/{id}/archive
ProjectsPATCH /api/api-projects/{id}/unarchive
ProjectsDELETE /api/api-projects/{id}
ProjectsGET /api/api-projects/{id}/assignments
ProjectsPOST /api/api-projects/{id}/assignments
ProjectsDELETE /api/api-projects/{id}/assignments/{userId}
ProjectsGET /api/api-projects/{id}/tags
ProjectsPOST /api/api-projects/{id}/tags
ProjectsPUT /api/api-projects/{id}/tags/{tagId}
ProjectsDELETE /api/api-projects/{id}/tags/{tagId}
ProjectsGET /api/api-projects/{id}/status-updates
ProjectsGET /api/api-projects/{id}/status-updates/latest
ProjectsPOST /api/api-projects/{id}/status-updates
ProjectsGET /api/api-projects/{id}/documents
ProjectsPOST /api/api-projects/{id}/documents
ProjectsPUT /api/api-projects/{id}/documents/{docId}
ProjectsDELETE /api/api-projects/{id}/documents/{docId}
ProjectsPOST /api/api-projects/{id}/sections
ProjectsPUT /api/api-projects/{id}/sections/{sectionKey}
ProjectsDELETE /api/api-projects/{id}/sections/{sectionKey}
Pipelines and stagesGET /api/api-pipelines
Pipelines and stagesGET /api/api-pipelines/{id}
Pipelines and stagesPOST /api/api-pipelines
Pipelines and stagesPUT /api/api-pipelines/{id}
Pipelines and stagesDELETE /api/api-pipelines/{id}
Pipelines and stagesPUT /api/api-pipelines/{id}/reorder
Pipelines and stagesGET /api/api-pipelines/{id}/stages
Pipelines and stagesPOST /api/api-pipelines/{id}/stages
Pipelines and stagesPUT /api/api-pipelines/{id}/stages/{stageId}
Pipelines and stagesPUT /api/api-pipelines/{id}/stages/reorder
Pipelines and stagesDELETE /api/api-pipelines/{id}/stages/{stageId}
ServicesGET /api/api-services
ServicesGET /api/api-services/{id}
ServicesPOST /api/api-services
ServicesPUT /api/api-services/{id}
ServicesDELETE /api/api-services/{id}
Invoices (read-only)GET /api/api-invoices
Invoices (read-only)GET /api/api-invoices/{id}
DocumentsGET /api/api-documents
DocumentsGET /api/api-documents/{id}
DocumentsDELETE /api/api-documents/{id}

Account: /me

Use this endpoint to check that a token is valid and to read the organization it belongs to.

GET/me

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
}
GET/crm/v1/me

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.

GET/crm/v1/lookups/{kind}

Response: { "data": [ ... ] }. Unknown kinds return 404; a token without the permission listed below gets 403.

kindPermissionFields of each item
usersleads.readid, name, email (active users of the organization)
tagsleads.readid, name, color (lead/client tags)
lead-sourcesleads.readid, name, category
statesleads.readid, name, is_lead (true = lead state, false = client state)
interestsleads.readid, name, parent_id, category_type
pipelinesleads.readid, name, is_default
stagesleads.readid, name, pipeline_id, pipeline_name, is_won, is_lost. Optional query pipeline_id (UUID) to filter.
custom-fieldsleads.readid, key, name, type, options, is_required, help_text (definitions of lead custom fields)
servicesservices.readid, name
projectstasks.readid, name (non-archived projects)
deal-statusesdeals.readid, 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:

FieldDescription
is_leadtrue 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_idSource name and, when the source is a configured one, its id.
opportunity_valueNumber or null.
tags, interestsArrays of names.
custom_fieldsObject keyed by field key: { "budget": { "label": "Budget", "type": "number", "value": "1500" } }
contactsContact people linked to a company: [{ id, name, email, phone, whatsapp_number, role, area, is_primary, is_referente }]
urlLink 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"
}
GET/crm/v1/leads/find leads.read

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

NameTypeDescription
emailstringEmail to match exactly.
phonestringPhone 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"
GET/crm/v1/leads/{id} leads.read

Returns one lead or client in the public shape: { "data": lead }.

POST/crm/v1/leads/{id}/tags leads.write

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

FieldTypeDescription
tagsstring[] (required)1 to 50 tag ids or names (max 100 characters each).
create_missingbooleanDefault 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}'
DELETE/crm/v1/leads/{id}/tags/{tag} leads.write

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.

PUT/crm/v1/leads/{id}/custom-fields leads.write

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

GET/crm/v1/leads/{id}/notes leads.read

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"
    }
  ]
}
POST/crm/v1/leads/{id}/notes leads.write

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

EventWhenPermission
lead.createdA lead or client is created.leads.read
lead.updatedA field visible to users changes (name, contacts, address, owner, pipeline, stage, state, source, value, consents).leads.read
lead.stage_changedThe pipeline or pipeline stage changes.leads.read
lead.state_changedThe lead/client state changes.leads.read
lead.tag_addedA tag is added to a lead or client.leads.read
deal.createdA deal is created.deals.read
deal.status_changedThe deal status changes (draft, sent, accepted, rejected).deals.read
task.createdA task is created in a project.tasks.read
task.completedA task is marked as completed.tasks.read
GET/crm/v1/hooks/events

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 }
  ]
}
POST/crm/v1/hooks <resource>.read

Subscribes a URL to one event. The token needs read on the event's resource (see the table above).

Body

FieldTypeDescription
eventstring (required)One of the events above.
target_urlstring (required)Public https URL, max 2000 characters. Private or internal addresses, plain http and URLs with credentials are refused (400).
sourcestringOptional 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"
}
GET/crm/v1/hooks

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"
    }
  ]
}
DELETE/crm/v1/hooks/{id}

Deletes a subscription created with this token. Response: { "deleted": true, "id": "..." }; 404 if it does not exist or belongs to another token.

GET/crm/v1/hooks/sample <resource>.read

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:

FieldDescription
idDelivery id (same as the X-Flusia-Delivery header). Use it to ignore duplicates.
eventEvent name.
occurred_atWhen the event happened (ISO 8601).
organization_idYour organization id.
dataThe current state of the record when the delivery is sent: the public lead shape, the public deal shape or the public task shape.
detailsPresent 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

HeaderValue
Content-Typeapplication/json
User-AgentFlusia-Webhooks/1.0
X-Flusia-EventEvent name.
X-Flusia-DeliveryDelivery id.
X-Flusia-TimestampUnix time in seconds when the request was signed.
X-Flusia-Signaturesha256= 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 2xx status 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 Gone to 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 id to deduplicate and occurred_at to 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_id or client_id) must be the id of a record of your organization, otherwise the request is rejected with 400 or 404 and 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.

GET/api/api-leads leads.read

Lists leads and clients.

Query parameters

NameTypeDescription
page, limitintegerSee Pagination.
searchstringCase-insensitive "contains" match on business_name, email, phone, vat_number.
sort_bystringcreated_at (default), updated_at, business_name, email, last_activity_date, lead_received_date, opportunity_value, lead_score. Other values return 400.
sort_orderstringasc 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 }
}
GET/api/api-leads/{id} leads.read

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

POST/api/api-leads leads.write

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

FieldTypeDescription
business_namestringPerson or company name. Defaults to "No Name".
entity_typestringindividual or company.
email, phone, whatsapp_number, pec, websitestringContact details.
vat_number, tax_code, client_code, trade_name, sdi_codestringBusiness identifiers.
street_address, street_number, postal_code, city, province, region, country, country_codestringAddress.
birth_datedateYYYY-MM-DD.
stato_clienteuuidA 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_iduuidPipeline. Defaults to the organization's default pipeline. Invalid ids return 400.
pipeline_stage_iduuidStage of that pipeline. Defaults to its first stage. A stage of another pipeline returns 400.
assigned_touuidOwner (see the users lookup).
lead_source, lead_source_details, lead_source_urlstringWhere the lead comes from.
lead_received_datedate-timeWhen the lead arrived.
lead_scoreinteger0 to 100.
opportunity_valuenumberEstimated value.
newsletter_subscribed, marketing_consent_email, marketing_consent_sms, marketing_consent_phonebooleanConsents.
general_comments, interest_notesstringFree text.
selected_tagsuuid[]Tag ids to assign (see the tags lookup). To add tags by name use POST /crm/v1/leads/{id}/tags.
selected_interest_categoriesuuid[]Interest ids (see the interests lookup).
contactsobject[]Contact people of a company: [{ "name", "role", "email", "phone", "whatsapp_number", "is_primary" }].
dedup_modestringcreate_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"
  }'
PUT/api/api-leads/{id} leads.write

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"}'
DELETE/api/api-leads/{id} leads.delete

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.

GET/api/api-deals deals.read

Query parameters

NameTypeDescription
page, limitintegerSee Pagination.
statusstringExact status.
client_id, lead_iduuidDeals of a lead or client. A deal can be linked through either field, so query both to get all deals of a record.
assigned_touuidOwner.
sort_bystringcreated_at (default), updated_at, title, amount, status, closing_probability, signed_at, closed_at. Other values return 400.
sort_orderstringasc 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 }
}
GET/api/api-deals/{id} deals.read

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"}.

POST/api/api-deals deals.write

Body

FieldTypeDescription
client_iduuid (required)The lead or client the deal is for.
titlestring (required)Deal title.
descriptionstringDescription and particular conditions.
amountnumberTotal amount. Default 0.
closing_probabilityinteger0 to 100.
statusstringdraft (default), sent, accepted, rejected.
assigned_touuidOwner (an active user of the organization; otherwise ignored).
lead_iduuidOptional lead link.
payment_method_iduuidActive payment method of the organization (otherwise ignored).
offer_referencestringYour reference.
send_notificationsbooleanWhether Flusia sends notifications for this deal.
servicesobject[]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 } ]
  }'
PUT/api/api-deals/{id} deals.write

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.

PATCH/api/api-deals/{id}/status deals.write

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"}'
DELETE/api/api-deals/{id} deals.delete

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.

GET/api/api-activities activities.read

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 }).

GET/api/api-activities/{id} activities.read

One sales task (client and lead include phone; deal includes status and amount). 404 {"error": "Activity not found"}.

POST/api/api-activities activities.write

Body

FieldTypeDescription
titlestring (required)Title.
typestring (required)See types above.
due_datedate-time (required)When it is scheduled.
description, outcomestringNotes and outcome.
statusstringDefault pending.
client_iduuidLead or client the sales task belongs to (set it for leads too).
lead_iduuidLead link.
deal_iduuidDeal link.
assigned_touuidAssignee (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", "...": "..." } }
PUT/api/api-activities/{id} activities.write

Partial update of title, description, type, status, due_date, client_id, deal_id, lead_id, assigned_to, outcome. Response: { "data": { ... } }.

DELETE/api/api-activities/{id} activities.delete

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.

GET/api/api-tasks tasks.read

Query parameters

NameTypeDescription
page, limitintegerSee Pagination.
project_id, assigned_touuidExact filters.
status, priority, sectionstringExact filters.
is_completedbooleantrue: only completed; false: only not completed.
is_overduebooleantrue: due date passed and not completed.
is_header, is_recurringbooleanSection headers / recurring tasks.
due_date_from, due_date_to, created_from, created_todate-timeRanges (inclusive).
searchstringCase-insensitive match on title or description.
sort_bystringcreated_at (default), updated_at, title, due_date, start_date, priority, status, completed_at.
sort_orderstringasc 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 } }]).

GET/api/api-tasks/{id} tasks.read

One task with project, assigned_user, created_by_user, comments, checklist and attachments. 404 {"error": "Task not found"}.

POST/api/api-tasks tasks.write

Body

FieldTypeDescription
titlestring (required)Title.
project_iduuid (required)Project (see the projects lookup). Unknown project: 404.
descriptionstringDescription.
status, prioritystringSee values above.
assigned_touuidAssignee (an active user; otherwise left empty).
start_date, due_date, review_atdate-timeDates.
estimated_hours, actual_hours, completion_percentagenumberEffort and progress.
section, orderstring, integerSection of the project and position in it (appended at the end by default).
dependenciesuuid[]Tasks this one depends on.
is_recurring, recurrence_type, recurrence_interval, recurrence_unit, recurrence_end_type, recurrence_end_date, recurrence_max_countmixedRecurrence: type daily, weekly, biweekly, monthly, custom; unit days, weeks, months; end never, on_date, after_count.
checklistobject[]Checklist items to create: [{ "title", "is_required" }].
tag_idsuuid[]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 } ]
  }'
PUT/api/api-tasks/{id} tasks.write

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": { ... } }.

PATCH/api/api-tasks/{id}/complete tasks.write

Marks the task as completed (status completed, 100%, completed_at now). No body.

PATCH/api/api-tasks/{id}/order tasks.write

Moves the task. Body: { "order": 3, "section": "..." } (both optional).

DELETE/api/api-tasks/{id} tasks.delete

Deletes the task with its comments, attachments and checklist. Response: { "message": "Task deleted successfully" }.

Task comments

GET/api/api-tasks/{id}/comments tasks.read

Comments, oldest first, each with created_by_user.

POST/api/api-tasks/{id}/comments tasks.write

Body { "content": "..." } (required). Response 201.

PUT/api/api-tasks/{id}/comments/{commentId} tasks.write

Body { "content": "..." }. Only comments written by the token's user can be edited (403 otherwise); system comments cannot be edited.

DELETE/api/api-tasks/{id}/comments/{commentId} tasks.write

Same ownership rule as edit.

Task checklist

GET/api/api-tasks/{id}/checklist tasks.read

Items ordered by sort_order: id, title, description, is_completed, is_required, sort_order, ...

POST/api/api-tasks/{id}/checklist tasks.write

Body: title (required, max 255), description, is_required (default false), sort_order (default last). Response 201.

PUT/api/api-tasks/{id}/checklist/{itemId} tasks.write

Partial update of the item.

PATCH/api/api-tasks/{id}/checklist/{itemId}/toggle tasks.write

Body { "is_completed": true }: sets the item as done (with completion time and user) or not done.

DELETE/api/api-tasks/{id}/checklist/{itemId} tasks.write

Deletes the item.

Task tags

GET/api/api-tasks/{id}/tags tasks.read

Assigned tags: [{ id, name, color, sort_order, assignment_id, assigned_at }].

POST/api/api-tasks/{id}/tags tasks.write

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.

DELETE/api/api-tasks/{id}/tags/{tagId} tasks.write

Removes the tag from the task.

Bulk operations

POST/api/api-tasks/bulk tasks.write

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": [] } }.

PATCH/api/api-tasks/bulk tasks.write

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.

DELETE/api/api-tasks/bulk tasks.delete

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.

GET/api/api-projects projects.read

Query parameters

NameTypeDescription
page, limitintegerSee Pagination.
status, urgencystringExact filters.
client_id, project_manager_iduuidExact filters.
archivedstringfalse (default, active projects), true (only archived) or all.
searchstringCase-insensitive match on name or description.
sort_bystringcreated_at (default), updated_at, name, status, start_date.
sort_orderstringasc or desc (default).

Each item has all project fields plus client, deal, project_manager, project_assignments and latest_status_update.

GET/api/api-projects/{id} projects.read

One project with client, deal, project_manager, tasks, project_assignments, project_task_tags and latest_status_update. 404 {"error": "Project not found"}.

POST/api/api-projects projects.write

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"]}'
PUT/api/api-projects/{id} projects.write

Partial update with the same fields. assigned_user_ids, when present, replaces the members.

PATCH/api/api-projects/{id}/status projects.write

Body { "status": "completed" } (one of the statuses above, otherwise 400). completed also sets completed_at.

PATCH/api/api-projects/{id}/archive projects.write
PATCH/api/api-projects/{id}/unarchive projects.write

Archives or restores the project. No body.

DELETE/api/api-projects/{id} projects.delete

Response: { "message": "Project deleted successfully" }.

Project members

GET/api/api-projects/{id}/assignments projects.read

Members: [{ id, user_id, role, assigned_at, assigned_by, user: { id, full_name, email, profile_image_url } }].

POST/api/api-projects/{id}/assignments projects.write

Body { "user_id": "..." }. 404 if the user is not in the organization, 400 if already a member. Response 201.

DELETE/api/api-projects/{id}/assignments/{userId} projects.write

Removes the member.

Project tags

Labels defined per project and assigned to its tasks.

GET/api/api-projects/{id}/tags projects.read

Tags ordered by sort_order.

POST/api/api-projects/{id}/tags projects.write

Body: name and color (required, for example #2383E2), sort_order, is_active. Response 201.

PUT/api/api-projects/{id}/tags/{tagId} projects.write

Updates name, color, sort_order, is_active.

DELETE/api/api-projects/{id}/tags/{tagId} projects.write

Deletes the tag and removes it from all tasks.

Project status updates

GET/api/api-projects/{id}/status-updates projects.read

Health updates, newest first, each with creator.

GET/api/api-projects/{id}/status-updates/latest projects.read

Only the latest update (or null).

POST/api/api-projects/{id}/status-updates projects.write

Body: health_status (required: on_track, at_risk, delayed), description, next_steps, completion_percentage. Response 201.

Project brief documents

GET/api/api-projects/{id}/documents projects.read

Documents and links of the project brief.

POST/api/api-projects/{id}/documents projects.write

Body: name and url (required), document_type (link default, or file), visible_to_client (default false). Response 201.

PUT/api/api-projects/{id}/documents/{docId} projects.write

Partial update.

DELETE/api/api-projects/{id}/documents/{docId} projects.write

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.

POST/api/api-projects/{id}/sections projects.write

Body { "name": "Design" }. Response 201 with the header row; its section field is the section key.

PUT/api/api-projects/{id}/sections/{sectionKey} projects.write

Body { "name": "..." }: renames the section.

DELETE/api/api-projects/{id}/sections/{sectionKey} projects.write

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.

GET/api/api-pipelines

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 }
  ]
}
GET/api/api-pipelines/{id}

One pipeline with its lead_pipeline_stages. 404 {"error": "Pipeline not found"}.

POST/api/api-pipelines pipelines.write

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.

PUT/api/api-pipelines/{id} pipelines.write

Partial update with the same fields.

DELETE/api/api-pipelines/{id} pipelines.write

Deletes the pipeline and its stages. It fails while leads are still in the pipeline: move them first.

PUT/api/api-pipelines/{id}/reorder pipelines.write

Reorders all pipelines; {id} can be the id of any of your pipelines. Body { "pipelines": [ { "id": "...", "sort_order": 0 }, { "id": "...", "sort_order": 1 } ] }.

GET/api/api-pipelines/{id}/stages

Stages of the pipeline ordered by sort_order: id, pipeline_id, name, description, color, sort_order, is_active, is_won, is_lost.

POST/api/api-pipelines/{id}/stages pipelines.write

Body: name (required), description, color, sort_order (default last), is_won, is_lost (not both true), is_active. Response 201.

PUT/api/api-pipelines/{id}/stages/{stageId} pipelines.write

Partial update.

PUT/api/api-pipelines/{id}/stages/reorder pipelines.write

Body { "stages": [ { "id": "...", "sort_order": 0 } ] }.

DELETE/api/api-pipelines/{id}/stages/{stageId} pipelines.write

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.

GET/api/api-services

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 }
}
GET/api/api-services/{id}

One service. 404 {"error": "Service not found"}.

POST/api/api-services services.write

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.

PUT/api/api-services/{id} services.write

Partial update.

DELETE/api/api-services/{id} services.write

Response: { "message": "Service deleted successfully" }.

Invoices (read-only)

GET/api/api-invoices invoices.read

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.

GET/api/api-invoices/{id} invoices.read

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.

GET/api/api-documents

Newest first. Query: page, limit, task_id.

GET/api/api-documents/{id}

One document. 404 {"error": "Document not found"}.

DELETE/api/api-documents/{id} documents.write

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.