For developers
Public API
A versioned REST API at /api/v1 for reading and creating reservations, quotes, customers and more from your own systems.
Authentication
Server-to-server calls authenticate with a secret key in the X-API-Key header. Secret keys start with rsk_live_, belong to one company, and are stored only as a SHA-256 hash, so a key is shown once when it is created. Keys are revoked individually: create the new key, deploy it, then revoke the old one.
Each key carries scopes, and every route checks for one of the scopes listed in the endpoint table below. A key without the scope gets 403. The API is available on plans that include API access; the webhook routes also need the webhooks feature.
Publishable keys (rpk_live_) are a different thing. They are locked to allowed browser origins and can only open a guest session for the hosted web booker at POST /api/v2/public/auth/web-booker/session. They cannot call /api/v1.
curl https://api.renaroapp.com/api/v1/bookings?limit=20 \
-H "X-API-Key: rsk_live_..." \
-H "Renaro-Version: 2026-07-16" Versioning
The contract is date-versioned. Send Renaro-Version (or X-Renaro-Version) on every request. Supported versions are 2026-07-04 and 2026-07-16. A request without the header resolves to 2026-07-04, so an integration never changes behavior without opting in. New integrations should pin 2026-07-16. An unsupported value is rejected with 400 VALIDATION_ERROR.
Every response echoes the resolved Renaro-Version and sets X-Renaro-API-Version: v1, Cache-Control: no-store and X-Content-Type-Options: nosniff.
| Behavior | 2026-07-04 (default) | 2026-07-16 |
|---|---|---|
| Error body | Flat: { status, code, message, details?, request_id } | Wrapped: { error: { code, message, details? }, meta } |
Free-form objects (metadata, fare_breakdown, pricing_snapshot) | Keys are camelCased on the way out | Returned exactly as stored |
Endpoints
Request bodies and query strings use snake_case. Response bodies use camelCase inside a { data, meta } envelope. Routes marked idempotent accept an Idempotency-Key header.
| Method and path | Scopes (any one) | Idempotent |
|---|---|---|
GET /api/v1/bookings | bookings.read | — |
GET /api/v1/bookings/:id | bookings.read | — |
GET /api/v1/bookings/:id/status | bookings.read | — |
GET /api/v1/bookings/:id/tracking | bookings.read | — |
POST /api/v1/bookings | bookings.write, bookings.create | Yes |
PATCH /api/v1/bookings/:id | bookings.write, bookings.update | Yes |
POST /api/v1/bookings/:id/cancel | bookings.write, bookings.update | Yes |
POST /api/v1/bookings/:id/assign | bookings.write, dispatch.create | Yes |
GET /api/v1/drivers | drivers.read | — |
GET /api/v1/drivers/:id | drivers.read | — |
GET /api/v1/drivers/:id/location | drivers.read, gps.read | — |
GET /api/v1/passengers | passengers.read | — |
GET /api/v1/passengers/:id | passengers.read | — |
POST /api/v1/passengers | passengers.write, passengers.create | Yes |
GET /api/v1/vehicles | fleet.read | — |
GET /api/v1/vehicles/:id | fleet.read | — |
POST /api/v1/quotes | quotes.write, bookings.create | Yes |
GET /api/v1/quotes/:id | quotes.read, quotes.write, bookings.create | — |
GET /api/v1/zones | zones.read, config.read | — |
| Webhook endpoints (see Webhooks) | webhooks.manage or the matching webhooks.* scope | Writes: yes |
Quote, then book at the quoted price
POST /api/v1/quotes prices a trip and returns 201 with a quote that holds its price for 15 minutes (expiresAt). The response says whether it can still be used: status is open or accepted, with acceptReady and expiresInSeconds. Once a quote expires, fetching it returns 404 NOT_FOUND.
To book at that price, send quote_id with the booking. The pickup and drop-off coordinates must match the quote's to within 0.0002 degrees. A quote books once; reusing an accepted quote returns 409 PUBLIC_QUOTE_ALREADY_ACCEPTED.
curl https://api.renaroapp.com/api/v1/quotes \
-H "X-API-Key: rsk_live_..." \
-H "Renaro-Version: 2026-07-16" \
-H "Idempotency-Key: 5d0c7c9e-2f1a-4b7e-9f55-0c3f1a2b9e10" \
-H "Content-Type: application/json" \
-d '{
"pickup_address": "Chicago O'\''Hare International Airport (ORD), Chicago, IL",
"pickup_lat": 41.97420,
"pickup_lng": -87.90730,
"dropoff_address": "100 Oakbrook Center, Oak Brook, IL 60523",
"dropoff_lat": 41.84876,
"dropoff_lng": -87.95301,
"vehicle_type_id": "184f7424-6654-453e-9374-328346b0124d",
"scheduled_pickup_at": "2026-10-02T14:30:00Z",
"passenger_count": 2
}' {
"data": {
"id": "9f0e8400-5c1b-4e9a-9d0b-2d7f3c1a8e21",
"createdAt": "2026-10-01T15:30:00.000Z",
"expiresAt": "2026-10-01T15:45:00.000Z",
"quote": {
"vehicleTypeId": "184f7424-6654-453e-9374-328346b0124d",
"currency": "USD",
"distanceMeters": 21400,
"durationMinutes": 28,
"estimatedFareCents": 9500,
"fareBreakdown": { "...": "..." },
"pricingSnapshot": { "...": "..." }
},
"acceptedAt": null,
"bookingId": null,
"status": "open",
"acceptReady": true,
"expiresInSeconds": 900
},
"meta": {
"requestId": "3b5e1f0a-8c2d-4f4e-a1b7-6d9c2e0f7a44",
"timestamp": "2026-10-01T15:30:00.000Z"
}
} {
"quote_id": "9f0e8400-5c1b-4e9a-9d0b-2d7f3c1a8e21",
"pickup_address": "Chicago O'Hare International Airport (ORD), Chicago, IL",
"pickup_lat": 41.97420,
"pickup_lng": -87.90730,
"dropoff_address": "100 Oakbrook Center, Oak Brook, IL 60523",
"dropoff_lat": 41.84876,
"dropoff_lng": -87.95301,
"vehicle_type_id": "184f7424-6654-453e-9374-328346b0124d",
"scheduled_pickup_at": "2026-10-02T14:30:00Z",
"passenger_count": 2,
"passenger_name": "Dana Whitfield",
"passenger_phone": "+13125550148",
"passenger_email": "dana.whitfield@example.com",
"flight_number": "UA 1542"
} Idempotency
Every idempotent route accepts an optional Idempotency-Key header. Send a new random key per logical operation and reuse it on retries. A retry with the same key and the same body returns the stored response instead of creating a second record. Keys are scoped to your company, fingerprinted on method, path and body, and kept for 24 hours. Only responses below 400 are stored, so a failed request can be retried with the same key.
- Same key, different body:
422 IDEMPOTENCY_KEY_REUSED. - Same key while the first request is still running:
409 IDEMPOTENCY_REQUEST_IN_FLIGHT. Back off and retry. - These two errors currently arrive in the flat 2026-07-04 error shape whatever version you pin, so read
codefrom either shape.
Pagination
List routes take cursor and limit (1 to 200, default 50; the drivers list allows up to 250). The response carries pagination: { cursor, hasMore, total? }, where cursor is the value to send for the next page. Cursors stay stable while new records arrive. GET /api/v1/zones is the exception: it returns every zone in one response.
Errors
Errors carry a stable code, a human-readable message and, for validation failures, details.fieldErrors keyed by field. Every response, success or failure, has a request id in the X-Request-Id header and in the body; send your own X-Request-Id and it is used instead of a generated UUID. Quote it when you contact us. A missing, revoked or malformed key returns 401 UNAUTHORIZED.
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": {
"fieldErrors": { "pickup_lat": ["Required"] }
}
},
"meta": {
"requestId": "3b5e1f0a-8c2d-4f4e-a1b7-6d9c2e0f7a44",
"timestamp": "2026-10-01T15:30:00.000Z"
}
} Rate limits
Limits are per key, in fixed 60-second windows: 400 requests for a secret key and 120 for a publishable key. Every response reports X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix seconds). A 429 adds Retry-After in seconds. Separately, each IP address is throttled before authentication at 600 requests a minute, or 400 for requests that carry X-API-Key.
Conventions
The dashboard carries the full OpenAPI description for your account under Settings, Developer, API Docs, with a Download OpenAPI specification button. Use it to generate a client.
- Money is integer cents with an ISO 4217
currency. - Timestamps are ISO 8601 in UTC with a
Zsuffix. - Ids are UUIDs.
- The request id travels with the work it starts: webhook deliveries caused by your request carry it as
X-Renaro-Request-Id.