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

EnvironmentBase URL
Productionhttps://ltcr-workshop-booking-api.bluecarrental.is
Staginghttps://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.

Partners own the customer-facing UI. Blue Car Rental owns this API and its database. There is no partner-facing admin panel.
v1.5 breaking changes for partners upgrading from v1:
2026-05-27 breaking change — 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:

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:

  1. Request an API key from Blue Car Rental (see Authentication).
  2. Call GET /api/v1/workshop/locations to list active workshop locations.
  3. Call GET /api/v1/workshop/service-types to discover the bookable services and their duration, lead-time, and lookahead policy. Cache for up to 5 minutes.
  4. Call GET /api/v1/workshop/availability with the location, the chosen service_type slug, and a date range. The returned slot grid already reflects that service type's duration.
  5. Call POST /api/v1/workshop/bookings with the chosen slot, the same service_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.

PartExampleNotes
bwsbwsScheme tag. Always bws.
Prefixa1b2c3d48 hex characters. Identifies your partner record. Safe to log.
Secrete5f6…b032 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:

Treat the secret half of the key like a password. Keep it on the server. Never embed an API key in a customer-facing web or mobile bundle.

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:

FieldNotes
slugStable machine identifier — use this on the wire. Examples in production today: dekk, smur, dekk_smur.
display_name_is / display_name_enBilingual labels for your UI. Blue Car Rental owns the canonical wording; do not translate on your end.
duration_minutesHow 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_daysHow many full calendar days of lead time the customer needs. 1 means "no same-day bookings".
max_lookahead_daysHow 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.

Multi-environment routing. When the booking is issued by a non-production backend (e.g. our staging API), the cancel URL carries an additional &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

ScopeLimit
Per IP, global1000 requests/minute
Per API key, all endpoints500 requests/minute
Per API key, POST /bookings60 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

GET /api/v1/workshop/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

GET /api/v1/workshop/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

FieldTypeNotes
slugstringStable machine identifier. Pass this back on /availability and POST /bookings.
display_name_isstringIcelandic display name. Render verbatim — do not translate.
display_name_enstringEnglish display name. Render verbatim.
duration_minutesintegerAlways a positive multiple of 30. Determines the slot length on /availability for this service.
min_lead_calendar_daysintegerMinimum calendar-day lead time. 0 permits same-day bookings; 1 requires at least the next calendar day.
max_lookahead_daysintegerMaximum calendar-day window the customer can book into the future, counted from today.
sort_orderintegerThe order Blue Car Rental wants you to render the services. Ascending. Stable across calls.
Response is cacheable for 5 minutes (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

Read schedule

GET /api/v1/workshop/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

NameTypeRequiredNotes
locationstringYesLocation 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

FieldTypeNotes
day_of_weekintegerISO-8601 day-of-week: 1 = Monday … 7 = Sunday. Days absent from the response are closed.
open_time / close_timestring (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_minutesintegerThe 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.
capacityintegerNumber 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_fromstring (YYYY-MM-DD)Date on which this rule started applying.
effective_untilstring or nullInclusive last date the rule applies, or null for open-ended.
Response is cacheable for 5 minutes (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

List holidays

GET /api/v1/workshop/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

NameTypeRequiredNotes
locationstringYesLocation slug.
fromdate (YYYY-MM-DD)YesInclusive start date.
todate (YYYY-MM-DD)YesInclusive 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

FieldTypeNotes
datestring (YYYY-MM-DD)Calendar date in the location's local timezone. Each entry covers the full local day.
reasonstring or nullHuman-readable reason. May be null for closures Blue Car Rental did not label.
reason_enstring or nullEnglish human-readable reason. null when no English label exists — show a generic "Closed" in an English UI.
Response is cacheable for 10 minutes (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

Check availability

GET /api/v1/workshop/availability

List bookable slots in a date range for a single location and service type.

Query parameters

NameTypeRequiredNotes
locationstringYesLocation slug (e.g. keflavik).
service_typestringYesService-type slug from GET /service-types. Determines slot duration. Missing or unknown slug → 400 validation_failed.
fromdate (YYYY-MM-DD)YesInclusive.
todate (YYYY-MM-DD)YesInclusive. 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"}
  ]
}
Slot duration reflects the requested 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.
Booking buffer. Each location can be configured by Blue Car Rental with an advance-notice buffer in whole hours. 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

Create a booking

POST /api/v1/workshop/bookings

Create a workshop booking.

Headers

NameRequiredNotes
X-API-KeyYesYour partner API key.
Content-TypeYesapplication/json.
Idempotency-KeyStrongly recommendedUp to 255 chars. See Concepts → Idempotency.

Request body

FieldTypeRequiredNotes
locationstringYesLocation slug.
slot_start_atstring (RFC 3339 UTC)YesMust align with a slot returned by /availability for the same service_type.
service_typestringYesService-type slug from GET /service-types. Must be active.
customer.namestringYes
customer.emailstringYesConfirmation email is sent here.
customer.phonestringYesE.164 recommended.
license_platestringYesPlate of the long-term rental car.
mileageintegerNoCurrent odometer.
messagestringNoFree text from the customer.
localestringNoCustomer'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

Read a booking

GET /api/v1/workshop/bookings/{id}

Read a booking record. Two authentication modes are supported.

Authentication modes

ModeHowWhen to use
API keyX-API-Key headerPartner backend reading the booking.
Cancel token?token=<cancel_token> query stringPartner 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

Cancel a booking

GET /api/v1/workshop/bookings/{id}/cancel

Token-authenticated cancellation. Called server-side by the partner's customer-facing cancellation page after the customer confirms.

As of 2026-05-27 the 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

NameRequiredNotes
tokenYesThe cancel token from the confirmation email.

Response

Determined by the Accept header:

200 Cancelled (JSON)

{
  "booking_id": "5f8e3b1c-4e2a-4a91-9b8f-2c7d6e5a4b3c",
  "status": "cancelled",
  "cancelled_at": "2026-05-17T14:23:08Z"
}

Error responses

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

StatusMeaningRetry?
400 validation_failedMalformed request body or missing/invalid field.No.
401 unauthorizedMissing or invalid API key.No. Stop and contact Blue Car Rental.
403 forbiddenCancel token does not match the booking.No.
404 not_foundBooking ID unknown.No.
409 lead_time_violationBooking is inside the service type's min_lead_calendar_days window.No. Refresh /service-types + /availability; offer a later slot.
409 lookahead_violationBooking is past the service type's max_lookahead_days window.No. Refresh and offer an earlier slot.
409 slot_off_gridSlot 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_takenEvery lane for that slot is already booked.No — terminal. Offer a different slot.
409 idempotency_conflictSame Idempotency-Key, different payload.No. Generate a new key.
409 already_cancelledBooking already cancelled (cancel endpoint only).No — treated as success.
409 too_late_to_cancelSlot start is in the past (cancel endpoint only).No.
422 slot_unavailableSlot falls on a blackout (national holiday or planned closure).No. Refresh /availability.
429 rate_limitedRate limit exceeded.Yes, after Retry-After.
5xx internal_errorServer-side failure.Yes, with the same Idempotency-Key and exponential backoff.

Retry guidance