Documentație API

API-ul BB123 reflectă structura reală a datelor: clientul este persoana, iar lead-ul este intenția lui de cumpărare într-un pipeline anume. Un client poate avea mai multe lead-uri simultan.

Bazele

URL de bază

https://bb123.lovable.app/api/public/v1

Autentificare

Fiecare cerere trimite cheia API în header. Cheile se creează în Setări → API și pot avea pipeline, etapă, sursă și consultant impliciți, folosiți când nu trimiți acele câmpuri.

X-API-Key: CHEIA_TA
Content-Type: application/json

Convenții

  • Succes: { "ok": true, "data": ... }. Listele adaugă total, page și limit (limit între 1 și 200, implicit 50).
  • Eroare: { "ok": false, "error": "...", "code": "...", "details"?: [...] }, unde details este un array<string> cu problemele concrete.
  • Toate numele de câmpuri sunt în engleză. Datele fără oră sunt date (YYYY-MM-DD) în fusul Europe/Chișinău; cele cu oră sunt datetime ISO 8601 (UTC).
  • Pipeline-urile, etapele, sursele, produsele și tipurile de interacțiune se pot trimite prin id sau nume (fără diferență între majuscule și minuscule).
  • La PATCH/PUT/POST de actualizare, câmpul absent rămâne neschimbat, iar null șterge valoarea.
  • Metode acceptate: GET, POST, PUT, PATCH, DELETE, OPTIONS. Limită: 60 de cereri pe minut per cheie.
  • Placeholder-ele neînlocuite de integrare ([[email]], {{phone}}, %nume%) sunt ignorate: câmpul rămâne gol, iar cererea primește un warnings în răspuns și un avertisment în Setări → API → Loguri API.

Formatele câmpurilor

stringText UTF-8. Se elimină spațiile de la capete. Fiecare câmp are lungime maximă.
integerNumăr întreg, fără zecimale (ex. `duration_minutes: 25`).
numberNumăr cu zecimale, separator punct (ex. `amount: 1250.5`). Se acceptă și ca text numeric („1250.5”) și se convertește automat.
boolean`true` / `false`. Din formulare se acceptă și „true” / „1”.
uuidIdentificator UUID v4, ex. `3f1b9c2e-8a41-4d76-9f0a-2b7c5e6d1a33`.
dateDoar data, `YYYY-MM-DD`, interpretată în fusul Europe/Chișinău.
datetimeData și ora ISO 8601 cu fus, ex. `2026-09-03T09:30:00.000Z`.
enumUna din valorile listate explicit; orice altă valoare întoarce 400.
objectObiect JSON imbricat; câmpurile lui sunt documentate separat.
array<T>Listă JSON de elemente de tipul `T` (ex. `array<object>`).
nullUn câmp marcat „poate fi null” acceptă `null`. La `PATCH`, `null` șterge valoarea, iar câmpul absent o lasă neschimbată.

Exemplu complet (creare lead + interacțiune)

BASE="https://bb123.lovable.app/api/public/v1"

# 1. Creează clientul și lead-ul într-un singur apel
LEAD=$(curl -s -X POST "$BASE/leads" \
  -H "X-API-Key: CHEIA_TA" -H "Content-Type: application/json" \
  -d '{"client":{"name":"Maria Popescu","phone":"+37360000000"},"source":"Formular website","warmth":"warm"}')

LEAD_ID=$(echo "$LEAD" | grep -o '"id":"[^"]*' | head -1 | cut -d'"' -f4)

# 2. Notează rezultatul primului apel
curl -s -X POST "$BASE/interactions" \
  -H "X-API-Key: CHEIA_TA" -H "Content-Type: application/json" \
  -d '{"lead_id":"'"$LEAD_ID"'","type":"Apel de calificare","duration_minutes":25,
       "outcome":"Programat pentru consultație","fields":{"Interes principal":"Somn de noapte"}}'

# 3. Mută lead-ul în etapa următoare
curl -s -X POST "$BASE/leads/$LEAD_ID/stage" \
  -H "X-API-Key: CHEIA_TA" -H "Content-Type: application/json" \
  -d '{"stage":"Apel 1"}'

Referințe

Endpointuri de citire pentru a afla ce valori poți trimite: pipeline-uri și etapele lor, surse, consultanți, produse și tipuri de interacțiune. Oriunde un câmp acceptă „id sau nume”, poți folosi numele exact așa cum apare aici. Toate câmpurile din API sunt în engleză.

GET/api/public/v1/pipelines

Pipeline-uri și etape

Lista pipeline-urilor active, fiecare cu etapele sale în ordine, inclusiv marcajele de câștigat/pierdut.

Exemplu de cerere (cURL)

curl -X GET "$BASE/pipelines" \
  -H "X-API-Key: CHEIA_TA"

Exemplu de răspuns

200 OK
{
  "ok": true,
  "data": [
    {
      "id": "3f1b9c2e-8a41-4d76-9f0a-2b7c5e6d1a33",
      "name": "Vânzări consultații",
      "description": "Fluxul principal de vânzare",
      "color": "#5B8DB8",
      "position": 1,
      "is_default": true,
      "stages": [
        { "id": "8c2d0f11-55a7-4f2b-9c31-6d0be1f9a742", "name": "Lead nou", "position": 1,
          "color": "#DCEAF7", "is_won": false, "is_lost": false },
        { "id": "b41e7a90-2c58-4f0d-8a17-9e3d5c6b2f08", "name": "Apel 1", "position": 2,
          "color": "#DCEAF7", "is_won": false, "is_lost": false },
        { "id": "d9a3c710-64bf-4e21-b8f5-1a2c3d4e5f60", "name": "Vândut", "position": 3,
          "color": "#A7D8B0", "is_won": true, "is_lost": false }
      ]
    }
  ]
}

Câmpurile răspunsului

iduuidId-ul pipeline-ului.
namestringNumele pipeline-ului.
descriptionstring | nullDescriere internă.
colorstring | nullCod HEX de culoare.
positionintegerOrdinea de afișare.
is_defaultbooleanPipeline-ul folosit când nu trimiți `pipeline`.
stagesarray<object>Etapele active, în ordinea `position`.
stages[].is_wonbooleanEtapă de câștig.
stages[].is_lostbooleanEtapă de pierdere.

Erori posibile

401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
GET/api/public/v1/sources

Surse de lead-uri

Sursele active configurate în aplicație. Dacă trimiți o sursă nouă la creare, ea se adaugă automat în listă exact cu numele trimis.

Exemplu de cerere (cURL)

curl -X GET "$BASE/sources" \
  -H "X-API-Key: CHEIA_TA"

Exemplu de răspuns

200 OK
{
  "ok": true,
  "data": [
    { "id": "5e7f8a90-1b2c-4d3e-8f90-a1b2c3d4e5f6", "name": "Formular website", "position": 1 },
    { "id": "6f8a9b01-2c3d-4e5f-9a01-b2c3d4e5f607", "name": "Instagram DM", "position": 2 }
  ]
}

Câmpurile răspunsului

iduuidId-ul sursei.
namestringNumele sursei (valoarea de trimis în `source`).
positionintegerOrdinea în listă.

Erori posibile

401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
GET/api/public/v1/consultants

Consultanți

Utilizatorii activi și rolul lor; folosește `id` pentru câmpul `consultant_id`.

Exemplu de cerere (cURL)

curl -X GET "$BASE/consultants" \
  -H "X-API-Key: CHEIA_TA"

Exemplu de răspuns

200 OK
{
  "ok": true,
  "data": [
    { "id": "a1b2c3d4-e5f6-4708-9a1b-2c3d4e5f6071", "name": "Ana Rusu", "role": "consultant" },
    { "id": "b2c3d4e5-f607-4819-a2b3-c4d5e6f70812", "name": "Vadim Ciobanu", "role": "admin" }
  ]
}

Câmpurile răspunsului

iduuidId-ul utilizatorului (`consultant_id`).
namestringNumele complet.
roleenum("admin"|"consultant")Rolul în aplicație.

Erori posibile

401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
GET/api/public/v1/products

Catalog de produse

Produsele nearhivate, cu preț, monedă, tip, livrare și intervalul de vârstă recomandat (în luni).

Exemplu de cerere (cURL)

curl -X GET "$BASE/products" \
  -H "X-API-Key: CHEIA_TA"

Exemplu de răspuns

200 OK
{
  "ok": true,
  "data": [
    {
      "id": "c3d4e5f6-0718-4920-b3c4-d5e6f7081923",
      "name": "Curs Montessori 0-6 luni",
      "description": "Curs video cu 12 module",
      "type": "curs",
      "category": "Montessori",
      "delivery_type": "digital",
      "price": 890,
      "currency": "MDL",
      "is_free": false,
      "lead_magnet": false,
      "age_min_months": 0,
      "age_max_months": 6,
      "is_active": true
    }
  ]
}

Câmpurile răspunsului

iduuidId-ul produsului.
namestringDenumirea (acceptată și în câmpul `product`).
descriptionstring | nullDescriere.
typestringTipul intern de produs (ex. „curs”, „consultație”).
categorystring | nullCategoria.
delivery_typeenum("digital"|"physical") | nullMod de livrare.
pricenumberPrețul unitar.
currencystringMoneda prețului.
is_freebooleanProdus gratuit.
lead_magnetbooleanProdus folosit ca lead magnet.
age_min_monthsinteger | nullVârsta minimă recomandată, luni.
age_max_monthsinteger | nullVârsta maximă recomandată, luni.
is_activebooleanProdus activ pentru vânzare.

Erori posibile

401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
GET/api/public/v1/interaction-types

Tipuri de interacțiune

Tipurile active și câmpurile personalizate ale fiecăruia. Le folosești la `POST /interactions`: cheia din `fields` poate fi `id`-ul câmpului sau eticheta lui exactă.

Exemplu de cerere (cURL)

curl -X GET "$BASE/interaction-types" \
  -H "X-API-Key: CHEIA_TA"

Exemplu de răspuns

200 OK
{
  "ok": true,
  "data": [
    {
      "id": "d4e5f607-1829-4a31-c4d5-e6f708192a34",
      "name": "Apel de calificare",
      "description": "Primul apel cu părintele",
      "stage": { "id": "b41e7a90-2c58-4f0d-8a17-9e3d5c6b2f08", "name": "Apel 1",
                 "pipeline_id": "3f1b9c2e-8a41-4d76-9f0a-2b7c5e6d1a33" },
      "fields": [
        { "id": "e5f60718-2a3b-4b42-d5e6-f708192a3b45", "label": "Interes principal",
          "description": "Ce îl preocupă cel mai mult", "type": "text", "options": null, "is_required": true },
        { "id": "f6071829-3b4c-4c53-e6f7-08192a3b4c56", "label": "Nr. treziri pe noapte",
          "description": null, "type": "number", "options": null, "is_required": false },
        { "id": "07182930-4c5d-4d64-f708-192a3b4c5d67", "label": "Rutina actuală",
          "description": null, "type": "select", "options": ["Adoarme în brațe", "Adoarme singur"],
          "is_required": false }
      ]
    }
  ]
}

Câmpurile răspunsului

iduuidId-ul tipului (valoare pentru `type`).
namestringNumele tipului (acceptat și el în `type`).
descriptionstring | nullDescriere.
stageobject | null`{ id, name, pipeline_id }` — etapa asociată.
fieldsarray<object>Câmpurile personalizate ale tipului.
fields[].iduuidCheia recomandată în obiectul `fields`.
fields[].labelstringEticheta afișată; acceptată și ca cheie.
fields[].typeenum("text"|"textarea"|"number"|"checkbox"|"select"|"date")Formatul valorii așteptate.
fields[].optionsarray<string> | nullOpțiunile pentru `select`.
fields[].is_requiredbooleanDacă lipsește, cererea întoarce 400.

Erori posibile

401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }

Clienți (clients)

Clientul este persoana (părintele). Datele de contact și copiii aparțin clientului, nu lead-ului. Un client poate avea oricâte lead-uri.

GET/api/public/v1/clients

Caută / listează clienți

Căutare după telefon, email sau nume, cu paginare. Fiecare client vine cu copiii și lead-urile lui.

Parametri query

phonestringPotrivire exactă pe telefon.
emailstringPotrivire exactă pe email.
namestringPotrivire parțială, fără majuscule/minuscule, în nume.
limitintegerElemente pe pagină.(1–200, implicit 50)
pageintegerPagina cerută.(≥ 1, implicit 1)

Exemplu de cerere (cURL)

curl -X GET "$BASE/clients?phone=%2B37360000000&limit=20&page=1" \
  -H "X-API-Key: CHEIA_TA"

Exemplu de răspuns

200 OK
{
  "ok": true,
  "data": [
    {
      "id": "11111111-2222-4333-8444-555555555555",
      "name": "Maria Popescu",
      "phone": "+37360000000",
      "email": "maria@exemplu.md",
      "telegram": null,
      "instagram": null,
      "messenger": null,
      "tiktok": null,
      "notes": null,
      "children": [
        { "id": "66666666-7777-4888-8999-aaaaaaaaaaaa", "name": "Ana", "age": 14,
          "age_unit": "weeks", "age_declared_on": "2026-09-03", "notes": null }
      ],
      "leads": [
        { "id": "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff", "client_id": "11111111-2222-4333-8444-555555555555",
          "title": null,
          "pipeline": { "id": "3f1b9c2e-8a41-4d76-9f0a-2b7c5e6d1a33", "name": "Vânzări consultații" },
          "stage": { "id": "8c2d0f11-55a7-4f2b-9c31-6d0be1f9a742", "name": "Lead nou" },
          "source": "Formular website", "warmth": "warm", "consultant_id": null,
          "lead_date": "2026-09-03", "status": "open", "notes": null, "closed_at": null,
          "created_at": "2026-09-03T09:30:00.000Z", "updated_at": "2026-09-03T09:30:00.000Z" }
      ],
      "created_at": "2026-09-03T09:30:00.000Z",
      "updated_at": "2026-09-03T09:30:00.000Z"
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 20
}

Câmpurile răspunsului

okboolean`true` la succes.
dataarray<object>Elementele paginii curente.
totalintegerNumărul total de rânduri care corespund filtrelor.
pageintegerPagina returnată (de la 1).
limitintegerNumărul de elemente pe pagină.
data[].iduuidId-ul clientului.
data[].namestringNumele părintelui.
data[].phonestring | nullTelefon.
data[].emailstring | nullEmail.
data[].telegramstring | nullContact Telegram.
data[].instagramstring | nullContact Instagram.
data[].messengerstring | nullContact Messenger.
data[].tiktokstring | nullContact TikTok.
data[].notesstring | nullNotițele persoanei.
data[].childrenarray<object>Copiii clientului (vezi tabelul copiilor).
data[].leadsarray<object>Lead-urile clientului, cele mai recente primele.
data[].created_atdatetimeMomentul creării.
data[].updated_atdatetimeUltima modificare.

Erori posibile

401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
POST/api/public/v1/clients

Creează un client

Creează clientul, opțional cu copii. Dacă `dedupe` găsește un client existent, îl întoarce cu `exists: true` și cod 200, fără să creeze duplicat; altfel răspunde 201 cu `exists: false`.

Câmpurile cererii

name*stringNumele părintelui.(1–160 caractere)
phonestringTelefon de contact.(max 60)
emailstringEmail de contact.(max 160)
telegramstringContact Telegram.(max 120)
instagramstringContact Instagram.(max 120)
messengerstringContact Messenger.(max 120)
tiktokstringContact TikTok.(max 120)
notesstringNote libere despre persoană.(max 4000)
dedupeenum("phone"|"email"|"both"|"none")Cum se caută duplicatele înainte de creare.(implicit „both”)
childrenarray<object>Copiii clientului. Vârsta se salvează împreună cu data de azi (Chișinău).(max 10 elemente)
children[].namestringNumele copilului.(max 120)
children[].agenumberVârsta declarată.(0–400)
children[].age_unitenum("weeks"|"months"|"years")Unitatea vârstei.(implicit „months”)
children[].notesstringNote despre copil.(max 2000)

Exemplu de corp al cererii (JSON)

{
  "name": "Maria Popescu",
  "phone": "+37360000000",
  "email": "maria@exemplu.md",
  "telegram": "@mariap",
  "notes": "A scris pe Instagram",
  "dedupe": "both",
  "children": [
    { "name": "Ana", "age": 14, "age_unit": "weeks", "notes": "Se trezește des" }
  ]
}

Exemplu de cerere (cURL)

curl -X POST "$BASE/clients" \
  -H "X-API-Key: CHEIA_TA" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Maria Popescu",
    "phone": "+37360000000",
    "email": "maria@exemplu.md",
    "children": [{ "name": "Ana", "age": 14, "age_unit": "weeks" }]
  }'

Exemplu de răspuns

201 Created (200 la duplicat găsit)
{
  "ok": true,
  "data": {
    "exists": false,
    "client": {
      "id": "11111111-2222-4333-8444-555555555555",
      "name": "Maria Popescu",
      "phone": "+37360000000",
      "email": "maria@exemplu.md",
      "telegram": "@mariap",
      "instagram": null,
      "messenger": null,
      "tiktok": null,
      "notes": "A scris pe Instagram",
      "children": [
        { "id": "66666666-7777-4888-8999-aaaaaaaaaaaa", "name": "Ana", "age": 14,
          "age_unit": "weeks", "age_declared_on": "2026-09-03", "notes": "Se trezește des" }
      ],
      "leads": [],
      "created_at": "2026-09-03T09:30:00.000Z",
      "updated_at": "2026-09-03T09:30:00.000Z"
    }
  }
}

Câmpurile răspunsului

existsboolean`true` dacă s-a găsit un client existent prin dedupe.
clientobjectClientul complet (vezi câmpurile mai jos).
client.iduuidId-ul clientului.
client.namestringNumele părintelui.
client.phonestring | nullTelefon.
client.emailstring | nullEmail.
client.telegramstring | nullContact Telegram.
client.instagramstring | nullContact Instagram.
client.messengerstring | nullContact Messenger.
client.tiktokstring | nullContact TikTok.
client.notesstring | nullNotițele persoanei.
client.childrenarray<object>Copiii clientului (vezi tabelul copiilor).
client.leadsarray<object>Lead-urile clientului, cele mai recente primele.
client.created_atdatetimeMomentul creării.
client.updated_atdatetimeUltima modificare.

Erori posibile

400 invalid_data
Câmpuri lipsă sau de tip greșit. `details` listează exact ce a eșuat.
{
  "ok": false,
  "error": "Date invalide.",
  "code": "invalid_data",
  "details": ["name: Required", "phone: String must contain at most 60 character(s)"]
}
401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
GET/api/public/v1/clients/{id}

Fișa completă a clientului

Clientul cu toți copiii și lead-urile lui, plus ultimele 20 de interacțiuni și toate vânzările înregistrate.

Exemplu de cerere (cURL)

curl -X GET "$BASE/clients/11111111-2222-4333-8444-555555555555" \
  -H "X-API-Key: CHEIA_TA"

Exemplu de răspuns

200 OK
{
  "ok": true,
  "data": {
    "client": {
      "id": "11111111-2222-4333-8444-555555555555",
      "name": "Maria Popescu",
      "phone": "+37360000000",
      "email": "maria@exemplu.md",
      "telegram": null, "instagram": null, "messenger": null, "tiktok": null,
      "notes": null,
      "children": [ { "id": "66666666-7777-4888-8999-aaaaaaaaaaaa", "name": "Ana", "age": 14,
        "age_unit": "weeks", "age_declared_on": "2026-09-03", "notes": null } ],
      "leads": [ { "id": "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff", "status": "open", "...": "..." } ],
      "created_at": "2026-09-03T09:30:00.000Z",
      "updated_at": "2026-09-03T09:30:00.000Z"
    },
    "interactions": [
      { "id": "22222222-3333-4444-8555-666666666666",
        "client_id": "11111111-2222-4333-8444-555555555555",
        "lead_id": "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff",
        "type": { "id": "d4e5f607-1829-4a31-c4d5-e6f708192a34", "name": "Apel de calificare" },
        "label": "Apel de calificare",
        "stage": { "id": "b41e7a90-2c58-4f0d-8a17-9e3d5c6b2f08", "name": "Apel 1" },
        "date": "2026-09-03T10:15:00.000Z", "duration_minutes": 25,
        "outcome": "Programat pentru consultație", "notes": null,
        "fields": { "e5f60718-2a3b-4b42-d5e6-f708192a3b45": "Somn de noapte" },
        "created_at": "2026-09-03T10:16:00.000Z" }
    ],
    "sales": [
      { "id": "33333333-4444-4555-8666-777777777777",
        "client_id": "11111111-2222-4333-8444-555555555555",
        "lead_id": "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff",
        "product": { "id": "c3d4e5f6-0718-4920-b3c4-d5e6f7081923", "name": "Curs Montessori 0-6 luni" },
        "produs_nume": "Curs Montessori 0-6 luni",
        "quantity": 1, "unit": "buc", "amount": 890, "currency": "MDL",
        "status": "paid", "date": "2026-09-03", "consultant_id": null, "notes": null,
        "created_at": "2026-09-03T11:00:00.000Z" }
    ]
  }
}

Câmpurile răspunsului

clientobjectClientul complet, cu `children` și `leads`.
interactionsarray<object>Ultimele 20 de interacțiuni, cele mai noi primele.
salesarray<object>Vânzările clientului, cele mai noi primele.

Erori posibile

400 invalid_data
Id-ul din cale nu este un UUID valid.
{ "ok": false, "error": "ID invalid.", "code": "invalid_data" }
404 not_found
Client nu există.
{ "ok": false, "error": "Client not_found.", "code": "not_found" }
401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
PATCH/api/public/v1/clients/{id}

Actualizează clientul

Se modifică doar câmpurile trimise; `null` șterge valoarea, iar câmpul absent rămâne neschimbat. `POST` pe aceeași cale are exact același comportament (update parțial).

Câmpurile cererii

namestringNumele părintelui.(1–160)
phonestring | nullTelefon.(max 60)
emailstring | nullEmail.(max 160)
telegramstring | nullTelegram.(max 120)
instagramstring | nullInstagram.(max 120)
messengerstring | nullMessenger.(max 120)
tiktokstring | nullTikTok.(max 120)
notesstring | nullNotițele persoanei.(max 4000)
notes_modeenum("replace"|"append")„append” adaugă la notițele existente, cu separator datat (ora Chișinău).(implicit „replace”)

Exemplu de corp al cererii (JSON)

{
  "phone": "+37360000001",
  "instagram": null,
  "notes": "A confirmat programarea",
  "notes_mode": "append"
}

Exemplu de cerere (cURL)

curl -X PATCH "$BASE/clients/11111111-2222-4333-8444-555555555555" \
  -H "X-API-Key: CHEIA_TA" \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+37360000001", "notes": "A confirmat programarea", "notes_mode": "append" }'

Exemplu de răspuns

200 OK
{
  "ok": true,
  "data": {
    "client": {
      "id": "11111111-2222-4333-8444-555555555555",
      "name": "Maria Popescu",
      "phone": "+37360000001",
      "instagram": null,
      "notes": "Prima notă\n\n— 03.09.2026, 12:30 —\nA confirmat programarea",
      "children": [],
      "leads": [],
      "created_at": "2026-09-03T09:30:00.000Z",
      "updated_at": "2026-09-03T09:35:00.000Z"
    }
  }
}

Câmpurile răspunsului

clientobjectClientul complet după actualizare.

Erori posibile

400 invalid_data
Id-ul din cale nu este un UUID valid.
{ "ok": false, "error": "ID invalid.", "code": "invalid_data" }
404 not_found
Client nu există.
{ "ok": false, "error": "Client not_found.", "code": "not_found" }
400 invalid_data
Câmpuri lipsă sau de tip greșit. `details` listează exact ce a eșuat.
{
  "ok": false,
  "error": "Date invalide.",
  "code": "invalid_data",
  "details": ["name: Required", "phone: String must contain at most 60 character(s)"]
}
401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
GET/api/public/v1/clients/{id}/children

Listează copiii clientului

Copiii în ordinea adăugării, cu vârsta declarată și data declarării.

Exemplu de cerere (cURL)

curl -X GET "$BASE/clients/11111111-2222-4333-8444-555555555555/children" \
  -H "X-API-Key: CHEIA_TA"

Exemplu de răspuns

200 OK
{
  "ok": true,
  "data": [
    { "id": "66666666-7777-4888-8999-aaaaaaaaaaaa", "name": "Ana", "age": 14,
      "age_unit": "weeks", "age_declared_on": "2026-09-03", "notes": null }
  ]
}

Câmpurile răspunsului

data[].iduuidId-ul copilului.
data[].namestring | nullNumele copilului.
data[].agenumber | nullVârsta declarată, în unitatea din `age_unit`.
data[].age_unitenum("weeks"|"months"|"years")Unitatea vârstei declarate.
data[].age_declared_ondateData la care s-a declarat vârsta (baza estimării ulterioare).
data[].notesstring | nullNote despre copil.

Erori posibile

400 invalid_data
Id-ul din cale nu este un UUID valid.
{ "ok": false, "error": "ID invalid.", "code": "invalid_data" }
401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
POST/api/public/v1/clients/{id}/children

Adaugă un copil

Vârsta se stochează împreună cu data declarării, ca vârsta actuală să poată fi estimată ulterior.

Câmpurile cererii

namestring | nullNumele copilului.(max 120)
agenumber | nullVârsta declarată.(0–400)
age_unitenum("weeks"|"months"|"years")Unitatea vârstei.(implicit „months”)
age_declared_ondateData la care a fost declarată vârsta.(implicit azi (Chișinău))
notesstring | nullNote despre copil.(max 2000)

Exemplu de corp al cererii (JSON)

{ "name": "Ana", "age": 4, "age_unit": "months", "age_declared_on": "2026-09-01" }

Exemplu de cerere (cURL)

curl -X POST "$BASE/clients/11111111-2222-4333-8444-555555555555/children" \
  -H "X-API-Key: CHEIA_TA" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Ana", "age": 4, "age_unit": "months" }'

Exemplu de răspuns

201 Created
{
  "ok": true,
  "data": { "id": "66666666-7777-4888-8999-aaaaaaaaaaaa", "name": "Ana", "age": 4,
            "age_unit": "months", "age_declared_on": "2026-09-01", "notes": null }
}

Câmpurile răspunsului

iduuidId-ul copilului.
namestring | nullNumele copilului.
agenumber | nullVârsta declarată, în unitatea din `age_unit`.
age_unitenum("weeks"|"months"|"years")Unitatea vârstei declarate.
age_declared_ondateData la care s-a declarat vârsta (baza estimării ulterioare).
notesstring | nullNote despre copil.

Erori posibile

400 invalid_data
Id-ul din cale nu este un UUID valid.
{ "ok": false, "error": "ID invalid.", "code": "invalid_data" }
404 not_found
Client nu există.
{ "ok": false, "error": "Client not_found.", "code": "not_found" }
400 invalid_data
Câmpuri lipsă sau de tip greșit. `details` listează exact ce a eșuat.
{
  "ok": false,
  "error": "Date invalide.",
  "code": "invalid_data",
  "details": ["name: Required", "phone: String must contain at most 60 character(s)"]
}
401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
PATCH/api/public/v1/clients/{id}/children

Actualizează un copil

Trimite `child_id` pentru a preciza copilul; fără el se actualizează primul copil adăugat. Dacă trimiți `age` fără `age_declared_on`, data declarării devine ziua de azi.

Câmpurile cererii

child_iduuidCopilul de modificat.(implicit primul copil)
namestring | nullNumele copilului.
agenumber | nullVârsta declarată.(0–400)
age_unitenum("weeks"|"months"|"years")Unitatea vârstei.
age_declared_ondateData declarării.
notesstring | nullNote despre copil.

Exemplu de corp al cererii (JSON)

{ "child_id": "66666666-7777-4888-8999-aaaaaaaaaaaa", "age": 5, "age_unit": "months" }

Exemplu de cerere (cURL)

curl -X PATCH "$BASE/clients/11111111-2222-4333-8444-555555555555/children" \
  -H "X-API-Key: CHEIA_TA" \
  -H "Content-Type: application/json" \
  -d '{ "child_id": "66666666-7777-4888-8999-aaaaaaaaaaaa", "age": 5 }'

Exemplu de răspuns

200 OK
{
  "ok": true,
  "data": { "id": "66666666-7777-4888-8999-aaaaaaaaaaaa", "name": "Ana", "age": 5,
            "age_unit": "months", "age_declared_on": "2026-09-03", "notes": null }
}

Câmpurile răspunsului

iduuidId-ul copilului.
namestring | nullNumele copilului.
agenumber | nullVârsta declarată, în unitatea din `age_unit`.
age_unitenum("weeks"|"months"|"years")Unitatea vârstei declarate.
age_declared_ondateData la care s-a declarat vârsta (baza estimării ulterioare).
notesstring | nullNote despre copil.

Erori posibile

400 invalid_data
Id-ul din cale nu este un UUID valid.
{ "ok": false, "error": "ID invalid.", "code": "invalid_data" }
404 not_found
Clientul nu are copii înregistrați sau `child_id` nu îi aparține.
{ "ok": false, "error": "Copil not_found.", "code": "not_found" }
400 invalid_data
Câmpuri lipsă sau de tip greșit. `details` listează exact ce a eșuat.
{
  "ok": false,
  "error": "Date invalide.",
  "code": "invalid_data",
  "details": ["name: Required", "phone: String must contain at most 60 character(s)"]
}
401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
DELETE/api/public/v1/clients/{id}/children

Șterge un copil

Copilul se indică prin parametrul de query `child_id`.

Parametri query

child_id*uuidCopilul de șters.

Exemplu de cerere (cURL)

curl -X DELETE "$BASE/clients/11111111-2222-4333-8444-555555555555/children?child_id=66666666-7777-4888-8999-aaaaaaaaaaaa" \
  -H "X-API-Key: CHEIA_TA"

Exemplu de răspuns

200 OK
{ "ok": true, "data": { "deleted": true } }

Câmpurile răspunsului

deletedboolean`true` dacă ștergerea a reușit.

Erori posibile

400 invalid_data
`child_id` lipsește sau nu e UUID.
{ "ok": false, "error": "Lipsește parametrul child_id.", "code": "invalid_data" }
401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }

Lead-uri (leads)

Lead-ul este intenția de cumpărare: aparține unui client și trăiește într-un pipeline, pe o etapă. Același client poate avea lead-uri în pipeline-uri diferite.

GET/api/public/v1/leads

Listează lead-uri

Filtre pe pipeline, etapă, status, consultant, client și interval de date. Fiecare lead include un rezumat al clientului.

Parametri query

pipelinestringId sau nume de pipeline (fără diferență de majuscule).
stageuuidId de etapă.
statusenum("open"|"won"|"lost")Starea lead-ului.
consultant_iduuidConsultantul atribuit.
client_iduuidToate lead-urile unui client.
fromdateData lead-ului ≥ valoare.(YYYY-MM-DD)
todateData lead-ului ≤ valoare.(YYYY-MM-DD)
limitintegerElemente pe pagină.(1–200, implicit 50)
pageintegerPagina cerută.(≥ 1, implicit 1)

Exemplu de cerere (cURL)

curl -X GET "$BASE/leads?status=open&pipeline=Vânzări%20consultații&limit=20" \
  -H "X-API-Key: CHEIA_TA"

Exemplu de răspuns

200 OK
{
  "ok": true,
  "data": [
    {
      "id": "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff",
      "client_id": "11111111-2222-4333-8444-555555555555",
      "title": null,
      "pipeline": { "id": "3f1b9c2e-8a41-4d76-9f0a-2b7c5e6d1a33", "name": "Vânzări consultații" },
      "stage": { "id": "8c2d0f11-55a7-4f2b-9c31-6d0be1f9a742", "name": "Lead nou" },
      "source": "Formular website",
      "warmth": "warm",
      "consultant_id": null,
      "lead_date": "2026-09-03",
      "status": "open",
      "notes": null,
      "closed_at": null,
      "created_at": "2026-09-03T09:30:00.000Z",
      "updated_at": "2026-09-03T09:30:00.000Z",
      "client": { "id": "11111111-2222-4333-8444-555555555555", "name": "Maria Popescu",
                  "phone": "+37360000000", "email": "maria@exemplu.md" }
    }
  ],
  "total": 42,
  "page": 1,
  "limit": 20
}

Câmpurile răspunsului

okboolean`true` la succes.
dataarray<object>Elementele paginii curente.
totalintegerNumărul total de rânduri care corespund filtrelor.
pageintegerPagina returnată (de la 1).
limitintegerNumărul de elemente pe pagină.
data[].iduuidId-ul lead-ului.
data[].client_iduuidClientul (persoana) căruia aparține lead-ul.
data[].titlestring | nullDenumirea scurtă a lead-ului.
data[].pipelineobject | null`{ id: uuid, name: string }`.
data[].stageobject | null`{ id: uuid, name: string }` — etapa curentă.
data[].sourcestring | nullNumele sursei, exact cum e în lista de surse.
data[].warmthenum("cold"|"warm"|"hot") | nullGradul de interes.
data[].consultant_iduuid | nullConsultantul atribuit.
data[].lead_datedateData lead-ului.
data[].statusenum("open"|"won"|"lost")Starea lead-ului.
data[].notesstring | nullNotițele lead-ului.
data[].closed_atdatetime | nullMomentul închiderii (won/lost).
data[].created_atdatetimeMomentul creării.
data[].updated_atdatetime | nullUltima modificare.
data[].clientobject | null`{ id, name, phone, email }` — rezumatul clientului.

Erori posibile

400 invalid_data
Numele de pipeline nu există; `details` listează pipeline-urile valide.
{ "ok": false, "error": "Pipeline „Vanzari” nu există sau nu e is_active.",
  "code": "invalid_data", "details": ["Vânzări consultații", "Recuperare"] }
401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
POST/api/public/v1/leads

Creează un lead

Trimite `client_id` pentru un client existent, sau obiectul `client` pentru a-l crea/regăsi într-un singur apel. Pipeline-ul și etapa se pot da prin id sau nume; dacă lipsesc, se folosesc valorile implicite ale cheii API, apoi pipeline-ul implicit și prima etapă a lui. Schimbarea etapei se scrie automat în istoric.

Câmpurile cererii

client_iduuidClientul existent. Obligatoriu dacă nu trimiți `client`.
clientobjectClient nou sau regăsit: `name` (obligatoriu), `phone`, `email`, `telegram`, `instagram`, `messenger`, `tiktok`, `notes`, `dedupe`.
pipelinestringId sau nume de pipeline.(implicit: cheia API, apoi pipeline-ul implicit)
stagestringId sau nume de etapă din pipeline-ul respectiv.(implicit prima etapă)
titlestringDenumire scurtă a lead-ului.(max 200)
sourcestringSursa; se adaugă automat în listă dacă e nouă.(max 120)
warmthenum("cold"|"warm"|"hot")Gradul de interes.
consultant_iduuid | nullConsultantul atribuit.(implicit consultantul cheii)
lead_datedateData lead-ului.(YYYY-MM-DD, implicit azi (Chișinău))
notesstringNote libere ale lead-ului.(max 4000)
childobjectCopil nou al clientului: `{ name, age, age_unit }`.
extraobjectOrice câmpuri suplimentare; se adaugă ca text („cheie: valoare”) în notițele lead-ului.

Exemplu de corp al cererii (JSON)

{
  "client": {
    "name": "Maria Popescu",
    "phone": "+37360000000",
    "email": "maria@exemplu.md",
    "dedupe": "both"
  },
  "pipeline": "Vânzări consultații",
  "stage": "Lead nou",
  "title": "Consultație somn 4 luni",
  "source": "Formular website",
  "warmth": "warm",
  "lead_date": "2026-09-03",
  "notes": "Vrea program de somn",
  "child": { "name": "Ana", "age": 14, "age_unit": "weeks" },
  "extra": { "utm_source": "facebook", "formular": "landing-somn" }
}

Exemplu de cerere (cURL)

curl -X POST "$BASE/leads" \
  -H "X-API-Key: CHEIA_TA" \
  -H "Content-Type: application/json" \
  -d '{
    "client": { "name": "Maria Popescu", "phone": "+37360000000" },
    "pipeline": "Vânzări consultații",
    "stage": "Lead nou",
    "source": "Formular website",
    "warmth": "warm",
    "child": { "age": 14, "age_unit": "weeks" }
  }'

Exemplu de răspuns

201 Created
{
  "ok": true,
  "data": {
    "client_exists": false,
    "lead": {
      "id": "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff",
      "client_id": "11111111-2222-4333-8444-555555555555",
      "title": "Consultație somn 4 luni",
      "pipeline": { "id": "3f1b9c2e-8a41-4d76-9f0a-2b7c5e6d1a33", "name": "Vânzări consultații" },
      "stage": { "id": "8c2d0f11-55a7-4f2b-9c31-6d0be1f9a742", "name": "Lead nou" },
      "source": "Formular website",
      "warmth": "warm",
      "consultant_id": null,
      "lead_date": "2026-09-03",
      "status": "open",
      "notes": "Vrea program de somn\n\nutm_source: facebook\nformular: landing-somn",
      "closed_at": null,
      "created_at": "2026-09-03T09:30:00.000Z",
      "updated_at": "2026-09-03T09:30:00.000Z"
    },
    "client": { "id": "11111111-2222-4333-8444-555555555555", "name": "Maria Popescu",
                "children": [ { "id": "66666666-7777-4888-8999-aaaaaaaaaaaa", "name": "Ana",
                  "age": 14, "age_unit": "weeks", "age_declared_on": "2026-09-03", "notes": null } ],
                "leads": [ { "id": "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff", "...": "..." } ],
                "...": "..." },
    "child_id": "66666666-7777-4888-8999-aaaaaaaaaaaa"
  }
}

Câmpurile răspunsului

client_existsboolean`true` dacă clientul exista deja (dedupe sau `client_id`).
leadobjectLead-ul creat (câmpurile de mai jos).
lead.iduuidId-ul lead-ului.
lead.client_iduuidClientul (persoana) căruia aparține lead-ul.
lead.titlestring | nullDenumirea scurtă a lead-ului.
lead.pipelineobject | null`{ id: uuid, name: string }`.
lead.stageobject | null`{ id: uuid, name: string }` — etapa curentă.
lead.sourcestring | nullNumele sursei, exact cum e în lista de surse.
lead.warmthenum("cold"|"warm"|"hot") | nullGradul de interes.
lead.consultant_iduuid | nullConsultantul atribuit.
lead.lead_datedateData lead-ului.
lead.statusenum("open"|"won"|"lost")Starea lead-ului.
lead.notesstring | nullNotițele lead-ului.
lead.closed_atdatetime | nullMomentul închiderii (won/lost).
lead.created_atdatetimeMomentul creării.
lead.updated_atdatetime | nullUltima modificare.
clientobjectClientul complet, cu copii și lead-uri.
child_iduuid | nullId-ul copilului creat din `child`, dacă a fost trimis.

Erori posibile

400 invalid_data
Nici `client_id`, nici `client` nu au fost trimise, sau câmpurile sunt invalide.
{ "ok": false, "error": "Date invalide.", "code": "invalid_data",
  "details": ["client_id: Trimite fie client_id, fie obiectul client."] }
400 invalid_data
Etapa nu există în pipeline-ul indicat; `details` listează etapele valide.
{ "ok": false, "error": "Etapa „Apel 5” nu există sau nu e activă în acest pipeline.",
  "code": "invalid_data", "details": ["Lead nou", "Apel 1", "Vândut"] }
404 not_found
Client nu există.
{ "ok": false, "error": "Client not_found.", "code": "not_found" }
401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
GET/api/public/v1/leads/{id}

Detaliile unui lead

Lead-ul, clientul cu toate canalele de contact, istoricul de etape (`stage_history`), interacțiunile și vânzările atașate lead-ului.

Exemplu de cerere (cURL)

curl -X GET "$BASE/leads/bbbbbbbb-cccc-4ddd-8eee-ffffffffffff" \
  -H "X-API-Key: CHEIA_TA"

Exemplu de răspuns

200 OK
{
  "ok": true,
  "data": {
    "lead": { "id": "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff", "status": "open",
      "pipeline": { "id": "3f1b9c2e-8a41-4d76-9f0a-2b7c5e6d1a33", "name": "Vânzări consultații" },
      "stage": { "id": "b41e7a90-2c58-4f0d-8a17-9e3d5c6b2f08", "name": "Apel 1" }, "...": "..." },
    "client": { "id": "11111111-2222-4333-8444-555555555555", "name": "Maria Popescu",
      "phone": "+37360000000", "email": "maria@exemplu.md",
      "telegram": null, "instagram": null, "messenger": null, "tiktok": null },
    "stage_history": [
      { "id": "44444444-5555-4666-8777-888888888888", "entered_at": "2026-09-03T09:30:00.000Z",
        "stage": { "id": "8c2d0f11-55a7-4f2b-9c31-6d0be1f9a742", "name": "Lead nou" } },
      { "id": "55555555-6666-4777-8888-999999999999", "entered_at": "2026-09-03T10:10:00.000Z",
        "stage": { "id": "b41e7a90-2c58-4f0d-8a17-9e3d5c6b2f08", "name": "Apel 1" } }
    ],
    "interactions": [ { "id": "22222222-3333-4444-8555-666666666666", "label": "Apel de calificare",
      "date": "2026-09-03T10:15:00.000Z", "duration_minutes": 25, "fields": {}, "...": "..." } ],
    "sales": [ { "id": "33333333-4444-4555-8666-777777777777", "amount": 890, "currency": "MDL",
      "status": "paid", "...": "..." } ]
  }
}

Câmpurile răspunsului

leadobjectLead-ul (aceleași câmpuri ca la listare, fără `client`).
clientobject | null`{ id, name, phone, email, telegram, instagram, messenger, tiktok }`.
stage_historyarray<object>`{ id, entered_at, stage }`, în ordine cronologică.
interactionsarray<object>Interacțiunile lead-ului, cele mai noi primele.
salesarray<object>Vânzările legate de lead.

Erori posibile

400 invalid_data
Id-ul din cale nu este un UUID valid.
{ "ok": false, "error": "ID invalid.", "code": "invalid_data" }
404 not_found
Lead nu există.
{ "ok": false, "error": "Lead not_found.", "code": "not_found" }
401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
PATCH/api/public/v1/leads/{id}

Actualizează lead-ul

Se modifică doar câmpurile trimise. La `status: won|lost` se completează automat `closed_at` (la `open` se golește); schimbarea etapei adaugă un rând în istoric; schimbarea pipeline-ului fără `stage` mută lead-ul pe prima etapă a noului pipeline. Aceeași cale acceptă și `PUT` sau `POST`, cu comportament identic de update parțial.

Câmpurile cererii

pipelinestringId sau nume de pipeline.(max 160)
stagestringId sau nume de etapă din pipeline.(max 160)
titlestring | nullDenumirea lead-ului.(max 200)
sourcestring | nullSursa; se creează dacă e nouă.(max 120)
warmthenum("cold"|"warm"|"hot") | nullGradul de interes.
consultant_iduuid | nullConsultantul atribuit.
lead_datedateData lead-ului.(YYYY-MM-DD)
statusenum("open"|"won"|"lost")Starea lead-ului.
notesstring | nullNotițele lead-ului.(max 4000)
notes_modeenum("replace"|"append")„append” adaugă la notițele existente, cu separator datat.(implicit „replace”)

Exemplu de corp al cererii (JSON)

{
  "stage": "Apel 1",
  "warmth": "hot",
  "consultant_id": "a1b2c3d4-e5f6-4708-9a1b-2c3d4e5f6071",
  "status": "open",
  "notes": "A cerut ofertă în scris",
  "notes_mode": "append"
}

Exemplu de cerere (cURL)

curl -X PATCH "$BASE/leads/bbbbbbbb-cccc-4ddd-8eee-ffffffffffff" \
  -H "X-API-Key: CHEIA_TA" \
  -H "Content-Type: application/json" \
  -d '{ "stage": "Apel 1", "warmth": "hot" }'

Exemplu de răspuns

200 OK
{
  "ok": true,
  "data": {
    "lead": {
      "id": "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff",
      "client_id": "11111111-2222-4333-8444-555555555555",
      "pipeline": { "id": "3f1b9c2e-8a41-4d76-9f0a-2b7c5e6d1a33", "name": "Vânzări consultații" },
      "stage": { "id": "b41e7a90-2c58-4f0d-8a17-9e3d5c6b2f08", "name": "Apel 1" },
      "warmth": "hot",
      "status": "open",
      "closed_at": null,
      "notes": "Vrea program de somn\n\n— 03.09.2026, 13:05 —\nA cerut ofertă în scris",
      "updated_at": "2026-09-03T10:05:00.000Z"
    }
  }
}

Câmpurile răspunsului

leadobjectLead-ul după actualizare.
lead.iduuidId-ul lead-ului.
lead.client_iduuidClientul (persoana) căruia aparține lead-ul.
lead.titlestring | nullDenumirea scurtă a lead-ului.
lead.pipelineobject | null`{ id: uuid, name: string }`.
lead.stageobject | null`{ id: uuid, name: string }` — etapa curentă.
lead.sourcestring | nullNumele sursei, exact cum e în lista de surse.
lead.warmthenum("cold"|"warm"|"hot") | nullGradul de interes.
lead.consultant_iduuid | nullConsultantul atribuit.
lead.lead_datedateData lead-ului.
lead.statusenum("open"|"won"|"lost")Starea lead-ului.
lead.notesstring | nullNotițele lead-ului.
lead.closed_atdatetime | nullMomentul închiderii (won/lost).
lead.created_atdatetimeMomentul creării.
lead.updated_atdatetime | nullUltima modificare.

Erori posibile

400 invalid_data
Id-ul din cale nu este un UUID valid.
{ "ok": false, "error": "ID invalid.", "code": "invalid_data" }
404 not_found
Lead nu există.
{ "ok": false, "error": "Lead not_found.", "code": "not_found" }
400 invalid_data
Câmpuri lipsă sau de tip greșit. `details` listează exact ce a eșuat.
{
  "ok": false,
  "error": "Date invalide.",
  "code": "invalid_data",
  "details": ["name: Required", "phone: String must contain at most 60 character(s)"]
}
401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
POST/api/public/v1/leads/{id}/stage

Mută lead-ul într-o etapă

Scurtătură pentru automatizări și chatboți: un singur câmp. Etapa trebuie să aparțină pipeline-ului curent al lead-ului; mutarea se scrie în istoric.

Câmpurile cererii

stage*stringId sau nume de etapă din pipeline-ul lead-ului.(1–160 caractere)

Exemplu de corp al cererii (JSON)

{ "stage": "Apel 1" }

Exemplu de cerere (cURL)

curl -X POST "$BASE/leads/bbbbbbbb-cccc-4ddd-8eee-ffffffffffff/stage" \
  -H "X-API-Key: CHEIA_TA" \
  -H "Content-Type: application/json" \
  -d '{ "stage": "Apel 1" }'

Exemplu de răspuns

200 OK
{
  "ok": true,
  "data": {
    "lead": {
      "id": "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff",
      "stage": { "id": "b41e7a90-2c58-4f0d-8a17-9e3d5c6b2f08", "name": "Apel 1" },
      "status": "open",
      "updated_at": "2026-09-03T10:10:00.000Z"
    }
  }
}

Câmpurile răspunsului

leadobjectLead-ul cu etapa nouă.

Erori posibile

400 invalid_data
Id-ul din cale nu este un UUID valid.
{ "ok": false, "error": "ID invalid.", "code": "invalid_data" }
404 not_found
Lead nu există.
{ "ok": false, "error": "Lead not_found.", "code": "not_found" }
400 invalid_data
Etapa nu există în pipeline-ul lead-ului; `details` listează etapele valide.
{ "ok": false, "error": "Etapa „Apel 9” nu există sau nu e activă în acest pipeline.",
  "code": "invalid_data", "details": ["Lead nou", "Apel 1", "Vândut"] }
401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
DELETE/api/public/v1/leads/{id}

Șterge lead-ul

Șterge definitiv lead-ul și datele atașate lui (istoric, interacțiuni, vânzări). Clientul rămâne.

Exemplu de cerere (cURL)

curl -X DELETE "$BASE/leads/bbbbbbbb-cccc-4ddd-8eee-ffffffffffff" \
  -H "X-API-Key: CHEIA_TA"

Exemplu de răspuns

200 OK
{ "ok": true, "data": { "deleted": true } }

Câmpurile răspunsului

deletedboolean`true` dacă ștergerea a reușit.

Erori posibile

400 invalid_data
Id-ul din cale nu este un UUID valid.
{ "ok": false, "error": "ID invalid.", "code": "invalid_data" }
401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }

Interacțiuni (interactions)

Jurnalul de apeluri și întâlniri. Câmpurile personalizate ale tipului de interacțiune se trimit în `fields`, cu id-ul câmpului sau cu eticheta lui exactă drept cheie.

GET/api/public/v1/interactions

Listează interacțiuni

Filtre pe `lead_id`, `client_id` și interval de timp, cele mai noi primele.

Parametri query

lead_iduuidInteracțiunile unui lead.
client_iduuidInteracțiunile unui client.
fromdatetime`occurred_at` ≥ valoare.(date sau ISO 8601)
todatetime`occurred_at` ≤ valoare.(date sau ISO 8601)
limitintegerElemente pe pagină.(1–200, implicit 50)
pageintegerPagina cerută.(≥ 1, implicit 1)

Exemplu de cerere (cURL)

curl -X GET "$BASE/interactions?lead_id=bbbbbbbb-cccc-4ddd-8eee-ffffffffffff&from=2026-09-01" \
  -H "X-API-Key: CHEIA_TA"

Exemplu de răspuns

200 OK
{
  "ok": true,
  "data": [
    {
      "id": "22222222-3333-4444-8555-666666666666",
      "client_id": "11111111-2222-4333-8444-555555555555",
      "lead_id": "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff",
      "type": { "id": "d4e5f607-1829-4a31-c4d5-e6f708192a34", "name": "Apel de calificare" },
      "label": "Apel de calificare",
      "stage": { "id": "b41e7a90-2c58-4f0d-8a17-9e3d5c6b2f08", "name": "Apel 1" },
      "date": "2026-09-03T10:15:00.000Z",
      "duration_minutes": 25,
      "outcome": "Programat pentru consultație",
      "notes": null,
      "fields": {
        "e5f60718-2a3b-4b42-d5e6-f708192a3b45": "Somn de noapte",
        "f6071829-3b4c-4c53-e6f7-08192a3b4c56": 4
      },
      "created_at": "2026-09-03T10:16:00.000Z"
    }
  ],
  "total": 3,
  "page": 1,
  "limit": 50
}

Câmpurile răspunsului

okboolean`true` la succes.
dataarray<object>Elementele paginii curente.
totalintegerNumărul total de rânduri care corespund filtrelor.
pageintegerPagina returnată (de la 1).
limitintegerNumărul de elemente pe pagină.
data[].iduuidId-ul interacțiunii.
data[].client_iduuidClientul.
data[].lead_iduuid | nullLead-ul asociat.
data[].typeobject | null`{ id, name }` — tipul de interacțiune.
data[].labelstringEticheta liberă (implicit numele tipului sau „apel”).
data[].stageobject | null`{ id, name }` — etapa lead-ului la acel moment.
data[].datedatetimeCând a avut loc.
data[].duration_minutesinteger | nullDurata în minute.
data[].outcomestring | nullRezultatul discuției.
data[].notesstring | nullNotițe.
data[].fieldsobjectValorile câmpurilor personalizate, cu id-ul câmpului drept cheie. `{}` dacă nu există.
data[].created_atdatetimeMomentul înregistrării.

Erori posibile

401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
POST/api/public/v1/interactions

Înregistrează o interacțiune

Trimite `lead_id` sau `client_id` (atunci se folosește cel mai recent lead al clientului). Etapa se preia automat din lead. Valorile din `fields` se convertesc după formatul câmpului (`number` → număr, `checkbox` → boolean, restul → text), iar câmpurile obligatorii lipsă întorc 400 cu lista lor în `details`.

Câmpurile cererii

lead_id*uuidLead-ul. Alternativ trimite `client_id`.
client_id*uuidClientul, dacă nu ai `lead_id`.
typestringId sau nume de tip de interacțiune.(max 160)
labelstringEtichetă liberă când nu trimiți `type`.(max 120, implicit numele tipului sau „apel”)
datedatetimeCând a avut loc.(ISO 8601, implicit acum)
duration_minutesintegerDurata discuției.(0–1440)
outcomestringRezultatul discuției.(max 500)
notesstringNotițe detaliate.(max 8000)
fieldsobjectValorile câmpurilor personalizate; cheia poate fi id-ul câmpului sau eticheta exactă (fără diferență de majuscule).

Exemplu de corp al cererii (JSON)

{
  "lead_id": "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff",
  "type": "Apel de calificare",
  "date": "2026-09-03T10:15:00.000Z",
  "duration_minutes": 25,
  "outcome": "Programat pentru consultație",
  "notes": "Copilul se trezește de 4 ori pe noapte",
  "fields": {
    "Interes principal": "Somn de noapte",
    "Nr. treziri pe noapte": 4,
    "Rutina actuală": "Adoarme în brațe"
  }
}

Exemplu de cerere (cURL)

curl -X POST "$BASE/interactions" \
  -H "X-API-Key: CHEIA_TA" \
  -H "Content-Type: application/json" \
  -d '{
    "lead_id": "LEAD_ID",
    "type": "Apel de calificare",
    "duration_minutes": 25,
    "outcome": "Programat pentru consultație",
    "fields": { "Interes principal": "Somn de noapte" }
  }'

Exemplu de răspuns

201 Created
{
  "ok": true,
  "data": {
    "id": "22222222-3333-4444-8555-666666666666",
    "client_id": "11111111-2222-4333-8444-555555555555",
    "lead_id": "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff",
    "type": { "id": "d4e5f607-1829-4a31-c4d5-e6f708192a34", "name": "Apel de calificare" },
    "label": "Apel de calificare",
    "stage": { "id": "b41e7a90-2c58-4f0d-8a17-9e3d5c6b2f08", "name": "Apel 1" },
    "date": "2026-09-03T10:15:00.000Z",
    "duration_minutes": 25,
    "outcome": "Programat pentru consultație",
    "notes": "Copilul se trezește de 4 ori pe noapte",
    "fields": {
      "e5f60718-2a3b-4b42-d5e6-f708192a3b45": "Somn de noapte",
      "f6071829-3b4c-4c53-e6f7-08192a3b4c56": 4,
      "07182930-4c5d-4d64-f708-192a3b4c5d67": "Adoarme în brațe"
    },
    "created_at": "2026-09-03T10:16:00.000Z"
  }
}

Câmpurile răspunsului

iduuidId-ul interacțiunii.
client_iduuidClientul.
lead_iduuid | nullLead-ul asociat.
typeobject | null`{ id, name }` — tipul de interacțiune.
labelstringEticheta liberă (implicit numele tipului sau „apel”).
stageobject | null`{ id, name }` — etapa lead-ului la acel moment.
datedatetimeCând a avut loc.
duration_minutesinteger | nullDurata în minute.
outcomestring | nullRezultatul discuției.
notesstring | nullNotițe.
fieldsobjectValorile câmpurilor personalizate, cu id-ul câmpului drept cheie. `{}` dacă nu există.
created_atdatetimeMomentul înregistrării.

Erori posibile

400 invalid_data
Câmpuri obligatorii ale tipului lipsă — `details` conține etichetele lor.
{ "ok": false, "error": "Câmpuri obligatorii lipsă.", "code": "invalid_data",
  "details": ["Interes principal"] }
400 invalid_data
Tipul de interacțiune nu există; `details` listează tipurile active.
{ "ok": false, "error": "Tipul de interacțiune „Apel X” nu există.",
  "code": "invalid_data", "details": ["Apel de calificare", "Consultație"] }
404 not_found
Lead-ul sau clientul nu există, ori clientul nu are niciun lead.
{ "ok": false, "error": "Clientul nu are niciun lead.", "code": "not_found" }
401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }

Vânzări (sales)

Produsele atribuite clienților. Prețul și denumirea se salvează ca instantaneu, deci rămân corecte chiar dacă produsul se modifică sau se arhivează.

GET/api/public/v1/sales

Listează vânzări

Filtre pe `lead_id`, `client_id`, `status` și interval de date, cele mai recente primele.

Parametri query

lead_iduuidVânzările unui lead.
client_iduuidVânzările unui client.
statusenum("paid"|"pending"|"refunded"|"free")Starea plății.
fromdateData vânzării ≥ valoare.(YYYY-MM-DD)
todateData vânzării ≤ valoare.(YYYY-MM-DD)
limitintegerElemente pe pagină.(1–200, implicit 50)
pageintegerPagina cerută.(≥ 1, implicit 1)

Exemplu de cerere (cURL)

curl -X GET "$BASE/sales?client_id=11111111-2222-4333-8444-555555555555&status=paid" \
  -H "X-API-Key: CHEIA_TA"

Exemplu de răspuns

200 OK
{
  "ok": true,
  "data": [
    {
      "id": "33333333-4444-4555-8666-777777777777",
      "client_id": "11111111-2222-4333-8444-555555555555",
      "lead_id": "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff",
      "product": { "id": "c3d4e5f6-0718-4920-b3c4-d5e6f7081923", "name": "Curs Montessori 0-6 luni" },
      "produs_nume": "Curs Montessori 0-6 luni",
      "quantity": 1,
      "unit": "buc",
      "amount": 890,
      "currency": "MDL",
      "status": "paid",
      "date": "2026-09-03",
      "consultant_id": null,
      "notes": null,
      "created_at": "2026-09-03T11:00:00.000Z"
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 50
}

Câmpurile răspunsului

okboolean`true` la succes.
dataarray<object>Elementele paginii curente.
totalintegerNumărul total de rânduri care corespund filtrelor.
pageintegerPagina returnată (de la 1).
limitintegerNumărul de elemente pe pagină.
data[].iduuidId-ul vânzării.
data[].client_iduuidClientul.
data[].lead_iduuid | nullLead-ul asociat.
data[].productobject | null`{ id, name }` — produsul din catalog.
data[].produs_numestring | nullDenumirea salvată ca instantaneu la vânzare.
data[].quantitynumberCantitatea.
data[].unitstringUnitatea de măsură (implicit „buc”).
data[].amountnumberValoarea totală.
data[].currencystringMoneda (ex. „MDL”).
data[].statusenum("paid"|"pending"|"refunded"|"free")Starea plății.
data[].datedateData vânzării.
data[].consultant_iduuid | nullConsultantul.
data[].notesstring | nullNotițe.
data[].created_atdatetimeMomentul înregistrării.

Erori posibile

401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }
POST/api/public/v1/sales

Înregistrează o vânzare

`product` acceptă id sau nume. Dacă nu trimiți `amount`, se calculează ca preț din catalog × `quantity`; moneda implicită e cea a produsului. Cu `client_id` se atașează automat cel mai recent lead al clientului, dacă există.

Câmpurile cererii

lead_id*uuidLead-ul. Alternativ trimite `client_id`.
client_id*uuidClientul, dacă nu ai `lead_id`.
product*stringId sau nume de produs nearhivat.(1–200)
quantitynumberCantitatea vândută.(0–100000, implicit 1)
unitstringUnitatea de măsură (ex. „buc”, „zile”).(max 40, implicit „buc”)
amountnumberValoarea totală.(0–10.000.000; implicit preț × cantitate)
currencystringMoneda.(max 10, implicit moneda produsului)
datedateData vânzării.(YYYY-MM-DD, implicit azi)
statusenum("paid"|"pending"|"refunded"|"free")Starea plății.(implicit „paid”)
consultant_iduuid | nullConsultantul care a vândut.
notesstringNotițe.(max 4000)

Exemplu de corp al cererii (JSON)

{
  "lead_id": "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff",
  "product": "Curs Montessori 0-6 luni",
  "quantity": 1,
  "unit": "buc",
  "amount": 890,
  "currency": "MDL",
  "date": "2026-09-03",
  "status": "paid",
  "notes": "Plătit prin transfer"
}

Exemplu de cerere (cURL)

curl -X POST "$BASE/sales" \
  -H "X-API-Key: CHEIA_TA" \
  -H "Content-Type: application/json" \
  -d '{ "lead_id": "LEAD_ID", "product": "Curs Montessori 0-6 luni", "quantity": 1, "status": "paid" }'

Exemplu de răspuns

201 Created
{
  "ok": true,
  "data": {
    "id": "33333333-4444-4555-8666-777777777777",
    "client_id": "11111111-2222-4333-8444-555555555555",
    "lead_id": "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff",
    "product": { "id": "c3d4e5f6-0718-4920-b3c4-d5e6f7081923", "name": "Curs Montessori 0-6 luni" },
    "produs_nume": "Curs Montessori 0-6 luni",
    "quantity": 1,
    "unit": "buc",
    "amount": 890,
    "currency": "MDL",
    "status": "paid",
    "date": "2026-09-03",
    "consultant_id": null,
    "notes": "Plătit prin transfer",
    "created_at": "2026-09-03T11:00:00.000Z"
  }
}

Câmpurile răspunsului

iduuidId-ul vânzării.
client_iduuidClientul.
lead_iduuid | nullLead-ul asociat.
productobject | null`{ id, name }` — produsul din catalog.
produs_numestring | nullDenumirea salvată ca instantaneu la vânzare.
quantitynumberCantitatea.
unitstringUnitatea de măsură (implicit „buc”).
amountnumberValoarea totală.
currencystringMoneda (ex. „MDL”).
statusenum("paid"|"pending"|"refunded"|"free")Starea plății.
datedateData vânzării.
consultant_iduuid | nullConsultantul.
notesstring | nullNotițe.
created_atdatetimeMomentul înregistrării.

Erori posibile

400 invalid_data
Produsul nu există sau e arhivat; `details` listează produsele disponibile.
{ "ok": false, "error": "Produsul „Curs X” nu există.", "code": "invalid_data",
  "details": ["Curs Montessori 0-6 luni", "Consultație individuală"] }
404 not_found
Lead nu există.
{ "ok": false, "error": "Lead not_found.", "code": "not_found" }
400 invalid_data
Câmpuri lipsă sau de tip greșit. `details` listează exact ce a eșuat.
{
  "ok": false,
  "error": "Date invalide.",
  "code": "invalid_data",
  "details": ["name: Required", "phone: String must contain at most 60 character(s)"]
}
401 invalid_key
Header-ul `X-API-Key` lipsește, e greșit sau cheia e dezactivată.
{ "ok": false, "error": "Cheie API invalidă sau dezactivată.", "code": "invalid_key" }
429 rate_limited
Peste 60 de cereri pe minut pentru aceeași cheie.
{ "ok": false, "error": "Prea multe cereri. Încearcă mai târziu.", "code": "rate_limited" }

Coduri de eroare

400invalid_dataCâmpuri lipsă sau invalide. `details` listează problemele.
401invalid_keyHeader `X-API-Key` lipsă, greșit sau cheie dezactivată.
404not_foundClientul, lead-ul sau resursa cerută nu există.
409conflictResursa există deja (duplicat detectat).
429rate_limitedPeste 60 de cereri pe minut pentru aceeași cheie.
500internal_errorEroare neașteptată pe server.