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.
/v1/channels/airbnb/transactionsParameters
account_idstringScope 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_datestringFrom this payout date (YYYY-MM-DD). A payout always comes back with all its lines, even a line dated the day before it.
end_datestringUp to this payout date (YYYY-MM-DD).
statusstringCOMPLETED (paid out) or UPCOMING (expected, not paid yet).
payout_idstringOne payout: its Payout row and every line in it.
confirmation_codestringEvery line for one reservation: each installment, adjustment and resolution.
typestringAirbnb's line type, e.g. Payout, Reservation, Adjustment, Resolution Payout.
limitintegerLines per page, up to 500 (default 100).
cursorstringThe 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.paidOutAmountexactly. 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. amountis what the line paid after Airbnb's host service fee.grossAmountis before it, andfees.hostServiceFeeis 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 hasreservationId: null. UPCOMINGlines are earnings Airbnb expects to pay. They have no payout yet. Once paid, the line appears under its payout asCOMPLETED, 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>, wherencounts 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>, withpayout.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.completedcarries the payout and all its lines — the same objects, with the sametransactionIds, 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 forGET. Add it to your webhook subscription to receive it.- Without a body,
POSTrefreshes 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.lastSyncedAtis when each account was last refreshed. If Airbnb refuses one of several accounts,POSTstill refreshes the others and reports that account inaccounts[].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 Totlines. - 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: truemeans 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: falsewithreason: "partial_account_staleness"means some accounts are fine and some are not. The response is usable; readaccounts[]to see which rows to distrust. Thereasonis emitted deliberately even thoughstaleisfalse, so a consumer reading only the aggregate is never told everything is fine while one account is down. - Top-level
stale: falsewith noreasonmeans every account is current. - With
?account_id=,accounts[]holds exactly that account and the top-level fields mirror it. reasonis one ofhost_disconnected_since_<iso>,host_disconnected,host_not_activated,sync_lag_>_24h,never_synced, orpartial_account_staleness.fixUrlis 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 }
}