Partners / API v1

QuickPickups Partner API

Live prices, real bookings, driver tracking and signed webhooks for private transfers in Barcelona and Catalonia. Every price comes from our live pricing engine with your agreed terms; every booking enters our real operation.

Overview

  • Live prices. Agreed route prices, distance-based fares, demand and last-minute rules, plus your agreed net rates or adjustment. No static price list.
  • Live availability. The same sales rules as our website: real seat and luggage capacity, minimum notice by area, vehicle class and distance, service area.
  • Real bookings. A QuickPickups booking number, a professional driver through our dispatch, flight tracking and a live-tracking page for the passenger.
  • Real-time status. Driver name, vehicle and plate, on the way, arrived, on board, completed; GPS position from one hour before pickup.
  • A full sandbox. Same endpoints, same webhooks, no real driver and no invoice.

Base URL for live and sandbox: https://backend.quickpickups.es/api/partners/v1. The key selects the environment. All amounts are EUR with VAT included; times are ISO 8601 (without offset they are local time in Barcelona).

Coverage

Use these place codes or send coordinates with an address. Read vehicle classes, capacities, child-seat types and notice rules from GET /coverage: they change with our fleet and are never hard-coded.

CodePlace
BCNBarcelona-El Prat Airport (add "terminal": "T1" or "T2")
GROGirona-Costa Brava Airport
REUReus Airport
BCN_PORTPort of Barcelona (cruise terminals)
SANTSBarcelona Sants railway station
FIRA_GRAN_VIAFira Barcelona Gran Via
BCN_CENTERBarcelona city centre (Plaça de Catalunya)
Request
curl https://backend.quickpickups.es/api/partners/v1/coverage

Authentication and security

Send your key as Authorization: Bearer <key>. Sandbox keys start with qp_test_, live keys with qp_live_. Keys are stored hashed and shown once; we rotate them with a grace period so you can switch without downtime. Keys can be limited by scope (quotes, bookings:read, bookings:write, tracking) and by IP allowlist.

Check your key
curl https://backend.quickpickups.es/api/partners/v1/me \
  -H "Authorization: Bearer $QP_KEY"

Rate limits. 60 requests per minute per key by default. Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; above the limit you get 429 with Retry-After. Every response has an X-Request-Id.

Quotes

POST/quotes

One option per vehicle class that can be sold for the request, with your price, locked until expires_at (30 minutes by default). For airport arrivals send the scheduled landing time: we track the flight.

Request
curl -X POST https://backend.quickpickups.es/api/partners/v1/quotes \
  -H "Authorization: Bearer $QP_KEY" -H "Content-Type: application/json" \
  -d '{
    "pickup":  {"place": "BCN", "terminal": "T1"},
    "dropoff": {"lat": 41.3870, "lng": 2.1701,
                "address": "Plaça de Catalunya 1, 08002 Barcelona"},
    "pickup_at": "2026-11-02T14:30:00+01:00",
    "passengers": 3,
    "luggage": 3,
    "child_seats": [{"type_id": 1, "count": 1}]
  }'
Response (abridged)
{
  "id": "qt_Jx8d2kq0Lw3mZp1a",
  "object": "quote",
  "status": "ok",
  "expires_at": "2026-10-16T12:30:00+02:00",
  "currency": "EUR",
  "options": [
    {
      "option_id": "qt_Jx8d2kq0Lw3mZp1a.3",
      "vehicle_class": {"id": 3, "code": "comfort", "name": "Comfort",
                        "max_passengers": 4, "max_luggage": 3,
                        "model_guaranteed": false},
      "vehicles": 1,
      "price_per_vehicle": …,
      "price": …,
      "currency": "EUR",
      "pricing_basis": "net_rate"
    }
  ],
  "unavailable": [
    {"vehicle_class": {"id": 9, "name": "Minibus 16"},
     "reason": "too_soon", "min_lead_hours": 48}
  ]
}

status: request_quote means nothing can be sold online for this request; reason says why (group_size, out_of_area, too_soon, stop_sale, long_distance, unpriceable) and our team quotes it by hand.

Bookings

POST/bookings

Books a quote option at the locked price. The Idempotency-Key header is required: retries with the same key and body return the same booking and never create a duplicate.

Request
curl -X POST https://backend.quickpickups.es/api/partners/v1/bookings \
  -H "Authorization: Bearer $QP_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2f0e-6c3a-4d0e-9a51-0b7f2b8f9e21" \
  -d '{
    "quote_id": "qt_Jx8d2kq0Lw3mZp1a",
    "option_id": "qt_Jx8d2kq0Lw3mZp1a.3",
    "partner_reference": "YOUR-REF-102938",
    "passenger": {"name": "Jane Doe", "phone": "+44 7700 900123"},
    "flight_number": "BA478",
    "notes": "Two large suitcases and a pushchair"
  }'
Response (abridged)
{
  "id": "QP26K4M7PX",
  "object": "booking",
  "environment": "live",
  "status": "confirmed",
  "partner_reference": "YOUR-REF-102938",
  "pickup_at": "2026-11-02T14:30:00+01:00",
  "pickup": {"place": "BCN", "terminal": "T1", "lat": 41.2889, "lng": 2.0731,
             "address": "Barcelona-El Prat Airport (BCN) - Terminal T1"},
  "vehicles": 1,
  "price": …,
  "currency": "EUR",
  "policies": {"free_waiting_minutes": 60, "meet_and_greet": true,
               "flight_tracking": true},
  "cancellation": {"cancellable": true,
                   "free_until": "2026-11-01T14:30:00+01:00",
                   "fee_pct": 0, "fee": 0},
  "tracking_url": "https://..."
}

GET/bookings/{id}

GET/bookings?partner_reference=&status=&pickup_from=&pickup_to=

Statuses

StatusMeaning
confirmedBooked and accepted.
driver_assignedA driver accepted the service: name, phone and vehicle are available.
driver_on_the_wayThe driver is heading to the pickup point.
driver_arrivedThe driver is at the pickup point (arrivals hall with a name sign at airports).
passenger_on_boardThe service has started.
completedThe service has finished.
cancelledCancelled by you (partner) or by us (operator).
no_showThe passenger did not show up within the free waiting time.

Amendments

PATCH/bookings/{id}

Passenger, flight number, notes and your reference change until the service starts. Route, time, group, child seats and vehicle class are re-priced by the live engine: if the price changes you get 409 price_changed with the new price; resend with accept_price_change: true. These changes close 2 hours before pickup by default. The driver is notified automatically.

curl -X PATCH https://backend.quickpickups.es/api/partners/v1/bookings/QP26K4M7PX \
  -H "Authorization: Bearer $QP_KEY" -H "Content-Type: application/json" \
  -d '{"pickup_at": "2026-11-02T16:00:00+01:00", "flight_number": "BA480"}'

# 409 price_changed  →  resend with "accept_price_change": true

Cancellation

POST/bookings/{id}/cancel

Free until cancellation.free_until (24 hours before pickup by default), then your agreed fee applies. Send dry_run: true to preview the fee. Prepaid balances are refunded instantly.

curl -X POST https://backend.quickpickups.es/api/partners/v1/bookings/QP26K4M7PX/cancel \
  -H "Authorization: Bearer $QP_KEY" -H "Content-Type: application/json" \
  -d '{"dry_run": true}'

{"dry_run": true, "fee": 0.0, "fee_pct": 0.0,
 "free_until": "2026-11-01T14:30:00+01:00", "currency": "EUR"}

Tracking

GET/bookings/{id}/tracking

Driver first name, phone and vehicle (make, model, colour, plate) once assigned; live GPS position and ETA from one hour before pickup until the end of the service; a passenger-to-driver chat link. Poll at most every 15 seconds and rely on webhooks for changes.

Response
{
  "id": "QP26K4M7PX",
  "status": "driver_on_the_way",
  "driver": {"first_name": "Marta", "phone": "+34 600 000 000",
             "vehicle": {"make": "Toyota", "model": "Corolla Hybrid",
                         "colour": "White", "plate": "1234 ABC"}},
  "location": {"lat": 41.305, "lng": 2.081,
               "updated_at": "2026-11-02T14:12:03+01:00"},
  "eta_minutes": 6,
  "location_status": "live",
  "passenger_chat_url": "https://..."
}

Webhooks

We POST signed JSON events to your HTTPS webhook URL (one for live, one for sandbox). Answer any 2xx within 5 seconds; otherwise we retry after 1 min, 5 min, 15 min, 1 h, 3 h, 6 h, 12 h and 24 h. Deduplicate by event id: an event can arrive twice or out of order.

EventWhen
booking.confirmedThe booking is created.
booking.updatedThe booking was amended through the API.
driver.assignedA driver accepted the service (again if the driver changes).
driver.on_the_wayThe driver set off to the pickup point.
driver.arrivedThe driver is at the pickup point.
passenger.on_boardThe service has started.
booking.completedThe service has finished.
booking.cancelledCancelled by you or by us.
passenger.no_showThe passenger did not show up.
flight.delayedThe tracked flight is 15+ minutes late (again for each further 15 minutes).
pingTest event from POST /webhooks/test.
Body
POST https://your-system.example.com/webhooks/quickpickups
QP-Signature: t=1792141200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
QP-Event-Id: evt_Qm3xB0c8Lr1s9TzKp2aWv
QP-Event-Type: driver.assigned

{
  "id": "evt_Qm3xB0c8Lr1s9TzKp2aWv",
  "type": "driver.assigned",
  "created_at": "2026-11-02T13:05:44+01:00",
  "environment": "live",
  "data": {"booking": { "...": "same object as GET /bookings/{id}" }}
}

Verify the signature. QP-Signature: t=<unix>,v1=<hex> where the hex is HMAC-SHA256 of "<t>.<raw body>" with your signing secret. Reject events older than 5 minutes.

Python
import hashlib, hmac, time

def verify(secret: str, raw_body: bytes, header: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body,
                        hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
Node.js
const crypto = require("crypto");

function verify(secret, rawBody, header) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = crypto.createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

POST/webhooks/test

Sends a signed ping event to your URL.

Sandbox

Sandbox keys use the same endpoints, the same live pricing engine and the same validations. Sandbox bookings are never dispatched to a driver, never invoiced and never charged, and sandbox and live data are fully separate.

POST/sandbox/bookings/{id}/advance

Moves a sandbox booking to the next status, or to to: driver_assigned, driver_on_the_way, driver_arrived, passenger_on_board, completed, no_show or flight_delayed. Each step sends the real signed webhook with a test driver and vehicle.

curl -X POST https://backend.quickpickups.es/api/partners/v1/sandbox/bookings/QP26K4M7PX/advance \
  -H "Authorization: Bearer $QP_TEST_KEY" -H "Content-Type: application/json" \
  -d '{"to": "driver_arrived"}'

# → webhooks driver.assigned, driver.on_the_way, driver.arrived

Errors

{
  "error": {"code": "quote_expired",
            "message": "The quote has expired. Request a new one."},
  "request_id": "req_3f9a1c2b7d4e5f60"
}
HTTPCodesWhat to do
400idempotency_key_required, invalid_jsonFix the request.
401missing_api_key, invalid_api_keyCheck the key; it may be revoked or expired.
402insufficient_balance, credit_limit_exceededTop up your balance or contact us.
403live_not_enabled, insufficient_scope, ip_not_allowed, environment_mismatch, not_sandboxUse the right key or ask us to change the setting.
404quote_not_found, booking_not_foundCheck the id and the environment.
409quote_expired, quote_already_used, price_mismatch, price_changed, already_cancelled, not_cancellable, not_amendable, amend_window_closedRe-quote, confirm the new price or contact us.
422validation_error, invalid_location, unknown_place, out_of_area, pickup_in_past, too_soon, stop_sale, invalid_option, invalid_passenger, idempotency_key_reusedFix the request or choose another option.
429rate_limitedWait Retry-After seconds.
5xxinternal_error, service_unavailableRetry with the same Idempotency-Key.

Service levels

Target
API availability99.5 % per month
POST /bookingsunder 1 second (p95)
POST /quotesunder 3 seconds (p95)
Webhook after the eventunder 60 seconds
Operations desk24/7 by phone and WhatsApp, +34 711 206 600
Technical support[email protected], reply within one business day

Planned maintenance is announced at least 48 hours in advance. Your agreed terms (payment, cancellation, waiting time, amendment cut-off, quote validity, rate limit) are returned by GET /me.