OAuth Connect
Let your users connect their Airbnb account to your app through a hosted, white-label OAuth page. You never handle tokens; we redirect the user back to your URL when they're done.
How it works
From your server, you create a Connect session. We give you a one-time URL on connect.repull.dev. Send your user there. We render a page styled with your brand, walk them through Airbnb's consent screen, exchange the OAuth code for tokens, and redirect them back to your app with ?status=connected appended.
- Your server calls
POST /v1/connect/airbnbwith aredirectUrl. - We return
oauthUrl— a URL onconnect.repull.dev. - Redirect your user to
oauthUrl. - We render your white-label Connect page → Airbnb consent → an account-confirmation screen (“Is this the right Airbnb account?”) → done.
- The user lands on your
redirectUrlwith status query params.
Same model as Stripe Connect or Plaid Link
Start a connect session
Server-to-server. Authenticate with your live API key. The session expires after 30 minutes if the user doesn't complete the flow.
curl -X POST 'https://api.repull.dev/v1/connect/airbnb' \
-H 'Authorization: Bearer sk_live_...' \
-H 'Content-Type: application/json' \
-d '{
"redirectUrl": "https://yourapp.com/airbnb/connected",
"accessType": "full_access"
}'Body parameters
redirectUrlstringRequiredWhere we send the user after the OAuth flow completes (or fails). We append status query params to this URL.
accessTypestringDefault: full_accessPermission scope requested from the host. One of full_access or read_only. Determines which Airbnb scopes are requested on the consent screen.
Choosing an access type
- full_access (default) — read + manage. Recommended for property managers and channel managers that handle messaging, calendars, and pricing on behalf of the host.
- read_only — read listings, reservations, calendars, messages, and reviews, but no writes back to Airbnb. Recommended for analytics, reporting, and BI tools that just need to pull data.
read_only requests fewer scopes on the Airbnb consent screen — none of the _write permissions are asked for. Hosts see a shorter, less alarming list of permissions, which generally improves conversion for read-only integrations.
Pick once, per host
accessType.Response
{
"oauthUrl": "https://connect.repull.dev/cs_8gQrT2v9k3M4nLp7wJxYzAbCdEfGhIjKlMnOp",
"provider": "airbnb",
"sessionId": "cs_8gQrT2v9k3M4nLp7wJxYzAbCdEfGhIjKlMnOp",
"expiresAt": "2026-04-29T18:25:14.000Z"
}Handle the redirect back
When the OAuth flow completes (or fails), we redirect the user to your redirectUrl with these query params appended:
| Param | Value | Meaning |
|---|---|---|
| status | connected | Connection successful. hostId + accountId are also set. |
| status | cancelled | User declined or an error occurred. code may explain (access_denied, account_conflict, …). |
| status | expired | User took too long; the link expired before they completed authorization. |
| hostId | 123456789 | Airbnb host ID. Use this to query GET /v1/properties. |
| accountId | 42 | Repull-side connection ID. Stable across token refreshes. |
// Your /airbnb/connected handler
public function connected(Request $request)
{
$status = $request->query('status');
if ($status === 'connected') {
$hostId = $request->query('hostId'); // Airbnb host ID
$accountId = $request->query('accountId'); // Repull connection ID
// Associate $hostId with the currently authenticated user, kick off
// any post-connect work in your app (welcome email, dashboard nudge).
return redirect()->route('dashboard')->with('success', 'Airbnb connected!');
}
if ($status === 'cancelled') {
$code = $request->query('code', 'unknown');
$message = match($code) {
'wrong_account' => 'You authorized the wrong Airbnb account. Try again.',
'access_denied' => 'You declined to authorize Airbnb.',
'account_conflict' => 'This Airbnb account is already connected elsewhere.',
default => "Connection failed: {$code}",
};
return redirect()->route('dashboard.connections')->with('error', $message);
}
if ($status === 'expired') {
return redirect()->route('dashboard.connections')->with('error', 'The connect link expired. Please try again.');
}
return redirect()->route('dashboard.connections');
}Associating the connection with your user
When the user lands back on your redirectUrl, you'll usually want to record hostId against the user in your database. Two ways to know who that user is:
Option 1 — Auth-required redirect (simplest)
If your redirectUrlrequires login, the user's session is intact when they land back — the OAuth round-trip happens in the same browser session. Your usual auth()->user() / request.user works.
Option 2 — Encode user context into the redirectUrl
We preserve all your existing query params. When the user lands back, we only append &status=...&hostId=...&accountId=... to the URL you gave us:
// Generate
$signed = hash_hmac('sha256', $userId . ':' . time(), config('app.key'));
'redirectUrl' => route('airbnb.connected', ['from' => $userId, 'sig' => $signed]),
// Receive
public function connected(Request $request)
{
$userId = (int) $request->query('from');
$sig = $request->query('sig');
if (! $this->verify($userId, $sig)) abort(403);
$hostId = $request->query('hostId');
User::find($userId)->airbnbHosts()->syncWithoutDetaching([$hostId]);
// ...
}Why sign it?
from value and link an Airbnb host to the wrong user. A signed nonce or short-lived token makes this safe.Check connection status anytime
After the user is connected, query the canonical state via GET /v1/connect/airbnb:
curl 'https://api.repull.dev/v1/connect/airbnb' \
-H 'Authorization: Bearer sk_live_...'
# {
# "connected": true,
# "provider": "airbnb",
# "id": 42,
# "status": "active",
# "externalAccountId": "123456789",
# "createdAt": "2026-04-29T18:01:42.000Z",
# "host": {
# "displayName": "Lidia",
# "displayNameLong": "Lidia",
# "avatarUrl": "https://a0.muscache.com/im/users/.../profile_pic.jpg",
# "avatarUrlLarge": "https://a0.muscache.com/im/users/.../profile_pic_large.jpg",
# "activationStatus": "active"
# }
# }After a successful connection, the response includes a hostobject with the host's display name and Airbnb avatar so you can render an account-level confirmation UI in your own app — no follow-up calls required.
Why no host email?
host.displayName + host.avatarUrlis what you need to confirm “you linked the right account” back to the user.For non-Airbnb providers (Booking, VRBO, Plumguide), host is currently null. Per-provider enrichment will land incrementally.
Failure modes
All non-success outcomes redirect back to your redirectUrl with ?status=cancelled&code=<reason> (or ?status=expired):
- access_denied — user clicked “Cancel” on Airbnb's consent screen.
- wrong_account — user clicked “Use a different account” on the confirmation screen. They likely had the wrong Airbnb account logged in. Mint a fresh session and ask them to retry (in another browser tab where they're logged in to the right account).
- account_conflict — the Airbnb account is already connected to a different Repull workspace. Disconnect it from there first.
- token_exchange_failed — Airbnb rejected our token exchange. Usually transient; retry.
- activation_failed — DB write failed during the final activate step. Rare; transient; retry.
- expired (status, not code) — connect link is older than 30 minutes. Generate a new one.
Webhooks
connection.created via webhooks. Coming soon.Custom domain (Scale plan)
On the Scale plan, you can swap connect.repull.dev for your own subdomain — e.g. connect.yourapp.com — so the URL bar matches your brand end-to-end. Add a CNAME to cname.vercel-dns.com, paste the hostname in your dashboard, and we'll provision SSL automatically.