AI Pricing
A recommended nightly rate is pre-computed for your listings, every night, for the upcoming calendar. You apply or decline. Applied rates are written to the listing calendar and pushed to its connected channels — Airbnb, Booking.com and VRBO. Powered by Atlas. Learn more about Atlas →
How it works
- Each listing-night is scored against demand, the comp set, occupancy, lead time, day of week, and a market events feed.
- The output is a per-night
recommendedPriceplus the factors that drove it. Read it fromGET /v1/listings/{id}/pricing. - You either apply (write the recommended price to the calendar and push it to the connected channels) or decline (the recommendation stops surfacing) via
POST /v1/listings/{id}/pricing.
Recommendations are advisory
Get recommendations
Returns one row per night for a date window, with the recommended price, the price currently on the calendar, and the contributing factors so you can show the "why".
/v1/listings/{id}/pricingcurl 'https://api.repull.dev/v1/listings/4118/pricing?startDate=2026-06-01&endDate=2026-06-30' \ -H 'Authorization: Bearer sk_live_...'
Query parameters
startDatestring (YYYY-MM-DD)First night in the window, inclusive. Defaults to today.
endDatestring (YYYY-MM-DD)Last night in the window, inclusive. Defaults to today + 90 days.
Response (excerpt)
{
"listingId": "4118",
"dateRange": { "start": "2026-06-01", "end": "2026-06-30" },
"recommendations": [
{
"date": "2026-06-12",
"currentPrice": 180,
"recommendedPrice": 235,
"minPrice": 150,
"maxPrice": 320,
"currency": "EUR",
"confidence": 0.82,
"bookingProbability": 0.64,
"expectedRevenue": 150.4,
"factors": { "demand": "high", "event": "Rock in Rio Lisboa", "weekend": true },
"status": "pending",
"modelVersion": "v2",
"generatedAt": "2026-06-01T03:14:09.000Z"
}
],
"listing": { "aiBasePrice": 190, "currency": "EUR" },
"compSummary": { "count": 48, "avgPrice": 224, "minPrice": 95, "maxPrice": 610 }
}status is pending, applied or declined. factors is free-form — render its keys as chips rather than depending on specific ones.
Apply or decline
One action per call, applied to every date you list. Apply writes the recommended price to the calendar for those dates and pushes it to the connected channels. Decline marks the recommendations as declined so they stop surfacing; a later model run can recommend again.
/v1/listings/{id}/pricingcurl -X POST 'https://api.repull.dev/v1/listings/4118/pricing' \
-H 'Authorization: Bearer sk_live_...' \
-H 'Content-Type: application/json' \
-d '{ "dates": ["2026-06-12", "2026-06-13"], "action": "apply" }'Body
datesstring[] (YYYY-MM-DD)RequiredThe nights the action applies to. Dates without a recommendation are skipped.
action"apply" | "decline"Requiredapply writes the recommended price and pushes it to the connected channels. decline stops the recommendation surfacing.
Response
{ "ok": true, "applied": 2 }Apply pushes to live channels
Many listings at once
POST /v1/listings/pricing/bulk applies or declines across up to 500 listing entries in one call. One item failing (no pending recommendation, a stale listing id, a channel hiccup) does not fail the batch — it is reported in failed[] so you can retry just those.
curl -X POST 'https://api.repull.dev/v1/listings/pricing/bulk' \
-H 'Authorization: Bearer sk_live_...' \
-H 'Content-Type: application/json' \
-d '{
"action": "apply",
"items": [
{ "listingId": "4118", "dates": ["2026-06-12", "2026-06-13"] },
{ "listingId": "4120", "dates": ["2026-06-12"] }
]
}'
# { "processed": 3, "succeeded": 3, "failed": [] }Pricing strategy
Per-listing settings that constrain what the model can recommend. GET returns the current strategy (or the defaults, flagged isDefault: true, when none has been saved). PUT saves it — send only the fields you want to change; repeated PUTs with the same body are idempotent.
/v1/listings/{id}/pricing/strategy·PUT/v1/listings/{id}/pricing/strategycurl -X PUT 'https://api.repull.dev/v1/listings/4118/pricing/strategy' \
-H 'Authorization: Bearer sk_live_...' \
-H 'Content-Type: application/json' \
-d '{
"mode": "recommend",
"minPrice": 95,
"maxPrice": 420,
"weekendMarkupPct": 18,
"maxDailyChangePct": 15,
"compPositionTarget": "match"
}'Fields
mode"recommend" | "auto"recommend surfaces suggestions for you to apply; auto applies them on the next sync.
minPricenumber | nullThe lowest nightly price the model may recommend.
maxPricenumber | nullThe highest nightly price the model may recommend.
maxDailyChangePctnumberDefault: 15Largest day-over-day change, in percent.
weekendMarkupPctnumber | nullPercentage added on Friday and Saturday nights.
dayOfWeekMultipliersRecord<string, number>Multiplier per weekday, keyed 0 (Sunday) to 6 (Saturday).
targetOccupancyPctnumber | nullOccupancy the model should aim for.
targetMonthlyRevenuenumber | nullMonthly revenue target.
ownerMinMonthlyPayoutnumber | nullMinimum monthly owner payout to protect.
compPositionTarget"below" | "match" | "above"Default: matchWhere to sit relative to comparable listings.
compAdjustPctnumberDefault: 0Extra adjustment against the comp median, -30 to +30.
eventBoostEnabledbooleanDefault: trueRaise prices around local events.
eventBoostMaxPctnumberDefault: 30Cap on the event boost, in percent.
Push your own rates
If you run your own pricing engine and want Repull as the delivery layer, skip the recommendations and write the calendar directly.
Default: write the calendar once, reach every channel
PUT /v1/availability/{propertyId} (one property) and PATCH /v1/availability/batch (up to 500 properties) write nightly price, open/closed, and min/max nights to the Repull calendar and push the change to every connected channel — Airbnb, Booking.com and VRBO — in the same step. The response reports how each push went. Use this for calendar values; see Update Pricing.
curl -X PUT 'https://api.repull.dev/v1/availability/4118' \
-H 'Authorization: Bearer sk_live_...' \
-H 'Content-Type: application/json' \
-d '{ "dates": ["2026-06-12", "2026-06-13"], "price": 235, "available": true }'Channel-specific settings
Use a channel endpoint only for settings that exist on that one channel. These write to that channel alone.
PUT /v1/channels/airbnb/listings/{id}/pricing— Airbnb pricing model, standard settings, length-of-stay records, rate plans, fees, currency, pricing rules, and per-date calendar operations (including closed-to-arrival/departure). See Update Airbnb Pricing.PUT /v1/channels/airbnb/listings/{id}/availability— Airbnb availability rules and per-date restrictions. See Push Availability to Airbnb.GET / PUT /v1/channels/booking/listings/{id}/pricing— Booking.com rates per room and rate plan.GET / PUT /v1/channels/plumguide/pricing— Plum Guide.- VRBO has no channel-specific pricing endpoint. The calendar write above pushes to VRBO.
Direct writes bypass your strategy
History
GET /v1/listings/{id}/pricing/history returns recommendations against applied prices for a listing, cursor-paginated. It covers ±90 days from today unless you pass startDate / endDate; follow pagination.nextCursor with ?cursor=. limit is capped at 500.
Common patterns
Apply all pending recommendations for the next 30 days
const today = new Date().toISOString().slice(0, 10)
const in30 = new Date(Date.now() + 30 * 86_400_000).toISOString().slice(0, 10)
const { recommendations } = await repull.listings.pricing.get(4118, { startDate: today, endDate: in30 })
const pending = recommendations.filter(n => n.status === 'pending').map(n => n.date)
if (pending.length > 0) {
await repull.listings.pricing.action(4118, { dates: pending, action: 'apply' })
}Decline weekends, apply weekdays
Owner only wants AI managing weekday rates and keeps a fixed weekend rate. One action per call, so split the dates.
const { recommendations } = await repull.listings.pricing.get(4118, { startDate, endDate })
const pending = recommendations.filter(n => n.status === 'pending')
const isWeekend = (date: string) => [5, 6].includes(new Date(date).getUTCDay()) // Fri, Sat
await repull.listings.pricing.action(4118, {
dates: pending.filter(n => !isWeekend(n.date)).map(n => n.date),
action: 'apply',
})
await repull.listings.pricing.action(4118, {
dates: pending.filter(n => isWeekend(n.date)).map(n => n.date),
action: 'decline',
})Compare what was suggested vs what is live
Each row in GET /v1/listings/{id}/pricingalready returns both. Diff them to render a "recommended vs live" chart.
const { recommendations } = await repull.listings.pricing.get(4118, { startDate, endDate })
const series = recommendations.map(n => ({
date: n.date,
recommended: n.recommendedPrice,
live: n.currentPrice,
}))Rate limits
Pricing endpoints follow the standard tier-based limits. See Rate Limits for per-tier numbers. POST /v1/listings/pricing/bulk counts as one call however many items it carries.