Platform
Public API and webhooks
A versioned REST API for embedding Renaro in your own booking site — with idempotency, cursor pagination, and replay-protected signed webhooks that most dispatch vendors simply do not offer.
Authentication and versioning
Server-to-server integrations authenticate with a secret API key in the X-API-Key header and call the versioned surface at /api/v1. Keys are organization-scoped, stored hashed, and revocable individually — rotate without downtime by overlapping two keys. Browser code uses a separate publishable key and never sees a secret key.
The API is date-versioned. Pin a contract with the Renaro-Version header; omit it and you resolve to the oldest supported version, so a live integration never changes behaviour without opting in. That is the discipline Stripe made standard — and something no ground-transport dispatch vendor we surveyed offers.
curl https://api.renaroapp.com/api/v1/bookings \
-H "X-API-Key: rsk_live_..." \
-H "Renaro-Version: 2026-07-16" Quote, then book at a locked price
Create a quote and its pricing snapshot is locked until expiresAt — a short, self-describing window, not an open-ended promise. Bind it into a booking by passing the quote id, and the quoted price is authoritative. The lifecycle is explicit in the response: status, acceptReady, and expiresInSeconds, so your client always knows whether a quote can still be used.
{
"data": {
"id": "9f0e8400-...",
"expiresAt": "2026-07-16T14:45:00Z",
"status": "open",
"acceptReady": true,
"expiresInSeconds": 900,
"quote": { "currency": "GBP", "estimatedFareCents": 4620 }
},
"meta": { "requestId": "req_...", "timestamp": "2026-07-16T14:30:00Z" }
} Idempotency, pagination, and errors
Every mutation accepts an Idempotency-Key. Retry the same request with the same key and you get the original response back; reuse a key with a different body and you get a clear IDEMPOTENCY_KEY_REUSED error rather than a duplicate booking. Lists are cursor-paginated. Errors are structured — a code, a message, and field-level details — and every response carries a request id you can quote to support.
- Idempotency-Key on every write, with replay and reuse detection.
- Cursor pagination on every list — no offset drift on a moving dataset.
- Per-key rate limits with X-RateLimit-* and Retry-After headers.
Signed webhooks with replay protection
Subscribe to booking, dispatch, driver, passenger, and payment events. Each delivery is signed with HMAC-SHA256 over the timestamp and body — the same construction as Stripe — and carries a unique event id so a redelivery is harmless. Verify the signature and enforce a timestamp tolerance: comparing the signature alone lets a captured payload replay forever; a tolerance window closes it. Deliveries only reach public HTTPS endpoints, and failures retry with exponential backoff.
- X-Renaro-Signature: t=<timestamp>,v1=<hmac-sha256>.
- booking.created, booking.assigned, booking.completed, booking.cancelled.
- dispatch.*, driver.*, passenger.*, payment.captured, payment.refunded.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, header, secret, toleranceSec = 300) {
const { t, v1 } = Object.fromEntries(
header.split(",").map((p) => p.trim().split("=")),
);
if (Math.abs(Date.now() - Date.parse(t)) > toleranceSec * 1000) return false;
const expected = createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(v1);
return a.length === b.length && timingSafeEqual(a, b);
} Design rules worth copying
Everything retryable is idempotent. Money amounts are integer cents. Timestamps are timezone-aware ISO-8601. Free-form data you send — booking metadata, pricing breakdowns — comes back exactly as you stored it, never silently renamed. Requests carry ids end to end, so a support conversation can reference the exact call.