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

# Pentru CRM-uri

> Fluxul recomandat pentru o sincronizare robustă între CRM-ul tău și Housera.

Pagina asta e pentru cei care sincronizează automat un stoc întreg: CRM-uri imobiliare, ERP-uri de agenție, scripturi care publică dintr-o bază de date. Tiparul de mai jos ține Housera la zi cu sistemul tău, rezistă la căderi de rețea și nu suprascrie niciodată munca agenților făcută direct în portal.

## Fluxul recomandat

<Steps>
  <Step title="Publici și actualizezi după id-ul tău">
    Pentru fiecare proprietate din CRM, trimiți `PUT /listings/ref/{id-ul tău}` cu toate câmpurile pe care le știi. Nu e nevoie să ții minte dacă anunțul există deja: prima dată se creează (`201`), apoi se actualizează (`200` sau `202`).

    ```bash theme={null}
    curl -X PUT https://housera.ro/api/v1/listings/ref/CRM-10293 \
      -H "Authorization: Bearer $HOUSERA_KEY" \
      -H "Accept: application/json" \
      -H "Content-Type: application/json" \
      -H 'If-Match: "9b2f6c1e4a7d3b8f0c5e2a1d6f4b7c9e"' \
      -d @CRM-10293.json
    ```

    Păstrează în CRM, lângă fiecare proprietate, `code` și `etag` din ultimul răspuns. La crearea unui anunț nou, trimiți fără `If-Match`.
  </Step>

  <Step title="Trimiți pozele">
    După ce anunțul există, `PUT /listings/{code}/photos` cu lista completă, în ordine. Retrimite lista doar când s-a schimbat ceva la poze în CRM: aceleași URL-uri nu se descarcă din nou, dar fiecare listă trimisă pentru un anunț publicat intră la moderare.
  </Step>

  <Step title="Asculți webhook-urile">
    Configurezi un webhook pentru toate evenimentele. Actualizezi în CRM starea anunțului la `listing.published`, `listing.rejected`, `listing.paused`, `listing.reactivated`, `listing.sold` și `listing.withdrawn`, iar la `listing.edited_in_portal` aplici regula ta de conflict (mai jos).
  </Step>

  <Step title="Recuperezi ce a scăpat">
    La pornirea serviciului și la câteva minute, rulezi `GET /events?after=<ultimul id procesat>`. Un webhook pierdut cât serverul tău a fost căzut nu mai e o schimbare pierdută.
  </Step>

  <Step title="Reconciliezi zilnic">
    O dată pe zi parcurgi `GET /account/listings?fields=code,external_ref,etag,updated_at,status` și compari cu CRM-ul: `etag` diferit înseamnă o schimbare pe Housera, un id care lipsește înseamnă un anunț de retrimis. Detaliile sunt în [Identificatori](/ro/concepts/identifiers#reconcilierea).
  </Step>
</Steps>

## Editările făcute în portal

Anunțurile publicate prin API rămân editabile și pe housera.ro: un agent poate corecta prețul sau descrierea direct din cont. Când se întâmplă asta:

1. Primești webhook-ul `listing.edited_in_portal`, cu noul `etag`.
2. `etag`-ul pe care îl ai în CRM nu mai e cel curent.
3. Următorul tău `PUT` sau `PATCH` cu `If-Match` vechi primește `412 listing_changed_meanwhile` și nu scrie nimic.

Ce faci la `412` e decizia ta. Variantele obișnuite:

<Tabs>
  <Tab title="Portalul câștigă">
    Citești anunțul cu `GET /account/listings/{code}`, actualizezi CRM-ul cu valorile de pe Housera și salvezi noul `etag`. Potrivit când agenții lucrează și direct în portal.
  </Tab>

  <Tab title="CRM-ul câștigă">
    Citești anunțul ca să iei noul `etag` și retrimiți corpul tău cu el în `If-Match`. Potrivit când CRM-ul e singura sursă de adevăr. Fă-o conștient: corectura agentului din portal se pierde.
  </Tab>

  <Tab title="Decide un om">
    Marchezi proprietatea în CRM ca „în conflict” și lași agentul să aleagă. Cea mai sigură variantă pentru câmpurile sensibile, precum prețul.
  </Tab>
</Tabs>

<Warning>
  Fără `If-Match`, `PUT` și `PATCH` se aplică peste orice versiune. Pentru o sincronizare automată, trimite întotdeauna `If-Match` pe anunțurile existente.
</Warning>

## Câmpurile pe care nu le cunoști

`PUT /listings/ref/{ref}` pe un anunț existent se aplică precum un patch: câmpurile pe care nu le trimiți **își păstrează valoarea**. Trimite doar câmpurile pe care CRM-ul tău chiar le gestionează. Dacă un agent completează în portal un câmp pe care CRM-ul nu-l are, sincronizarea ta nu-l șterge.

Ca să golești explicit un câmp, îl trimiți cu `null`.

## Retrimiterile fără schimbări

Un corp identic cu ultima scriere reușită nu scrie nimic și nu ajunge la moderare: primești `200` cu antetul `X-Housera-Unchanged: true`. Poți deci să retrimiți tot stocul la fiecare sincronizare. Ca să profiți de asta, serializează corpul la fel de fiecare dată (aceeași ordine a câmpurilor, același format al numerelor).

## Robustețea

### Reîncercări

| Răspuns | Reîncerci? | Cum |
| - | - | - |
| `409 listing_busy` | Da | După 1–2 secunde, de câteva ori. |
| `429` | Da | După câte secunde spune `Retry-After`. |
| `5xx`, timeout, conexiune întreruptă | Da | Cu pauze tot mai mari (1 s, 2 s, 4 s, 8 s…), plus o mică variație aleatoare, până la un plafon. |
| `412 listing_changed_meanwhile` | Nu orbește | Aplici regula de conflict de mai sus. |
| `409 listing_action_refused` | Nu | Citești `detail`: motivul nu dispare la o nouă încercare. |
| `422` | Nu | Repari datele. Codurile sunt în [Erori](/ro/errors). |
| `401`, `403` | Nu | Verifici cheia și drepturile ei. |
| `404` | Nu | Anunțul nu există, nu e al tău sau acțiunea nu e posibilă din starea lui. |

### Idempotența

* `PUT /listings/ref/{ref}` e sigur de repetat: același id nu creează niciodată două anunțuri.
* Pentru `POST /listings`, trimite mereu `Idempotency-Key` și refolosește aceeași valoare la reîncercările aceleiași operații.
* `PATCH` și `PUT /listings/{code}/photos` duc la același rezultat dacă le repeți. Cu `If-Match`, o repetare după o scriere reușită primește `412`, pentru că versiunea s-a schimbat chiar prin prima scriere: citești anunțul și verifici dacă are deja valorile tale.
* O tranziție (`/pause`, `/resume`, `/sold`) repetată după ce a reușit primește `404`, pentru că anunțul nu mai e în starea de plecare. Tratează asta ca succes dacă starea curentă e cea dorită.

### Ritmul

Limita e de 300 de cereri pe minut pentru fiecare cheie. Urmărește `X-RateLimit-Remaining` și încetinește când se apropie de zero, în loc să aștepți `429`. La această limită, 3.000 de anunțuri se retrimit în aproximativ 10 minute, cu tot cu cele neschimbate. Vezi [Limite](/ro/limits).

### Validarea înainte de lansare

Înainte de prima sincronizare reală, trece tot stocul prin `POST /listings/validate` (în sandbox sau în producție, nu scrie nimic) și curăță atât `errors[]`, cât și `warnings[]`. Codurile de eroare sunt stabile, deci le poți transforma în mesaje clare pentru agenții care completează datele în CRM.

## Lista de verificare

<AccordionGroup>
  <Accordion title="Înainte de lansare">
    * Cheie de sandbox cu `listings:read`, `listings:write`, `media:write`.
    * Tot stocul trece de `POST /listings/validate`, fără `warnings[]`.
    * Webhook configurat, semnătura verificată, cu toleranța de 5 minute.
    * Tratezi `412`, `409`, `429` și `5xx` ca mai sus.
    * `GET /events` rulat periodic, cu ultimul id păstrat.
  </Accordion>

  <Accordion title="La trecerea în producție">
    * Cheie nouă `hsr_live_` din contul de pe housera.ro.
    * Webhook-ul creat din nou în producție, cu secretul lui.
    * Adresa de bază schimbată în `https://housera.ro/api/v1`.
    * O reamintire în calendar înainte de expirarea cheii.
  </Accordion>
</AccordionGroup>


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