L’API REST publique Nowistay connecte vos scripts et intégrations partenaires à votre compte. Consultez et modifiez les logements, calendriers, réservations et missions ; gérez les fiches voyageurs, conversations, connaissances privées et livrets d’accueil ; demandez des paiements et remboursements d’annulation ; consultez les options, achats et rapports de votre portefeuille. Les schémas détaillés sont disponibles sur /public/docs et /public/openapi.json.
Ce guide vous montre trois chemins : la façon la plus rapide d'essayer l'API (le playground interactif des docs), la façon la plus simple de faire tourner vos propres scripts (un identifiant API personnel créé dans l'application) et le flux OAuth standard que vous utiliserez pour construire une application destinée à d'autres hôtes.
L’accès dépend de la propriété du logement et de son droit d’accès à l’assistant. Une connexion PMS active est requise pour les opérations qui en dépendent, notamment la gestion du calendrier et la création de réservations directes. Les autres ressources ne nécessitent pas une connexion PMS uniquement pour être consultées ou modifiées.
https://api.nowistay.com./public/v1/* exigent un token OAuth2, envoyé sous la forme Authorization: Bearer ....Ouvrez https://api.nowistay.com/public/docs dans votre navigateur. La page est un Swagger UI complet de l'API, avec un playground OAuth intégré qui enregistre automatiquement un client pour vous. Pas besoin de coder pour tester.
Cmd+Maj+R sur Mac, Ctrl+F5 sur Windows)./public/docs, déjà authentifié.GET /public/v1/properties, cliquez sur Try it out puis sur Execute.À partir de là, vous pouvez essayer chaque endpoint en un clic, voir le JSON exact que Nowistay renvoie et copier la commande cURL correspondante. C'est la façon recommandée d'explorer l'API avant d'écrire du code.
Important. Le client du playground vit uniquement dans votre navigateur. C'est très bien pour tester, mais pour une vraie intégration il faut créer un identifiant API personnel ou enregistrer votre propre client OAuth (les deux sont expliqués plus bas).
Si vous automatisez votre propre compte (un script de reporting, une synchronisation avec votre outil de comptabilité, un petit tableau de bord), vous n'avez pas besoin du flux OAuth complet. Créez un identifiant API personnel dans l'application et échangez-le contre un token en un seul appel. Pas d'écran de consentement, pas de refresh token à gérer.
Script de reporting) et cochez uniquement les autorisations nécessaires.Votre script échange ensuite l'identifiant contre un token d'accès (valable 1 heure) et appelle l'API :
curl -X POST https://api.nowistay.com/public/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=YOUR_CLIENT_ID" \
--data-urlencode "client_secret=YOUR_CLIENT_SECRET"Utilisez le champ access_token renvoyé comme jeton Bearer. L’exemple GET /public/v1/properties nécessite explicitement properties.read.
Quand le token expire, redemandez-en un de la même façon. Il n'y a pas de refresh token sur ce flux, et c'est voulu : deux lignes de code, rien à surveiller.
Vous pouvez créer jusqu'à 10 identifiants, chacun avec son nom et ses autorisations. Un identifiant ne lit et n'écrit que les données accessibles à votre propre compte, et il nécessite le même abonnement AI Channel Manager actif que le reste de l'API.
Les scopes d’un token ou d’un identifiant personnel existant ne sont pas modifiables. Créez un identifiant de remplacement avec toutes les permissions nécessaires à votre intégration. Les clients, tokens d’accès, codes d’autorisation et refresh tokens existants conservent leurs permissions initiales. Aucun scope n’est ajouté automatiquement.
https://app.nowistay.com/hub/user/developer. Relevez les permissions utilisées par votre intégration actuelle.calendar.write, envoyer un message nécessite messages.write et demander un paiement nécessite bookings.write plus payments.write.POST /public/oauth/token avec grant_type=client_credentials et les nouveaux identifiants. Vérifiez le champ scope de la réponse et testez un endpoint autorisé. GET /public/v1/properties nécessite properties.read.Régénérer la clé n’ajoute aucun scope. Cette action remplace seulement le secret du même identifiant ; l’ancienne clé reste valable 7 jours. Renouveler un token ne peut pas étendre ses permissions initiales. Un scope d’écriture n’inclut pas automatiquement le scope de lecture.
POST /public/oauth/register avec le jeu complet de scopes souhaité et les informations habituelles de l’application. Un enregistrement équivalent avec exactement les mêmes scopes réutilise le client correspondant ; des scopes différents créent un nouvel enregistrement. Conservez les identifiants renvoyés.Dans le playground, actualisez la page puis autorisez à nouveau les scopes nécessaires. Les permissions affichent leur identifiant et un libellé lisible en anglais, français, espagnol, allemand, néerlandais et italien.
L'API utilise OAuth 2.1 standard avec le flux authorization code et PKCE. En une phrase : l'utilisateur ouvre une page Nowistay, donne son accord, vous recevez un code court, vous l'échangez contre un token d'accès, puis vous appelez l'API avec ce token. Les tokens expirent et peuvent être renouvelés sans déranger l'utilisateur.
Les trois endpoints OAuth que vous allez utiliser :
/public/oauth/register (POST)/public/oauth/authorize (GET, redirection navigateur)/public/oauth/token (POST)Vous pouvez aussi récupérer les métadonnées OAuth à /.well-known/oauth-authorization-server/public-api, qui liste chaque endpoint et toutes les options supportées.
N'importe qui peut enregistrer un client OAuth. Les hôtes ont donc besoin de distinguer une application toute neuve d'une intégration établie. C'est pourquoi toute application nouvellement enregistrée démarre non vérifiée :
Vous construisez quelque chose pour plus de 3 comptes ? Écrivez au support avec le nom de votre application, son site web et votre cas d'usage. Une fois l'application vérifiée, l'avertissement disparaît et la limite de comptes est levée.
L’API REST prend en charge 25 scopes, dont dix permissions supplémentaires pour les logements, messages, livrets, connaissances, créations de voyageurs, options et paiements. Sélectionnez seulement celles nécessaires à votre intégration.
properties.read : consulter les logements et leurs paramètres.properties.write : modifier les logements et leurs paramètres.calendar.read : consulter les disponibilités, tarifs et restrictions.calendar.write : modifier les disponibilités, tarifs et restrictions.bookings.read : consulter les réservations, montants et statuts des demandes de paiement.bookings.sensitive.read : inclure coordonnées, adresses postales et codes d’accès dans les réponses de réservation ; les lectures nécessitent aussi bookings.read.bookings.write : créer et modifier des réservations directes. Les paiements et remboursements nécessitent aussi payments.write.booking_parameters.read : consulter les définitions et valeurs des paramètres personnalisés.booking_parameters.write : créer et modifier les définitions et valeurs des paramètres.missions.read : consulter les missions et rapports de réalisation.missions.write : créer, modifier et assigner les missions.activity.read : consulter les journaux d’activité des logements.team_members.read : consulter les membres d’équipe, coordonnées et logements assignés.webhooks.read : consulter les abonnements webhook.webhooks.write : créer, modifier, suspendre et supprimer des abonnements webhook.customers.read : consulter les fiches voyageurs, coordonnées et historiques de séjour.customers.write : créer des fiches voyageurs.upsells.read : consulter le catalogue d’options et les achats liés aux réservations.payments.write : demander un paiement à la création d’une réservation et indiquer un remboursement total ou partiel à l’annulation ; nécessite aussi bookings.write.messages.read : lire les conversations et messages voyageurs.messages.write : envoyer des messages aux voyageurs.guides.read : consulter les livrets d’accueil et leurs pages.guides.write : modifier les livrets, créer et modifier des pages, changer leur position et importer des images.knowledge.read : consulter les questions-réponses privées des logements.knowledge.write : créer et modifier les connaissances privées. La copie nécessite aussi knowledge.read.Les huit scopes REST par défaut restent properties.read, calendar.read, bookings.read, booking_parameters.read, missions.read, activity.read, team_members.read et webhooks.read. Ces valeurs par défaut ne modifient pas les tokens existants.
bookings.write + payments.write. Une annulation sans remboursement utilise seulement bookings.write.customers.read + bookings.read.upsells.read + bookings.read. Le catalogue nécessite seulement upsells.read.knowledge.read + knowledge.write.bookings.write + booking_parameters.write. Un PATCH limité aux paramètres conserve booking_parameters.write et fonctionne aussi sur les réservations OTA.Les nouveaux endpoints d’écriture renvoient un résultat minimal sauf si le token possède aussi le scope de lecture correspondant. Le masquage existant des données de réservation reste en place. Les missions et conversations incluent leur contexte de réservation sous leurs propres scopes.
Ces exemples utilisent des identifiants et valeurs fictifs à remplacer. Ce flux concerne les applications partenaires ; les identifiants personnels utilisent client_credentials, comme indiqué plus haut.
Appelez POST /public/oauth/register avec les permissions nécessaires. Conservez le client_id et le client_secret renvoyés de manière sécurisée. Cet exemple demande la lecture des logements, réservations et missions.
curl -X POST https://api.nowistay.com/public/oauth/register \
-H "Content-Type: application/json" \
-d '{"clientName":"My integration","redirectUris":["https://my-app.example.com/oauth/callback"],"grantTypes":["authorization_code","refresh_token"],"responseTypes":["code"],"tokenEndpointAuthMethod":"client_secret_post","scope":"properties.read bookings.read missions.read"}'Pour un client public sans backend, utilisez "tokenEndpointAuthMethod": "none" et PKCE, sans secret client.
Générez un vérificateur PKCE, son challenge S256 et un state aléatoire. Redirigez l’utilisateur vers l’endpoint d’autorisation. L’URL ci-dessous est sur une seule ligne ; encodez les valeurs des paramètres.
https://api.nowistay.com/public/oauth/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=https%3A%2F%2Fmy-app.example.com%2Foauth%2Fcallback&scope=properties.read%20bookings.read%20missions.read&state=RANDOM_STATE&code_challenge=BASE64URL_S256_HASH&code_challenge_method=S256L’hôte se connecte, examine les permissions et accepte ou refuse l’accès. Après acceptation, le callback contient un code à usage unique et votre state. Rejetez un callback dont le state ne correspond pas.
https://my-app.example.com/oauth/callback?code=AUTHORIZATION_CODE&state=RANDOM_STATEcurl -X POST https://api.nowistay.com/public/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code=AUTHORIZATION_CODE" \
--data-urlencode "redirect_uri=https://my-app.example.com/oauth/callback" \
--data-urlencode "client_id=YOUR_CLIENT_ID" \
--data-urlencode "client_secret=YOUR_CLIENT_SECRET" \
--data-urlencode "code_verifier=ORIGINAL_PKCE_VERIFIER"La réponse contient access_token, token_type, expires_in, refresh_token et scope. Conservez les tokens de façon sécurisée. Un token d’accès dure normalement une heure ; utilisez l’expiration renvoyée.
Cet exemple nécessite properties.read. Consultez les champs data et pagination dans la réponse.
curl "https://api.nowistay.com/public/v1/properties" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"curl -X POST https://api.nowistay.com/public/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=refresh_token" \
--data-urlencode "refresh_token=YOUR_REFRESH_TOKEN" \
--data-urlencode "client_id=YOUR_CLIENT_ID" \
--data-urlencode "client_secret=YOUR_CLIENT_SECRET"Conservez le nouveau refresh token renvoyé par la réponse. Si le renouvellement échoue, recommencez l’autorisation. Le renouvellement conserve les scopes déjà accordés. Les identifiants personnels n’ont pas de refresh token : demandez un nouveau token d’accès avec client_credentials.
GET /public/v1/properties/{property_id}/calendar nécessite calendar.read. Indiquez les dates inclusives from et to ; une requête peut couvrir jusqu'à 730 jours.
GET /public/v1/properties/101/calendar?from=2026-12-18&to=2026-12-20
Chaque journée peut renvoyer availability, rate, minStay, maxStay, stopSell, closedToArrival, closedToDeparture, restricted et restrictionReasons. availability correspond au stock encore vendable après déduction des réservations confirmées. Les champs de restriction expliquent pourquoi une date, une arrivée ou un départ est restreint.
PATCH /public/v1/properties/{property_id}/calendar nécessite calendar.write. La requête est partielle : envoyez uniquement les paramètres à modifier ; les champs omis restent inchangés.
{
"from": "2026-12-18",
"to": "2026-12-31",
"availability": 1,
"rate": 145.0,
"minStay": 2,
"maxStay": 14,
"stopSell": false,
"closedToArrival": false,
"closedToDeparture": false,
"daysOfWeek": [0, 1, 2, 3, 4]
}
daysOfWeek est facultatif : 0 correspond au lundi et 6 au dimanche. Omettez-le pour modifier toutes les dates de la plage. La réponse indique daysUpdated ainsi que la mise à jour normalisée qui a été appliquée. Les changements sont synchronisés via le PMS Nowistay.
Les opérations de calendrier nécessitent la propriété du logement, le droit d’accès à l’assistant et une gestion de calendrier Nowistay active. Pour ajouter les scopes de calendrier à une intégration, suivez la section Comment ajouter des scopes à un token API existant. Un renouvellement de token ou une régénération du secret ne suffit pas.
Les filtres comprennent propertyId, plusieurs propertyIds, status, channel, fromDate et toDate. La pagination JSON standard utilise limit (maximum 100) et offset. Les dates conservent la logique de chevauchement : departure >= fromDate et arrival <= toDate.
curl "https://api.nowistay.com/public/v1/bookings?propertyId=101&fromDate=2026-12-01&toDate=2026-12-31&status=confirmed" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"curl "https://api.nowistay.com/public/v1/bookings/9001" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Nécessite le scope supplémentaire bookings.sensitive.read. Sans lui, email, phone et le code de serrure ne sont pas inclus.
Les modifications des champs principaux nécessitent bookings.write et une réservation directe éligible. Les réservations OTA restent en lecture seule pour ces champs. Un PATCH limité à customParameters fonctionne aussi sur les réservations OTA et nécessite booking_parameters.write.
curl -X PATCH "https://api.nowistay.com/public/v1/bookings/9001" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"notes": "Guest requested a baby cot.",
"reservationNumber": "DIRECT-9001",
"silent": true
}'Les PATCH de réservation existants utilisent silent=true par défaut. Mettez silent=false pour déclencher les notifications habituelles. À la création, la valeur par défaut est silent=false. Les champs omis restent inchangés et les valeurs null déjà prises en charge permettent toujours d’effacer des champs. La synchronisation des disponibilités reste intégrée à l’opération.
Les paramètres personnalisés de réservation sont utiles pour refléter sur chaque réservation Nowistay une donnée qui vient de votre propre système.
curl -X POST "https://api.nowistay.com/public/v1/booking-parameters" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"key": "external_status",
"name": "External status"
}'
curl -X PATCH "https://api.nowistay.com/public/v1/bookings/9001" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"customParameters": {
"external_status": "ready"
}
}'Les deux exemples nécessitent booking_parameters.write. Une modification mêlant champs principaux et paramètres nécessite les deux scopes d’écriture ; un échec de validation ne modifie ni les uns ni les autres.
Envoyez null comme valeur pour effacer un champ personnalisé sur une réservation.
Nécessite missions.write. Les workflows d’assignation et de notification s’appliquent aussi aux missions créées par l’API.
curl -X POST "https://api.nowistay.com/public/v1/missions" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"propertyId": 101,
"bookingId": 9001,
"missionType": "cleaning",
"title": "Checkout cleaning",
"scheduledAt": "2026-12-18T11:30:00Z",
"timezone": "Europe/Paris",
"assignedTeamMemberId": 301
}'Nécessite activity.read.
curl "https://api.nowistay.com/public/v1/properties/101/activity?limit=20" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Les événements sont regroupés en trois catégories : booking, mission et automation (messages voyageur, événements de serrure).
Les chemins de cette section sont relatifs à /public/v1. Remplacez les identifiants par des ressources appartenant au compte connecté. Le schéma OpenAPI détaille les champs, valeurs autorisées et formats de réponse.
Utilisez GET /properties/{id} avec properties.read et PATCH /properties/{id} avec properties.write. La liste accepte search. Les modifications prises en charge comprennent nom, adresse, capacité, type, liens de location, couverture, heures d’arrivée/départ, délais de réservation et paramètres de tarif et de séjour par défaut.
curl -X PATCH "https://api.nowistay.com/public/v1/properties/101" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"capacity": 4,
"defaultMinStay": 2,
"cutOffDays": 1
}'POST /bookings nécessite bookings.write, le droit d’accès à l’assistant et une connexion PMS active. Indiquez les dates, l’adresse email et les informations du voyageur, ainsi que les champs tarifaires et opérationnels souhaités. requestPayment=true nécessite aussi payments.write et crée la réservation en attente de paiement.
curl -X POST "https://api.nowistay.com/public/v1/bookings" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"propertyId": 101,
"arrival": "2026-12-18",
"departure": "2026-12-20",
"guestFirstName": "Maya",
"guestLastName": "Martin",
"guestEmail": "maya@example.com",
"adults": 2,
"amount": "290.00",
"currency": "EUR",
"requestPayment": true
}'La réservation peut être créée même si la génération du lien de paiement échoue. La réponse contient toujours la réservation enregistrée ou son identifiant, avec paymentRequest.sent=false et une erreur explicite. Vérifiez ce résultat avant de réessayer pour éviter une réservation en double. Une URL de paiement est renvoyée uniquement par une action de demande de paiement autorisée.
GET /bookings/{id}/payment-request, avec bookings.read, permet de consulter le statut de la réservation et de sa demande de paiement sans exposer l’URL de paiement. Cette version n’ajoute pas d’API autonome d’administration des paiements.
Le PATCH de réservation accepte désormais reservationNumber, cancellationRefundMode (none, full ou partial) et cancellationRefundAmount. Un remboursement explicite total ou partiel à l’annulation nécessite bookings.write et payments.write. Une annulation sans remboursement nécessite seulement bookings.write.
curl -X PATCH "https://api.nowistay.com/public/v1/bookings/9001" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "cancelled",
"cancellationRefundMode": "partial",
"cancellationRefundAmount": "50.00"
}'Une réservation annulée ne prouve pas qu’un remboursement a abouti. Consultez le statut du paiement avant de confirmer le remboursement au voyageur. payments.write autorise les demandes de paiement et remboursements d’annulation, mais pas la modification du prix d’un paiement déjà créé.
POST /guests utilise customers.write ; une création en double renvoie 409. Les lectures existantes de la base clients du propriétaire restent disponibles. La liste accepte propertyId, plusieurs propertyIds et orderBy avec created_at, updated_at, full_name, bookings_count, total_amount ou last_stay. Préfixez la valeur par - pour un ordre décroissant ; la valeur par défaut est -updated_at.
curl -X POST "https://api.nowistay.com/public/v1/guests" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Maya",
"lastName": "Martin",
"email": "maya@example.com",
"language": "en"
}'GET /guests/{id}/bookings nécessite customers.read et bookings.read et renvoie l’historique des séjours éligibles.
GET /missions/{id} et GET /missions/{id}/report utilisent missions.read. Une mission valide sans rapport de réalisation renvoie 200 avec report: null. La liste accepte les dates UTC planifiées inclusives fromDate/toDate, plusieurs propertyIds et teamMemberIds, en complément des filtres existants.
curl "https://api.nowistay.com/public/v1/missions?propertyIds=101&propertyIds=102&teamMemberIds=301&fromDate=2026-12-01&toDate=2026-12-31" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"GET /conversations accepte les filtres propertyId et bookingId. GET /conversations/{id}/messages lit l’historique. Ces deux lectures nécessitent messages.read. Pour envoyer une réponse, utilisez POST /messages avec messages.write et exactement un champ parmi conversationId et bookingId. Les champs facultatifs channelKind ou threadChannelId sélectionnent un canal ; n’indiquez pas les deux.
curl -X POST "https://api.nowistay.com/public/v1/messages" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"bookingId": 9001,
"message": "Hello Maya, your arrival instructions are ready."
}'Les messages suivent le circuit habituel des réponses hôtes, y compris le routage des réservations multi-logements. Un message enregistré dont la livraison échoue renvoie son identifiant et son statut : vérifiez le résultat avant de réessayer. Ces endpoints ne prennent pas en charge les pièces jointes.
Utilisez GET /properties/{id}/knowledge-questions et GET /properties/{id}/knowledge-questions/{questionId} avec knowledge.read. Utilisez POST sur la collection et PATCH sur une question avec knowledge.write.
curl -X POST "https://api.nowistay.com/public/v1/properties/101/knowledge-questions" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"question": "Where is the recycling area?",
"answer": "In the courtyard, next to the bicycle shelter."
}'POST /properties/{id}/knowledge-questions/copy utilise {id} comme source et nécessite knowledge.read plus knowledge.write. Indiquez targetPropertyIds pour fusionner les réponses dans d’autres logements. La copie ne supprime ni n’écrase les réponses des cibles ; toutes les cibles sont validées avant le début des écritures.
curl -X POST "https://api.nowistay.com/public/v1/properties/101/knowledge-questions/copy" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"targetPropertyIds": [
102,
103
]
}'GET/PATCH /properties/{id}/welcome-guide : consulter ou modifier le livret, notamment ses introductions multilingues.GET/POST /properties/{id}/welcome-guide/pages : lister ou créer des pages.GET/PATCH /properties/{id}/welcome-guide/pages/{pageId} : consulter ou modifier une page et sa position.POST /properties/{id}/welcome-guide/images : importer une image depuis une URL publique prise en charge ou des données base64.Les lectures nécessitent guides.read et les écritures guides.write. Conservez les blocs EditorJS content/contents, leurs identifiants, la mise en forme imbriquée et les médias lors de la modification des pages structurées. Les langues omises restent inchangées. Le remplacement d’une page structurée par du texte brut est refusé. Les tokens de livret et liens d’accès voyageurs ne sont pas renvoyés.
curl -X PATCH "https://api.nowistay.com/public/v1/properties/101/welcome-guide" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"intros": {
"en": "Welcome! Your practical information is below.",
"fr": "Bienvenue ! Retrouvez les informations pratiques ci-dessous."
}
}'Les téléchargements d’images doivent utiliser des adresses publiques. Les redirections sont vérifiées et des limites de taille s’appliquent. Consultez le schéma de la requête d’image pour les champs URL/base64 et limites pris en charge.
GET /upsells nécessite upsells.read. GET /bookings/{id}/upsell-purchases nécessite upsells.read et bookings.read. Ces endpoints sont en lecture seule.
curl "https://api.nowistay.com/public/v1/bookings/9001/upsell-purchases" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Ajoutez responseFormat=table aux listes de réservations ou de missions pour obtenir columns, rows et pagination, avec limit jusqu’à 500. Le JSON standard reste le format par défaut, limité à 100. Utilisez offset pour récupérer les pages suivantes. Si propertyIds et propertyId sont fournis ensemble, leurs filtres sont intersectés.
curl "https://api.nowistay.com/public/v1/bookings?propertyIds=101&propertyIds=102&fromDate=2026-12-01&toDate=2026-12-31&responseFormat=table&limit=500&offset=0" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Les tableaux de réservations utilisent bookings.read et ceux de missions missions.read. Les assignations de ménage dans un rapport de réservations nécessitent aussi missions.read, et la présence d’une conversation nécessite messages.read. Les dates de réservation gardent leur logique de chevauchement ; les dates de mission correspondent à leur planification UTC.
Les routes REST existantes, valeurs par défaut JSON, pagination, masquage des données, effacement par null pris en charge et réglages de notification restent disponibles. Les restrictions liées aux paiements décrites plus haut sont désormais appliquées. REST et MCP partagent les opérations sous-jacentes et leurs effets, tandis que les contrats des outils MCP et leur fonctionnement de prévisualisation/confirmation restent inchangés.
Une page Nowistay contient un document EditorJS par langue. Le tableau blocks définit l’ordre d’affichage. Chaque objet de ce tableau représente un bloc ; les exemples ci-dessous sont à placer dans content.blocks ou dans contents.<langue>.blocks. Les valeurs d’exemple sont fictives et doivent être adaptées à votre logement.
Structure commune : id est une chaîne stable et unique dans le document ; conservez les identifiants existants et créez-en un nouveau pour chaque nouveau bloc. type est le nom exact du bloc, sensible à la casse. data est un objet dont les champs dépendent du type. Conservez aussi les éventuels tunes et métadonnées du document renvoyés par la lecture, sans les reconstruire inutilement.
Requête de création : POST /public/v1/properties/{id}/welcome-guide/pages avec guides.write. Utilisez locale + title + content pour une seule langue, ou titles + contents pour plusieurs langues. icon vaut info par défaut. position accepte start, end ou after_page ; ce dernier exige afterPageId. Voici un corps de requête complet en anglais et français :
{
"titles": {
"en": "Arrival",
"fr": "Arrivée"
},
"contents": {
"en": {
"blocks": [
{
"id": "arrival001",
"type": "header",
"data": {
"text": "Getting here",
"level": 2
}
},
{
"id": "arrival002",
"type": "paragraph",
"data": {
"text": "Use the <b>main entrance</b>."
}
}
]
},
"fr": {
"blocks": [
{
"id": "arrival001",
"type": "header",
"data": {
"text": "Venir au logement",
"level": 2
}
},
{
"id": "arrival002",
"type": "paragraph",
"data": {
"text": "Utilisez l’<b>entrée principale</b>."
}
}
]
}
},
"position": "end"
}data.text contient le titre de section. Utilisez data.level: 2, comme dans l’éditeur du livret. Le titre de la page se place dans title ou titles, en dehors des blocs.
{
"id": "guide00001",
"type": "header",
"data": {
"text": "Arrival / Arrivée",
"level": 2
}
}data.text est une chaîne. Les balises <b>, <i> et <a href="https://…"> permettent le gras, l’italique et les liens. Échappez les guillemets dans les chaînes JSON. N’envoyez pas une page HTML complète ou du Markdown dans content.
{
"id": "guide00002",
"type": "paragraph",
"data": {
"text": "<b>Wi-Fi</b>: GuestNetwork. <i>Welcome / Bienvenue !</i> <a href=\"https://example.com/\">Information</a>"
}
}Utilisez style: unordered. items contient des objets, pas des chaînes. Chaque élément possède content (texte mis en forme), meta (objet) et items (sous-éléments, tableau vide au dernier niveau). Répétez cette structure pour les sous-listes.
{
"id": "guide00003",
"type": "list",
"data": {
"style": "unordered",
"meta": {},
"items": [
{
"content": "Kitchen / Cuisine",
"meta": {},
"items": [
{
"content": "Coffee machine / Machine à café",
"meta": {},
"items": []
}
]
}
]
}
}Utilisez style: ordered et meta.counterType: numeric à la racine. Conservez la même structure d’éléments et de sous-listes que pour une liste à puces.
{
"id": "guide00004",
"type": "list",
"data": {
"style": "ordered",
"meta": {
"counterType": "numeric"
},
"items": [
{
"content": "Close the windows / Fermez les fenêtres",
"meta": {},
"items": []
},
{
"content": "Return the keys / Rendez les clés",
"meta": {},
"items": []
}
]
}
}Utilisez style: checklist. L’état initial de chaque case est le booléen meta.checked de l’élément. Il s’agit du contenu du livret, pas d’un rapport de mission.
{
"id": "guide00005",
"type": "list",
"data": {
"style": "checklist",
"meta": {},
"items": [
{
"content": "Windows closed / Fenêtres fermées",
"meta": {
"checked": false
},
"items": []
}
]
}
}data.text contient la citation, caption son attribution (ou une chaîne vide), et alignment le réglage d’alignement de l’éditeur. Utilisez left ; la vue voyageur applique sa propre présentation.
{
"id": "guide00006",
"type": "quote",
"data": {
"text": "Please respect the neighbours / Merci de respecter le voisinage.",
"caption": "Your host / Votre hôte",
"alignment": "left"
}
}Un séparateur visuel possède un objet data vide. Sa position dans blocks détermine où il apparaît.
{
"id": "guide00007",
"type": "delimiter",
"data": {}
}data.content est un tableau de lignes ; chaque ligne est un tableau de chaînes représentant les cellules. Gardez le même nombre de cellules par ligne. Avec withHeadings: true, la première ligne contient les en-têtes. Utilisez false si toutes les lignes contiennent des données ordinaires.
{
"id": "guide00008",
"type": "table",
"data": {
"withHeadings": true,
"content": [
[
"Service",
"Time / Horaire"
],
[
"Check-in / Arrivée",
"16:00"
],
[
"Check-out / Départ",
"10:00"
]
]
}
}data.file.url est l’URL de l’image importée, pas un nom de fichier local ni une chaîne base64. caption est une chaîne ; withBorder, withBackground et stretched sont des booléens. Importez d’abord l’image via POST /public/v1/properties/{id}/welcome-guide/images, puis insérez le editorJsImageBlock renvoyé dans content.blocks. Conservez ses champs et un identifiant de bloc unique. Remplacez l’URL illustrative ci-dessous par l’URL réellement renvoyée.
{
"id": "guide00009",
"type": "image",
"data": {
"file": {
"url": "https://images.example.com/replace-with-uploaded-image.jpg"
},
"caption": "Entrance / Entrée",
"withBorder": false,
"withBackground": false,
"stretched": false
}
}Utilisez service: youtube pour une vidéo classique ou youtubeShort pour un Short. source est l’URL d’origine ; embed est l’URL YouTube d’intégration correspondante. width et height sont des nombres, caption une chaîne. L’éditeur utilise 580 × 320 pour une vidéo classique et 315 × 560 pour un Short. Remplacez VIDEO_ID_11 dans les deux URL par le même identifiant réel de 11 caractères ; pour un Short, le chemin source est /shorts/VIDEO_ID_11. Du HTML iframe libre n’est pas un format de bloc.
{
"id": "guide00010",
"type": "video",
"data": {
"service": "youtube",
"source": "https://www.youtube.com/watch?v=VIDEO_ID_11",
"embed": "https://www.youtube.com/embed/VIDEO_ID_11",
"width": 580,
"height": 320,
"caption": "Arrival instructions / Instructions d’arrivée"
}
}embed utilise les mêmes champs data que video. Conservez le type existant lors d’une modification ; les deux affichent YouTube et YouTube Shorts. Ne supposez pas que tous les fournisseurs d’intégration d’EditorJS sont pris en charge par Nowistay.
{
"id": "guide00011",
"type": "embed",
"data": {
"service": "youtubeShort",
"source": "https://www.youtube.com/shorts/VIDEO_ID_11",
"embed": "https://www.youtube.com/embed/VIDEO_ID_11",
"width": 315,
"height": 560,
"caption": ""
}
}Utilisez un objet data vide pour afficher ici le catalogue d’options éligibles. Les produits, prix et disponibilités proviennent du catalogue configuré et du contexte voyageur ; ils ne sont pas stockés dans ce bloc.
{
"id": "guide00012",
"type": "upsellCatalog",
"data": {}
}data.itemId est l’identifiant numérique d’une option existante. Retrouvez-le avec GET /public/v1/upsells et le scope upsells.read ; remplacez l’identifiant fictif 401 ci-dessous. Conservez l’identifiant d’origine sauf si vous choisissez volontairement une autre option. L’écriture de la page utilise guides.write ; ce bloc ne crée pas de produit, ne fixe pas de prix et n’effectue aucun achat.
{
"id": "guide00013",
"type": "upsellProduct",
"data": {
"itemId": 401
}
}Validation REST : les créations et modifications de pages rejettent avec HTTP 422 les types de blocs non pris en charge et les données mal formées. Toutes les langues fournies sont vérifiées avant toute écriture, y compris les éléments des listes imbriquées, les cellules des tableaux et les champs des médias. L’erreur indique le document et le bloc concernés. Les identifiants restent facultatifs pour les documents existants ; s’ils sont fournis, ils doivent être des chaînes non vides et uniques. Les métadonnées et la mise en forme sont conservées. Cette validation de structure ne vérifie pas la disponibilité réelle d’une image ni l’existence d’une option.
imageLibrary est un contrôle de sélection d’image de l’éditeur, pas une galerie à envoyer à l’API. Utilisez des blocs image. linkPreview est un réglage de bloc (tunes), pas un type de contenu indépendant ; préservez les données existantes. N’inventez pas de blocs html, code, button ou de fournisseurs vidéo non documentés : l’acceptation d’un objet JSON ne garantit pas son affichage dans le livret.
Lisez d’abord GET /public/v1/properties/{id}/welcome-guide/pages/{pageId} avec guides.read pour récupérer content et contents. Travaillez sur une copie du document complet de la langue concernée, modifiez seulement les blocs voulus, puis envoyez ce document avec PATCH sur la même URL et guides.write. Un bloc envoyé seul remplace le document par ce seul bloc ; les blocs ne sont pas fusionnés par id. Les autres langues omises restent inchangées. Pour déplacer une page existante, utilisez order, pas position.
Les champs text et texts ne remplacent pas le document structuré. Respectez les indications de lecture simpleTextUpdateAllowed, recommendedUpdateMode et recommendedUpdateField ; une page structurée refuse un remplacement par du texte brut. Les exemples unitaires de cette section décrivent des blocs, pas des requêtes PATCH complètes pour une page existante. Après la modification, relisez la page et vérifiez les langues, identifiants, liens et médias. Avec seulement guides.write, la réponse d’écriture est minimale ; ajoutez guides.read pour recevoir le détail.
Si vous ne voulez pas interroger l'API en boucle pour détecter les changements, vous pouvez vous abonner à des webhooks. Nowistay appellera alors une URL que vous fournissez chaque fois qu'une réservation est créée ou modifiée sur la propriété, en envoyant les mêmes données que GET /public/v1/bookings/{id}.
Deux événements sont supportés aujourd'hui :
booking.created : une nouvelle réservation a été enregistrée sur la propriété.booking.updated : une réservation existante a changé (dates, statut, infos voyageur, code de serrure, etc.).Chaque webhook est lié à une seule propriété et vous pouvez en enregistrer jusqu'à trois par propriété. Les livraisons sont signées (HMAC-SHA256) pour que vous puissiez vérifier qu'elles viennent bien de Nowistay, réessayées avec un délai croissant en cas d'échec, et le endpoint est désactivé automatiquement s'il continue d'échouer pendant plus de 72 heures. Les payloads webhook ne contiennent pas les champs voyageur sensibles (email, téléphone, adresse, code de serrure). Si vous en avez besoin, appelez l'API avec le scope bookings.sensitive.read.
La procédure complète et la vérification de signature sont dans l'article dédié : API publique : recevoir les webhooks de réservation en temps réel.
Quand vous atteignez la limite, l'API renvoie 429 Rate limit exceeded. Attendez quelques secondes avant de réessayer.
Toutes les erreurs suivent le même format, avec un code stable, un message lisible et un requestId à inclure dans vos demandes au support.
{
"error": {
"code": "validation_error",
"message": "The request contains an invalid value.",
"requestId": "example-request-id"
}
}silent en écriture. Mettez-le à true quand vous synchronisez en masse et que vous ne voulez pas déclencher les notifications voyageur.requestId dans vos logs. Ça accélère beaucoup la résolution côté support./public/openapi.json.