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

# Conectează cu Housera

> Cum publică un CRM pentru agențiile lui fără ca vreo agenție să-i lipească o cheie.

Dacă publici pentru mai multe agenții, nu le cere să-și facă o cheie și să ți-o trimită. Integrezi butonul **Conectează cu Housera**: agentul se autentifică pe housera.ro, vede ce cere aplicația ta și acceptă, iar tu primești cheia contului lui direct, server la server. Agenția îți vede legătura în cont și o poate deconecta oricând.

Fluxul e OAuth 2.0 cu cod de autorizare și PKCE ([RFC 6749](https://www.rfc-editor.org/rfc/rfc6749), [RFC 7636](https://www.rfc-editor.org/rfc/rfc7636)), deci orice bibliotecă OAuth îl poate face. Pașii de mai jos îl arată fără bibliotecă.

## Înainte de toate: aprobarea

Accesul de integrator îl dă Housera. Scrie-ne la **[office@housera.ro](mailto:office@housera.ro)** cu:

* numele aplicației, așa cum vrei să-l vadă agențiile;
* site-ul;
* **adresele de întoarcere** (`redirect_uri`), exact cum le vei trimite. Le comparăm caracter cu caracter, fără wildcard. Acceptăm doar `https`, plus `http://localhost` pentru dezvoltare;
* drepturile de care ai nevoie (`listings:read`, `listings:write`, `media:write`);
* opțional, o adresă `https` de webhook pentru evenimentele conturilor conectate.

Primești `client_id` (`hsrc_…`), `client_secret` (`hsrs_…`) și, dacă ai cerut webhook, secretul lui (`whsec_…`). Le primești o singură dată; păstrează-le doar pe server. Pentru sandbox primești date separate, pe `staging.housera.ro`.

<Warning>
  `client_secret` nu ajunge niciodată în browser, într-o aplicație mobilă sau într-un depozit de cod. Oricine îl are poate schimba coduri în numele tău.
</Warning>

## Fluxul

<Steps>
  <Step title="Pregătești cererea">
    Pentru fiecare conectare generezi două valori aleatoare și le păstrezi în sesiunea agentului, pe serverul tău:

    * `state`: protejează împotriva cererilor falsificate; îl verifici la întoarcere;
    * `code_verifier`: 43–128 de caractere din `A-Z a-z 0-9 - . _ ~`. Din el calculezi `code_challenge = BASE64URL(SHA256(code_verifier))`, fără `=` la final.

    <CodeGroup>
      ```php PHP theme={null}
      $verifier = rtrim(strtr(base64_encode(random_bytes(48)), '+/', '-_'), '=');
      $challenge = rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '=');
      $state = bin2hex(random_bytes(16));
      ```

      ```javascript Node.js theme={null}
      import { createHash, randomBytes } from 'node:crypto';

      const verifier = randomBytes(48).toString('base64url');
      const challenge = createHash('sha256').update(verifier).digest('base64url');
      const state = randomBytes(16).toString('hex');
      ```

      ```python Python theme={null}
      import base64, hashlib, secrets

      verifier = secrets.token_urlsafe(48)
      challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b'=').decode()
      state = secrets.token_hex(16)
      ```
    </CodeGroup>
  </Step>

  <Step title="Trimiți agentul pe pagina de acord">
    Redirecționezi browserul agentului la:

    ```text theme={null}
    https://housera.ro/setari/api/conectare
      ?response_type=code
      &client_id=hsrc_…
      &redirect_uri=https%3A%2F%2Fcrm.exemplu.ro%2Fhousera%2Fcallback
      &scope=listings%3Aread%20listings%3Awrite%20media%3Awrite
      &state=…
      &code_challenge=…
      &code_challenge_method=S256
    ```

    În sandbox, adresa e `https://staging.housera.ro/setari/api/conectare`. Staging-ul nu e deschis publicului: pentru testele tale îți dăm, la cerere, un cont de test cu acces la el. Varianta în engleză, `/en/settings/api/connect`, e echivalentă.

    `scope` e opțional: fără el ceri toate drepturile pe care Housera ți le-a aprobat. Nu poți cere mai mult de atât, iar catalogul platformei nu se acordă niciodată prin conectare.
  </Step>

  <Step title="Agentul acceptă sau refuză">
    Dacă agentul nu e autentificat, housera.ro îi cere întâi să intre în cont. Apoi vede numele aplicației tale, contul sau agenția pentru care acordă accesul, drepturile și domeniul la care se întoarce.

    Browserul revine la adresa ta de întoarcere:

    * la acord: `https://crm.exemplu.ro/housera/callback?code=…&state=…`;
    * la refuz: `…?error=access_denied&state=…`.

    Verifici că `state` e cel din sesiune. Dacă nu e, oprești fluxul.
  </Step>

  <Step title="Schimbi codul pe cheie, de pe server">
    Codul expiră în **60 de secunde** și merge o singură dată. Îl trimiți imediat la `POST /oauth/token`:

    ```bash theme={null}
    curl https://housera.ro/api/v1/oauth/token \
      -d grant_type=authorization_code \
      -d code="$CODE" \
      -d redirect_uri=https://crm.exemplu.ro/housera/callback \
      -d code_verifier="$VERIFIER" \
      -d client_id="$HOUSERA_CLIENT_ID" \
      -d client_secret="$HOUSERA_CLIENT_SECRET"
    ```

    ```json theme={null}
    {
      "access_token": "1234|hsr_live_…",
      "token_type": "Bearer",
      "expires_in": 31536000,
      "scope": "listings:read listings:write media:write",
      "connection_id": 1234
    }
    ```

    Poți trimite `client_id` și `client_secret` și prin HTTP Basic, în loc de corp.
  </Step>

  <Step title="Lucrezi cu cheia, ca orice cheie">
    Păstrezi `access_token` și `connection_id` lângă agenția din CRM-ul tău și folosești cheia exact ca în restul documentației: `Authorization: Bearer …`. Aceleași reguli, aceleași limite, aceeași moderare.
  </Step>
</Steps>

## Ce se întâmplă cu anunțurile

* Anunțurile pe care le publici sunt **ale agenției**. Agenții le pot edita și din portal; atunci primești `listing.edited_in_portal` și noul `etag`.
* Id-urile tale (`PUT /listings/ref/{ref}`) sunt doar ale tale: două CRM-uri conectate la aceeași agenție nu se încurcă, chiar dacă folosesc aceleași id-uri.
* În jurnalul cererilor, agenția vede fiecare cerere făcută prin legătura ta, cu numele aplicației.

## Evenimentele

Dacă ai declarat un webhook la aprobare, primești pe el evenimentele **tuturor** conturilor conectate, semnate cu secretul webhook-ului (vezi [Webhook-uri](/ro/webhooks)). Fiecare livrare are în plus câmpul `connection`, egal cu `connection_id` din răspunsul de la schimbul de cod:

```json theme={null}
{
  "id": "01JZ8K3V7Q2W4E6R8T0Y1U3I5O",
  "type": "listing.published",
  "created_at": "2026-10-19T09:41:00+00:00",
  "data": { "code": "HEWPUTA8" },
  "connection": 1234
}
```

Fără webhook, recuperezi evenimentele fiecărui cont cu `GET /events?after=…`, folosind cheia lui.

## Expirarea și deconectarea

* Cheia expiră după **365 de zile** (`expires_in`). Înainte de termen, treci agentul din nou prin pagina de acord: noua cheie o înlocuiește pe cea veche.
* O nouă conectare a aceluiași cont îți dă o cheie nouă și o invalidează pe cea veche.
* Agenția te poate deconecta din **Setări → API → Aplicații conectate**. Din clipa aceea, cheia primește `401 unauthenticated`, iar webhook-ul nu mai primește evenimentele contului. Tratezi `401` ca pe o deconectare și îi propui agentului să se reconecteze.
* Dacă Housera îți dezactivează accesul, toate cheile tale primesc `401` până la reactivare.

## Erori

Pe pagina de acord, cât timp aplicația sau adresa de întoarcere nu sunt confirmate, eroarea rămâne pe housera.ro și nu ajunge la tine:

| Situația | Ce vede agentul |
| - | - |
| `client_id` necunoscut sau aplicație dezactivată | „Aplicația nu e aprobată de Housera sau a fost dezactivată.” |
| `redirect_uri` nedeclarat | „Adresa la care aplicația vrea să te întoarcă nu e declarată la Housera.” |

Odată confirmate, erorile revin la adresa ta de întoarcere, cu `error` și `state`:

| `error` | Cauza |
| - | - |
| `access_denied` | Agentul a refuzat. |
| `invalid_request` | Lipsește `state`, `code_challenge` nu e un SHA-256 în base64url sau `code_challenge_method` nu e `S256`. |
| `invalid_scope` | Ai cerut un drept pe care nu ți l-am aprobat. |
| `unsupported_response_type` | `response_type` nu e `code`. |

La `POST /oauth/token`, erorile au forma RFC 6749, `{"error": "…", "error_description": "…"}`:

| Status | `error` | Cauza |
| - | - | - |
| `401` | `invalid_client` | `client_id` sau `client_secret` greșite, ori aplicație dezactivată. |
| `400` | `invalid_grant` | Cod necunoscut, expirat sau deja folosit, alt `redirect_uri` decât în cerere, `code_verifier` greșit. Un cod folosit a doua oară revocă și cheia pe care a dat-o. |
| `400` | `unsupported_grant_type` | `grant_type` nu e `authorization_code`. |


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