> ## Documentation Index
> Fetch the complete documentation index at: https://docs.corsair.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Melo plugin

> French real-estate listings through the Melo API (Stream Estate) - search properties across listing sites, read price-per-meter and points-of-interest indicators, look up locations and cities, and manage saved searches.

Use **Melo** through Corsair: one client, typed API calls.

**Melo is the product, Stream Estate is the API.** melo.io is the French real-estate aggregator; its developer API is served from `https://api.stream.estate` and documented at docs.stream.estate (docs.melo.io redirects there). The plugin calls `https://api.stream.estate` and authenticates with the `X-API-KEY` header. Create the key on the API Keys page of the Stream Estate console (stream.estate/console). The API is paid: a subscription, or pay-as-you-go per returned item.

**Checked live on 11-11** (pay-as-you-go account: free starting credit, then EUR 0.01 per returned item, so ask for small pages). All 13 operations ran through the plugin and `api.test.ts` passes. What the run showed:

* Response shapes match the schemas, with one exception now fixed: advert `events` carry booleans in `fieldOldValue` / `fieldNewValue` when an advert expires, not strings. Location `id` is an integer. No enum value outside the documented ones appeared, though output enums stay open in case one does.
* `searches.create` needs more than the spec says: a location (`includedCities`, `includedDepartments`, `includedZipcodes`, `lat`/`lon`/`radius` or `geoShapes`) and one of `budgetMin`, `budgetMax`, `pricePerMeterMin` or `pricePerMeterMax`, or Melo answers 400 naming what is missing.
* `searches.update` is a PUT, but fields left out keep their saved values: an update without `budgetMax` or `includedDepartments` kept both.
* `webhooks.simulate` answers 200 with `{"success":true,"code":200,"error":null}` and delivers one POST to the endpoint (a `match` payload, or `event` / `adEvent` / `match` for an event type).
* `properties.listSimilar` is served by a redirect to a `/documents/properties` search; fetch follows it.
* `indicators.pointsOfInterest` answered an empty collection to every query tried, and sometimes took 30 to 40 s, past the 20 s request timeout (the retry then succeeded). Its item shape is still unverified.
* No rate-limit headers were seen, so the 429 handling (three retries, 1s/2s/4s backoff, `Retry-After` if one arrives) is still untested.
* The update example in the documentation sets a webhook without `notificationEnabled`, which the notification rules in the same spec forbid, so the plugin follows the rules, not the example.

**The OpenAPI spec was the source of truth over earlier notes.** The locations route is `/public/location-autocomplete`, the cities route is `/cities`, and the webhook test route is `GET /webhook-tester`. There are 13 operations, exactly the ones in the spec.

Things to know before you call:

* **Filters are IRIs.** `includedCities`, `includedDepartments` and the other place filters take IRIs such as `/departments/77` (Paris; department ids are Melo's own, not INSEE codes), not names. Get them from `indicators.locations` (which needs a key here even though Melo serves the route publicly) or `indicators.cities`.
* **Numbers stand for words.** `transactionType` is 0 for sale and 1 for rent. `propertyTypes` is 0 apartment, 1 house, 2 building, 3 parking, 4 office, 5 land, 6 shop. Inputs are strict, so a misspelled filter throws instead of silently returning unfiltered data.
* **Paging.** `properties.list` and the other collections return the Hydra fields untouched plus a `nextPage` number, or `null` on the last page. Pass it back as `page`. `itemsPerPage` is capped at 30.
* **Saved searches.** `searches.create` and `searches.update` need `title`, `transactionType` and `propertyTypes`. A webhook endpoint needs `notificationEnabled: true`, and an event endpoint also needs at least one `subscribedEvents`. `searches.delete` is irreversible.
* **Webhook deliveries are not handled.** The plugin does not receive webhooks. `webhooks.simulate` is an API call: it asks Melo to send a test `match` (or the event in `eventType`) to an endpoint you give it, so it is a write with a side effect. It is not replayed after a 5xx or a transport failure; a 429 is retried, like every other operation.
* **Content type.** Melo answers `application/ld+json` only when asked for it, so the plugin sends `Accept: application/ld+json` and decodes the body itself. A wrong key answers 401 with the plain text `Authentication failed`.

**What you get:**

* 13 typed API operations

## Setup

**1. Install**

```bash npm theme={null}
npm install corsair @corsair-dev/melo better-sqlite3
npm install --save-dev @types/better-sqlite3
```

```bash yarn theme={null}
yarn add corsair @corsair-dev/melo better-sqlite3
yarn add --dev @types/better-sqlite3
```

```bash pnpm theme={null}
pnpm install corsair @corsair-dev/melo better-sqlite3
pnpm install --save-dev @types/better-sqlite3
```

```bash bun theme={null}
bun add corsair @corsair-dev/melo better-sqlite3
bun add --dev @types/better-sqlite3
```

**2. Add the plugin**

```ts corsair.ts theme={null}
import Database from 'better-sqlite3';
import { createCorsair } from 'corsair';
import { melo } from '@corsair-dev/melo';

export const corsair = createCorsair({
	plugins: [
		melo(),
	],
	database: new Database('corsair.db'),
	kek: process.env.CORSAIR_KEK!,
	multiTenancy: true,
	hub: {
		projectApiKey: process.env.CORSAIR_API_KEY!,
		signingSecret: process.env.CORSAIR_SIGNING_SECRET!,
	},
});
```

With `multiTenancy: true`, scope calls with `corsair.withTenant(id)`. See [Quick start](https://docs.corsair.dev/quick-start.md) for KEK + Hub keys, and [Multi-tenancy](https://docs.corsair.dev/concepts/multi-tenancy.md) for account isolation.

**3. Choose authentication**

> [tab] API Key (Recommended)
>
> No setup required yet. When you make your first request as a tenant, Corsair prompts for the API key.
>
> ```ts theme={null}
> melo()
> ```
>
> More: [API Key](https://docs.corsair.dev/concepts/api-key)

**4. Connect a tenant**

Mint a connect link and send the tenant to it. Hub hosts the page and delivers the result to your app — see [Connect / OAuth](https://docs.corsair.dev/management/connect).

```ts theme={null}
const { connectUrl } = await corsair.manage.connect.createLink({
	plugin: 'melo',
	tenantId: 'acme',
});
// redirect the user's browser to connectUrl
```

## Example API calls

**Find the place IRIs for a name**

```ts theme={null}
const tenant = corsair.withTenant('acme');
await tenant.melo.api.indicators.locations({ query: 'paris' });
```

**Send a test match to a webhook endpoint**

```ts theme={null}
const tenant = corsair.withTenant('acme');
await tenant.melo.api.webhooks.simulate({ endpoint: 'https://hooks.example.com/melo' });
```

See the full list on the [API](https://docs.corsair.dev/plugins/melo/api.md) page.

## What's next

> [card] [API reference](https://docs.corsair.dev/plugins/melo/api.md)
>
> Every `melo.api.*` operation with input and output types.

> [card] [Connect / OAuth](https://docs.corsair.dev/management/connect)
>
> createLink, Hub delivery, and tenant connect flows.

> [card] [Use with agents](https://docs.corsair.dev/mcp-adapters/mcp-adapters)
>
> Expose this plugin's operations as MCP tools.


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