OmniStay API
Version 1 · Last updated: 22 October 2026
Connect your other software to OmniStay: accounting, dashboards, maintenance tools, Zapier or Make. The API gives access to your properties, bookings, jobs, owners and team, and lets you act: jobs, quotes, invoices, calendar, prices… Webhooks notify your software as soon as a booking, a message or a job comes in. Your AI (ChatGPT, Claude, Gemini) can connect too, through the MCP server.
At a glance
- Base URL:
https://escale-roan.vercel.app/api/v1 - Format: JSON in UTF-8. Dates use the
AAAA-MM-JJformat (year-month-day), timesHH:MM(Paris time), amounts in euros. - Authentication: an API key for your company in the
Authorization: Bearer esk_…header. - Permissions: each key is either read-only or read and write (to act: create jobs, and the 91 actions).
- Connected AI: MCP server at
https://app.omnistay.ai/mcp, OAuth or API key connection (details). - Limit: 120 calls per minute per key.
Create a key
- In OmniStay, open Settings › API keys. Only the account owner and administrators have access.
- Give the key the name of the software to connect, and allow writing only if that software needs to act in OmniStay.
- Copy the key straight away: it starts with
esk_and is only shown once. OmniStay only keeps a fingerprint of it. - A key you no longer use, or that someone else may have seen: revoke it. It stops working immediately.
Authenticate
Every call carries the key. To check that it works:
curl https://escale-roan.vercel.app/api/v1/me \
-H "Authorization: Bearer esk_VOTRE_CLE"
{
"data": {
"organization": { "id": "11111111-1111-4111-8111-111111111111", "name": "Les Clés du Canal" },
"key": { "name": "Comptabilité", "prefix": "esk_Ab12Cd", "scopes": ["read"] }
}
}
The X-Api-Key: esk_… header is also accepted.
Resources
| Method | Endpoint | What it returns |
|---|---|---|
| GET | /me | Your company and the key used |
| GET | /properties, /properties/{id} | Listings |
| GET | /bookings, /bookings/{id} | Bookings |
| GET | /missions, /missions/{id} | Jobs: cleaning, keys, maintenance… |
| POST | /missions | Creates a job (key with write permission) |
| GET | /actions | The 91 actions, with their parameter schemas |
| POST | /actions/{nom} | Runs an action (key with write permission to act) |
| POST | /webhooks | Subscribes a URL to events (webhooks) |
| GET | /webhooks, /webhooks/{id} | Subscriptions created with this key |
| DELETE | /webhooks/{id} | Deletes a subscription |
| GET | /owners, /owners/{id} | Owners |
| GET | /team, /team/{id} | Team |
Listings
GET /properties
{
"data": [{
"id": "p1", "name": "Loft Canal Saint-Martin", "internal_name": "Valmy 3e G", "type": "appartement", "status": "active",
"address": { "street": "42 quai de Valmy", "zip": "75010", "city": "Paris" },
"surface_m2": 58,
"capacity": { "guests": 4, "double_beds": 1, "single_beds": 0, "sofa_beds_double": 1, "sofa_beds_single": 0 },
"check_in": { "from": "16:00", "until": "23:00", "mode": "lockbox" }, "check_out": "11:00",
"platforms": ["airbnb", "booking"], "base_price": 145, "cleaning_fee": 70,
"owner_id": "c1", "created_at": "2026-10-17", "updated_at": "2026-10-17T09:12:44.512Z"
}],
"total": 6, "limit": 100, "offset": 0
}
Access codes (key box, lock) and Wi-Fi are never returned by the API.
Bookings
GET /bookings?from=2026-10-01&to=2026-11-01&property_id=p1
| Filter | Effect |
|---|---|
from, to | Bookings that overlap the period: checkout after from, check-in before to |
property_id | A single property |
status | confirmed ou cancelled |
updated_since | Only what has changed since an ISO 8601 date and time (2026-10-17T09:00:00Z): handy for syncing |
{
"data": [{
"id": "cx_8f2a", "property_id": "p1", "platform": "airbnb", "reference": "HMX4K2P9QA", "status": "confirmed",
"check_in": "2026-10-17", "check_out": "2026-10-21", "nights": 4, "arrival_time": "16:30", "departure_time": null,
"guest": { "first_name": "Camille", "last_name": "Laurent", "email": "camille@exemple.fr", "phone": "+33 6 12 34 56 78",
"language": "fr", "country": "France", "count": 2 },
"amounts": { "currency": "EUR", "guest_paid": 690.8, "accommodation": 600, "nightly": 150, "cleaning": 70, "linen": 15,
"tourist_tax": 20.8, "platform_fees": 103.85, "payout": 566.15, "tax_collected_by_platform": true },
"created_at": "2026-09-30", "updated_at": "2026-10-16T18:02:11.870Z"
}],
"total": 1, "limit": 100, "offset": 0
}
The checkout day (check_out) is not a booked night. platform is airbnb, booking, vrbo, expedia, direct or manual.
Jobs
GET /missions?from=2026-10-20&to=2026-10-31&type=cleaning
Filters: from and to (job date, inclusive), property_id, member_id, type, status, updated_since.
{
"data": [{
"id": "cl_8f2a", "type": "cleaning", "title": "Ménage de fin de séjour", "description": null,
"property_id": "p1", "booking_id": "cx_8f2a", "member_id": "t1",
"date": "2026-10-21", "time": "11:00", "duration": "02:30", "pay": 55, "billed": 70, "status": "accepted",
"report": null, "created_at": "2026-09-30", "updated_at": "2026-10-16T18:02:12.004Z"
}],
"total": 1, "limit": 100, "offset": 0
}
| Field | Values |
|---|---|
type | cleaning (cleaning), keys (keys), diy, linen, plumbing, electricity, photo, delivery, pool, tour, decoration, pests, hvac, taxi, massage, food, babysitting, pets, housekeeper, other |
status | unassigned, offered (offered), accepted, done (done, awaiting approval), validated, cancelled |
pay, billed | Paid to the worker, and recharged to the owner (euros) |
report | After the job: number of photos, comment, duration in minutes, missing items and damage |
Create a job
With a key that has write permission. The job is offered to the chosen person (member_id), otherwise to the property’s team; they accept it in their app, and the job appears in OmniStay straight away.
curl -X POST https://escale-roan.vercel.app/api/v1/missions \
-H "Authorization: Bearer esk_VOTRE_CLE" \
-H "Content-Type: application/json" \
-d '{ "type": "plumbing", "property_id": "p1", "date": "2026-10-18", "time": "09:00",
"member_id": "t2", "description": "Fuite sous l’évier de la cuisine" }'
| Field | Required | Default |
|---|---|---|
property_id | Yes | — |
date | Yes | — |
type | No | cleaning |
time | No | The property’s checkout time for a cleaning, 16:00 for keys, 10:00 otherwise |
duration | No | The property’s cleaning duration, otherwise 02:00 |
pay, billed | No | The property’s settings for a cleaning |
member_id, booking_id | No | None |
title, description | No | The name of the job type |
Response 201 with the created job, in the same format as GET /missions.
Actions
Everything the AI Assistant can do in OmniStay also goes through the API: 91 actions (quotes, invoices, bookings, calendar, prices, jobs, team and absences, owners, properties, messages, reviews, deposits, incidents, damage and disputes, prospects, revenue estimates, guest welcome and scheduled messages, tourist tax, booking statistics and forecast…), with the same controls: permissions of the person who created the key, journal, undo for 24 hours from the app. The list, with each action’s JSON parameter schema:
curl https://escale-roan.vercel.app/api/v1/actions \
-H "Authorization: Bearer esk_VOTRE_CLE"
{ "data": [{ "name": "creer_devis", "title": "Créer un devis", "module": "Facturation", "kind": "write", "confirmation": false,
"description": "…", "input_schema": { "type": "object", "properties": { … } },
"method": "POST", "path": "/api/v1/actions/creer_devis" }], "total": 91 }
To run an action, send its parameters as JSON:
curl -X POST https://escale-roan.vercel.app/api/v1/actions/creer_devis \
-H "Authorization: Bearer esk_VOTRE_CLE" \
-H "Content-Type: application/json" \
-d '{ "client": "Sophie Martin", "objet": "Ménage de printemps",
"lignes": [{ "designation": "Grand ménage", "prix_unitaire": 120 }] }'
{ "status": "done", "message": "Devis en brouillon pour Sophie Martin : « Ménage de printemps »…",
"data": { "id": "doc_…" }, "url": "https://app.omnistay.ai/app/#/facturation/document/doc_…", "journal_id": "…" }
- Response
201when the action is done ("status": "done"),200for a read ("status": "ok"). - Add
"dry_run": trueto see what the action would do, without changing anything: response"status": "preview"with the preview. - Actions with
"confirmation": truesend something or can’t be undone (issuing an invoice, sending a message, calendar and prices sent to the platforms, charging a deposit…). Through the API, they’re carried out as soon as they’re called: try them withdry_runfirst. - Actions with
"kind": "write"require a key with write permission. Never any access code or Wi-Fi password in the responses.
Connected AI (MCP)
OmniStay is an MCP (Model Context Protocol) server: ChatGPT, Claude, Gemini, Claude Code, Gemini CLI or your own agents read and act in OmniStay with the 91 actions above, as tools. Running OmniStay from an AI is part of the AI Assistant add-on.
- URL:
https://app.omnistay.ai/mcp(streamable HTTP, JSON responses, no session). - Protocol versions: 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25 and 2026-07-28.
- OAuth 2.1 connection (ChatGPT, Claude, Gemini): discovery through
/.well-known/oauth-protected-resourceand/.well-known/oauth-authorization-server, dynamic client registration (/oauth/register) or client metadata document, PKCE S256 required, resource-bound tokens, refresh with rotation. The person logs in, chooses their company and can allow read access only; they disconnect the AI with one tap in Settings › API keys. - API key connection (Claude Code, Gemini CLI, your agents):
Authorization: Bearer esk_…header. - Confirmation: tools that send something or can’t be undone have a
confirmerparameter. Without it, they do nothing and return a preview starting with “Rien n’est fait” (nothing has been done); the AI shows it to the person, then calls the tool again with"confirmer": trueonce they agree. - Journal: every action is logged in Settings › AI journal, in the name of the person who connected the AI, and can be undone for 24 hours. 120 calls per minute per connected AI.
Claude Code:
claude mcp add --transport http omnistay https://app.omnistay.ai/mcp \
--header "Authorization: Bearer esk_VOTRE_CLE"
Gemini CLI, in ~/.gemini/settings.json:
{ "mcpServers": { "omnistay": { "httpUrl": "https://app.omnistay.ai/mcp",
"headers": { "Authorization": "Bearer esk_VOTRE_CLE" } } } }
ChatGPT, Claude and Gemini: the up-to-date steps are in OmniStay, Settings › API keys.
Webhooks
OmniStay notifies your software as soon as something happens: it sends a JSON POST to the URL you provide. Subscribe in the app (Settings › Integrations, with “Send a test” and the latest deliveries) or through the API with a key, following the REST Hooks model.
| Event | When |
|---|---|
booking.created | An upcoming booking comes in: platform, direct booking or entered manually. A booking made on a property manager’s or host’s website is sent once paid, never while it’s awaiting payment (nor if it’s cancelled before) |
booking.updated | Dates, property, check-in or checkout times, guest or amounts changed (several changes before delivery: a single delivery, the most recent) |
booking.cancelled | A booking is cancelled |
precheckin.completed | The guest has completed their pre-check-in: data.precheckin (completed_at, guests, forms), never the police registration forms |
message.received | A new message from a guest, a message sent (text, translation, sent_at) |
review.received | A new review: kind is review (platform) or first_impression (guest app) |
incident.opened | A guest reports a problem (hot water, keys, Wi-Fi, leak…) |
mission.created, mission.accepted, mission.done | An upcoming job is created, taken by someone on the team, completed (with its report) |
invoice.issued | An invoice, a deposit invoice or a credit note gets its number: source is invoicing (Invoicing) or owner_statement (an owner’s monthly invoice) |
lead.received | Someone fills in the form on your showcase website |
Past stays and jobs, old reviews and messages, and anything added by an import from previous software trigger nothing.
What your URL receives
POST https://votre-outil.exemple.fr/omnistay
Content-Type: application/json; charset=utf-8
User-Agent: OmniStay-Webhooks/1.0
X-OmniStay-Event: booking.created
X-OmniStay-Event-Id: evt_6f1c0b7e2d4a4c1f9b3e8d5a7c2f1e09
X-OmniStay-Delivery: 1842
X-OmniStay-Signature: t=1792656000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
{
"id": "evt_6f1c0b7e2d4a4c1f9b3e8d5a7c2f1e09",
"type": "booking.created",
"created_at": "2026-10-22T09:14:03Z",
"test": false,
"organization": { "id": "11111111-1111-4111-8111-111111111111", "name": "Les Clés du Canal" },
"data": {
"id": "cx_8f2a", "property_id": "p1", "property": { "id": "p1", "name": "Loft Canal Saint-Martin" },
"platform": "airbnb", "reference": "HMX4K2P9QA", "status": "confirmed",
"check_in": "2026-10-24", "check_out": "2026-10-28", "nights": 4, "arrival_time": "16:30", "departure_time": null,
"guest": { "first_name": "Camille", "last_name": "Laurent", "email": "camille@exemple.fr", "phone": "+33 6 12 34 56 78",
"language": "fr", "country": "France", "count": 2 },
"amounts": { "currency": "EUR", "guest_paid": 690.8, "payout": 566.15, "…": "…" },
"created_at": "2026-10-22"
}
}
datafollows the API format (booking, job…), with the name of the property (property), the team member (member) or the guest (guest). Never any access code, Wi-Fi, deposit or police registration form.- Respond with a
2xxcode within 8 seconds; redirects are not followed. - The same event can arrive twice (retry):
idlets you ignore a duplicate. Delivery order isn’t guaranteed:created_atgives the order. - “Send a test”, in the app, sends a sample of the chosen event with
"test": trueand sample data: all the fields are there, so Zapier or Make can learn the shape of the data.
Verifying the signature
The X-OmniStay-Signature header is t=<horodatage>,v1=<signature> (horodatage: the timestamp). The signature is the HMAC-SHA256 (in hexadecimal) of <t>.<corps brut> (the raw body), calculated with the webhook secret (whsec_…, given at creation and visible in the app). Reject a timestamp older than 5 minutes.
import crypto from 'node:crypto';
// corps : le texte brut reçu, avant tout JSON.parse
function signatureValable(secret, entete, corps) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(entete || '');
if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false;
const attendue = crypto.createHmac('sha256', secret).update(`${m[1]}.${corps}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(attendue), Buffer.from(m[2]));
}
Retries and deactivation
- Without a
2xxresponse: retry after 1 min, 5 min, 30 min, 2 h then 8 h (6 attempts in all), with exactly the same body and the sameid. - 24 hours of consecutive failures: the webhook is deactivated. It can be reactivated in the app.
- A
410 Goneresponse deactivates it immediately (REST Hooks model). - Revoking a key deactivates the webhooks it created. Delivery history is kept for 30 days.
Subscribing through the API
| Method | Endpoint | Effect |
|---|---|---|
| POST | /webhooks | Creates a subscription: { "url": "https://…", "events": ["booking.created"], "name": "…" } (target_url and event are also accepted). Response 201 with data and secret, given only once by the API |
| GET | /webhooks, /webhooks/{id} | Subscriptions created with this key, and the list of events (events) |
| DELETE | /webhooks/{id} | Deletes a subscription created with this key |
curl -X POST https://escale-roan.vercel.app/api/v1/webhooks \
-H "Authorization: Bearer esk_VOTRE_CLE" \
-H "Content-Type: application/json" \
-d '{ "url": "https://votre-outil.exemple.fr/omnistay", "events": ["booking.created", "booking.cancelled"] }'
A read-only key is enough. https:// URLs only (port 443), never an IP, local or internal address; at most 20 webhooks per company.
Zapier and Make
OmniStay has no official app in Zapier or Make: the connection goes through webhooks (to be notified) and the API key (to act). The steps are in the app, Settings › Integrations; the step-by-step guide, with three examples (bookings in Google Sheets, guest messages in Slack, a job created from a form), is in the help centre: “Relier OmniStay à Zapier” and “Relier OmniStay à Make” (in French).
| Get notified | Act in OmniStay | |
|---|---|---|
| Zapier | “Webhooks by Zapier” › “Catch Hook” (Zapier paid plans) | “Webhooks by Zapier” › “Custom Request”: Method, URL, Data (JSON) and, in Headers, Authorization: Bearer esk_… |
| Make | “Webhooks” › “Custom webhook” | “HTTP” › “Make a request”: Authentication type “API key” (key Bearer esk_… in the Authorization header), body application/JSON |
Owners and team
GET /owners
{ "data": [{ "id": "c1", "civility": "Mme", "first_name": "Sophie", "last_name": "Martin", "email": "sophie@exemple.fr",
"phone": "06 12 34 56 78", "address": "8 rue Lepic, 75018 Paris", "commission": { "mode": "percent", "value": 20 },
"since": "2023-04-02", "property_ids": ["p1", "p2"] }], "total": 1, "limit": 100, "offset": 0 }
GET /team
{ "data": [{ "id": "t1", "first_name": "Fatima", "last_name": "B.", "role": "cleaning", "status": "active",
"property_ids": ["p1", "p2"] }], "total": 1, "limit": 100, "offset": 0 }
role is cleaning, keys, maintenance or other. Former team members don’t appear.
Lists and pagination
Lists return data, total, limit and offset. 100 items by default; limit goes up to 500. For the next page, increase offset: ?limit=100&offset=100. Bookings are sorted by check-in date, jobs by date and time.
Errors
In case of error, the response contains a stable code for your programs and a message in French:
{ "error": { "code": "read_only_key", "message": "Cette clé est en lecture seule : créez une clé avec le droit d’écriture." } }
| Status | Codes | Meaning |
|---|---|---|
| 400 | invalid_date, invalid_period, invalid_status, invalid_type, invalid_limit, invalid_offset, invalid_json | A filter or the request body is malformed |
| 401 | missing_key, invalid_key | Key missing, unknown or revoked |
| 403 | read_only_key | The key doesn’t have write permission |
| 404 | not_found | Endpoint or item not found |
| 405 | method_not_allowed | Method not supported on this endpoint |
| 422 | property_required, unknown_property, unknown_member, unknown_booking, invalid_time, invalid_duration, invalid_amount, invalid_text | The job to create is not valid |
| 403, 404, 409, 422 | read_only, forbidden, unknown_action, not_found, conflict, invalid_input, invalid_state, ambiguous, already_exists… | Actions: insufficient permissions, unknown action or item, item modified in the meantime (try again), parameters to correct, several possible items (be more specific), already done. The message says what to do. |
| 409, 422 | url_required, invalid_url, events_required, already_exists, too_many_webhooks | Webhooks: URL missing or refused (public https only), no known event, URL already subscribed, more than 20 webhooks |
| 429 | rate_limited | More than 120 calls in the minute: try again a little later |
| 500, 503 | server_error, not_configured | Problem on our side: try again in a moment |
Limits and security
- A key gives access to your company’s data: keep it like a password. Use it from a server or an automation tool, never in a web page or a public app.
- A key only sees its own company. It never gives access to access codes, Wi-Fi or payment methods; deposits can only be released or charged through the dedicated actions, logged in the journal.
- Your guests’, owners’ and team members’ data remain your responsibility: only connect software you trust, and revoke a key as soon as it’s no longer in use.
- 120 calls per minute per key, at most 20 active keys per company.
- Version 1 endpoints won’t change. New features will come as new fields or new endpoints.
If you ask for help, give the name of the key used, never the key itself.