AI Pricing

A recommended nightly rate is pre-computed for every one of your listings, every night, out 365 days. You apply or decline. Applied rates fan out to every connected channel — Airbnb, Booking.com, Vrbo, Plum Guide — automatically. Powered by Atlas. Learn more about Atlas →

How it works

  1. Each listing-night is scored against demand, comp set, occupancy, lead time, day of week, and a market events feed.
  2. The output is a per-night recommended_rate plus the factors that drove it. Read it from GET /v1/listings/{id}/pricing.
  3. You either apply (push to all connected channels) or decline (no-op, recorded for the audit trail) via POST /v1/listings/{id}/pricing.
  4. Behind the scenes, applying triggers the channel adapters — Airbnb pushes through the Airbnb pricing API, Booking.com through CNS, Vrbo through Expedia rates, Plum Guide through their pricing endpoint.

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 range, with the recommended rate, the rate currently live on your channels, and the contributing factors so you can show the "why".

GET/v1/listings/{id}/pricing
curl 'https://api.repull.dev/v1/listings/lst_abc123/pricing?from=2026-06-01&to=2026-06-30' \
  -H 'Authorization: Bearer sk_live_...'

Query parameters

fromstring (YYYY-MM-DD)Required

First night in the range. Inclusive.

tostring (YYYY-MM-DD)Required

Last night in the range. Inclusive. Max 365 nights from `from`.

Response (excerpt)

{
  "listing_id": "lst_abc123",
  "currency": "EUR",
  "data": [
    {
      "date": "2026-06-12",
      "current_rate": 180,
      "recommended_rate": 235,
      "delta_pct": 0.306,
      "applied": false,
      "factors": {
        "demand": 1.74,
        "comp_set": 224,
        "occupancy": 0.92,
        "lead_time": "11d",
        "day_of_week": "Friday",
        "events": ["Rock in Rio Lisboa — Day 1"]
      }
    },
    {
      "date": "2026-06-13",
      "current_rate": 180,
      "recommended_rate": 248,
      "delta_pct": 0.378,
      "applied": false,
      "factors": {
        "demand": 1.81,
        "comp_set": 238,
        "occupancy": 0.95,
        "lead_time": "12d",
        "day_of_week": "Saturday",
        "events": ["Rock in Rio Lisboa — Day 2", "Festas de Lisboa"]
      }
    }
  ],
  "computed_at": "2026-04-30T03:14:09.000Z"
}

Apply or decline

Apply pushes the recommended rate to every connected channel for that night. Decline records a no-op so the audit trail and ML feedback loop know you saw the recommendation and chose not to take it.

POST/v1/listings/{id}/pricing
curl -X POST 'https://api.repull.dev/v1/listings/lst_abc123/pricing' \
  -H 'Authorization: Bearer sk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{
    "decisions": [
      { "date": "2026-06-12", "action": "apply" },
      { "date": "2026-06-13", "action": "apply" },
      { "date": "2026-06-14", "action": "decline", "reason": "owner block" }
    ]
  }'

Body

decisions[].datestring (YYYY-MM-DD)Required

The night the decision applies to.

decisions[].action"apply" | "decline"Required

apply pushes the recommended rate to every connected channel. decline is a no-op recorded for audit + ML feedback.

decisions[].ratenumber

Optional override. If you pass a rate, that rate is pushed instead of the recommendation. Useful when a human edits the suggestion before applying.

decisions[].reasonstring

Free-form note attached to the decline. Stored on the audit trail.

Response (excerpt)

{
  "listing_id": "lst_abc123",
  "applied": 2,
  "declined": 1,
  "fanout": {
    "airbnb": { "ok": 2, "errors": 0 },
    "booking": { "ok": 2, "errors": 0 },
    "vrbo":    { "ok": 2, "errors": 0 }
  },
  "audit_id": "pa_8gQrT2v9k3M4nLp7"
}

Apply pushes to live channels

An apply call fans out immediately 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.

Pricing strategy

Per-listing knobs that constrain what the model can recommend. Floors and ceilings, weekday vs weekend multipliers, and lead-time curves are the most common.

GET/v1/listings/{id}/pricing/strategy·PUT/v1/listings/{id}/pricing/strategy
curl -X PUT 'https://api.repull.dev/v1/listings/lst_abc123/pricing/strategy' \
  -H 'Authorization: Bearer sk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{
    "floor": 95,
    "ceiling": 420,
    "weekend_multiplier": 1.18,
    "lead_time_curve": [
      { "days_out": 1,  "multiplier": 0.85 },
      { "days_out": 7,  "multiplier": 0.95 },
      { "days_out": 30, "multiplier": 1.00 },
      { "days_out": 90, "multiplier": 1.05 }
    ],
    "weekday_overrides": {
      "monday":    0.92,
      "tuesday":   0.92,
      "wednesday": 0.95,
      "thursday":  1.00,
      "friday":    1.18,
      "saturday":  1.22,
      "sunday":    1.05
    }
  }'

Fields

floornumber

The minimum nightly rate. Recommendations are clamped at this value — the model will never suggest below it.

ceilingnumber

The maximum nightly rate. Same idea, opposite end. Useful for owner-imposed caps.

weekend_multipliernumberDefault: 1.0

Friday + Saturday multiplier applied on top of the base recommendation. 1.18 = +18%.

lead_time_curveArray<{ days_out, multiplier }>

Per lead-time multiplier. The model interpolates between points. Common pattern is to discount last-minute and slightly mark up far-out dates.

weekday_overridesRecord<weekday, multiplier>

Per-weekday multiplier. Overrides weekend_multiplier when both are set on Friday/Saturday.

Direct channel push (advanced)

For power users who already run their own pricing engine and just want Repull as the delivery layer. Skip the recommendation pipeline entirely and push raw rates straight to a single channel.

  • GET /v1/channels/airbnb/listings/{id}/pricing — read the rates currently live on Airbnb.
  • PUT /v1/channels/airbnb/listings/{id}/pricing — push raw rates to Airbnb.
  • GET / PUT /v1/channels/plumguide/pricing — same pattern, Plum Guide.
  • /v1/channels/booking/... and /v1/channels/vrbo/... — coming next.
curl -X PUT 'https://api.repull.dev/v1/channels/airbnb/listings/lst_abc123/pricing' \
  -H 'Authorization: Bearer sk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{
    "rates": [
      { "date": "2026-06-12", "rate": 235, "currency": "EUR" },
      { "date": "2026-06-13", "rate": 248, "currency": "EUR" }
    ]
  }'

Direct push bypasses your strategy

Floors, ceilings, and multipliers are not enforced on direct channel calls. Whatever rate you pass is what gets pushed. Make sure your own engine has the guardrails it needs.

Bulk operations (coming soon)

A bulk-apply endpoint for fanning a single decision policy across many listings is in flight. Until it ships, the right pattern is to fetch your listing IDs, fan out POST /v1/listings/{id}/pricing calls in parallel, and dedupe per-channel push errors on your side.

History audit (coming soon)

A pricing history endpoint that returns every recommendation, decision, applied rate, and per-channel push outcome for a listing-night is in flight. The audit_id returned by the apply call today will be the lookup key when it ships.

Common patterns

Apply all 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 { data } = await repull.pricing.list('lst_abc123', { from: today, to: in30 })

await repull.pricing.decide('lst_abc123', {
  decisions: data
    .filter(n => !n.applied)
    .map(n => ({ date: n.date, action: 'apply' as const })),
})

Decline weekends, apply weekdays

Owner only wants AI managing weekday rates and keeps a fixed weekend rate.

const { data } = await repull.pricing.list('lst_abc123', { from, to })

await repull.pricing.decide('lst_abc123', {
  decisions: data.map(n => {
    const day = new Date(n.date).getUTCDay()  // 0 Sun, 5 Fri, 6 Sat
    const isWeekend = day === 5 || day === 6
    return isWeekend
      ? { date: n.date, action: 'decline' as const, reason: 'owner-managed weekend' }
      : { date: n.date, action: 'apply' as const }
  }),
})

Compare what was suggested vs what was applied

Each row in GET /v1/listings/{id}/pricingalready returns both. Diff them to render a "recommended vs live" chart.

const { data } = await repull.pricing.list('lst_abc123', { from, to })

const series = data.map(n => ({
  date: n.date,
  recommended: n.recommended_rate,
  live: n.current_rate,
  delta_pct: n.delta_pct,
}))

// Plot 'recommended' and 'live' on the same axis,
// shade the area between them by sign(delta_pct).

Rate limits

Pricing endpoints follow the standard tier-based limits. See Rate Limits for per-tier numbers. Apply calls are higher cost than reads because each one fans out to multiple channel adapters — budget for one apply call per listing-night, not per underlying channel.

AI