Docs/Channels/Airbnb

Airbnb Transactions

Every payout Airbnb sent the host, with the lines it paid: reservations and their installments, adjustments, resolution payouts and cancellation fees. Each line has a stable id and a signed amount, and a payout's lines add up to what reached the bank, so you can reconcile payouts automatically instead of importing CSV statements. POST on the same path refreshes it from Airbnb.

GET/v1/channels/airbnb/transactions

Parameters

account_idstring

Scope the response to ONE connected Airbnb account. The value is the Airbnb host id — the same accounts[].externalAccountId that GET /v1/connect/airbnb returns and DELETE /v1/connect/airbnb?accountId= accepts. Omit it to read every connected account (the default). An id that is not connected to this workspace returns 404 not_found with field: "account_id" and your own ids in valid_values. This is not the X-Account-Id header, which carries a connection id and cannot tell two Airbnb hosts apart.

start_datestring

From this payout date (YYYY-MM-DD). A payout always comes back with all its lines, even a line dated the day before it.

end_datestring

Up to this payout date (YYYY-MM-DD).

statusstring

COMPLETED (paid out) or UPCOMING (expected, not paid yet).

payout_idstring

One payout: its Payout row and every line in it.

confirmation_codestring

Every line for one reservation: each installment, adjustment and resolution.

typestring

Airbnb's line type, e.g. Payout, Reservation, Adjustment, Resolution Payout.

limitinteger

Lines per page, up to 500 (default 100).

cursorstring

The pagination.nextCursor of the previous page.

Reconciling payouts

The response reads like a statement: newest payout first, each Payout row followed by the lines it paid.

  • A payout's lines add up to its payout.paidOutAmount exactly. Negative lines are included: an adjustment offset against a later payout appears under the payout it reduced.
  • Every settled line names its payout (payout.payoutId) and its position in it (payout.lineIndex). ?payout_id= returns one payout and all its lines.
  • A long stay paid in installments has one line per payout, each with the same confirmationCode. ?confirmation_code= returns all of them.
  • amount is what the line paid after Airbnb's host service fee. grossAmount is before it, and fees.hostServiceFee is the fee as a negative number.
  • Lines on listings you have deactivated are still returned, flagged onInactiveListing: true, so every payout adds up. A line that matches no reservation in your workspace has reservationId: null.
  • UPCOMING lines are earnings Airbnb expects to pay. They have no payout yet. Once paid, the line appears under its payout as COMPLETED, and the next refresh removes the upcoming one.

Stable ids

Airbnb does not give lines an id, so Repull builds one from the payout and the line. The same line has the same transactionId on every refresh and every page: upsert on it.

  • Payout row: Airbnb's payout id, e.g. M-HQLLNSWKUWK7R.
  • Settled line: <payoutId>:<type>:<confirmationCode>:<n>, where n counts repeats inside that payout.
  • Upcoming line: upcoming:<accountId>:<type>:<confirmationCode>:<date>:<n>. It is replaced by a settled id once paid.
  • A payout that nets to $0.00 has no Airbnb id. It gets Z-<date>-<hash>, with payout.payoutIdSynthetic: true.

Refreshing

Repull refreshes every connected Airbnb account automatically, about once a day, and sends a payout.completed webhook for each new payout. GET reads what Repull has stored and never calls Airbnb. POST refreshes on demand.

  • payout.completed carries the payout and all its lines — the same objects, with the same transactionIds, that ?payout_id= returns — so you can upsert from webhooks and from reads without duplicates. It is sent once per payout, for payouts dated in the last 14 days; older history is for GET. Add it to your webhook subscription to receive it.
  • Without a body, POST refreshes the last 12 months of payouts and the next 12 months of upcoming earnings, for every connected account (or only ?account_id=).
  • Send { "start_date": "2026-05-01", "end_date": "2026-05-31" } to refresh one window. Refreshing is safe to repeat: nothing is duplicated, and a window never removes lines outside it.
  • dataFreshness.lastSyncedAt is when each account was last refreshed. If Airbnb refuses one of several accounts, POST still refreshes the others and reports that account in accounts[].error.
curl -X POST "https://api.repull.dev/v1/channels/airbnb/transactions" \
  -H "Authorization: Bearer sk_live_YOUR_KEY"

# {"synced": true, "count": 1554,
#  "accounts": [{"accountId": "10000001", "count": 1554, "payouts": 495, "upcomingRemoved": 0}]}

What Airbnb does not include

Listed on every line in unavailableFields, and never guessed:

  • Taxes Airbnb collects and remits itself, and the total the guest paid. These are on the reservation: see Reservations. Pass-through occupancy tax that Airbnb pays to the host does appear, as its own Pass Through Tot lines.
  • Which earlier line a refund or reversal reverses. Airbnb names the stay (confirmationCode) and, for resolutions, the resolution (reference).
  • Currency conversion. Airbnb reports amounts in the payout currency only.

Several Airbnb accounts

A workspace can connect more than one Airbnb account. By default this route returns every connected account's rows; pass ?account_id=<airbnb host id> to scope to one. Every row carries accountId + accountName either way (the older hostId / hostName remain as aliases), so you can group without a second call. Airbnb host ids exceed 2^53 — keep them as strings and never parse them as numbers: Number("1000000000000000002") is 1772489413932732200, a different account, and returns 404. A two-account walkthrough lives on the Account scope page (/docs/scoping).

# Every connected account — the default, unchanged
curl 'https://api.repull.dev/v1/channels/airbnb/transactions' \
  -H "Authorization: Bearer sk_live_YOUR_KEY"

# Just one account
curl 'https://api.repull.dev/v1/channels/airbnb/transactions?account_id=1000000000000000002' \
  -H "Authorization: Bearer sk_live_YOUR_KEY"

# An id this workspace has not connected — 404, with your own ids to copy
# {
#   "error": {
#     "code": "not_found",
#     "message": "No connected Airbnb account `1772489413932732200` in this workspace.",
#     "field": "account_id",
#     "value_received": "1772489413932732200",
#     "valid_values": ["1000000000000000002", "80000001"]
#   }
# }

Data freshness

Airbnb reads are served from Repull's local mirror and never call Airbnb upstream, so every response carries a dataFreshness envelope telling you whether to trust it. lastSyncedAt is the last import that actually landed data — a run that failed or was rate-limited never moves it. accounts[] gives the verdict per connected account; it is omitted when the workspace has no Airbnb account to attribute.

  • Top-level stale: true means every connected account is stale — nothing in the response is current. For the single-account workspace this is exactly the old behaviour.
  • Top-level stale: false with reason: "partial_account_staleness" means some accounts are fine and some are not. The response is usable; read accounts[] to see which rows to distrust. The reason is emitted deliberately even though stale is false, so a consumer reading only the aggregate is never told everything is fine while one account is down.
  • Top-level stale: false with no reason means every account is current.
  • With ?account_id=, accounts[] holds exactly that account and the top-level fields mirror it.
  • reason is one of host_disconnected_since_<iso>, host_disconnected, host_not_activated, sync_lag_>_24h, never_synced, or partial_account_staleness. fixUrl is the dashboard screen that resolves it.
{
  "dataFreshness": {
    "lastSyncedAt": "2026-09-18T04:12:09.000Z",
    "stale": false,
    "reason": "partial_account_staleness",
    "fixUrl": "https://repull.dev/dashboard/connections",
    "accounts": [
      {
        "accountId": "1000000000000000002",
        "accountName": "Seaside Stays",
        "lastSyncedAt": "2026-09-18T04:12:09.000Z",
        "stale": false
      },
      {
        "accountId": "80000001",
        "accountName": "Casey",
        "lastSyncedAt": null,
        "stale": true,
        "reason": "host_disconnected",
        "fixUrl": "https://repull.dev/dashboard/connections"
      }
    ]
  }
}

Example

# The latest payouts and their lines
curl "https://api.repull.dev/v1/channels/airbnb/transactions?status=COMPLETED" \
  -H "Authorization: Bearer sk_live_YOUR_KEY"

# One payout, every line in it
curl "https://api.repull.dev/v1/channels/airbnb/transactions?payout_id=M-HQLLNSWKUWK7R" \
  -H "Authorization: Bearer sk_live_YOUR_KEY"

# Payouts in a date range
curl "https://api.repull.dev/v1/channels/airbnb/transactions?start_date=2026-09-01&end_date=2026-09-30" \
  -H "Authorization: Bearer sk_live_YOUR_KEY"

Response

{
  "data": [
    {
      "transactionId": "M-HQLLNSWKUWK7R",
      "type": "Payout",
      "isPayout": true,
      "status": "COMPLETED",
      "date": "2026-09-11",
      "currency": "USD",
      "amount": 2552.38,
      "payout": { "payoutId": "M-HQLLNSWKUWK7R", "payoutIdSynthetic": false, "payoutDate": "2026-09-11", "lineIndex": null, "paidOutAmount": 2552.38 },
      "description": "Account Holder, Checking 0000 (USD)"
    },
    {
      "transactionId": "M-HQLLNSWKUWK7R:adjustment:HMQYRTJ5D8:1",
      "type": "Adjustment",
      "isPayout": false,
      "status": "COMPLETED",
      "date": "2026-09-10",
      "currency": "USD",
      "amount": -681.53,
      "grossAmount": -556.51,
      "fees": { "hostServiceFee": -125.02, "cleaningFee": -91.64 },
      "payout": { "payoutId": "M-HQLLNSWKUWK7R", "payoutIdSynthetic": false, "payoutDate": "2026-09-11", "lineIndex": 1, "paidOutAmount": null },
      "confirmationCode": "HMQYRTJ5D8",
      "reservationId": "235939",
      "listingId": "1234567890123456789",
      "onInactiveListing": false,
      "reference": null,
      "unavailableFields": ["taxes", "guest_paid_total", "original_transaction_id", "currency_conversion", "management_fee"]
    }
  ],
  "pagination": { "nextCursor": "eyJvIjoxMDB9", "hasMore": true, "total": 1554 },
  "dataFreshness": { "lastSyncedAt": "2026-09-25T15:04:50.662Z", "stale": false }
}
AI