Introduction
Blue's LTCR Workshop Booking API lets long-term-rental partner integrators — such as the team that operates bluecarrental.is — book workshop service appointments (tire change, oil and lube service, or other work) on behalf of the long-term-rental customers they serve, for the cars those customers are renting from Blue Car Rental.
Base URLs
| Environment | Base URL |
|---|---|
| Production | https://ltcr-workshop-booking-api.bluecarrental.is |
| Staging | https://ltcr-workshop-booking-api.staging.bluecarrental.is |
API version
This document covers v1.5. All paths are prefixed with /api/v1/workshop. All timestamps are RFC 3339 in UTC. Requests and responses are JSON.
service_typeis now a required query parameter onGET /availability. Slot duration depends on the type, so the grid is type-specific.- Service type slugs are now a discoverable, policy-driven catalogue. The v1 slugs
dekkjaskipti/smurthjonustawere renamed todekk/smur, and a newdekk_smur(combined, 60 min) slug is available. The legacyannadslug is hidden — useGET /service-typesto discover the active set. POST /bookingscan now return new409conflicts (lead_time_violation,lookahead_violation,slot_off_grid) when a booking violates the per-service-type policy. See Errors.
cancel_url format
The cancel_url in POST /bookings responses (and the customer confirmation email) now points at a partner-operated customer-facing page, not directly at the cancel API endpoint:
- Old:
https://ltcr-workshop-booking-api.bluecarrental.is/api/v1/workshop/bookings/{id}/cancel?token=… - New:
https://www.bluecarrental.is/langtimaleiga/afboka/{booking_id}?token=…
The cancel API endpoint and its token mechanics are unchanged. The partner page is expected to confirm the cancellation with the customer and then server-side call the existing GET /api/v1/workshop/bookings/{id}/cancel endpoint to perform it. Each partner must serve a page at /langtimaleiga/afboka/{booking_id}?token={token} on their customer-facing host before this format reaches their customers. See Cancellation flow.
Getting started
Five steps to your first booking:
- Request an API key from Blue Car Rental (see Authentication).
- Call
GET /api/v1/workshop/locationsto list active workshop locations. - Call
GET /api/v1/workshop/service-typesto discover the bookable services and their duration, lead-time, and lookahead policy. Cache for up to 5 minutes. - Call
GET /api/v1/workshop/availabilitywith the location, the chosenservice_typeslug, and a date range. The returned slot grid already reflects that service type's duration. - Call
POST /api/v1/workshop/bookingswith the chosen slot, the sameservice_type, and the customer's contact details. The response includes a tokenised cancel URL Blue Car Rental will email the customer.
End-to-end example
# 1. List locations
curl -s https://ltcr-workshop-booking-api.bluecarrental.is/api/v1/workshop/locations \
-H "X-API-Key: bws_a1b2c3d4_<your-secret>"
# 2. Discover service types
curl -s https://ltcr-workshop-booking-api.bluecarrental.is/api/v1/workshop/service-types \
-H "X-API-Key: bws_a1b2c3d4_<your-secret>"
# 3. Check availability for "Dekk og smur" (60-min combined service) in Keflavík next week
curl -s "https://ltcr-workshop-booking-api.bluecarrental.is/api/v1/workshop/availability?location=keflavik&service_type=dekk_smur&from=2026-05-19&to=2026-05-23" \
-H "X-API-Key: bws_a1b2c3d4_<your-secret>"
# 4. Book the 09:00 slot on 2026-05-19
curl -s -X POST https://ltcr-workshop-booking-api.bluecarrental.is/api/v1/workshop/bookings \
-H "X-API-Key: bws_a1b2c3d4_<your-secret>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9b1c-7e2f-2026-05-19-keflavik-AB123" \
-d '{
"location": "keflavik",
"slot_start_at": "2026-05-19T09:00:00Z",
"service_type": "dekk_smur",
"customer": {"name": "Kalli Bimbo", "email": "kalli@example.is", "phone": "+3546901588"},
"license_plate": "AB123"
}'
Authentication
Every request must include an API key in the X-API-Key header. Keys are issued by Blue Car Rental — there is no self-service signup.
Header
X-API-Key: bws_a1b2c3d4_e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
Key format
An API key is three underscore-separated parts: a fixed scheme tag, a public prefix, and a secret.
| Part | Example | Notes |
|---|---|---|
bws | bws | Scheme tag. Always bws. |
| Prefix | a1b2c3d4 | 8 hex characters. Identifies your partner record. Safe to log. |
| Secret | e5f6…b0 | 32 hex characters. Never log, never expose to the customer browser. |
Onboarding
Email Blue Car Rental to request a key. You will receive one production key and one staging key. Keys are not recoverable: if you lose a key, Blue Car Rental rotates it and issues a new one.
Rotation and deactivation
Blue Car Rental can deactivate any key at any time. To rotate, Blue Car Rental issues a new key and deactivates the old one — there is no in-place rotation. Your integration should handle:
401 unauthorized— the key is missing, malformed, deactivated, or no longer recognised. Stop sending requests with that key and contact Blue Car Rental.
Concepts
Locations
A location is a physical workshop site. The v1 launch ships with one location: keflavik (Keflavík). The schema is multi-location-ready; new locations are added over time by Blue Car Rental.
Slots and availability
Slots are not stored — they are derived per request from operating-hour rules, blackouts, and existing bookings. Always call /availability immediately before showing a calendar to the customer. Do not cache slots client-side. The server caches the response for 30 seconds.
Service types
A service type is a bookable service offered at a workshop — for example a tire change or a combined tires + oil-and-lube visit. Service types are a dynamic, policy-driven catalogue: Blue Car Rental can add new services, change durations, or adjust lead-time and lookahead policy at any time without an API version bump. Do not hard-code the catalogue in your integration.
Each active service type carries:
| Field | Notes |
|---|---|
slug | Stable machine identifier — use this on the wire. Examples in production today: dekk, smur, dekk_smur. |
display_name_is / display_name_en | Bilingual labels for your UI. Blue Car Rental owns the canonical wording; do not translate on your end. |
duration_minutes | How long the appointment takes. The /availability slot grid uses this — a 60-minute service returns slots that span two 30-minute cells on the underlying schedule. |
min_lead_calendar_days | How many full calendar days of lead time the customer needs. 1 means "no same-day bookings". |
max_lookahead_days | How far into the future this service can be booked. |
Discovery flow. Call GET /service-types on partner-backend startup (and at most every 5 minutes thereafter — the response is cacheable for max-age=300). Render the returned list to the customer. Pass the chosen slug to GET /availability as service_type, then to POST /bookings. A slug that is unknown or no longer active is rejected — see Errors.
The v1 slugs dekkjaskipti and smurthjonusta were renamed to dekk and smur. annad ("other") was retired from the partner-facing catalogue. Existing v1 bookings remain valid.
Idempotency
Required for safe integration. If your backend ever retries a POST /bookings call under any failure condition (timeout, 429, 5xx), an Idempotency-Key is the only way to prevent a duplicate booking. The POST /bookings endpoint accepts an Idempotency-Key header (≤255 characters). Replaying the same key with the same payload returns the original booking instead of creating a duplicate. Replaying the same key with a different payload returns 409 idempotency_conflict. Keys are honoured for 24 hours; after that they are pruned and a replay creates a new booking.
Replay returns a partial body. The replay response is the original 201 Created but with cancel_url omitted — the API stores only a hash of the cancel token, so the plaintext token cannot be re-derived after the first response. Your integration must persist cancel_url (or the token query-string value) from the first successful response and re-use that copy across retries. The customer's confirmation email contains the original tokenised link in either case.
Cancellation flow
When a booking is created, Blue Car Rental emails the customer a confirmation with a tokenised cancel link. The link target is a partner-operated page at the partner's customer-facing host — for example https://www.bluecarrental.is/langtimaleiga/afboka/{booking_id}?token={token}. The customer clicks the link and sees the partner's confirmation UI, and the partner page server-side calls GET /api/v1/workshop/bookings/{id}/cancel?token=... with the same token to actually perform the cancellation. On successful cancellation, Blue Car Rental sends the customer a second email confirming it. Both emails are queued and delivered asynchronously — the booking and cancellation API responses do not wait for SMTP.
Partner responsibility. Each partner must serve a page at the path embedded in the cancel URL (/langtimaleiga/afboka/{booking_id}?token={token}) on their customer-facing host and call the cancel API server-side from that page. The partner page is the place to add any customer-facing confirmation step, fraud checks, or downstream notifications the partner needs.
&env={value} suffix — currently &env=staging — so a single partner cancel page can dispatch its server-side cancel call to the matching API host. Production-issued tokens carry no env suffix and existing customer-facing links are unchanged.Rate limits
| Scope | Limit |
|---|---|
| Per IP, global | 1000 requests/minute |
| Per API key, all endpoints | 500 requests/minute |
Per API key, POST /bookings | 60 requests/minute |
Exceeded → 429 rate_limited with a Retry-After header. Back off and retry.
Time zones
All slot_start_at values are UTC. The workshop in Keflavík operates in Atlantic/Reykjavik. Render times in the customer's local timezone in your UI, but always send and store UTC.
List locations
List active workshop locations.
Request
curl -s https://ltcr-workshop-booking-api.bluecarrental.is/api/v1/workshop/locations \ -H "X-API-Key: bws_a1b2c3d4_<your-secret>"
Response
200 Success
{
"locations": [
{"slug": "keflavik", "name": "Keflavík", "timezone": "Atlantic/Reykjavik"}
]
}
Use the slug value as the location parameter in subsequent calls.
List service types
Discover the active, policy-driven catalogue of bookable services.
Request
curl -s https://ltcr-workshop-booking-api.bluecarrental.is/api/v1/workshop/service-types \ -H "X-API-Key: bws_a1b2c3d4_<your-secret>"
Response
200 Success
{
"service_types": [
{
"slug": "dekk",
"display_name_is": "Dekkjaskipti",
"display_name_en": "Tire change",
"duration_minutes": 30,
"min_lead_calendar_days": 1,
"max_lookahead_days": 60,
"sort_order": 10
},
{
"slug": "smur",
"display_name_is": "Smurþjónusta",
"display_name_en": "Oil & lube service",
"duration_minutes": 30,
"min_lead_calendar_days": 1,
"max_lookahead_days": 60,
"sort_order": 20
},
{
"slug": "dekk_smur",
"display_name_is": "Dekk og smur",
"display_name_en": "Tires + oil & lube",
"duration_minutes": 60,
"min_lead_calendar_days": 1,
"max_lookahead_days": 60,
"sort_order": 30
}
]
}
Fields
| Field | Type | Notes |
|---|---|---|
slug | string | Stable machine identifier. Pass this back on /availability and POST /bookings. |
display_name_is | string | Icelandic display name. Render verbatim — do not translate. |
display_name_en | string | English display name. Render verbatim. |
duration_minutes | integer | Always a positive multiple of 30. Determines the slot length on /availability for this service. |
min_lead_calendar_days | integer | Minimum calendar-day lead time. 0 permits same-day bookings; 1 requires at least the next calendar day. |
max_lookahead_days | integer | Maximum calendar-day window the customer can book into the future, counted from today. |
sort_order | integer | The order Blue Car Rental wants you to render the services. Ascending. Stable across calls. |
Cache-Control: public, max-age=300). Inactive types are intentionally absent — there is no active field on the wire. The list may be empty during a brief maintenance window; treat empty as a transient condition and retry.Error responses
401 unauthorized— missing or invalid API key.429 rate_limited— see Concepts → Rate limits.5xx internal_error— safe to retry.
Read schedule
Read the currently-effective operating-hour rules for a single location, by day of week. Useful for greying out closed weekdays in your calendar UI.
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
location | string | Yes | Location slug (e.g. keflavik). |
Request
curl -s "https://ltcr-workshop-booking-api.bluecarrental.is/api/v1/workshop/schedule?location=keflavik" \ -H "X-API-Key: bws_a1b2c3d4_<your-secret>"
Response
200 Success
{
"location": "keflavik",
"schedule": [
{
"day_of_week": 1,
"open_time": "09:00",
"close_time": "15:00",
"slot_length_minutes": 30,
"capacity": 1,
"effective_from": "2026-05-20",
"effective_until": null
},
{
"day_of_week": 2,
"open_time": "09:00",
"close_time": "15:00",
"slot_length_minutes": 30,
"capacity": 1,
"effective_from": "2026-05-20",
"effective_until": null
}
]
}
Fields
| Field | Type | Notes |
|---|---|---|
day_of_week | integer | ISO-8601 day-of-week: 1 = Monday … 7 = Sunday. Days absent from the response are closed. |
open_time / close_time | string (HH:MM) | Wall-clock open and close in the location's local timezone (from /locations). The booking grid runs [open_time, close_time). |
slot_length_minutes | integer | The schedule's underlying slot cell size (typically 30). The actual bookable slot size on /availability is service_type.duration_minutes, which is a multiple of this cell size. |
capacity | integer | Number of parallel lanes at this location on this weekday. 1 means one car at a time; higher means multiple bookings at the same start time are allowed. |
effective_from | string (YYYY-MM-DD) | Date on which this rule started applying. |
effective_until | string or null | Inclusive last date the rule applies, or null for open-ended. |
Cache-Control: public, max-age=300). The schedule changes only by admin action. An empty schedule array is a valid response — it means the location currently has no effective rules (e.g. a seasonal closure).Error responses
400 validation_failed—locationmissing.401 unauthorized— missing or invalid API key.404 not_found— unknown location.429 rate_limited— see Concepts → Rate limits.5xx internal_error— safe to retry.
List holidays
List labelled closed dates (national holidays, planned closures) for a single location across a date range. Use to render why a closed day is closed in your calendar UI — /availability already excludes blackouts, but it does not tell the customer they are looking at "Þjóðhátíðardagur" rather than an arbitrary closure.
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
location | string | Yes | Location slug. |
from | date (YYYY-MM-DD) | Yes | Inclusive start date. |
to | date (YYYY-MM-DD) | Yes | Inclusive end date. Max 366 days from from. |
Request
curl -s "https://ltcr-workshop-booking-api.bluecarrental.is/api/v1/workshop/holidays?location=keflavik&from=2026-06-01&to=2026-12-31" \ -H "X-API-Key: bws_a1b2c3d4_<your-secret>"
Response
200 Success
{
"location": "keflavik",
"holidays": [
{"date": "2026-06-17", "reason": "Þjóðhátíðardagur", "reason_en": "Icelandic National Day"},
{"date": "2026-12-24", "reason": "Aðfangadagur", "reason_en": "Christmas Eve"},
{"date": "2026-12-25", "reason": "Jóladagur", "reason_en": "Christmas Day"}
]
}
Fields
| Field | Type | Notes |
|---|---|---|
date | string (YYYY-MM-DD) | Calendar date in the location's local timezone. Each entry covers the full local day. |
reason | string or null | Human-readable reason. May be null for closures Blue Car Rental did not label. |
reason_en | string or null | English human-readable reason. null when no English label exists — show a generic "Closed" in an English UI. |
Cache-Control: public, max-age=600). Holidays change rarely. This endpoint is a subset of the closed-day data /availability already filters out — it exists so partners can show the closures instead of leaving the customer guessing.Error responses
400 validation_failed—location,from, ortomissing; bad date format;to> 366 days afterfrom.401 unauthorized— missing or invalid API key.404 not_found— unknown location.429 rate_limited— see Concepts → Rate limits.5xx internal_error— safe to retry.
Check availability
List bookable slots in a date range for a single location and service type.
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
location | string | Yes | Location slug (e.g. keflavik). |
service_type | string | Yes | Service-type slug from GET /service-types. Determines slot duration. Missing or unknown slug → 400 validation_failed. |
from | date (YYYY-MM-DD) | Yes | Inclusive. |
to | date (YYYY-MM-DD) | Yes | Inclusive. Max 90 days from from. |
Request
curl -s "https://ltcr-workshop-booking-api.bluecarrental.is/api/v1/workshop/availability?location=keflavik&service_type=dekk_smur&from=2026-05-19&to=2026-05-23" \ -H "X-API-Key: bws_a1b2c3d4_<your-secret>"
Response
200 Success
{
"buffer_hours": 24,
"earliest_bookable_at": "2026-05-19T08:00:00Z",
"location": "keflavik",
"service_type": "dekk_smur",
"slots": [
{"start_at": "2026-05-19T09:00:00Z", "end_at": "2026-05-19T10:00:00Z"},
{"start_at": "2026-05-19T10:00:00Z", "end_at": "2026-05-19T11:00:00Z"}
]
}
service_type. A 60-minute service (e.g. dekk_smur) returns slots that span two underlying 30-minute schedule cells; a 30-minute service (dekk, smur) returns 30-minute slots. The lead-time and lookahead policy of the chosen service are also applied — slots before today + min_lead_calendar_days or after today + max_lookahead_days are excluded. Closed days, blackouts, and already-booked slots are simply absent. Response is cached for 30 seconds at the HTTP layer.buffer_hours is that configured value (an integer in 0..720; 0 means no buffer). earliest_bookable_at is the canonical cutoff (server now + buffer_hours): any slot whose start_at is earlier than earliest_bookable_at is filtered out of slots[]. Partners that render their own calendar should use earliest_bookable_at as the floor rather than recomputing it client-side from a local clock and buffer_hours — the server clock is canonical.Error responses
400 validation_failed— missinglocation/service_type/from/to; unknownservice_type; bad date format;to> 90 days afterfrom.401 unauthorized— missing or invalid API key.404 not_found— unknown location.429 rate_limited— see Concepts → Rate limits.5xx internal_error— safe to retry.
Create a booking
Create a workshop booking.
Headers
| Name | Required | Notes |
|---|---|---|
X-API-Key | Yes | Your partner API key. |
Content-Type | Yes | application/json. |
Idempotency-Key | Strongly recommended | Up to 255 chars. See Concepts → Idempotency. |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
location | string | Yes | Location slug. |
slot_start_at | string (RFC 3339 UTC) | Yes | Must align with a slot returned by /availability for the same service_type. |
service_type | string | Yes | Service-type slug from GET /service-types. Must be active. |
customer.name | string | Yes | |
customer.email | string | Yes | Confirmation email is sent here. |
customer.phone | string | Yes | E.164 recommended. |
license_plate | string | Yes | Plate of the long-term rental car. |
mileage | integer | No | Current odometer. |
message | string | No | Free text from the customer. |
locale | string | No | Customer's preferred language for confirmation/cancellation emails and the cancellation page: "is" or "en" (lowercase). Omit, send an empty string, or send JSON null for unspecified — all three are equivalent and reproduce today's existing behavior: combined bilingual emails (Icelandic + English). Any other value returns 400 validation_failed. |
Request
curl -s -X POST https://ltcr-workshop-booking-api.bluecarrental.is/api/v1/workshop/bookings \
-H "X-API-Key: bws_a1b2c3d4_<your-secret>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9b1c-7e2f-2026-05-19-keflavik-AB123" \
-d '{
"location": "keflavik",
"slot_start_at": "2026-05-19T09:00:00Z",
"service_type": "dekk_smur",
"customer": {"name": "Kalli Bimbo", "email": "kalli@example.is", "phone": "+3546901588"},
"license_plate": "AB123",
"mileage": 12345,
"message": "Vil skipta yfir í sumardekk og taka smurningu.",
"locale": "en"
}'
Response
201 Created
{
"booking_id": "5f8e3b1c-4e2a-4a91-9b8f-2c7d6e5a4b3c",
"status": "active",
"location": "keflavik",
"slot_start_at": "2026-05-19T09:00:00Z",
"slot_end_at": "2026-05-19T10:00:00Z",
"service_type": "dekk_smur",
"locale": "en",
"cancel_url": "https://www.bluecarrental.is/langtimaleiga/afboka/5f8e3b1c-4e2a-4a91-9b8f-2c7d6e5a4b3c?token=..."
}
slot_end_at is derived from slot_start_at + service_type.duration_minutes — so a 60-minute dekk_smur booking returns a one-hour range even if the underlying schedule is on a 30-minute grid.
locale echo and effect. The locale value is echoed back as "locale": string|null on both this 201 response and on GET /bookings/{id}. "en" produces pure English confirmation and cancellation emails and renders the cancellation confirmation page English-first. "is" produces pure Icelandic emails. Omitted or unspecified (null) preserves the existing default behavior: combined bilingual emails with subject "<IS> / <EN>" and a body containing the Icelandic copy, a separator, then the English copy. Existing integrations that do not send locale see no change — the field is fully additive and backward compatible.
cancel_url on replay. If this request is an idempotent replay (same Idempotency-Key, same payload, within 24 hours), the response is the original 201 Created with the same booking_id but with cancel_url omitted. The cancel token is stored as a hash, so the plaintext URL cannot be reconstructed for a replay. Persist cancel_url from the first successful response — see Concepts → Idempotency.
Error responses
400 validation_failed— malformed body, missing field, invalid email, unknownlocation, unknown or inactiveservice_type, invalidlocale(must be"is"or"en"), or slot is in the past or inside the configured booking buffer (see availability →earliest_bookable_at).401 unauthorized— missing or invalid API key.409 lead_time_violation— slot is inside the service type'smin_lead_calendar_dayswindow (e.g. same-day when the service requires next-day or later). Refresh/service-typesand/availabilityand offer the customer a later slot.409 lookahead_violation— slot is past the service type'smax_lookahead_dayswindow. Refresh and offer an earlier slot.409 slot_off_grid—slot_start_atdoes not align with the location's schedule grid for this service (e.g. a 09:15 start when the grid begins at 09:00 with 30-minute cells), or the slot's full duration runs past the day'sclose_time. Always readslot_start_atvalues from/availabilityrather than constructing them.409 slot_taken— every lane for that slot is already booked. Terminal — do not retry. Refresh/availabilityand show the customer a different slot. Withcapacity > 1at a location, the same start time can succeed again until every lane is taken.409 idempotency_conflict— sameIdempotency-Keyreused with a different payload. Generate a new key.422 slot_unavailable— slot falls on a blackout (national holiday or planned closure). Refresh/availability.429 rate_limited— see Concepts → Rate limits.5xx internal_error— safe to retry with the sameIdempotency-Key.
Read a booking
Read a booking record. Two authentication modes are supported.
Authentication modes
| Mode | How | When to use |
|---|---|---|
| API key | X-API-Key header | Partner backend reading the booking. |
| Cancel token | ?token=<cancel_token> query string | Partner frontend confirmation page (no API secret in the browser bundle). |
Request
curl -s https://ltcr-workshop-booking-api.bluecarrental.is/api/v1/workshop/bookings/5f8e3b1c-4e2a-4a91-9b8f-2c7d6e5a4b3c \ -H "X-API-Key: bws_a1b2c3d4_<your-secret>"
Response
200 Success
{
"booking_id": "5f8e3b1c-4e2a-4a91-9b8f-2c7d6e5a4b3c",
"status": "active",
"location": "keflavik",
"slot_start_at": "2026-05-19T09:00:00Z",
"slot_end_at": "2026-05-19T10:00:00Z",
"service_type": "dekk_smur",
"customer": {"name": "Kalli Bimbo", "email": "kalli@example.is", "phone": "+3546901588"},
"license_plate": "AB123",
"mileage": 12345,
"message": "Vil skipta yfir í sumardekk og taka smurningu.",
"locale": "en",
"cancelled_at": null
}
Error responses
401 unauthorized— missing or invalid API key (API-key mode only).403 forbidden— cancel token does not match this booking.404 not_found— booking ID does not exist.
Cancel a booking
Token-authenticated cancellation. Called server-side by the partner's customer-facing cancellation page after the customer confirms.
cancel_url in the booking response and the customer email points at the partner-operated page (e.g. https://www.bluecarrental.is/langtimaleiga/afboka/{booking_id}?token=...), not at this endpoint. The partner page is responsible for calling this endpoint server-side to perform the cancellation. See Concepts → Cancellation flow.Query parameters
| Name | Required | Notes |
|---|---|---|
token | Yes | The cancel token from the confirmation email. |
Response
Determined by the Accept header:
text/html(the default for browsers) — renders a bilingual confirmation page. If the partner has acancel_redirect_urlconfigured, the response is a303 See Otherredirect there with?status=cancelledappended.application/json— returns the JSON shape below.
200 Cancelled (JSON)
{
"booking_id": "5f8e3b1c-4e2a-4a91-9b8f-2c7d6e5a4b3c",
"status": "cancelled",
"cancelled_at": "2026-05-17T14:23:08Z"
}
Error responses
403 forbidden— token invalid.404 not_found— booking ID unknown.409 already_cancelled— booking is already cancelled. Idempotent: still returns200with the existingcancelled_at.409 too_late_to_cancel— the slot start is in the past.
Errors
Errors return a uniform JSON envelope:
{
"error": "slot_taken",
"message": "That slot was just booked. Please choose another time.",
"details": {
"slot_start_at": "2026-05-19T09:00:00Z"
}
}
HTTP status codes
| Status | Meaning | Retry? |
|---|---|---|
400 validation_failed | Malformed request body or missing/invalid field. | No. |
401 unauthorized | Missing or invalid API key. | No. Stop and contact Blue Car Rental. |
403 forbidden | Cancel token does not match the booking. | No. |
404 not_found | Booking ID unknown. | No. |
409 lead_time_violation | Booking is inside the service type's min_lead_calendar_days window. | No. Refresh /service-types + /availability; offer a later slot. |
409 lookahead_violation | Booking is past the service type's max_lookahead_days window. | No. Refresh and offer an earlier slot. |
409 slot_off_grid | Slot does not align to the schedule grid for this service, or runs past close_time. | No. Read slot_start_at from /availability rather than constructing it. |
409 slot_taken | Every lane for that slot is already booked. | No — terminal. Offer a different slot. |
409 idempotency_conflict | Same Idempotency-Key, different payload. | No. Generate a new key. |
409 already_cancelled | Booking already cancelled (cancel endpoint only). | No — treated as success. |
409 too_late_to_cancel | Slot start is in the past (cancel endpoint only). | No. |
422 slot_unavailable | Slot falls on a blackout (national holiday or planned closure). | No. Refresh /availability. |
429 rate_limited | Rate limit exceeded. | Yes, after Retry-After. |
5xx internal_error | Server-side failure. | Yes, with the same Idempotency-Key and exponential backoff. |
Retry guidance
- Retry
429and5xxwith exponential backoff (e.g. 1s, 2s, 4s, 8s, cap at 60s). - Always include the original
Idempotency-Keywhen retryingPOST /bookings— without it, retries may create duplicate bookings. - Never retry any other
4xx.