Properties
Activate or deactivate properties in bulk — the billing and visibility switch.
/v1/listings/statusNote
Request
curl -X POST https://api.repull.dev/v1/listings/status \
-H "Authorization: Bearer $REPULL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"listingIds": ["23900", "23901"], "active": false}'Response
{
"active": false,
"updated": ["23900", "23901"],
"unchanged": ["23902"]
}Body Parameters
listingIdsarrayRequiredBetween 1 and 500 unique property ids, as integers or digit strings.
activebooleanRequired`false` stops managing the properties: not billed, not readable through the API, no webhooks. Their data keeps syncing and returns when you activate them again.
The physical rooms under a listing (Mews, Cloudbeds room types)
/v1/listings/:id/unitsNote
Request
curl https://api.repull.dev/v1/listings/30594/units \ -H "Authorization: Bearer $REPULL_API_KEY"
Response
{
"listingId": "30594",
"total": 5,
"data": [
{ "id": "9868b6d9-1e6d-4e85-a64a-b731628a0da2", "name": "101", "active": true, "parentId": null, "housekeepingStatus": "Dirty", "floor": "2", "source": "mews" }
]
}Get the markup each channel adds to a listing's price
/v1/listings/:id/markupsNote
Request
curl https://api.repull.dev/v1/listings/30198/markups \ -H "Authorization: Bearer $REPULL_API_KEY"
Response
{
"id": "30198",
"airbnb": [{ "airbnbId": "1781522409437931174", "markupPercent": 35 }],
"booking": [{ "hotelId": "17325248", "markupPercent": 18, "listingIds": ["30198"] }]
}Set a listing's markup on one channel
/v1/listings/:id/markupsNote
Request
curl -X PUT https://api.repull.dev/v1/listings/30198/markups \
-H "Authorization: Bearer $REPULL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"channel": "booking", "markupPercent": 18}'Response
{
"id": "30198",
"channel": "booking",
"markupPercent": 20,
"changed": true,
"hotelId": "17325248",
"affectedListingIds": ["30198"],
"pricesResent": true
}Body Parameters
channelstringRequired`airbnb` or `booking`
markupPercentnumberRequiredPercent added on that channel: `15` for 15%. `null` removes the markup. A value between 0 and 1 is refused as a probable fraction.
hotelIdstringBooking.com property — required when the listing is on more than one.
List listings
/v1/listingsNote
Request
curl https://api.repull.dev/v1/listings?limit=50 \ -H "Authorization: Bearer $REPULL_API_KEY"
Response
{
"data": [{ "id": "4118", "name": "Lakeview Loft", "active": true }],
"pagination": { "nextCursor": null, "hasMore": false }
}Query Parameters
cursorstringOpaque cursor from the previous response's `pagination.nextCursor`. Omit for the first page.
offsetintegerAlias for shallow paging (0..10000). Mutually exclusive with `cursor`.
limitintegerPage size. Hard cap 100.
qstringCase-insensitive substring search on name, street or city.
statusstringDefaults to `active`. Pass `inactive` for listings you can activate, `archived` for archived ones.
channelstringOnly listings published on this channel (`airbnb`, `booking`, `vrbo`, …).
includestringComma-separated expansions: `content`, `details`, `thumbnail`.
Get a listing, with what the API can do with it
/v1/listings/:idNote
Request
curl https://api.repull.dev/v1/listings/4118 \ -H "Authorization: Bearer $REPULL_API_KEY"
Response
{
"id": "4118",
"name": "Lakeview Loft",
"active": true,
"capabilities": {
"reservations": {
"managedBy": "pms",
"provider": "hostaway",
"create": true,
"modify": true,
"cancel": true,
"quote": true,
"customPrice": true,
"notes": "Direct-channel bookings: create (at a set total, or priced by Hostaway's calculator), quote, change dates, guest count, guest details and notes, cancel. … Verified against Hostaway's API documentation only; not yet run against a live Hostaway account.",
"verifiedAgainst": "vendor_docs"
},
"pms": {
"provider": "hostaway",
"connected": true,
"reservations": { "respond": false, "preapprove": false },
"reviews": { "read": true, "reply": false },
"listings": { "title": true, "descriptions": true, "times": true, "capacity": true, "amenities": true, "houseRules": false, "address": true, "photosAdd": true, "photosDelete": true, "photosReorder": true, "photoCaptions": true },
"guests": { "create": false, "update": false },
"conversations": { "send": true, "attachments": false, "channelSelect": true },
"calendar": { "write": true },
"payments": { "read": false },
"tasks": { "read": true, "write": true },
"notes": { "listings": "Guest-facing and internal name, descriptions, check-in/out times (whole hours), … House-rule flags (pets, smoking, events, children) are not writable; …" }
}
}
}Query Parameters
includestringComma-separated expansions: `amenities`, `content`, `details`.
List all properties in the account — the older name for GET /v1/listings
/v1/propertiesRequest
curl https://api.repull.dev/v1/properties?limit=50 \ -H "Authorization: Bearer $REPULL_API_KEY"
Response
{
"data": [
{
"id": "123",
"name": "Example",
"address": "Example text",
"city": "Example text",
"currency": "USD",
"status": "active",
"lifecycleStatus": "live",
"channels": [],
"updatedAt": "2026-06-01T12:00:00.000Z"
}
],
"pagination": {
"nextCursor": "eyJpZCI6MTIzfQ==",
"hasMore": true,
"total": 0
}
}Captured from a real response. Regenerated on every release, and CI fails if this shape stops matching the API.
Query Parameters
accountstringOnly one connected account's records, as `provider:externalAccountId` (e.g. `airbnb:79730216`, `booking:10167991`, `vrbo:36`, `hostaway:…`) — the pair on every webhook `account` block, on `connect.session.completed`, and on each record's `account`. See /for-platforms.
limitintegerPage size (max 100). Requests over the cap return 422.
cursorstringOpaque cursor returned in the previous response's `pagination.nextCursor`. Omit to fetch the first page.
offsetintegerFirst-class alias for cursor-based pagination. Mutually exclusive with `cursor` — passing both returns 422. Accepts integers in `[0, 10000]`; deeper walks must use `cursor` (constant per-page cost). The response always includes `pagination.nextCursor` so consumers can switch from offset → cursor mid-walk for deep pagination without re-keying.
qstringCase-insensitive substring search on name, street, or city.
statusstringFilter by status. Default returns active only; pass `inactive` to invert or `all` to include both. Inactive properties carry identity fields only — `id`, `name`, `status`, `lifecycleStatus`, `channels` and `updatedAt` — never `address`, `city` or `currency`.
lifecycle_statusstringFilter by lifecycle status (e.g. `live`, `draft`, `archived`). Pass `all` to disable the filter.
channelstringFilter to properties with an active link on the given OTA/channel (airbnb, booking). Omit to include every channel. Each property also returns a `channels` array listing the OTAs it is published on.
updated_sincedateIncremental sync: return only records whose `updatedAt` is at or after this instant. This is the only filter on record **mutation** time — every `check_*` filter targets guest **stay** dates. **Accepted formats.** ISO 8601, with `Z` or a numeric offset — both work: - `2026-08-01T00:00:00Z` - `2026-08-01T00:00:00.123Z` - `2026-08-01T00:00:00+00:00` - `2026-08-01T02:30:00-07:00` (offset colon optional: `-0700`) - `2026-08-01T00:00` (seconds optional) - `2026-08-01T00:00:00` — no zone designator, interpreted as **UTC** - `2026-08-01` — date only, means midnight UTC Anything else returns 422 `invalid_params` naming the field; the value is never silently ignored. **Ordering changes when you pass this.** Results are ordered `updatedAt ASC, id ASC` (instead of the endpoint default) and the cursor keys on the same pair. That is required for correctness: under the default ordering a record amended mid-walk can move behind the cursor and never be emitted — which is exactly the event you are polling for. Ascending mutation time is monotonic with the cursor, so anything touched during a walk resurfaces later in it or on the next poll. **Cursors are not interchangeable between the two orderings.** Keep `updated_since` on every page of an incremental walk; replaying a cursor from the other ordering returns 422 rather than a page that silently skips rows. **Watermark.** The bound is inclusive (`updatedAt >= value`), so the last row of the final page is the watermark for the next poll — re-polling with it re-emits that row. Delivery is at-least-once; upsert by `id`.
include_totalbooleanWhen `true` (default), the response's `pagination.total` carries the count of rows matching the current filter, across all pages. Pass `false` to skip the count for very large workspaces where the per-page COUNT(*) cost matters.
Get a single property by ID — the older name for GET /v1/listings/{id}
/v1/properties/:idRequest
curl https://api.repull.dev/v1/properties/123 \ -H "Authorization: Bearer $REPULL_API_KEY"
Response
{
"id": "123",
"name": "Example",
"address": "Example text",
"city": "Example text",
"latitude": "Example text",
"longitude": "Example text",
"currency": "USD",
"status": "active",
"lifecycleStatus": "live",
"createdAt": "2026-06-01T12:00:00.000Z"
}Captured from a real response. Regenerated on every release, and CI fails if this shape stops matching the API.