> ## Documentation Index
> Fetch the complete documentation index at: https://api.housera.ro/llms.txt
> Use this file to discover all available pages before exploring further.

# Cererile clienților

> Mesajele, cererile de apel și de vizionare ale clienților: le citești cu leads:read și le lucrezi ca în portal cu leads:write.

Când un client trimite un mesaj despre un anunț, cere să fie sunat sau cere o vizionare, cererea ajunge în căsuța din portalul Housera. Cu dreptul `leads:read` o citești și prin API, cu datele de contact ale clientului, și o aduci în CRM-ul tău.

## Cine are acces

Cheia API vede cererile pe care contul le vede în portal, în firma cheii, plus cererile lui de pe anunțurile publicate în afara unei firme:

* titularul și administratorii văd cererile întregii firme;
* un manager de punct de lucru vede cererile punctului său (la un dezvoltator imobiliar, ale proiectelor sale);
* un agent vede cererile pe care le are în grijă;
* un cont fără firmă își vede cererile proprii.

Cererile altei firme nu apar, nici pentru cineva care lucrează în amândouă.

Cât timp firma e suspendată, cheia vede doar cererile pe care contul le are în grijă, ca în portal. Cererile colegilor nu mai apar și nici evenimentele despre ele, iar după reactivare revin. Când firma se închide, cheia rămâne doar cu cererile contului din afara firmei.

Pe cheia proprie, `leads:read` îl bifezi singur, în Setări → API. Un integrator îl primește doar dacă Housera i l-a aprobat, iar pagina de acord îi spune contului că integratorul va primi datele de contact ale clienților. Fără acest drept, apelurile la `/leads` primesc `403 missing_ability`.

## Lista cererilor

`GET /leads` întoarce cererile de la cea mai veche la cea mai nouă, cu paginare prin cursor.

```bash theme={null}
curl "https://housera.ro/api/v1/leads?since=2026-11-01T00:00:00Z&per_page=50" \
  -H "Authorization: Bearer $HOUSERA_KEY" \
  -H "Accept: application/json"
```

| Parametru | Ce face |
| - | - |
| `listing` | Doar cererile pentru anunțul cu acest cod. |
| `external_ref` | Doar cererile pentru anunțul cu acest id din sistemul tău. |
| `since` | Doar cererile primite sau schimbate după acest moment (ISO 8601): una nouă, una la care același client a revenit sau una a cărei stare s-a schimbat în portal. |
| `status` | Doar cererile din această etapă: `new`, `contacted`, `viewing`, `offer` sau `closed`. |
| `source` | Doar cererile venite pe acest canal: `message`, `phone`, `viewing_request`, `referral` sau `other`. |
| `agent` | Doar cererile pe care le are colegul cu acest email. Pentru un email care nu e al nimănui primești o listă goală. |
| `office` | Doar cererile punctului de lucru cu acest id, cel din `office.id` al cererii. |
| `complex` | Doar cererile unui proiect de dezvoltator, după codul ansamblului: cele de pe ansamblu și cele de pe unitățile lui. |
| `q` | Caută ca bara de căutare din portal: în nume, în notă, la începutul emailului și în telefon. Telefonul îl poți scrie oricum, de exemplu `0740 111 222` găsește `+40740111222`. Titularul, administratorii și managerii găsesc cererile și după numele colegului care le are. |
| `per_page` | Câte cereri pe pagină, de la 1 la 100 (implicit 50). |
| `cursor` | Cursorul paginii următoare, din `meta.next_cursor`, trimis exact cum l-ai primit. Altă valoare primește `422 pagination_cursor`. |

Un cod sau un id pe care Housera nu îl găsește întoarce o listă goală. Filtrele se pot combina între ele și cu `inbox=true`, iar o valoare care nu e în listă (de exemplu `status=unassigned`) primește `422`.

`since` se compară cu `updated_at`, așa că o cerere deja importată poate apărea din nou. După `id` știi că o ai deja.

## O cerere

`GET /leads/{id}` întoarce o singură cerere. Pentru o cerere la care cheia nu are acces, API-ul răspunde `404`, ca pentru una care nu există.

```json theme={null}
{
  "data": {
    "id": 4182,
    "source": "message",
    "status": "new",
    "listing": { "code": "HEWPUTA8", "external_ref": "CRM-10293" },
    "complex": null,
    "contact": { "name": "Andrei Ionescu", "phone": "+40740123456", "email": "andrei@example.com" },
    "message": "Bună ziua, mai este disponibil apartamentul? Aș putea veni sâmbătă la vizionare.",
    "note": null,
    "price": null,
    "agent": { "name": "Maria Popescu", "email": "maria.popescu@agentia.ro", "display_name": "Maria Popescu" },
    "requests": { "callback_at": null, "viewing_at": "2026-11-02T08:14:03+00:00" },
    "office": { "id": 12, "name": "Tătărași" },
    "inbox": null,
    "created_at": "2026-11-02T08:14:03+00:00",
    "updated_at": "2026-11-02T08:14:03+00:00"
  }
}
```

| Câmp | Ce conține |
| - | - |
| `source` | Cum a venit cererea: `message`, `phone` (cerere de apel), `viewing_request`, `referral` sau `other`. |
| `status` | Unde e cererea în portal: `new`, `contacted`, `viewing`, `offer` sau `closed`. |
| `listing` | Anunțul pentru care a venit cererea, cu codul Housera și id-ul tău (`external_ref`, `null` dacă nu l-ai pus tu). `null` pentru o cerere fără anunț. |
| `complex` | La un dezvoltator, proiectul cererii, cu `code`, `title` și `url`: ansamblul pe care a venit cererea sau cel din care face parte unitatea. `null` în afara unui proiect. |
| `contact` | Numele, telefonul și emailul clientului. |
| `message` | Ce a scris clientul. `null` dacă n-a scris nimic, de exemplu la o cerere de apel. |
| `note` | Nota contului despre cerere, ca în portal. La o cerere trimisă de pe pagina agenției, nota pornește cu textul clientului. |
| `price` | Suma oferită de client, în euro întregi. `null` dacă nu e trecută. |
| `agent` | Cine a preluat cererea în portal: `name` e numele contului, `email` emailul lui, iar `display_name` numele public aprobat, pe care îl arată portalul în dreptul cererii. Cele două nume diferă când colegul are aprobat un alt nume public. `email` e `null` când nu avem o adresă reală a colegului, de exemplu la un coleg venit dintr-un CRM, fără email în fișa lui. `agent` e `null` cât timp cererea așteaptă în căsuța firmei. |
| `requests` | Când a cerut clientul ultima oară să fie sunat (`callback_at`) și să vină la vizionare (`viewing_at`), ca în portal. `null` dacă n-a cerut. `source` spune doar canalul primei cereri, deci un client care ți-a scris și apoi a cerut să-l suni are `source: message` și `callback_at` completat. |
| `office` | Punctul de lucru al cererii, cu `id` și `name`. Sediul are numele „Sediu”, în limba răspunsului. `null` pentru o cerere din afara unei firme. |
| `inbox` | Doar pentru o cerere din „De preluat”: `reason` spune de ce așteaptă, iar `since` când a venit cererea. `null` cât timp cererea are un responsabil. |

Valorile lui `inbox.reason` sunt cele din portal:

* `no_listing`: clientul a scris de pe pagina firmei, deci cererea nu are anunț;
* `left`: agentul care avea cererea sau anunțul a plecat din firmă ori a fost dezactivat, iar munca lui a ajuns în „De preluat”;
* `no_agent`: anunțul nu are un agent activ.

## Lucrul cu cererile

Cu dreptul `leads:write` faci prin API ce faci cu o cerere în portal: îi schimbi etapa, nota și suma, o ștergi, adaugi contacte de mână și preiei cererile care nu au încă responsabil. Regulile sunt aceleași ca în portal, iar schimbarea se vede imediat acolo.

`leads:write` merge doar împreună cu `leads:read`, fiindcă fiecare răspuns conține datele de contact ale clientului. Pe cheia proprie îl bifezi în Setări → API. Un integrator îl primește doar dacă Housera i l-a aprobat, iar contul îl acceptă separat pe pagina de acord. O cheie sau o legătură care are doar `leads:read` nu poate scrie: primește `403 missing_ability`.

### Etapa, nota și datele de contact

`PATCH /leads/{id}` schimbă câmpurile pe care le trimiți și le lasă pe celelalte cum sunt. Un câmp trimis cu `null` se golește, în afară de nume și etapă, care nu pot rămâne goale.

```bash theme={null}
curl -X PATCH "https://housera.ro/api/v1/leads/4182" \
  -H "Authorization: Bearer $HOUSERA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "viewing", "note": "Vine sâmbătă la 11.", "price": 91000}'
```

| Câmp | Ce schimbă |
| - | - |
| `status` | Etapa: `new`, `contacted`, `viewing`, `offer` sau `closed`. Orice etapă poate urma oricăreia. |
| `note` | Nota contului, până la 500 de caractere. |
| `price` | Suma oferită, în euro întregi. |
| `name`, `phone`, `email` | Datele de contact, cu aceleași verificări ca în portal. Telefonul se păstrează în format internațional (`+40…`). |
| `source` | Canalul: `message`, `phone`, `viewing_request`, `referral` sau `other`. |

Răspunsul conține cererea, cu valorile noi. Dacă alt contact al aceluiași client pe același anunț are deja telefonul sau emailul trimis, primești `422`, cu codul `lead_duplicate` pe `/phone`.

`DELETE /leads/{id}` șterge cererea, ca din portal. Răspunsul e `204`, iar cererea dispare din liste și din `GET /leads/{id}`.

O cerere pe care cheia nu o vede, a unui coleg sau a altei firme, primește `404`, ca una care nu există.

### Un contact adăugat de mână

`POST /leads` adaugă un contact, ca butonul „Adaugă” din portal, cu tine ca responsabil. Doar `name` e obligatoriu, iar fără `status` contactul pornește din etapa `new`. Trimite un `Idempotency-Key` diferit pentru fiecare contact nou și același la reîncercarea unei cereri: în 10 minute, aceeași valoare întoarce contactul adăugat prima dată, cu `200` și cu același `meta.possible_duplicate`, fără să adauge altul.

Titularul, administratorii și managerii adaugă contactul în cererile firmei. Dacă vrei să rămână doar în cererile tale, trimiți `"board": "personal"`. La un agent, contactul ajunge mereu în cererile lui, iar `"board": "firm"` primește `403`.

#### Contactul există deja?

Înainte să salveze, portalul verifică dacă ai deja contactul: caută în aceleași cereri (ale firmei sau ale tale) ultimul contact cu același telefon sau cu exact același nume. Prin API faci aceeași verificare, între cererile pe care cheia le vede, cu `POST /leads/duplicate-check`, cu `name`, `phone` și, opțional, `board`, ca la `POST /leads`:

```bash theme={null}
curl -X POST "https://housera.ro/api/v1/leads/duplicate-check" \
  -H "Authorization: Bearer $HOUSERA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Andrei Ionescu", "phone": "0740 123 456"}'
```

Dacă găsește contactul, primești cererea în `data`, ca la `GET /leads/{id}`, iar în `meta.matched_on` ce s-a potrivit: `phone`, `name` sau amândouă. Dacă nu, primești `"data": null` și `meta.matched_on` gol. Verificarea nu schimbă nimic și cere aceleași drepturi ca adăugarea, `leads:read` și `leads:write`.

Doi oameni pot avea același telefon, de exemplu linia unei agenții, așa că verificarea doar te avertizează. Tu decizi dacă adaugi contactul. `POST /leads` îl adaugă oricum și îți spune în `meta.possible_duplicate` contactul pe care îl găsește aceeași verificare, cu `id` și `matched_on`, sau `null`.

### Cererile fără responsabil

Când un anunț al firmei nu are un agent activ, sau clientul scrie de pe pagina agenției, cererea ajunge la „De preluat” și o poate lua oricine din punctul de lucru. Prin API:

* `GET /leads?inbox=true` întoarce cererile din „De preluat” pe care contul le poate prelua. Un agent le vede doar pe cele ale punctului său de lucru, iar titularul și administratorii, pe toate. `GET /leads/{id}` le citește și pe ele.
* `POST /leads/{id}/take` îți dă cererea. Dacă un coleg a preluat-o înainte, primești `409 lead_already_taken`. Dacă ai deja un contact cu același client, fără anunț, cererea se unește cu el, ca în portal: răspunsul e contactul tău, iar `meta.merged_from` e id-ul cererii unite, care nu mai există.
* `POST /leads/{id}/assign`, cu `{"agent_email": "coleg@agentia.ro"}`, dă cererea unui coleg din echipa ta. Merge pentru titular, administratori și manageri, iar colegul primește notificarea din portal. Un email care nu e al unui coleg din echipă primește `422 agent_not_member`, iar un coleg dezactivat, `422 agent_deactivated`.

După ce o cerere e preluată, apare în `GET /leads` al responsabilului, cu `agent` completat.

## Conversația cu clientul

Când clientul îți scrie pe un anunț, cererea are o conversație, aceeași pe care o vezi în Mesaje în portal. Prin API o citești și îi răspunzi clientului, ca din portal.

Conversația o citește și îi răspunde doar contul care se ocupă de anunț, ca în portal. Titularul sau managerul care vede cererea unui agent primește `403 conversation_not_held` la conversația ei. O cerere fără conversație (o cerere de apel, un contact adăugat de mână, un mesaj de pe pagina agenției) întoarce o listă goală.

### Mesajele

`GET /leads/{id}/messages` întoarce mesajele de la cel mai vechi, cu paginare prin cursor. Cu `after` primești doar mesajele de după unul pe care îl ai deja. Citirea prin API nu marchează mesajele ca citite, deci nu le ascunde de cel care lucrează în portal.

```json theme={null}
{
  "data": [
    {
      "id": 90211,
      "direction": "in",
      "author": { "name": "Andrei Ionescu" },
      "body": "Bună ziua, mai este disponibil apartamentul?",
      "kind": null,
      "deleted": false,
      "edited_at": null,
      "read_at": "2026-11-02T08:20:11+00:00",
      "created_at": "2026-11-02T08:14:03+00:00"
    }
  ]
}
```

| Câmp | Ce conține |
| - | - |
| `direction` | `in` pentru un mesaj de la client, `out` pentru unul trimis de partea anunțului, de tine sau de un coleg care s-a ocupat înainte de anunț. |
| `author` | Numele autorului, așa cum îl arată conversația din portal. |
| `body` | Textul. `null` pentru un mesaj șters, care își păstrează locul în conversație. |
| `kind` | `viewing_request` pentru mesajul cu care clientul a cerut o vizionare, altfel `null`. |
| `read_at` | Când a citit mesajul cealaltă parte. `null` cât timp nu l-a citit. |

### Răspunsul

`POST /leads/{id}/messages` trimite un mesaj clientului, ca din portal. Clientul îl vede în conversație și primește aceleași notificări ca la un mesaj scris în portal.

```bash theme={null}
curl -X POST "https://housera.ro/api/v1/leads/4182/messages" \
  -H "Authorization: Bearer $HOUSERA_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2b0e-8a4d-4c3e-9b7a-2d5f1e0c9a11" \
  -d '{"body": "Bună ziua! Da, e disponibil. Vă convine sâmbătă la 11?"}'
```

Răspunsul e `201`, cu mesajul. Trimite un `Idempotency-Key` diferit pentru fiecare mesaj nou și același la reîncercarea unui mesaj: în 10 minute, o cerere cu aceeași valoare întoarce mesajul trimis prima dată, cu `200`, și nu trimite altul. Textul are cel mult 2.000 de caractere.

Răspunsul e refuzat cu `409` când cererea nu are conversație (`no_conversation`), când conversația e doar de citit, fiindcă anunțul sau contul clientului a fost șters (`conversation_closed`), și când una dintre părți a blocat-o pe cealaltă (`conversation_blocked`). Răspunsurile prin API și cele din portal au aceeași limită, 20 pe minut și 200 pe oră pe cont.

### Editarea, ștergerea și citirea

* `PATCH /leads/{id}/messages/{message}`, cu `{"body": "…"}`, schimbă textul unui mesaj scris de tine, în primele 15 minute și cât timp conversația e deschisă. După 15 minute primești `409 edit_window_closed`. Clientul vede mesajul marcat ca editat.
* `DELETE /leads/{id}/messages/{message}` șterge un mesaj scris de tine, oricând. Mesajul rămâne în conversație ca „Mesaj șters”, fără text.
* Un mesaj al clientului nu se editează și nu se șterge: primești `403 message_not_yours`.
* `POST /leads/{id}/messages/read` marchează mesajele clientului ca citite, ca atunci când deschizi conversația în portal. Clientul nu află nimic.

### Evenimentul `lead.message_received`

Când clientul scrie din nou în conversație, contul care se ocupă de anunț primește evenimentul `lead.message_received`, pe webhook și în `GET /events`, dacă are o cheie cu `leads:read`. Evenimentul are `lead_id` și `message_id`, fără textul mesajului, pe care îl citești cu `GET /leads/{id}/messages?after=…`. Primul mesaj al clientului, cel care deschide conversația, vine ca `lead.created` sau, dacă clientul avea deja o cerere pe anunț, ca `lead.updated`, deci nu produce și `lead.message_received`.

> [!WARNING]
>
> Mesajele clienților sunt date personale, ca datele lor de contact. Păstrează-le doar cât îți trebuie pentru a răspunde cererii.

## Evenimentul `lead.created`

Când un client îți trimite prima cerere pe un anunț, primești evenimentul `lead.created`, pe webhook și în `GET /events`. Evenimentul nu conține datele clientului: are `lead_id` și `source`, iar datele le citești cu `GET /leads/{id}`. În `GET /events` îl vezi cu orice cheie care are `leads:read`, chiar dacă nu are `listings:read`.

Evenimentul ajunge doar la conturile care văd cererea în căsuță și au o cheie cu `leads:read`. Din ziua pe care ți-o anunțăm, o cerere care ajunge la „De preluat” produce `lead.created` și pentru cei care o pot prelua, adică pentru toți cei din punctul de lucru pe care îi anunță și portalul, agenții inclusiv. Un webhook îl primește doar dacă are în spate dreptul `leads:read`. Detaliile sunt în [Webhook-uri](https://api.housera.ro/ro/webhooks.md).

## Evenimentele `lead.assigned` și `lead.removed`

Din ziua pe care ți-o anunțăm, afli și ce se întâmplă cu o cerere după ce a venit.

`lead.assigned` vine când cererea are un responsabil nou. `by` spune cum: `take` (cineva a preluat-o din „De preluat”), `assign` (titularul, un administrator sau un manager a dat-o unui coleg) sau `handover` (a trecut la un coleg odată cu munca cuiva care a plecat din firmă sau a fost dezactivat, ori cu un anunț mutat la alt agent). `agent` e noul responsabil, iar `previous_agent` cel de dinainte, fiecare cu `name` și `email`; `previous_agent` e `null` când cererea aștepta în „De preluat” sau când contul colegului de dinainte a fost șters. Îl primesc cei care văd cererea acum.

`lead.removed` vine când cererea nu mai e a ta, cu `reason`:

* `deleted`: cererea a fost ștearsă, din portal sau prin API;
* `merged`: cererea s-a adăugat la cererea aceluiași client pe care o avea deja colegul care a preluat-o;
* `out_of_reach`: cererea nu mai e în raza ta, de exemplu a preluat-o un coleg din „De preluat” sau ai predat-o.

Îl primesc cei care primeau până atunci evenimentele cererii. După `lead.removed`, `GET /leads/{id}` răspunde `404`, dar evenimentul rămâne în `GET /events`. Ștergi cererea din sistemul tău sau o marchezi ca ieșită din raza ta.

Ambele evenimente au `lead_id`, ca `lead.created`, și cer dreptul `leads:read`. Orice webhook care primește `lead.created` le primește și pe ele, fără să le bifezi.

## Evenimentul `lead.updated`

Când același client revine pe același anunț cu o cerere nouă, de exemplu îți cere să-l suni după ce ți-a scris, cererea nouă se adaugă la cea pe care o ai deja. Nu apare o cerere separată, dar primești evenimentul `lead.updated`, cu același `lead_id`. `source` din eveniment spune pe ce canal a venit cererea nouă (aici `phone`), iar `source` din `GET /leads/{id}` rămâne canalul primei cereri. Momentul cererii noi e `created_at` al evenimentului. `updated_at` al cererii devine momentul cererii noi, deci `GET /leads?since=` o aduce din nou. La fel primești `lead.updated` când clientul scrie din nou de pe pagina firmei după ce un coleg i-a preluat cererea, și când o cerere din „De preluat” se unește, la preluare, cu contactul pe care îl ai deja cu același client.

`lead.updated` ajunge la aceleași conturi și webhook-uri ca `lead.created` și cere tot dreptul `leads:read`. Pe un webhook din Setări → API îl bifezi separat.

> [!WARNING]
>
> Datele de contact ale clienților sunt date personale. Păstrează-le doar cât îți trebuie pentru a răspunde cererii și nu le folosi în alt scop.

## Ștergerea datelor clientului

Housera șterge datele unui client în două cazuri, iar tu trebuie să le ștergi și din sistemul tău.

Când clientul își șterge contul, cererile lui rămân în portal, dar fără datele lui: numele devine anonim, iar telefonul, emailul, mesajul și nota se golesc. Cererea primește `erased: true`, iar `updated_at` devine momentul ștergerii, așa că `GET /leads?since=` o aduce din nou. Primești și evenimentul `lead.erased`, cu `reason: account_deleted`, chiar și pentru o cerere pe care ai șters-o deja din portal.

O cerere fără nicio activitate de 24 de luni, sau una ștearsă din portal de mai mult de 30 de zile, se șterge definitiv. Chiar înainte primești `lead.erased`, cu `reason: retention`. După aceea `GET /leads/{id}` răspunde `404`, iar evenimentul rămâne în `GET /events` 35 de zile.

`lead.erased` ajunge la aceleași conturi ca `lead.created` și cere tot dreptul `leads:read`. Orice webhook care primește `lead.created` îl primește și pe el; nu apare în lista de evenimente a webhook-ului și nu se bifează.

> [!WARNING]
>
> Când primești `lead.erased` sau vezi o cerere cu `erased: true`, ștergi din sistemul tău numele, telefonul, emailul, mesajul și nota clientului din cererea respectivă. Ai primit datele de la Housera, deci ștergerea lor la noi te privește și pe tine (GDPR, art. 17 alin. 2 și art. 19).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.