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

# First listing in 5 minutes

> From sandbox key to a listing with pictures, sent for moderation.

In this guide, you publish an apartment in the sandbox: you check your data, create the listing, read it back, and add pictures. You only need a Housera account with a confirmed email.

<Steps>
  <Step title="Create a sandbox key">
    Enter the sandbox environment at **Settings → API** (`https://staging.housera.ro/setari/api`), and create a key with rights `listings:read`, `listings:write`, and `media:write`.

    The key looks like this: `12|hsr_test_…`. You see it **only once**, right after creation, so copy it immediately to a safe location. The details are in [Authentication](/en/authentication).

    In the examples below, the key is stored in the environment variable `HOUSERA_KEY`:

    ```bash theme={null}
    export HOUSERA_KEY="12|hsr_test_..."
    ```
  </Step>

  <Step title="Validate the listing">
    `POST /listings/validate` applies exactly the publishing rules but does not write anything. It’s the fastest way to check your field mapping.

    <CodeGroup>
      ```bash curl theme={null}
      curl https://staging.housera.ro/api/v1/listings/validate \
        -H "Authorization: Bearer $HOUSERA_KEY" \
        -H "Accept: application/json" \
        -H "Content-Type: application/json" \
        -d @apartament.json
      ```

      ```php PHP theme={null}
      <?php
      // composer require guzzlehttp/guzzle
      $client = new GuzzleHttp\Client([
          'base_uri' => 'https://staging.housera.ro/api/v1/',
          'headers' => [
              'Authorization' => 'Bearer '.getenv('HOUSERA_KEY'),
              'Accept' => 'application/json',
          ],
          'http_errors' => false,
      ]);

      $listing = json_decode(file_get_contents('apartament.json'), true);
      $response = $client->post('listings/validate', ['json' => $listing]);

      echo $response->getStatusCode(), PHP_EOL, $response->getBody();
      ```

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

      const BASE = 'https://staging.housera.ro/api/v1';
      const headers = {
        Authorization: `Bearer ${process.env.HOUSERA_KEY}`,
        Accept: 'application/json',
        'Content-Type': 'application/json',
      };

      const listing = JSON.parse(await readFile('apartament.json', 'utf8'));
      const res = await fetch(`${BASE}/listings/validate`, {
        method: 'POST',
        headers,
        body: JSON.stringify(listing),
      });

      console.log(res.status, await res.json());
      ```

      ```python Python theme={null}
      import json, os, requests

      BASE = "https://staging.housera.ro/api/v1"
      session = requests.Session()
      session.headers.update({
          "Authorization": f"Bearer {os.environ['HOUSERA_KEY']}",
          "Accept": "application/json",
      })

      with open("apartament.json", encoding="utf-8") as f:
          listing = json.load(f)

      res = session.post(f"{BASE}/listings/validate", json=listing)
      print(res.status_code, res.json())
      ```
    </CodeGroup>

    The content of file `apartament.json`:

    ```json apartament.json theme={null}
    {
      "type": "apartment",
      "subtype": "standard",
      "transaction": "sale",
      "title": "Apartament 3 camere decomandat, Copou, Iași",
      "description": "Apartament luminos cu trei camere decomandate, renovat în 2022, la cinci minute de Parcul Copou. Bucătărie închisă, centrală proprie pe gaz, ferestre noi și boxă la subsol.",
      "city": "Iași",
      "county": "Iași",
      "area": "Copou",
      "street": "Strada Vasile Lupu",
      "street_number": "12",
      "latitude": 47.1752,
      "longitude": 27.5701,
      "price": 98000,
      "price_currency": "EUR",
      "negotiable": true,
      "rooms": 3,
      "bedrooms": 2,
      "bathrooms": 1,
      "floor": "floor_2",
      "total_floors": 4,
      "usable_area_sqm": 65.5,
      "built_area_sqm": 72,
      "partitioning": "detached",
      "year_built": 1985,
      "condition": "renovated",
      "heating_types": ["gas"],
      "energy_certificate": "b",
      "features": ["ac", "elevator", "intercom"]
    }
    ```

    If everything is fine, you receive `200` with `{"valid": true}`. Otherwise, you receive `422`, with each issue in its field:

    ```json theme={null}
    {
      "type": "https://housera.ro/problems/validation",
      "title": "Datele trimise nu sunt valide.",
      "status": 422,
      "errors": [
        { "pointer": "/street", "code": "required", "detail": "Completează strada." }
      ]
    }
    ```

    <Tip>
      The contact phone for the listing is, by default, the one in your account. If you do not have a phone in your account, send field `contact_phone`. The name and contact email are always those of the account.
    </Tip>
  </Step>

  <Step title="Create the listing">
    The same body, at `POST /listings`. Add header `Idempotency-Key` with a unique value for this operation (for example, a UUID): if the request gets lost along the way and you repeat it with the same key, you receive the same listing, not two.

    <CodeGroup>
      ```bash curl theme={null}
      curl https://staging.housera.ro/api/v1/listings \
        -H "Authorization: Bearer $HOUSERA_KEY" \
        -H "Accept: application/json" \
        -H "Content-Type: application/json" \
        -H "Idempotency-Key: 6f1c2b0e-8a4d-4c3e-9b7a-2d5f1e0c9a11" \
        -d @apartament.json
      ```

      ```php PHP theme={null}
      $response = $client->post('listings', [
          'json' => $listing,
          'headers' => ['Idempotency-Key' => '6f1c2b0e-8a4d-4c3e-9b7a-2d5f1e0c9a11'],
      ]);
      $created = json_decode((string) $response->getBody(), true);
      $code = $created['data']['code'];
      ```

      ```javascript JavaScript theme={null}
      const created = await fetch(`${BASE}/listings`, {
        method: 'POST',
        headers: { ...headers, 'Idempotency-Key': crypto.randomUUID() },
        body: JSON.stringify(listing),
      }).then((r) => r.json());

      const code = created.data.code;
      ```

      ```python Python theme={null}
      import uuid

      res = session.post(
          f"{BASE}/listings",
          json=listing,
          headers={"Idempotency-Key": str(uuid.uuid4())},
      )
      code = res.json()["data"]["code"]
      ```
    </CodeGroup>

    The response is `201 Created`. The listing is in state `pending`: waiting for moderation.

    ```json theme={null}
    {
      "data": {
        "code": "HEWPUTA8",
        "url": "https://housera.ro/anunt/hewputa8/iasi/apartament-de-vanzare-3-camere-65-mp-copou",
        "type": "apartment",
        "subtype": "standard",
        "transaction": "sale",
        "title": "Apartament 3 camere decomandat, Copou, Iași",
        "price": 98000,
        "currency": "EUR",
        "price_period": "total",
        "price_declared": { "amount": 98000, "currency": "EUR" },
        "external_ref": null,
        "status": "pending",
        "moderation": { "edit_pending": false, "rejection_reason": null, "rejection_codes": [] },
        "media": { "processing": false },
        "etag": "\"9b2f6c1e4a7d3b8f0c5e2a1d6f4b7c9e\""
      },
      "warnings": []
    }
    ```

    We shortened the object: the full response also includes the location, `facts`, `features`, `images`, and publication data. Keep `code`: it is the identifier of the listing on Housera.
  </Step>

  <Step title="Read the listing">
    `GET /account/listings/{code}` shows you the listing with its current state, whatever state it may be in.

    <CodeGroup>
      ```bash curl theme={null}
      curl https://staging.housera.ro/api/v1/account/listings/HEWPUTA8 \
        -H "Authorization: Bearer $HOUSERA_KEY" \
        -H "Accept: application/json"
      ```

      ```php PHP theme={null}
      $response = $client->get("account/listings/{$code}");
      $etag = $response->getHeaderLine('ETag');
      ```

      ```javascript JavaScript theme={null}
      const res = await fetch(`${BASE}/account/listings/${code}`, { headers });
      const etag = res.headers.get('ETag');
      const { data } = await res.json();
      ```

      ```python Python theme={null}
      res = session.get(f"{BASE}/account/listings/{code}")
      etag = res.headers["ETag"]
      print(res.json()["data"]["status"])
      ```
    </CodeGroup>

    Header `ETag` (and field `etag`) is the version of the listing. You send it back in `If-Match` when you edit, so you don’t overwrite a change made in the meantime. See [Editing](/en/concepts/editing).
  </Step>

  <Step title="Add pictures">
    `PUT /listings/{code}/photos` receives the complete list of pictures, in order. The first is the cover. The easiest way is to provide public HTTPS URLs, which Housera will download.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X PUT https://staging.housera.ro/api/v1/listings/HEWPUTA8/photos \
        -H "Authorization: Bearer $HOUSERA_KEY" \
        -H "Accept: application/json" \
        -H "Content-Type: application/json" \
        -d '{
          "photos": [
            { "url": "https://cdn.exemplu.ro/anunturi/1029/living.jpg", "rooms": ["living"] },
            { "url": "https://cdn.exemplu.ro/anunturi/1029/bucatarie.jpg", "rooms": ["kitchen"] },
            { "url": "https://cdn.exemplu.ro/anunturi/1029/dormitor.jpg", "rooms": ["bedroom"] }
          ]
        }'
      ```

      ```php PHP theme={null}
      $response = $client->put("listings/{$code}/photos", ['json' => ['photos' => [
          ['url' => 'https://cdn.exemplu.ro/anunturi/1029/living.jpg', 'rooms' => ['living']],
          ['url' => 'https://cdn.exemplu.ro/anunturi/1029/bucatarie.jpg', 'rooms' => ['kitchen']],
      ]]]);
      ```

      ```javascript JavaScript theme={null}
      await fetch(`${BASE}/listings/${code}/photos`, {
        method: 'PUT',
        headers,
        body: JSON.stringify({
          photos: [
            { url: 'https://cdn.exemplu.ro/anunturi/1029/living.jpg', rooms: ['living'] },
            { url: 'https://cdn.exemplu.ro/anunturi/1029/bucatarie.jpg', rooms: ['kitchen'] },
          ],
        }),
      });
      ```

      ```python Python theme={null}
      session.put(f"{BASE}/listings/{code}/photos", json={"photos": [
          {"url": "https://cdn.exemplu.ro/anunturi/1029/living.jpg", "rooms": ["living"]},
          {"url": "https://cdn.exemplu.ro/anunturi/1029/bucatarie.jpg", "rooms": ["kitchen"]},
      ]})
      ```
    </CodeGroup>

    The response comes immediately, `202 Accepted`, with `{"queued": 3}`. The pictures are downloaded and processed in the background. When `media.processing` from `GET /account/listings/{code}` becomes `false` again, the pictures are on the listing. For files from your system, without a public URL, see [Pictures](/en/guides/photos).
  </Step>
</Steps>

## What’s next

The listing remains `pending` until a moderator approves it. Then it becomes `active` and appears in search. To find out without always asking, set up a webhook for `listing.published` and `listing.rejected`.

<CardGroup cols={2}>
  <Card title="Moderation" icon="shield-check" href="/en/concepts/moderation">
    The states of the listing and transitions.
  </Card>

  <Card title="Webhooks" icon="bell" href="/en/webhooks">
    You are notified immediately when a listing is published or rejected.
  </Card>
</CardGroup>


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