# Create and update listings

> Upload photos, then create, read, update and delete your agency's listings through the property API.

These endpoints manage your agency's own listings. Writes need the `properties:write` scope, reads `properties:read`, and photo uploads `media:write`. Every write is scoped to the key's organization. Send the key as `Authorization: Bearer <key>`, as in every example here. `X-API-Key: <key>` is still accepted on these routes, and so is `Authorization: ApiKey <key>`.

## Upload photos

### `POST /properties/media/upload`

Uploads one image (multipart field `file`, at most 20 MB). Every photo, thumbnail and floor plan on a listing must be an image your organization owns, so upload first and use the returned URLs.

```bash
curl -X POST https://api.fondaro.com/properties/media/upload \
  -H "Authorization: Bearer fondaro_pk_abc123" \
  -F "file=@living-room.jpg"
```

```json
{
  "success": true,
  "images": {
    "fullSize": "https://…/properties/images/3f2a.jpg",
    "thumbnail": "https://…/properties/thumbnails/3f2a.webp"
  }
}
```

Put `fullSize` in `images[].url` and `thumbnail` in `thumbnails[].url`. Photos uploaded in the dashboard, a URL the listing already has in the same field, and a thumbnail that repeats an `images` URL in the same request are accepted too; any other URL is a `400` naming the field.

Fondaro records each upload's pixel size and returns it as `images[].width` and `height`; you never need to send them. An upload that no listing uses within 7 days may be removed, so upload right before you create or update.

## Create a listing

### `POST /properties`

Creates a listing as `pending` with a generated reference (`FDR-` and eight letters and digits). See [Own-listing lifecycle](https://www.fondaro.com/docs/api/properties/own-listings.md) for statuses.

```bash
curl -X POST https://api.fondaro.com/properties \
  -H "Authorization: Bearer fondaro_pk_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "listingType": "sale",
    "propertyCategory": "residential",
    "propertyType": "house_detached",
    "city": "Marbella",
    "countryCode": "ES",
    "currency": "EUR",
    "price": 1250000,
    "bedrooms": 4,
    "bathrooms": 3,
    "livingArea": 320,
    "plotArea": 800,
    "areaUnit": "sqm",
    "features": ["pool", "sea_view"],
    "images": [{ "url": "https://…/properties/images/3f2a.jpg", "order": 0, "isFeatured": true }]
  }'
```

```json
{
  "id": "29a1e184-237a-40c8-a1fe-624b0bcbb2f4",
  "referenceNumber": "FDR-7KQ2MX9A",
  "status": "pending",
  "listingType": "sale",
  "propertyType": "house_detached",
  "city": "Marbella",
  "price": 1250000,
  "currency": "EUR",
  "features": ["pool", "sea_view"],
  "hasPool": true,
  "hasSeaView": true,
  "createdAt": "2026-10-02T10:00:00.000Z"
}
```

## Listing fields

**Required on create:**

| Field | Values |
|---|---|
| `listingType` | `sale`, `rent`, `sale_or_rent`, `fraction` |
| `propertyCategory` | `residential`, `commercial`, `industrial`, `land` |
| `propertyType` | A type that belongs to the category (below) |
| `city` | Up to 100 characters |
| `countryCode` | ISO 3166-1 alpha-2, for example `ES` |
| `currency` | ISO 4217, for example `EUR` |

**Optional:**

| Field | Type | Notes |
|---|---|---|
| `title`, `translatedTitles` | string, object | Headline up to 140 characters; translations as `{ "es": "..." }` |
| `description`, `translatedDescriptions` | string, object | |
| `addressLine1`, `addressLine2`, `postalCode` | string | |
| `community`, `region`, `provinceState` | string | |
| `latitude`, `longitude` | number | Must lie inside `countryCode`. Without a point, Fondaro looks up `addressLine1` and fills it in on an exact match |
| `price` | number | |
| `rentalPrice`, `rentalPriceLowSeason`, `rentalPriceHighSeason`, `rentalPeriod` | number, string | Rent for `rent` and `sale_or_rent`; period `day`, `week`, `month`, `year` |
| `availableFrom`, `completionDate` | date | `YYYY-MM-DD` |
| `bedrooms`, `parkingSpaces` | integer | |
| `bathrooms` | number | Halves allowed, up to 99.9 |
| `livingArea`, `plotArea`, `areaUnit` | number, string | Unit `sqm` or `sqft` |
| `yearBuilt` | integer | 1801 to five years ahead |
| `isNewConstruction`, `isExterior` | boolean | |
| `condition` | string | `new`, `excellent`, `good`, `needs_renovation`, `under_construction` |
| `floor`, `totalFloors` | integer | Basements are negative |
| `orientation` | string | `N`, `NE`, `E`, `SE`, `S`, `SW`, `W`, `NW` |
| `parkingType` | string | `garage`, `underground`, `covered`, `open`, `street`, `communal` |
| `energyRating` | string | `A+`, `A` to `G`, `exempt`, `in_progress` |
| `energyConsumptionKwh`, `co2Emissions`, `energyRatingDetails` | number, string | |
| `communityFeesPerYear`, `ibiPerYear`, `basuraPerYear` | number | Yearly costs in `currency` |
| `features`, `furnished` | string[], string | See [Amenities and furnishing](#amenities-and-furnishing) |
| `images` | array | `[{ url, alt?, order, isFeatured? }]` |
| `thumbnails` | array | `[{ url, alt?, order, originalImageIndex }]` |
| `floorPlans` | array | `[{ url, alt?, order }]` |
| `videoUrls`, `virtualTourUrls` | string[] | Up to 20 http(s) links each |
| `sharedCommission`, `sharedCommissionNotes` | number, string | What you offer a collaborating agency (0 to 100 %) and its terms; every agency on the network sees them |
| `commission` | number | Your own commission %, private to your agency |
| `viewingInstructions` | string | Keys and access notes, private to your agency |
| `agentId` | string | The assigned agent from your roster |
| `additionalFeatures` | object | Your own key-value facts |
| `autoRenewEnabled` | boolean | Renew automatically at `expiresAt` (default `false`) |

`propertyType` must belong to `propertyCategory`; `other` fits every category.

| Category | Types |
|---|---|
| `residential` | `apartment`, `condominium`, `house_detached`, `house_semi_detached`, `house_terraced`, `townhouse`, `multi_family_home`, `penthouse`, `studio` |
| `commercial` | `office`, `retail`, `commercial_other`, `hospitality` |
| `industrial` | `industrial_warehouse`, `industrial_other` |
| `land` | `land_residential`, `land_commercial`, `land_agricultural`, `land_other` |

### Amenities and furnishing

`features` lists amenities as slugs: `pool`, `sea_view`, `garage`, `terrace`, `garden`, `air_conditioning`, `lift`, `beachfront`, `frontline_golf`, `panoramic_views`, `gated_complex`, `storage_room`, `built_in_wardrobes`, `luxury`. Furnishing is its own field, `furnished` (`no`, `partly`, `fully`).

The older booleans (`hasPool`, `hasSeaView`, `hasGarage`, `hasTerrace`, `hasGarden`, `hasAirConditioning`, `hasLift`, `isFurnished`) still work:

- With `features`, the list is stored and the booleans follow it; booleans sent beside it are ignored.
- With booleans only, each adds or removes its own amenity and leaves every other slug as it was.
- `furnished` wins over `isFurnished`. Sent alone, `isFurnished: true` stores `fully` and `false` clears `furnished`.

Responses always carry both `features` and the booleans.

## Read a listing

### `GET /properties/:id`

Returns your own listing in any status, or another agency's active listing. Anything else is a `404`.

| Query parameter | Meaning |
|---|---|
| `includeBranding` | Add the agency's branding and the agent card |
| `includeAdCount`, `includeAdCreatives` | Add the ad creative count or the creatives |

```bash
curl "https://api.fondaro.com/properties/29a1e184-237a-40c8-a1fe-624b0bcbb2f4?includeBranding=true" \
  -H "Authorization: Bearer fondaro_pk_abc123"
```

```json
{
  "id": "29a1e184-237a-40c8-a1fe-624b0bcbb2f4",
  "referenceNumber": "FDR-7KQ2MX9A",
  "status": "active",
  "city": "Marbella",
  "price": 1250000,
  "currency": "EUR",
  "sharedCommission": 2.5,
  "sharedCommissionNotes": "Paid on completion.",
  "agent": { "firstName": "Ana", "lastName": "Ruiz", "title": "Sales agent" },
  "organizationBranding": { "name": "Costa Homes" }
}
```

Another agency's listing omits only what is private to it: `commission`, `viewingInstructions` and import-only `additionalFeatures` keys. It keeps the street address, postcode, exact coordinates and the shared-commission offer. The agent's email and phone numbers appear only when that agent shares their contact with the network (the default).

## Update a listing

### `PUT /properties/:id`

Send only the fields to change; any field you leave out keeps its value. Every create field is accepted, plus `status` (`sold`, `rented` or `deleted`; see [Own-listing lifecycle](https://www.fondaro.com/docs/api/properties/own-listings.md#change-status)).

```bash
curl -X PUT https://api.fondaro.com/properties/29a1e184-237a-40c8-a1fe-624b0bcbb2f4 \
  -H "Authorization: Bearer fondaro_pk_abc123" \
  -H "Content-Type: application/json" \
  -d '{"price": 1150000, "commission": 4.5}'
```

Returns the updated listing. The required create fields and the lists `features`, `floorPlans`, `videoUrls` and `virtualTourUrls` cannot be cleared with `null`; send `[]` to empty a list. A changed type is checked against the category and a changed point against the country. `referenceNumber` never changes.

## Delete a listing

### `DELETE /properties/:id`

Sets the listing's status to `deleted`. It leaves search and portal feeds but its history is kept.

```bash
curl -X DELETE https://api.fondaro.com/properties/29a1e184-237a-40c8-a1fe-624b0bcbb2f4 \
  -H "Authorization: Bearer fondaro_pk_abc123"
```

```json
{ "success": true }
```

## Errors

| Status | When |
|---|---|
| `400` | A field fails validation. `message` is an array of strings, each led by the field path, for example `"areaUnit must be one of the following values: sqm, sqft"` or `"images.2.url is not an image this organization owns"` |
| `403` | The key lacks the scope (`API_KEY_SCOPE_MISSING`) |
| `404` | The listing does not exist, is not yours (writes), or is another agency's non-active listing (reads) |

Nothing is saved when a request is refused.

Source: https://www.fondaro.com/docs/api/properties/crud
