# Pricing

Two endpoints set prices:

| Endpoint | Sets | Scope |
| --- | --- | --- |
| `PUT /v1/properties/{id}/rates` | Property defaults | `properties:write` |
| `PUT /v1/calendar` | Specific dates | `calendar:write` |

Values resolve in three layers. A per-date price beats the default, and a channel
markup applies on top of whichever won:

```
default 100 €
  → 31 Dec override 150 €
    → Airbnb markup ×1.1
      = 165 € on Airbnb, 150 € everywhere else
```

## Default rates

One call sets the base rate, its bounds, the property-level stay rules and the
per-channel markup:

```bash
curl -X PUT https://api.wearenoe.fr/v1/properties/$ID/rates \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "base_price_cents": 12500,
    "floor_price_cents": 8000,
    "ceiling_price_cents": 40000,
    "min_stay": 2,
    "channels": [
      { "channel": "airbnb", "markup_multiplier": 1.1 }
    ]
  }'
```

`GET /v1/properties/{id}/rates` returns the same shape, so you can read it,
change one field and send it back.

`floor_price_cents` and `ceiling_price_cents` bound what an automated pricing
tool may set. They are not enforced against explicit calendar writes.

Omitted fields are left untouched; omitting the floor does not clear it.

## Per-date prices and stay rules

One call covers many properties and many date ranges:

```bash
curl -X PUT https://api.wearenoe.fr/v1/calendar \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "updates": [
      {
        "property_id": "…",
        "from": "2026-12-27",
        "to": "2027-01-02",
        "price_cents": 24000,
        "min_stay": 4,
        "closed_to_departure": true
      }
    ]
  }'
```

`closed_to_arrival` and `closed_to_departure` forbid a stay from starting or
ending on those dates, without closing the night to guests already staying. To
close a night entirely use `stop_sell`, or create a
[block](/developpeurs/api/blocks) to record a reason such as an owner stay.

Items are applied independently. The response is a `207` with a per-item result —
check each entry rather than the HTTP status.

Validation is the exception: every item must be schema-valid, so one malformed
date rejects the whole payload with a single `400` and nothing is applied. The
per-item results cover failures that happen while applying an item, such as a
property you cannot reach.

### One channel only

Add `channel` to apply a value to a single channel, leaving the others on the
all-channel figure:

```json
{ "property_id": "…", "from": "2026-07-01", "to": "2026-07-31",
  "price_cents": 18000, "channel": "airbnb" }
```

A channel-specific value wins over the all-channel one, so setting an Airbnb
price does not affect Booking.com. Pass `?channel=airbnb` to `GET /v1/calendar`
to read what a single channel is showing.

To remove a channel override, set it back to the all-channel value.

## Channel markup

The markup is a multiplier, not a percentage: `1.1` publishes every rate 10%
higher on that channel. It applies on top of the resolved price, and is set on
the same `rates` endpoint:

```bash
curl -X PUT https://api.wearenoe.fr/v1/properties/$ID/rates \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "channels": [
      { "channel": "airbnb", "markup_multiplier": 1.1, "guests_included": 2, "extra_person_price": 15 }
    ]
  }'
```

<Callout type="caution">
`markup_multiplier` is a multiplier. `10` does not mean 10% — it multiplies your
prices tenfold. Send `null` to clear it.
</Callout>

Overlays are supported on Airbnb only.

The response is re-read after the write, so it reflects what was stored rather
than what you sent. Some channels round or clamp values.

## Stay rules

`min_stay` and `max_stay` exist at two levels:

- **Per date**, on `PUT /v1/calendar`.
- **Property level**, on `PUT /v1/properties/{id}/rates` — a floor for every
  per-date minimum. Set it to 3 and a per-date `min_stay: 2` resolves to 3.

The `stay_rules` block on `GET /v1/properties/{id}` is read-only and reflects the
property-level values.

## Propagation

Writes go through the same pipeline as changes made in the Noé interface, so
propagation, price history and the audit trail are identical either way.

Pushes are queued and coalesced per property, so a bulk calendar write does not
become hundreds of separate channel updates. Expect changes to appear within a
few minutes.
