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

  1. Each listing-night is scored against demand, the comp set, occupancy, lead time, day of week, and a market events feed.
  2. The output is a per-night recommendedPrice plus the factors that drove it. Read it from GET /v1/listings/{id}/pricing.
  3. 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

Nothing changes on your channels until you apply. Recommendations are surfaced; your code decides. Wire applies behind a button, an automation, or a nightly cron — whatever fits your workflow.

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".

GET/v1/listings/{id}/pricing
curl '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.

POST/v1/listings/{id}/pricing
curl -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)Required

The nights the action applies to. Dates without a recommendation are skipped.

action"apply" | "decline"Required

apply 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

An apply call pushes to every channel the listing is connected to. There is no preview / dry-run mode on this endpoint — if you want a preview, render the recommendation in your UI and only call apply on confirm.

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.

GET/v1/listings/{id}/pricing/strategy·PUT/v1/listings/{id}/pricing/strategy
curl -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 | null

The lowest nightly price the model may recommend.

maxPricenumber | null

The highest nightly price the model may recommend.

maxDailyChangePctnumberDefault: 15

Largest day-over-day change, in percent.

weekendMarkupPctnumber | null

Percentage added on Friday and Saturday nights.

dayOfWeekMultipliersRecord<string, number>

Multiplier per weekday, keyed 0 (Sunday) to 6 (Saturday).

targetOccupancyPctnumber | null

Occupancy the model should aim for.

targetMonthlyRevenuenumber | null

Monthly revenue target.

ownerMinMonthlyPayoutnumber | null

Minimum monthly owner payout to protect.

compPositionTarget"below" | "match" | "above"Default: match

Where to sit relative to comparable listings.

compAdjustPctnumberDefault: 0

Extra adjustment against the comp median, -30 to +30.

eventBoostEnabledbooleanDefault: true

Raise prices around local events.

eventBoostMaxPctnumberDefault: 30

Cap 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

Minimum and maximum prices and the other strategy settings apply to recommendations, not to direct calendar or channel writes. Whatever price you send is what gets pushed, so keep the guardrails in your own engine.

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.

AI