Mobile & partner API — v1

FairCar Developer API

One documented REST surface behind the FairCar iOS and Android apps. Every endpoint runs the same pricing, host rules and permissions as faircar.app, so a native client can never drift from the website.

Getting started

1. Base URL

https://faircar.app/api/public/v1

Responses are JSON. Amounts are USD; fields ending in _cents are integers in cents. Trip dates are YYYY-MM-DD; timestamps are ISO-8601 UTC.

2a. Get a token from an API key

POST https://faircar.app/api/public/v1/auth/token
Content-Type: application/json

{ "api_key": "fc_live_…" }
→ { "access_token": "eyJ…", "refresh_token": "…", "token_type": "bearer", "expires_in": 3600 }

Any signed-in FairCar member can create a personal key under Account → API keys. The key is shown once, acts only on that member's own data, and can be revoked at any time. Exchange it for an access token, then send Authorization: Bearer <access_token> on every authenticated call. Keep keys server-side; never ship one inside a mobile or browser app.

2b. Or sign each user in directly

POST https://npsimakbiyqxrmwgzhjn.supabase.co/auth/v1/token?grant_type=password
apikey: sb_publishable_9k2hA5eNNChKf0xWtX6k7g_edcPMOE9
Content-Type: application/json

{ "email": "guest@example.com", "password": "…" }
→ { "access_token": "eyJ…", "refresh_token": "…", "expires_in": 3600 }

This is the right path for consumer apps: use an official Supabase SDK (email + password, Google, Apple or magic link) with the publishable key above, then send the returned access token as Authorization: Bearer …. Tokens last one hour — let the SDK refresh them, or call /auth/token again with your API key.

3. Call the API

curl "https://faircar.app/api/public/v1/vehicles?location=Nashville&start_date=2026-10-04&end_date=2026-10-08&limit=20"

curl -H "Authorization: Bearer $ACCESS_TOKEN" "https://faircar.app/api/public/v1/me"

curl -X POST "https://faircar.app/api/public/v1/bookings" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"vehicle_id":"…","start_date":"2026-10-04","end_date":"2026-10-08",
       "return_url":"yourapp://checkout/return","environment":"live"}'

Endpoints

Discovery

No token needed. Everything a browsing screen needs before sign-in.

GET
/vehicles

Search active listings. Filters: start_date, end_date, location, make, model, type, min_seats, min_daily_rate, max_daily_rate, instant_book, sort, limit, offset.

GET
/vehicles/{vehicle_id}

Full listing detail including photos, features, fees, rules and pickup locations.

GET
/vehicles/{vehicle_id}/availability

Dates blocked by the host or already booked, from today forward.

GET
/vehicles/{vehicle_id}/reviews

Reviews and the combined rating shown on the web listing.

POST
/vehicles/{vehicle_id}/quote

Authoritative trip price: subtotal, discount, 10% guest fee, tax, total, all-in per day.

GET
/cities

Supported city slugs, names, states.

GET
/airports

Supported airports with IATA codes.

GET
/profiles/{user_id}

Public profile and badges for a host or guest.

Account

The signed-in user. Requires a Supabase access token.

POST
/auth/token

Exchange a personal API key (body: api_key, or header X-API-Key) for a short-lived access token.

GET
/me

Profile, roles, host/admin flags, verification state.token required

PATCH
/me

Update display name, bio, city, region, country, languages.token required

GET
/me/favorites

Saved cars.token required

POST
/me/favorites

Toggle a saved car (body: vehicle_id).token required

GET
/me/trips

Trips as a guest, optional ?status= filter.token required

Bookings & payment

Quote → create → pay with Stripe → poll for confirmation.

POST
/bookings

Runs every host rule and gate, holds the dates for 10 minutes, returns a Stripe Checkout client secret.token required

GET
/bookings

My bookings.token required

GET
/bookings/{booking_id}

Booking detail for the guest or the host, with pickup/return details once authorised.token required

GET
/bookings/{booking_id}/cancellation-quote

Refund preview under the listing's cancellation policy.token required

POST
/bookings/{booking_id}/cancel

Cancel and refund (guest or host).token required

POST
/bookings/{booking_id}/release-hold

Free the dates immediately when checkout is abandoned.token required

Messaging

Guest ↔ host conversations tied to a booking.

GET
/threads?role=guest|host

Inbox with participant profiles and unread counts.token required

GET
/threads/{thread_id}

Thread with all messages; marks it read.token required

GET
/threads/{thread_id}/messages

Messages only, oldest first.token required

POST
/threads/{thread_id}/messages

Send a message (body: body).token required

Reviews

Post-trip ratings in both directions.

GET
/reviews

Completed trips still awaiting my review.token required

POST
/reviews

Submit a review (booking_id, rating 1–5, comment, tags).token required

Host tools

Host-side screens for the native app.

GET
/host/listings

All of my listings, including paused and unpublished.token required

GET
/host/bookings

Bookings across my cars, optional ?status= filter.token required

GET
/host/vehicles/{vehicle_id}/calendar

Blocked days plus booking spans with guest metadata.token required

POST
/host/vehicles/{vehicle_id}/calendar

Block or unblock dates (blocked_dates, block).token required

Booking and payment flow

  1. 1. Price the trip with POST /vehicles/{id}/quote and show the exact breakdown.
  2. 2. Call POST /bookings. Host rules (minimum trip length, advance notice, age and ID gates) are enforced server-side; the dates are held for 10 minutes and a Stripe Checkout client secret comes back.
  3. 3. Complete payment with the Stripe mobile SDK or a web view, returning to the return_url deep link you supplied.
  4. 4. Poll GET /bookings/{id} until status is confirmed — our Stripe webhook confirms the trip, never the client.
  5. 5. If the guest backs out, call POST /bookings/{id}/release-hold to free the dates immediately.

Errors

{ "error": { "code": "unauthorized", "message": "A valid Supabase access token is required." } }

unauthorized 401 · forbidden 403 · not_found 404 · conflict 409 · validation_failed 422 · bad_request 400. CORS is open for all origins, and every path accepts OPTIONS preflight.