{"openapi":"3.1.0","info":{"title":"Lola Dispatch Integration API","version":"1.0.0","description":"B2B API for booking phlebotomy services through Lola Dispatch (v1). See https://loladispatch.com/developers for the rendered Scalar portal."},"servers":[{"url":"https://app.loladispatch.com","description":"Production"}],"security":[{"ApiKey":[]}],"paths":{"/api/integration/v1/booking-url":{"post":{"summary":"Mint a hosted-booking URL","description":"Creates a single-use JWT booking link the patient can open on book.loladispatch.com. The scope discriminator controls which fulfillment channels the patient can pick (mobile only, venue only, a specific venue, or mobile-with-venue-fallback). Capability gates: scope `all-mobile` requires `canUseMobilePhleb`; `all-venues` requires `canUseVenueNetwork`; `mobile-then-venue-fallback` requires both.","tags":["Hosted Booking"],"operationId":"mintBookingUrl","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingUrlRequest"}}}},"responses":{"200":{"description":"Booking URL minted. The token is single-use and expires in 24 hours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingUrlResponse"}}}},"400":{"description":"Validation failed or capability denied with `error: \"VALIDATION\"`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid `X-API-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but the client capability gate denied this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found or not accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (60 req/min per key by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error — internal exception; safe to retry with same idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/integration/v1/bookings/{id}/cancel":{"post":{"summary":"Cancel a booking","description":"Cancels a non-completed, non-disputed booking. Blocks cancellation within 24h of the appointment (mirrors the refund policy). Fires a `job.cancelled` webhook on success. Idempotent: cancelling an already-CANCELLED booking returns 200 with the same shape.","tags":["Bookings"],"operationId":"cancelBooking","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Booking cancelled (or was already cancelled — idempotent).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingCancelResponse"}}}},"400":{"description":"Validation failed or capability denied with `error: \"VALIDATION\"`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid `X-API-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but the client capability gate denied this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found or not accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Cancellation refused — booking is under dispute or within the 24h lock window.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (60 req/min per key by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error — internal exception; safe to retry with same idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/integration/v1/bookings/{id}/sample":{"get":{"summary":"Get the latest sample for a booking","description":"Returns the most recent `Sample` row for the booking, or `sample: null` if collection has not been logged yet. `resultsReadyAt` and `resultsUrl` always null in v1 — LV1 (lab inbound) wires them.","tags":["Bookings"],"operationId":"getBookingSample","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Sample (or null if uncollected).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingSampleResponse"}}}},"400":{"description":"Validation failed or capability denied with `error: \"VALIDATION\"`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid `X-API-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but the client capability gate denied this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found or not accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (60 req/min per key by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error — internal exception; safe to retry with same idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/integration/v1/bookings/{id}":{"get":{"summary":"Get a booking by id","description":"Returns the integration-facing projection of a `Job`. Cross-tenant access is gated by a `clientId`-scoped `findFirst` (no leak across API keys).","tags":["Bookings"],"operationId":"getBooking","parameters":[{"name":"id","in":"path","required":true,"description":"Job id (`Job.id`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Booking found and accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingDetailResponse"}}}},"400":{"description":"Validation failed or capability denied with `error: \"VALIDATION\"`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid `X-API-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but the client capability gate denied this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found or not accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (60 req/min per key by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error — internal exception; safe to retry with same idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/integration/v1/bookings":{"get":{"summary":"List bookings","description":"Cursor-paginated list of this client's bookings (`Job` rows), newest first. Filter by `status`, `bookingSource`, and a `createdFrom` / `createdTo` ISO range.","tags":["Bookings"],"operationId":"listBookings","parameters":[{"name":"limit","in":"query","required":false,"description":"Page size (default 25, max 100).","schema":{"type":"integer"}},{"name":"cursor","in":"query","required":false,"description":"Opaque forward cursor from the previous page's `nextCursor`.","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"Filter by Job status.","schema":{"type":"string"}},{"name":"bookingSource","in":"query","required":false,"description":"Filter by booking source (INTERNAL | INTEGRATION_API | INTEGRATION_HOSTED).","schema":{"type":"string"}},{"name":"createdFrom","in":"query","required":false,"description":"ISO timestamp lower bound (inclusive).","schema":{"type":"string"}},{"name":"createdTo","in":"query","required":false,"description":"ISO timestamp upper bound (inclusive).","schema":{"type":"string"}}],"responses":{"200":{"description":"Page of bookings + a forward cursor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingListResponse"}}}},"400":{"description":"Validation failed or capability denied with `error: \"VALIDATION\"`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid `X-API-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but the client capability gate denied this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found or not accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (60 req/min per key by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error — internal exception; safe to retry with same idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"summary":"Create a booking","description":"Creates a `Job` in PENDING / PROCESSING. WEEKLY_INVOICE clients get an admin booking URL. PATIENT_PAY clients get a hosted payment URL the patient must open. IN_VENUE requires `canUseVenueNetwork`. A `job.created` webhook fires on success.","tags":["Bookings"],"operationId":"createBooking","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingRequest"}}}},"responses":{"200":{"description":"Booking accepted. Status discriminator depends on the client billing mode.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingResponse"}}}},"400":{"description":"Validation failed or capability denied with `error: \"VALIDATION\"`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid `X-API-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but the client capability gate denied this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found or not accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (60 req/min per key by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error — internal exception; safe to retry with same idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/integration/v1/disputes/{id}":{"get":{"summary":"Get a dispute by id","description":"Returns one dispute. Cross-tenant access is gated through the parent Job's `clientId`.","tags":["Billing"],"operationId":"getDispute","parameters":[{"name":"id","in":"path","required":true,"description":"Dispute id (`Dispute.id`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Dispute found and accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisputeDetailResponse"}}}},"400":{"description":"Validation failed or capability denied with `error: \"VALIDATION\"`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid `X-API-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but the client capability gate denied this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found or not accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (60 req/min per key by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error — internal exception; safe to retry with same idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/integration/v1/disputes":{"get":{"summary":"List disputes","description":"Cursor-paginated list of chargebacks / disputes against this client's jobs, newest first. Filter by `status` and `jobId`. Tenancy is resolved through the parent Job.","tags":["Billing"],"operationId":"listDisputes","parameters":[{"name":"limit","in":"query","required":false,"description":"Page size (default 25, max 100).","schema":{"type":"integer"}},{"name":"cursor","in":"query","required":false,"description":"Opaque forward cursor from the previous page's `nextCursor`.","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"Filter by Stripe dispute status string.","schema":{"type":"string"}},{"name":"jobId","in":"query","required":false,"description":"Filter to disputes for a single Job.","schema":{"type":"string"}}],"responses":{"200":{"description":"Page of disputes + a forward cursor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisputeListResponse"}}}},"400":{"description":"Validation failed or capability denied with `error: \"VALIDATION\"`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid `X-API-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but the client capability gate denied this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found or not accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (60 req/min per key by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error — internal exception; safe to retry with same idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/integration/v1/invoices/{id}":{"get":{"summary":"Get an invoice by id","description":"Returns one invoice with its stored line items and linked jobs. Cross-tenant access is gated by a `clientId`-scoped `findFirst`.","tags":["Billing"],"operationId":"getInvoice","parameters":[{"name":"id","in":"path","required":true,"description":"Invoice id (`Invoice.id`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Invoice found and accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoiceDetailResponse"}}}},"400":{"description":"Validation failed or capability denied with `error: \"VALIDATION\"`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid `X-API-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but the client capability gate denied this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found or not accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (60 req/min per key by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error — internal exception; safe to retry with same idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/integration/v1/invoices":{"get":{"summary":"List invoices","description":"Cursor-paginated list of this client's weekly invoices, newest first. Filter by `status`, and a `periodStart` / `periodEnd` ISO range.","tags":["Billing"],"operationId":"listInvoices","parameters":[{"name":"limit","in":"query","required":false,"description":"Page size (default 25, max 100).","schema":{"type":"integer"}},{"name":"cursor","in":"query","required":false,"description":"Opaque forward cursor from the previous page's `nextCursor`.","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"Filter by invoice status.","schema":{"type":"string"}},{"name":"periodStart","in":"query","required":false,"description":"ISO timestamp; only invoices whose periodStart >= this.","schema":{"type":"string"}},{"name":"periodEnd","in":"query","required":false,"description":"ISO timestamp; only invoices whose periodEnd <= this.","schema":{"type":"string"}}],"responses":{"200":{"description":"Page of invoices + a forward cursor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoiceListResponse"}}}},"400":{"description":"Validation failed or capability denied with `error: \"VALIDATION\"`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid `X-API-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but the client capability gate denied this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found or not accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (60 req/min per key by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error — internal exception; safe to retry with same idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/integration/v1/refunds/{id}":{"get":{"summary":"Get a refund by id","description":"Returns one refund. Cross-tenant access is gated through the parent Job's `clientId`.","tags":["Billing"],"operationId":"getRefund","parameters":[{"name":"id","in":"path","required":true,"description":"Refund id (`Refund.id`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Refund found and accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundDetailResponse"}}}},"400":{"description":"Validation failed or capability denied with `error: \"VALIDATION\"`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid `X-API-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but the client capability gate denied this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found or not accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (60 req/min per key by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error — internal exception; safe to retry with same idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/integration/v1/refunds":{"get":{"summary":"List refunds","description":"Cursor-paginated list of refunds against this client's jobs, newest first. Filter by `status` and `jobId`. Tenancy is resolved through the parent Job (refunds carry no client column).","tags":["Billing"],"operationId":"listRefunds","parameters":[{"name":"limit","in":"query","required":false,"description":"Page size (default 25, max 100).","schema":{"type":"integer"}},{"name":"cursor","in":"query","required":false,"description":"Opaque forward cursor from the previous page's `nextCursor`.","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"Filter by refund status.","schema":{"type":"string"}},{"name":"jobId","in":"query","required":false,"description":"Filter to refunds for a single Job.","schema":{"type":"string"}}],"responses":{"200":{"description":"Page of refunds + a forward cursor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundListResponse"}}}},"400":{"description":"Validation failed or capability denied with `error: \"VALIDATION\"`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid `X-API-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but the client capability gate denied this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found or not accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (60 req/min per key by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error — internal exception; safe to retry with same idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/integration/v1/test-panels":{"get":{"summary":"List test panels","description":"Returns the `JobTemplate` rows the API key can use — this client's private templates plus globally-published Lola-curated ones. Use the `id` as `appointment.testType` on `POST /bookings`.","tags":["Catalogue"],"operationId":"listTestPanels","parameters":[{"name":"limit","in":"query","required":false,"description":"Page size (default 25, max 100).","schema":{"type":"integer"}},{"name":"cursor","in":"query","required":false,"description":"Opaque forward cursor from the previous page's `nextCursor`.","schema":{"type":"string"}}],"responses":{"200":{"description":"Test panel list (global panels appear first).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestPanelsResponse"}}}},"400":{"description":"Validation failed or capability denied with `error: \"VALIDATION\"`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid `X-API-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but the client capability gate denied this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found or not accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (60 req/min per key by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error — internal exception; safe to retry with same idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/integration/v1/venue-field-definitions":{"get":{"summary":"Get the venue completion field definitions","description":"Returns the venue-completion field definitions configured on this client's `ClientCapability.venueCompletionFields`, plus the configured `labShipmentMode`. Use these to render the venue-staff completion form for IN_VENUE bookings — and use `key` values as `Sample.clientMetadata` keys when reading sample data back from `GET /bookings/{id}/sample`.","tags":["Catalogue"],"operationId":"getVenueFieldDefinitions","responses":{"200":{"description":"Venue completion field config (empty array when nothing is configured).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VenueFieldDefinitionsResponse"}}}},"400":{"description":"Validation failed or capability denied with `error: \"VALIDATION\"`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid `X-API-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but the client capability gate denied this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found or not accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (60 req/min per key by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error — internal exception; safe to retry with same idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/integration/v1/venues/{id}/availability":{"get":{"summary":"Venue slot availability","description":"Returns bookable slots for a venue in the requested ISO window (max 30 days). Capacity per slot reflects `phlebotomyStations` minus already-booked Jobs at that start time. Requires `canUseVenueNetwork`.","tags":["Venues"],"operationId":"getVenueAvailability","parameters":[{"name":"id","in":"path","required":true,"description":"Venue id (`Venue.id`).","schema":{"type":"string"}},{"name":"from","in":"query","required":true,"description":"ISO timestamp window start.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":true,"description":"ISO timestamp window end. Must be > `from` and within 30 days.","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Slot list. Empty `slots` means fully booked or outside operating hours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VenueAvailabilityResponse"}}}},"400":{"description":"Validation failed or capability denied with `error: \"VALIDATION\"`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid `X-API-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but the client capability gate denied this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found or not accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (60 req/min per key by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error — internal exception; safe to retry with same idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/integration/v1/venues/{id}":{"get":{"summary":"Get a venue by id","description":"Returns the full venue detail projection for a single venue. Only verified, active, public-booking-enabled venues are surfaced. Requires `canUseVenueNetwork`.","tags":["Venues"],"operationId":"getVenue","parameters":[{"name":"id","in":"path","required":true,"description":"Venue id (`Venue.id`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Venue found and bookable by public.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VenueDetailResponse"}}}},"400":{"description":"Validation failed or capability denied with `error: \"VALIDATION\"`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid `X-API-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but the client capability gate denied this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found or not accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (60 req/min per key by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error — internal exception; safe to retry with same idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/integration/v1/venues":{"get":{"summary":"List public-bookable venues","description":"Returns venues this client may dispatch into. Only verified, active, public-booking-enabled venues are surfaced. Requires `canUseVenueNetwork`. Filters are applied at the SQL layer.","tags":["Venues"],"operationId":"listVenues","parameters":[{"name":"postcode","in":"query","required":false,"description":"UK postcode prefix filter (first segment, case-insensitive).","schema":{"type":"string"}},{"name":"type","in":"query","required":false,"description":"Venue type filter (matches `Venue.venueType` enum).","schema":{"type":"string"}},{"name":"capability","in":"query","required":false,"description":"Venue capability filter (matches `VenueCapability.capability` enum).","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Max venues to return. Clamped to [1, 50]. Default 50.","schema":{"type":"integer","minimum":1,"maximum":50,"default":50}}],"responses":{"200":{"description":"Venue list (max 50). `total` is the response length, not a cross-page count.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VenuesListResponse"}}}},"400":{"description":"Validation failed or capability denied with `error: \"VALIDATION\"`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid `X-API-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but the client capability gate denied this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found or not accessible to this client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (60 req/min per key by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error — internal exception; safe to retry with same idempotency key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"schemas":{"BookingUrlRequest":{"type":"object","properties":{"scope":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"all-mobile"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"all-venues"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"venue"},"venueId":{"type":"string","minLength":1}},"required":["kind","venueId"]},{"type":"object","properties":{"kind":{"type":"string","const":"mobile-then-venue-fallback"}},"required":["kind"]}],"type":"object"},"externalUserId":{"type":"string","maxLength":200},"externalOrderId":{"type":"string","maxLength":200},"passthrough":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"patient":{"$ref":"#/components/schemas/PatientHint"}},"required":["scope"]},"PatientHint":{"type":"object","properties":{"firstName":{"type":"string","minLength":1},"lastName":{"type":"string","minLength":1},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"phone":{"type":"string"},"dateOfBirth":{"type":"string"},"gender":{"type":"string","enum":["MALE","FEMALE"]}}},"BookingRequest":{"oneOf":[{"type":"object","properties":{"fulfillmentChannel":{"type":"string","const":"MOBILE"},"patient":{"$ref":"#/components/schemas/BookingPatient"},"location":{"$ref":"#/components/schemas/BookingMobileLocation"},"appointment":{"$ref":"#/components/schemas/BookingMobileAppointment"},"externalUserId":{"type":"string","maxLength":200},"externalOrderId":{"type":"string","maxLength":200},"passthrough":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["fulfillmentChannel","patient","location","appointment"]},{"type":"object","properties":{"fulfillmentChannel":{"type":"string","const":"IN_VENUE"},"patient":{"$ref":"#/components/schemas/BookingPatient"},"venueId":{"type":"string","minLength":1},"venueSlotStart":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"appointment":{"$ref":"#/components/schemas/BookingVenueSlot"},"externalUserId":{"type":"string","maxLength":200},"externalOrderId":{"type":"string","maxLength":200},"passthrough":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["fulfillmentChannel","patient","venueId","venueSlotStart","appointment"]}],"type":"object"},"BookingPatient":{"type":"object","properties":{"firstName":{"type":"string","minLength":1},"lastName":{"type":"string","minLength":1},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"phone":{"description":"E.164 phone number; UK numbers accepted in national form, international numbers must include the country dial (`+33…`).","type":"string","minLength":1},"dateOfBirth":{"type":"string","minLength":1,"description":"ISO date (YYYY-MM-DD)."},"gender":{"type":"string","enum":["MALE","FEMALE"]}},"required":["firstName","lastName","email","phone","dateOfBirth","gender"]},"BookingMobileLocation":{"type":"object","properties":{"addressLine1":{"type":"string","minLength":1},"addressLine2":{"type":"string"},"city":{"type":"string","minLength":1},"county":{"type":"string"},"postcode":{"type":"string","pattern":"^[A-Z]{1,2}\\d[A-Z\\d]?\\s?\\d[A-Z]{2}$"}},"required":["addressLine1","city","postcode"]},"BookingMobileAppointment":{"type":"object","properties":{"preferredDate":{"type":"string","minLength":1,"description":"ISO date the patient prefers."},"timePreference":{"type":"string","enum":["morning","afternoon","evening"]},"testType":{"type":"string","minLength":1,"description":"Free-text test type label; matches client templates."},"specialInstructions":{"type":"string"}},"required":["preferredDate","timePreference","testType"]},"BookingVenueSlot":{"type":"object","properties":{"testType":{"type":"string","minLength":1},"specialInstructions":{"type":"string"}},"required":["testType"]},"BookingUrlResponse":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/BookingUrlData"}},"required":["success","data"],"additionalProperties":false},"BookingUrlData":{"type":"object","properties":{"url":{"type":"string","description":"Patient hosted-booking URL on book.loladispatch.com."},"expiresAt":{"type":"string","description":"ISO timestamp when the JWT token in the URL stops validating (24 h after mint)."},"tokenId":{"type":"string","description":"The token `jti` — single-use; once submitted, the URL is consumed."}},"required":["url","expiresAt","tokenId"],"additionalProperties":false},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","const":false},"error":{"type":"string","description":"Machine-readable error code (e.g. INVALID_API_KEY, VALIDATION)."},"message":{"description":"Human-readable detail (omitted when no extra context).","type":"string"}},"required":["success","error"],"additionalProperties":false},"BookingCancelResponse":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/BookingCancelData"}},"required":["success","data"],"additionalProperties":false},"BookingCancelData":{"type":"object","properties":{"jobId":{"type":"string"},"status":{"type":"string","const":"CANCELLED"}},"required":["jobId","status"],"additionalProperties":false},"BookingSampleResponse":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/BookingSampleData"}},"required":["success","data"],"additionalProperties":false},"BookingSampleData":{"type":"object","properties":{"sample":{"anyOf":[{"$ref":"#/components/schemas/BookingSample"},{"type":"null"}]}},"required":["sample"],"additionalProperties":false},"BookingSample":{"type":"object","properties":{"barcode":{"anyOf":[{"type":"string"},{"type":"null"}]},"status":{"type":"string","description":"Open string — mirrors `Sample.status` enum (do not pin until LV1 results wave)."},"sampleDate":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO timestamp of collection."},"mailTracking":{"anyOf":[{"type":"string"},{"type":"null"}]},"clientMetadata":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"collectionNotes":{"anyOf":[{"type":"string"},{"type":"null"}]},"shipmentMode":{"anyOf":[{"type":"string"},{"type":"null"}]},"resultsReadyAt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Always null until LV1 lab inbound wave lands."},"resultsUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Always null until LV1 lab inbound wave lands."}},"required":["barcode","status","sampleDate","mailTracking","clientMetadata","collectionNotes","shipmentMode","resultsReadyAt","resultsUrl"],"additionalProperties":false},"BookingDetailResponse":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/BookingDetailData"}},"required":["success","data"],"additionalProperties":false},"BookingDetailData":{"type":"object","properties":{"job":{"$ref":"#/components/schemas/BookingDetailJob"}},"required":["job"],"additionalProperties":false},"BookingDetailJob":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","description":"Job status (e.g. PENDING, CONFIRMED, COMPLETED, CANCELLED)."},"paymentStatus":{"type":"string"},"fulfillmentChannel":{"type":"string","enum":["MOBILE","IN_VENUE"]},"venueId":{"anyOf":[{"type":"string"},{"type":"null"}]},"venue":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"city":{"type":"string"},"postcode":{"type":"string"}},"required":["id","name","slug","city","postcode"],"additionalProperties":false},{"type":"null"}],"description":"Venue block populated for IN_VENUE bookings; null for MOBILE."},"venueSlotStart":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO timestamp; null for MOBILE."},"checkedInAt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO timestamp for IN_VENUE check-in (same column as MOBILE `startedAt`; semantically a check-in for IN_VENUE)."},"externalUserId":{"anyOf":[{"type":"string"},{"type":"null"}]},"externalOrderId":{"anyOf":[{"type":"string"},{"type":"null"}]},"passthrough":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"shipmentMode":{"anyOf":[{"type":"string"},{"type":"null"}]},"sample":{"anyOf":[{"type":"object","properties":{"clientMetadata":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"collectionNotes":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["clientMetadata","collectionNotes"],"additionalProperties":false},{"type":"null"}]}},"required":["id","status","paymentStatus","fulfillmentChannel","venueId","venue","venueSlotStart","checkedInAt","externalUserId","externalOrderId","passthrough","shipmentMode","sample"],"additionalProperties":false},"BookingListResponse":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/BookingListData"}},"required":["success","data"],"additionalProperties":false},"BookingListData":{"type":"object","properties":{"bookings":{"type":"array","items":{"$ref":"#/components/schemas/BookingListItem"}},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pass back as `?cursor=` for the next page; null when exhausted."}},"required":["bookings","nextCursor"],"additionalProperties":false},"BookingListItem":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"paymentStatus":{"type":"string"},"fulfillmentChannel":{"type":"string","enum":["MOBILE","IN_VENUE"]},"bookingSource":{"type":"string"},"venueId":{"anyOf":[{"type":"string"},{"type":"null"}]},"venueSlotStart":{"anyOf":[{"type":"string"},{"type":"null"}]},"externalUserId":{"anyOf":[{"type":"string"},{"type":"null"}]},"externalOrderId":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string"}},"required":["id","status","paymentStatus","fulfillmentChannel","bookingSource","venueId","venueSlotStart","externalUserId","externalOrderId","createdAt"],"additionalProperties":false},"BookingResponse":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/BookingResponseData"}},"required":["success","data"],"additionalProperties":false},"BookingResponseData":{"anyOf":[{"type":"object","properties":{"jobId":{"type":"string"},"status":{"type":"string","const":"PENDING"},"bookingUrl":{"type":"string","description":"Admin URL for staff follow-up. WEEKLY_INVOICE clients only."}},"required":["jobId","status","bookingUrl"],"additionalProperties":false},{"type":"object","properties":{"jobId":{"type":"string"},"status":{"type":"string","const":"PENDING_PAYMENT"},"paymentRequiredUrl":{"type":"string","description":"Patient hosted-payment URL on book.loladispatch.com. PATIENT_PAY clients only."}},"required":["jobId","status","paymentRequiredUrl"],"additionalProperties":false}]},"DisputeDetailResponse":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/DisputeDetailData"}},"required":["success","data"],"additionalProperties":false},"DisputeDetailData":{"type":"object","properties":{"dispute":{"$ref":"#/components/schemas/DisputeDetail"}},"required":["dispute"],"additionalProperties":false},"DisputeDetail":{"type":"object","properties":{"id":{"type":"string"},"jobId":{"anyOf":[{"type":"string"},{"type":"null"}]},"amountPence":{"type":"number","description":"Disputed amount in pence (Stripe-native)."},"currency":{"type":"string"},"reason":{"type":"string"},"status":{"type":"string"},"stripeDisputeId":{"type":"string"},"stripeChargeId":{"type":"string"},"stripePaymentIntentId":{"anyOf":[{"type":"string"},{"type":"null"}]},"evidenceDueBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","jobId","amountPence","currency","reason","status","stripeDisputeId","stripeChargeId","stripePaymentIntentId","evidenceDueBy","createdAt","updatedAt"],"additionalProperties":false},"DisputeListResponse":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/DisputeListData"}},"required":["success","data"],"additionalProperties":false},"DisputeListData":{"type":"object","properties":{"disputes":{"type":"array","items":{"$ref":"#/components/schemas/DisputeListItem"}},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pass back as `?cursor=` for the next page; null when exhausted."}},"required":["disputes","nextCursor"],"additionalProperties":false},"DisputeListItem":{"type":"object","properties":{"id":{"type":"string"},"jobId":{"anyOf":[{"type":"string"},{"type":"null"}]},"amountPence":{"type":"number","description":"Disputed amount in pence (Stripe-native)."},"currency":{"type":"string"},"reason":{"type":"string","description":"Stripe dispute reason (e.g. fraudulent, product_not_received)."},"status":{"type":"string","description":"Stripe dispute status string (needs_response | under_review | won | lost | ...)."},"stripeDisputeId":{"type":"string"},"evidenceDueBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string"}},"required":["id","jobId","amountPence","currency","reason","status","stripeDisputeId","evidenceDueBy","createdAt"],"additionalProperties":false},"InvoiceDetailResponse":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/InvoiceDetailData"}},"required":["success","data"],"additionalProperties":false},"InvoiceDetailData":{"type":"object","properties":{"invoice":{"$ref":"#/components/schemas/InvoiceDetail"}},"required":["invoice"],"additionalProperties":false},"InvoiceDetail":{"type":"object","properties":{"id":{"type":"string"},"invoiceNumber":{"type":"string"},"status":{"type":"string"},"subtotalPence":{"type":"number"},"vatPence":{"type":"number"},"totalPence":{"type":"number"},"dueDate":{"type":"string"},"periodStart":{"type":"string"},"periodEnd":{"type":"string"},"jobCount":{"type":"number"},"lineItems":{"description":"Stored line-item JSON used for PDF generation."},"hostedInvoiceUrl":{"anyOf":[{"type":"string"},{"type":"null"}]},"paidAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string"},"jobs":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceJob"}}},"required":["id","invoiceNumber","status","subtotalPence","vatPence","totalPence","dueDate","periodStart","periodEnd","jobCount","lineItems","hostedInvoiceUrl","paidAt","createdAt","jobs"],"additionalProperties":false},"InvoiceJob":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"fulfillmentChannel":{"type":"string","enum":["MOBILE","IN_VENUE"]},"externalOrderId":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","status","fulfillmentChannel","externalOrderId"],"additionalProperties":false},"InvoiceListResponse":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/InvoiceListData"}},"required":["success","data"],"additionalProperties":false},"InvoiceListData":{"type":"object","properties":{"invoices":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceListItem"}},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pass back as `?cursor=` for the next page; null when exhausted."}},"required":["invoices","nextCursor"],"additionalProperties":false},"InvoiceListItem":{"type":"object","properties":{"id":{"type":"string"},"invoiceNumber":{"type":"string"},"status":{"type":"string","description":"Invoice status (PENDING | PROCESSING | PAID | FAILED | REFUNDED | OVERDUE)."},"subtotalPence":{"type":"number"},"vatPence":{"type":"number"},"totalPence":{"type":"number"},"dueDate":{"type":"string"},"periodStart":{"type":"string"},"periodEnd":{"type":"string"},"jobCount":{"type":"number"},"hostedInvoiceUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Stripe-hosted invoice URL once issued; null before."},"paidAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string"}},"required":["id","invoiceNumber","status","subtotalPence","vatPence","totalPence","dueDate","periodStart","periodEnd","jobCount","hostedInvoiceUrl","paidAt","createdAt"],"additionalProperties":false},"RefundDetailResponse":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/RefundDetailData"}},"required":["success","data"],"additionalProperties":false},"RefundDetailData":{"type":"object","properties":{"refund":{"$ref":"#/components/schemas/RefundDetail"}},"required":["refund"],"additionalProperties":false},"RefundDetail":{"type":"object","properties":{"id":{"type":"string"},"jobId":{"type":"string"},"amount":{"type":"number","description":"Refunded amount in GBP (pounds)."},"currency":{"type":"string"},"reason":{"type":"string"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}]},"status":{"type":"string"},"stripeRefundId":{"anyOf":[{"type":"string"},{"type":"null"}]},"stripePaymentIntentId":{"anyOf":[{"type":"string"},{"type":"null"}]},"cancelledJob":{"type":"boolean"},"createdAt":{"type":"string"}},"required":["id","jobId","amount","currency","reason","notes","status","stripeRefundId","stripePaymentIntentId","cancelledJob","createdAt"],"additionalProperties":false},"RefundListResponse":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/RefundListData"}},"required":["success","data"],"additionalProperties":false},"RefundListData":{"type":"object","properties":{"refunds":{"type":"array","items":{"$ref":"#/components/schemas/RefundListItem"}},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pass back as `?cursor=` for the next page; null when exhausted."}},"required":["refunds","nextCursor"],"additionalProperties":false},"RefundListItem":{"type":"object","properties":{"id":{"type":"string"},"jobId":{"type":"string"},"amount":{"type":"number","description":"Refunded amount in GBP (pounds)."},"currency":{"type":"string"},"reason":{"type":"string"},"status":{"type":"string","description":"Refund status (SUCCEEDED | PENDING | FAILED | RECONCILED_FROM_STRIPE)."},"stripeRefundId":{"anyOf":[{"type":"string"},{"type":"null"}]},"cancelledJob":{"type":"boolean"},"createdAt":{"type":"string"}},"required":["id","jobId","amount","currency","reason","status","stripeRefundId","cancelledJob","createdAt"],"additionalProperties":false},"TestPanelsResponse":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/TestPanelsData"}},"required":["success","data"],"additionalProperties":false},"TestPanelsData":{"type":"object","properties":{"panels":{"type":"array","items":{"$ref":"#/components/schemas/TestPanel"}},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pass back as `?cursor=` for the next page; null when exhausted."}},"required":["panels","nextCursor"],"additionalProperties":false},"TestPanel":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"isGlobal":{"type":"boolean","description":"true = Lola-curated panel available to all clients; false = client-private."},"steps":{"type":"array","items":{"$ref":"#/components/schemas/TestPanelStep"}}},"required":["id","name","description","isGlobal","steps"],"additionalProperties":false},"TestPanelStep":{"type":"object","properties":{"name":{"type":"string"},"order":{"type":"number"},"isMandatory":{"type":"boolean"},"requiresPhoto":{"type":"boolean"}},"required":["name","order","isMandatory","requiresPhoto"],"additionalProperties":false},"VenueFieldDefinitionsResponse":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/VenueFieldDefinitionsData"}},"required":["success","data"],"additionalProperties":false},"VenueFieldDefinitionsData":{"type":"object","properties":{"venueCompletionFields":{"type":"array","items":{"$ref":"#/components/schemas/VenueCompletionFieldDefinition"}},"labShipmentMode":{"type":"string","enum":["VENUE_OR_PATIENT_TO_LAB","LOLA_COURIER_PICKUP","CLIENT_HANDLES_OFFLINE"]}},"required":["venueCompletionFields","labShipmentMode"],"additionalProperties":false},"VenueCompletionFieldDefinition":{"type":"object","properties":{"key":{"type":"string","description":"JSON-safe object key — matches /^[a-zA-Z][a-zA-Z0-9_]*$/ at write time."},"label":{"type":"string"},"type":{"type":"string","enum":["text","number","select","boolean"]},"required":{"type":"boolean"},"options":{"description":"Required when type='select'.","type":"array","items":{"type":"string"}},"helpText":{"type":"string"}},"required":["key","label","type","required"],"additionalProperties":false},"VenueAvailabilityResponse":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/VenueAvailabilityData"}},"required":["success","data"],"additionalProperties":false},"VenueAvailabilityData":{"type":"object","properties":{"slots":{"type":"array","items":{"$ref":"#/components/schemas/VenueSlot"}}},"required":["slots"],"additionalProperties":false},"VenueSlot":{"type":"object","properties":{"start":{"type":"string","description":"ISO timestamp slot start."},"end":{"type":"string","description":"ISO timestamp slot end."},"capacity":{"type":"number","description":"Remaining bookable parallel slots at this start time."}},"required":["start","end","capacity"],"additionalProperties":false},"VenueDetailResponse":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/VenueDetailData"}},"required":["success","data"],"additionalProperties":false},"VenueDetailData":{"type":"object","properties":{"venue":{"$ref":"#/components/schemas/VenueDetail"}},"required":["venue"],"additionalProperties":false},"VenueDetail":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"venueType":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"addressLine1":{"type":"string"},"addressLine2":{"anyOf":[{"type":"string"},{"type":"null"}]},"city":{"type":"string"},"county":{"anyOf":[{"type":"string"},{"type":"null"}]},"postcode":{"type":"string"},"averageRating":{"anyOf":[{"type":"number"},{"type":"null"}]},"totalReviews":{"type":"number"},"capabilities":{"type":"array","items":{"type":"string"}},"operatingHours":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"phlebotomyStations":{"anyOf":[{"type":"number"},{"type":"null"}]},"logoUrl":{"anyOf":[{"type":"string"},{"type":"null"}]},"photos":{"type":"array","items":{"type":"string"}},"cqcRegistered":{"type":"boolean"},"cqcNumber":{"anyOf":[{"type":"string"},{"type":"null"}]},"latitude":{"anyOf":[{"type":"number"},{"type":"null"}]},"longitude":{"anyOf":[{"type":"number"},{"type":"null"}]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}]},"website":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}]},"equipment":{"type":"array","items":{"type":"string"}},"basePrice":{"anyOf":[{"type":"number"},{"type":"null"}]},"acceptsPublicBookings":{"type":"boolean"},"slotDurationMinutes":{"type":"number"},"timezone":{"type":"string"}},"required":["id","slug","name","venueType","description","addressLine1","addressLine2","city","county","postcode","averageRating","totalReviews","capabilities","operatingHours","phlebotomyStations","logoUrl","photos","cqcRegistered","cqcNumber","latitude","longitude","phone","website","email","equipment","basePrice","acceptsPublicBookings","slotDurationMinutes","timezone"],"additionalProperties":false},"VenuesListResponse":{"type":"object","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/VenuesListData"}},"required":["success","data"],"additionalProperties":false},"VenuesListData":{"type":"object","properties":{"venues":{"type":"array","items":{"$ref":"#/components/schemas/VenueListItem"}},"total":{"type":"number"}},"required":["venues","total"],"additionalProperties":false},"VenueListItem":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"venueType":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"addressLine1":{"type":"string"},"city":{"type":"string"},"postcode":{"type":"string"},"averageRating":{"anyOf":[{"type":"number"},{"type":"null"}]},"totalReviews":{"type":"number"},"capabilities":{"type":"array","items":{"type":"string"}},"operatingHours":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"phlebotomyStations":{"anyOf":[{"type":"number"},{"type":"null"}]},"logoUrl":{"anyOf":[{"type":"string"},{"type":"null"}]},"photos":{"type":"array","items":{"type":"string"}},"cqcRegistered":{"type":"boolean"},"cqcNumber":{"anyOf":[{"type":"string"},{"type":"null"}]},"latitude":{"anyOf":[{"type":"number"},{"type":"null"}]},"longitude":{"anyOf":[{"type":"number"},{"type":"null"}]}},"required":["id","slug","name","venueType","description","addressLine1","city","postcode","averageRating","totalReviews","capabilities","operatingHours","phlebotomyStations","logoUrl","photos","cqcRegistered","cqcNumber","latitude","longitude"],"additionalProperties":false}},"securitySchemes":{"ApiKey":{"type":"apiKey","in":"header","name":"X-API-Key","description":"`pj_live_*` per-client API key. Mint one from the client portal (Settings → Integration) — admin staff can mint on a client's behalf from /admin/clients/[id]/integration."}}}}