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

# Pozele

> Pozele unui anunț: din URL-uri publice sau urcate direct, cu ordinea și etichetele lor.

Pozele unui anunț le setezi cu `PUT /listings/{code}/photos`, care primește **lista completă**, în ordine. Fiecare poză vine fie dintr-un URL public, fie dintr-un fișier pe care l-ai urcat direct pe Housera. Ai nevoie de o cheie cu dreptul `media:write`.

## Lista e adevărul

```json theme={null}
{
  "photos": [
    { "url": "https://cdn.exemplu.ro/anunturi/1029/living.jpg", "rooms": ["living"] },
    { "key": "tmp/uploads/4812/3f6d2a90-5b1e-4c8f-a7d2-91e0b6c4f3a8.jpg", "rooms": ["kitchen"] },
    { "url": "https://cdn.exemplu.ro/anunturi/1029/fatada.jpg", "rooms": ["facade"], "floor": "ground" }
  ]
}
```

* **Ordinea din listă e ordinea de pe anunț.** Prima poză e coperta.
* **O poză care lipsește din listă se șterge** de pe anunț.
* **O poză deja pe anunț rămâne**, dacă o trimiți din nou cu același URL: nu se descarcă a doua oară, doar își ia noua poziție și noile etichete.
* **O listă goală** (`"photos": []`) șterge toate pozele anunțului.
* Un anunț are **cel mult 100 de poze**.

Răspunsul vine imediat:

```http theme={null}
HTTP/1.1 202 Accepted

{ "queued": 3 }
```

Pozele se descarcă și se procesează în fundal. Cât timp lucrul nu s-a terminat, anunțul are `"media": { "processing": true }`. Când redevine `false`, pozele sunt pe anunț, în `images`.

<Tip>
  Vrei să înlocuiești conținutul unei poze? Folosește un URL nou (de exemplu cu un parametru de versiune, `living.jpg?v=2`). Același URL e considerat aceeași poză și nu se mai descarcă.
</Tip>

## Varianta 1: URL-uri publice

Cea mai simplă variantă, dacă pozele tale sunt deja pe un server accesibil din internet. Housera le descarcă singură.

Condiții pentru un URL:

* **HTTPS**, cel mult 2.048 de caractere;
* pe o adresă publică (nu o rețea internă);
* răspunde în cel mult 20 de secunde, cu cel mult două redirecționări;
* fișierul are cel mult 20 MB și e o imagine JPEG, PNG sau WebP.

Un URL care nu poate fi descărcat e sărit, iar celelalte poze se aplică normal. Dacă **niciun** URL dintr-o listă nevidă nu poate fi descărcat (de exemplu, serverul tău de poze e căzut), Housera nu atinge pozele existente: o problemă temporară la tine nu golește niciodată un anunț. Retrimiți lista după ce serverul revine.

## Varianta 2: upload direct

Pentru fișiere care nu au un URL public (stau pe discul serverului tău sau într-un spațiu privat), le urci direct, în trei pași.

<Steps>
  <Step title="Ceri un URL de upload">
    `POST /uploads` cu tipul și mărimea exactă a fișierului, în octeți:

    ```bash theme={null}
    curl https://housera.ro/api/v1/uploads \
      -H "Authorization: Bearer $HOUSERA_KEY" \
      -H "Accept: application/json" \
      -H "Content-Type: application/json" \
      -d '{ "content_type": "image/jpeg", "content_length": 2483117 }'
    ```

    ```json theme={null}
    {
      "method": "PUT",
      "url": "https://.../tmp/uploads/4812/3f6d2a90-5b1e-4c8f-a7d2-91e0b6c4f3a8.jpg?Expires=...&Signature=...",
      "headers": { "Content-Type": "image/jpeg" },
      "key": "tmp/uploads/4812/3f6d2a90-5b1e-4c8f-a7d2-91e0b6c4f3a8.jpg"
    }
    ```

    `content_type` e `image/jpeg`, `image/png` sau `image/webp`. `content_length` e cel mult 20 MB (20.971.520 de octeți).
  </Step>

  <Step title="Urci fișierul direct la URL">
    Un `PUT` cu fișierul în corp, la `url`-ul primit, **fără** antetul `Authorization`. `Content-Type` și `Content-Length` trebuie să fie exact cele declarate la pasul anterior: fac parte din semnătură, iar un fișier cu alt tip sau altă mărime e refuzat.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X PUT "$UPLOAD_URL" \
        -H "Content-Type: image/jpeg" \
        --data-binary @living.jpg
      ```

      ```php PHP theme={null}
      $client = new GuzzleHttp\Client();
      $client->put($upload['url'], [
          'headers' => $upload['headers'] + ['Content-Length' => filesize('living.jpg')],
          'body' => fopen('living.jpg', 'r'),
      ]);
      ```

      ```javascript JavaScript theme={null}
      import { readFile } from 'node:fs/promises';

      const file = await readFile('living.jpg');
      await fetch(upload.url, {
        method: upload.method,
        headers: upload.headers, // Content-Length se calculează din corp
        body: file,
      });
      ```

      ```python Python theme={null}
      with open("living.jpg", "rb") as f:
          data = f.read()
      requests.put(upload["url"], headers=upload["headers"], data=data)
      ```
    </CodeGroup>

    URL-ul e valabil **15 minute**. Un răspuns `200` de la acest URL înseamnă că fișierul a ajuns.
  </Step>

  <Step title="Pui cheia în lista de poze">
    Trimiți `key` primit la pasul 1 în `PUT /listings/{code}/photos`, în locul unui URL:

    ```json theme={null}
    { "photos": [ { "key": "tmp/uploads/4812/3f6d2a90-5b1e-4c8f-a7d2-91e0b6c4f3a8.jpg", "rooms": ["living"] } ] }
    ```

    O cheie nefolosită se șterge după aproximativ 24 de ore, așa că o folosești în aceeași zi. O cheie poate fi folosită doar de contul care a cerut-o.
  </Step>
</Steps>

Poți amesteca liber cele două variante în aceeași listă.

<Note>
  La crearea anunțului poți trimite direct cheile urcate, în `photo_keys` (o listă de chei, în ordine) și, opțional, `photo_meta` (o listă de etichete `{floor, rooms}`, aliniată cu `photo_keys`). Pentru orice schimbare ulterioară folosești `PUT /listings/{code}/photos`.
</Note>

## Etichetele

Fiecare poză poate avea două etichete opționale, afișate pe anunț și folosite la gruparea pozelor:

<ParamField body="rooms" type="string[]">
  Încăperile din poză, cel mult 3 coduri: `living`, `kitchen`, `bedroom`, `bathroom`, `balcony`, `yard`, `facade`, `view` și altele. Lista completă e în `photo_rooms` din [vocabular](/ro/vocabulary).
</ParamField>

<ParamField body="floor" type="string">
  Nivelul pe care e făcută poza, ca cod de nivel: `ground`, `floor_1`, `basement`, `attic`… (vezi `levels` din vocabular).
</ParamField>

Un cod necunoscut primește `422`, cu `pointer` pe poza respectivă (de exemplu `/photos/2/rooms/0`).

## Ce se întâmplă cu o poză

Înainte să apară pe anunț, fiecare poză e verificată și pregătită:

* **conținutul** trebuie să fie chiar o imagine JPEG, PNG sau WebP, de cel mult 100 de megapixeli;
* **metadatele de localizare** (coordonatele GPS din EXIF) sunt șterse, ca locația exactă a fotografului să nu ajungă publică;
* **antivirusul** verifică fiecare fișier;
* **duplicatele** (aceeași imagine de două ori în listă) se atașează o singură dată.

O poză refuzată la verificare nu ajunge pe anunț și produce evenimentul `media.failed`, cu numărul de poze și motivul:

| `reason` | Motiv |
| - | - |
| `location` | Metadatele de localizare nu au putut fi șterse, deci poza nu se publică. |
| `infected` | Antivirusul a găsit o problemă în fișier. |
| `unprocessable` | Fișierul nu a putut fi procesat ca imagine. |
| `failed` | Procesarea a eșuat de partea Housera; se reîncearcă automat. |

## Pozele pe un anunț publicat

Pe un anunț `active`, o listă nouă de poze nu se aplică imediat: se alătură editării care așteaptă moderarea (`moderation.edit_pending: true`), exact ca o modificare de text. Pe un anunț încă nepublicat (`pending`), pozele se aplică direct. Pe un anunț respins (`rejected`), o listă nouă de poze se aplică și retrimite anunțul la moderare, ca orice corectură.


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