Vivin Booking Engine integration
The vivin-booking-engine-integration API connects a white-label booking site (the Vivin Booking Engine) to VIVIN, for the developers who build or maintain that site.
Before you start
| Item | Value |
|---|---|
| Base URL | https://api.vivin.app/vivin-booking-engine-integration. Swagger is at the same address; the OpenAPI file at https://api.vivin.app/vivin-booking-engine-integration-json |
| Authentication | Authorization: Bearer <partner-token> on every route, plus vivin-api-key: <account-key> on POST /property-listing (Authentication) |
| Scope | The token is not tied to one account: GET /listings returns the booking-engine units of every account that uses the engine. A site serving one account keeps the units whose landlordEmail is that account's Integration Email |
| Account side | Landlords manage the Vivin Booking Engine card under Settings → Integrations → Booking Platforms |
Endpoints
| Method | Path | What it does |
|---|---|---|
GET | /listings | Lists every published unit with its calendar and prices |
GET | /listings/{externalId} | Returns one unit |
PUT | /listing | Writes rent and stay limits of one linked unit back into VIVIN |
POST | /property-listing | Creates a property and its units in one account |
POST | /bookings | Sends a confirmed booking. VIVIN queues it and creates it later |
GET | /listings/{externalId}/housemates | Lists the people who live in the unit's property today |
Which units the API returns
A unit is published to the engine only when all of these are true:
- It is linked to the booking engine. An operator opens the unit in Portfolio → Listings, and in the Channels section of the Setup tab clicks the pencil (Add listing URL) on the booking-engine row, which shows the account's logo or the VIVIN logo. They paste the unit's page address on your site, in the form
…/house/<property id>/<room id>. Units created withPOST /property-listingare linked automatically. - It is Included in feed: in the unit's Full integration tab, the Integration listings section shows the VIVIN row as included. This is the default.
- The unit is not archived, and its property is not archived.
- It has a usable rent: above zero, and for seasonal rent, above zero in every month.
The externalId comes from the unit's page address on your site:
| Page address on your site | externalId |
|---|---|
…/house/8812/3 (a room) | 8812_3 |
…/house/8812 (a whole property or studio) | 8812 |
A unit linked by its property ID alone is still found with a reference such as 8812_3.
Read listings
GET /listings and GET /listings/{externalId} return the same listing object as every partial feed: externalId, availableFrom, minStayPeriod, maxStayPeriod, isRentFixed, rent, rentsPerMonth, capacity, the fees and deposit, unavailabilities, bookingWindows and landlordEmail. Every field is described in The listing object.
Example request
curl https://api.vivin.app/vivin-booking-engine-integration/listings/8812_3 \
-H "Authorization: Bearer <partner-token>"
- Both responses are live: a change in VIVIN shows up on the next call.
rentandrentsPerMonthinclude the Markup on the Vivin Booking Engine card. When the rent varies by month,rentis the highest month.unavailabilities[].tois the last blocked night: a tenant who checks out on 23 December shows"to": "2026-12-22", plus any preparation days. To get the next possible move-in, readavailableFrom(Reading availability).- With a Calendar Block (days) on the card, a unit whose last booking or block ends more than that many days from today also gets a block for the two years after it.
GET /listingsleaves out blocks that ended before today; the single-unit call returns them all.- No marketing copy, amenities or photos.
Errors
| Status | message | Cause | What to do |
|---|---|---|---|
404 | Listing not found for vivinbookingengine with externalId <id> | The unit is unknown, not linked to the booking engine, excluded from the feed, or archived (unit or property) | Check the link in the unit's Channels |
404 | Listing not available | The unit has no usable rent | Set a rent with PUT /listing |
401 | Missing integration credentials and others | Missing or wrong partner token | See Authentication |
Update a listing
PUT /listing writes price and stay changes made on your site back into VIVIN. Use it only for changes that start on your site: changes made in VIVIN are pushed to you (Updates VIVIN sends).
Request fields
The schema requires every field below, but VIVIN saves only some of them.
| Name | Type | Required | Saved | Description |
|---|---|---|---|---|
externalId | string | Yes | — | The unit to update |
isRentFixed | boolean | Yes | Yes | true clears the monthly schedule. false uses rentsPerMonth |
rent | number | Yes | Yes | Monthly rent to store, without markup. 0 leaves the unit without a usable rent, so it leaves the API until priced |
rentsPerMonth | array | Yes | Yes | Entries with month (1–12) and rent (0 or more). A month you leave out takes rent. Send [] for a fixed rent |
minStayPeriod | number | Yes | Yes | Minimum stay in months, a whole number, 0 or more |
maxStayPeriod | number | Yes | Yes | Maximum stay in months, a whole number, 0 or more. 0: no maximum |
extraPricePerTenant | number | Yes | Yes | Extra monthly rent for each occupant after the first |
availableFrom | date | Yes | No | VIVIN works it out from its own calendar |
capacity, cleaningFeeValue, billsIncludedMaxValue, adminFeeValue, depositValue | number | Yes | No | Change these in VIVIN |
landlordEmail | string | Yes | No | |
unavailabilities | array | Yes | No | Entries with from, to (dates) and reason. To block dates, an operator adds a block in VIVIN, or you send a booking |
Send the amounts you want stored, not the prices you read: reads add the platform markup, so writing a read price back adds it twice. Operators see the changes in the unit's Changelog.
Example request
PUT /vivin-booking-engine-integration/listing HTTP/1.1
Host: api.vivin.app
Authorization: Bearer <partner-token>
Content-Type: application/json
{
"externalId": "8812_3",
"landlordEmail": "[email protected]",
"availableFrom": "2026-10-01",
"minStayPeriod": 3,
"maxStayPeriod": 12,
"isRentFixed": true,
"rent": 650,
"capacity": 1,
"extraPricePerTenant": 0,
"cleaningFeeValue": 0,
"billsIncludedMaxValue": 0,
"adminFeeValue": 0,
"depositValue": 650,
"rentsPerMonth": [],
"unavailabilities": []
}
Response
200 OK with an empty body. Swagger shows true as the response, but the body is empty.
Errors
| Status | message | Cause | What to do |
|---|---|---|---|
400 | <field> must be a number conforming to the specified constraints, rentsPerMonth.0.month must not be greater than 12 and similar | A missing field or a wrong type | Send every field with its type |
400 | property <name> should not exist | An unknown key | Remove it |
400 | Min stay period must be a non-negative integer (or Max stay period) | A negative or fractional stay limit | Send a whole number |
400 | Min stay period cannot be greater than max stay period | minStayPeriod above a maxStayPeriod that is not 0 | Fix the limits |
404 | Listing not found for vivinbookingengine with externalId <id> | The unit is not linked to the engine, excluded from the feed, or archived (unit or property) | Check the link. A unit without a usable rent can still be updated |
410 | Gone: this account is served by the native VIVIN Booking Engine… | The request carries a vivin-api-key of an account that moved to the native engine | Stop sending requests for that account |
Create a property and its units
POST /property-listing creates one property and one unit for each room, in the account that vivin-api-key identifies.
Request headers
| Name | Required | Description |
|---|---|---|
Authorization | Yes | Bearer <partner-token> |
vivin-api-key | Yes | The landlord account's key. Landlords see its last characters as the read-only Integration API key on the Vivin Booking Engine card. Swagger cannot send it: use curl |
Request fields
Only id and address are required. The schema accepts many more fields (photos, amenities, visit times, house rules) that are not saved; unknown top-level keys return 400.
| Name | Type | Required | What VIVIN does with it |
|---|---|---|---|
id | string | Yes | Your property ID. It becomes the first part of each unit's externalId |
address | string | Yes | The property address |
floor | string | No | The floor |
internal_name | string | No | The property name |
incompleteRentType | string | No | How partial months are charged: daily, monthly, or biweekly / fortnightly |
paidAtMovinDate | string | No | Security Deposit: only the deposit is due at move-in. Any other value: the deposit and the last rent |
securityDeposit | string | No | The deposit amount as text, for example "650" |
extraAdministrativeFee | number | No | The admin fee, switched on when above 0 |
cleaningLadyPrice | number | No | The cleaning fee, switched on when above 0 |
billsIncluded | boolean | No | Bills included in the rent |
billsMax | number | No | The cap on included bills |
rooms | array | No | One unit for each entry |
Each entry in rooms:
| Name | Type | What VIVIN does with it |
|---|---|---|
id | string or number | Your room ID. The unit's externalId becomes <property id>_<room id> |
accommodation | string | rooms creates a room; any other value a whole-property unit |
internalName | string | The unit name. It must be unique in the account |
number | number | The room label: 3 becomes Room 3 (default Room 1) |
rent, fixedRent | number, boolean | The monthly rent. Send fixedRent: true: seasonal rent cannot be created here |
periodStayMin | number | The minimum stay in months |
periodStayMax, periodStayMaxBoolean | number, string | The maximum stay in months, used only when periodStayMaxBoolean is the string "true" |
numPeople, extraTenantsPrice | number | The unit's capacity (default 1) and the extra rent for each additional tenant |
Other property rules take defaults: the contract type is Traditional rental, and the First rent is due to confirm a booking. Operators can change them in the property editor.
Example request
curl -X POST https://api.vivin.app/vivin-booking-engine-integration/property-listing \
-H "Authorization: Bearer <partner-token>" \
-H "vivin-api-key: <account-key>" \
-H "Content-Type: application/json" \
-d '{
"id": "8812",
"address": "Example Street 10, 1000-001 Lisbon",
"internal_name": "Example Street 10",
"incompleteRentType": "daily",
"paidAtMovinDate": "Security Deposit",
"securityDeposit": "650",
"rooms": [
{
"id": "3",
"accommodation": "rooms",
"internalName": "Example Street 10 - Room 3",
"number": 3,
"rent": 650,
"fixedRent": true,
"periodStayMin": 3,
"numPeople": 1
}
]
}'
Response
201 Created with { "property": { … }, "listings": [ … ] }, the records VIVIN created. { "property": null, "listings": [] } means VIVIN has turned property creation off for that account: contact VIVIN.
Errors
| Status | message | Cause | What to do |
|---|---|---|---|
400 | id must be a string, address must be a string | A required field is missing or has the wrong type | Send both as strings |
400 | property <name> should not exist | An unknown top-level key | Remove it |
400 | Listing with this internal name already exists | A room's internalName is already used in the account | Use a unique name |
400 | Rent per month is required when rent is not fixed | A room with fixedRent: false or no fixedRent | Create it with a fixed rent, then send monthly prices with PUT /listing |
401 | Missing vivin-api-key header | No vivin-api-key, or an empty one | Add the account key |
401 | Unauthorized | The vivin-api-key matches no account, or the partner token is wrong | Check both keys |
410 | Gone: this account is served by the native VIVIN Booking Engine… | The account moved to the native engine | Stop sending requests for that account |
When a room fails, VIVIN returns the error and emails the account. The property and earlier rooms may already exist, so check Listings before you retry.
Send a booking
POST /bookings works as on every partner prefix: Creating bookings has the fields, the 201 response and the errors.
{
"externalId": "8812_3",
"checkInDate": "2026-10-01",
"checkOutDate": "2027-06-30",
"bookingId": "3f6c2a9e-8b1d-4c7a-9e2f-5d4b7a1c0e93",
"numberOfExtraTenants": 1,
"tenant": {
"firstName": "Casey",
"lastName": "Sample",
"email": "[email protected]",
"phone": "+15555550100",
"fiscalId": "123456789",
"nationality": "PT"
}
}
201means the booking was queued, not created. An unknownexternalIdor overlapping dates still get201, and the booking then fails in the background (Booking lifecycle and validations).platformProviderPaymentValueis optional here: when you leave it out, VIVIN works it out from the unit's rent.numberOfExtraTenantsis the total number of people:1means one tenant.tenant.nationalityis the tenant's two-letter ISO 3166-1 country code, in either letter case (PTorpt). VIVIN shows it on the booking's tenant details. A tenant who already exists in the account keeps their details (Tenant fields).- Duplicates are dropped silently: use a new
bookingIdfor each new reservation (Retries and duplicates).
Housemates
GET /listings/{externalId}/housemates supports a "meet your housemates" section on a room page.
curl https://api.vivin.app/vivin-booking-engine-integration/listings/8812_3/housemates \
-H "Authorization: Bearer <partner-token>"
Response
200 OK with an array:
[
{
"name": "Casey Sample",
"nationality": "PT",
"moveIn": "2026-02-01",
"moveOut": "2026-11-30",
"photo": null,
"age": null,
"occupation": null,
"gender": null
}
]
| Field | Type | Description |
|---|---|---|
name | string | The tenant's first and last name |
nationality | string or null | null when VIVIN has none |
moveIn, moveOut | date | The booking's contract dates |
photo, age, occupation, gender | null | Always null |
- The list covers everyone living in the same property today: bookings whose contract dates include today, not cancelled. It includes the current tenant of the requested unit.
- A whole-property unit returns
[]. - The response holds tenants' names. Show it only where your site's privacy notice covers it.
- An unknown or unpublished unit returns
404 Listing not found for vivinbookingengine with externalId <id>.
Updates VIVIN sends to the engine
When a linked unit changes in VIVIN, VIVIN pushes it to the booking engine without waiting for a call from you. It checks for changed units every minute and sends each one as a POST to the engine's /updateListing endpoint, with the account's key in the vivin-api-key header. The body has the same fields as GET /listings/{externalId}, except that the unit reference is named listing_id instead of externalId.
Answer 200. Any other status marks the push as failed; VIVIN does not retry it, and sends the unit again only after its next change. To catch up after downtime, read GET /listings.
Errors
Every route can also return the standard errors. Specific to this prefix:
| Status | message | Cause | What to do |
|---|---|---|---|
401 | Missing vivin-api-key header | POST /property-listing without the account key | Add vivin-api-key |
410 | Gone: this account is served by the native VIVIN Booking Engine… | Any request carrying the vivin-api-key of an account that moved to VIVIN's native booking engine | Stop sending requests for that account. Retrying does not help |
Troubleshooting
| Symptom | Likely cause and fix |
|---|---|
| Prices grow every time your site saves a unit | You write back the prices you read, which include the markup. Send the amounts to store |
A unit disappeared after PUT /listing | Its rent is now 0, or a month is at 0. Set a rent |
POST /property-listing returns an empty result | Property creation is turned off for that account. Contact VIVIN |
POST /property-listing failed half-way | Some rooms may exist already. Check Listings before you retry |
| Your site shows other landlords' units | The token covers every account on the engine. Filter on landlordEmail |
Related
- Listings and availability: every field of the listing responses
- Creating bookings: the full
POST /bookingscontract - Booking lifecycle and validations: what happens to a queued booking
- Property and unit mapping: how units get their
externalId - Integrations settings: the Vivin Booking Engine card