Holds
Reserve a bookable slot while your patient finishes booking, then book it with the Jump EHR Appointments API.
A hold reserves one slot from the Availability API for a few minutes, so nobody else can book it while your patient enters their details or pays. When they are done, book it with POST /holds/{id}/confirm, or with POST /appointments and the hold's id as hold_id. The two do the same thing.
Holds are the same reservations Jump's own online booking uses: a held slot disappears from GET /availability and from the practice's online booking until the hold is used, released or expires.
The booking flow
GET /availabilityto list slots.POST /holdsnaming the slot, either asappointment_type_id,clinician_id,dateandstart_time(HH:MM, the slot's local time), or by echoing the slot'sappointment_template_id,clinician_profile_idandstart_timeexactly as availability returned them.- Create or update the patient with
POST /patientsorPATCH /patients/{id}. To record their NHS GP, send agp_registrationblock, naming the practice bygp_practice_ods_code(see Patients). POST /holds/{id}/confirmwithpatient_id(orPOST /appointmentswithhold_idandpatient_id). The time, clinician, template and location come from the hold.
If the patient abandons the flow, DELETE /holds/{id} frees the slot straight away.
Holds last 15 minutes by default. Set hold_minutes between 5 and 35 when you need longer (for example while a payment page is open). A hold cannot be extended; if it lapses, hold the slot again.
Errors to handle
| Code | Status | Meaning | What to do |
|---|---|---|---|
SLOT_UNAVAILABLE | 409 | The slot was booked or held by someone else, or the diary changed | Query GET /availability again and offer another slot |
HOLD_EXPIRED | 410 | The hold ran out before booking | Hold the slot again (it may have gone) |
HOLD_RELEASED | 410 | The hold was released | Hold the slot again |
HOLD_ALREADY_USED | 409 | The hold already became an appointment | Look up the appointment; do not book twice |
VALIDATION_ERROR (TIMEZONE_REQUIRED) | 422 | start_time had no Z and no UTC offset | Send the slot's start_time exactly as GET /availability returned it |
GET /holds/{id} returns expired and released holds with that status rather than a 404, and DELETE /holds/{id} on a hold that is already inactive succeeds without changing it.
Holds use the write_appointments scope to create and release, and read_appointments to read.
Try-it is disabled for POST /holds
This operation writes data. Running it from these docs would target the production API and change a real record. Copy the request sample and run it against staging first.
Authorization
BearerApiKey API key with the pk_live_ prefix. pk_test_ keys are refused (403 TEST_MODE_UNAVAILABLE) until a sandbox exists.
In: header
Header Parameters
Optional UUID for request deduplication. Same key + same body returns the cached response. Same key + different body returns 409 CONFLICT. Keys are scoped to your organisation and endpoint, and expire after 24 hours.
uuidRequest Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Name the template, the clinician and the slot's start. Each of the first two has two accepted names (send either); the start is either date plus an HH:MM start_time, or a start_time echoed from GET /availability.
Response Body
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/holds" \ -H "Content-Type: application/json" \ -d '{ "appointment_type_id": "123e4567-e89b-12d3-a456-426614174013", "clinician_id": "123e4567-e89b-12d3-a456-426614174012", "location_id": "123e4567-e89b-12d3-a456-426614174014", "date": "2026-10-05", "start_time": "09:00" }'{ "data": { "id": "123e4567-e89b-12d3-a456-426614174020", "status": "active", "appointment_type_id": "123e4567-e89b-12d3-a456-426614174013", "appointment_template_id": "123e4567-e89b-12d3-a456-426614174013", "clinician_id": "123e4567-e89b-12d3-a456-426614174012", "clinician_profile_id": "123e4567-e89b-12d3-a456-426614174012", "location_id": "123e4567-e89b-12d3-a456-426614174014", "patient_id": null, "start_time": "2026-10-05T09:00:00+01:00", "end_time": "2026-10-05T09:30:00+01:00", "timezone": "Europe/London", "is_remote": false, "remote_channel": null, "expires_at": "2026-10-01T12:15:00.000Z", "released_at": null, "appointment_id": null, "created_at": "2026-10-01T12:00:00.000Z" }, "meta": { "request_id": "req_abc123" }}Authorization
BearerApiKey API key with the pk_live_ prefix. pk_test_ keys are refused (403 TEST_MODE_UNAVAILABLE) until a sandbox exists.
In: header
Path Parameters
uuidResponse Body
application/json
application/json
application/json
curl -X GET "https://example.com/holds/497f6eca-6276-4993-bfeb-53cbbbba6f08"{ "data": { "id": "123e4567-e89b-12d3-a456-426614174020", "status": "active", "appointment_type_id": "123e4567-e89b-12d3-a456-426614174013", "appointment_template_id": "123e4567-e89b-12d3-a456-426614174013", "clinician_id": "123e4567-e89b-12d3-a456-426614174012", "clinician_profile_id": "123e4567-e89b-12d3-a456-426614174012", "location_id": "123e4567-e89b-12d3-a456-426614174014", "patient_id": null, "start_time": "2026-10-05T09:00:00+01:00", "end_time": "2026-10-05T09:30:00+01:00", "timezone": "Europe/London", "is_remote": false, "remote_channel": null, "expires_at": "2026-10-01T12:15:00.000Z", "released_at": null, "appointment_id": null, "created_at": "2026-10-01T12:00:00.000Z" }, "meta": { "request_id": "req_abc123" }}Try-it is disabled for DELETE /holds/{id}
This operation writes data. Running it from these docs would target the production API and change a real record. Copy the request sample and run it against staging first.
Authorization
BearerApiKey API key with the pk_live_ prefix. pk_test_ keys are refused (403 TEST_MODE_UNAVAILABLE) until a sandbox exists.
In: header
Path Parameters
uuidResponse Body
application/json
application/json
application/json
application/json
curl -X DELETE "https://example.com/holds/497f6eca-6276-4993-bfeb-53cbbbba6f08"{ "data": { "id": "123e4567-e89b-12d3-a456-426614174020", "status": "active", "appointment_type_id": "123e4567-e89b-12d3-a456-426614174013", "appointment_template_id": "123e4567-e89b-12d3-a456-426614174013", "clinician_id": "123e4567-e89b-12d3-a456-426614174012", "clinician_profile_id": "123e4567-e89b-12d3-a456-426614174012", "location_id": "123e4567-e89b-12d3-a456-426614174014", "patient_id": null, "start_time": "2026-10-05T09:00:00+01:00", "end_time": "2026-10-05T09:30:00+01:00", "timezone": "Europe/London", "is_remote": false, "remote_channel": null, "expires_at": "2026-10-01T12:15:00.000Z", "released_at": null, "appointment_id": null, "created_at": "2026-10-01T12:00:00.000Z" }, "meta": { "request_id": "req_abc123" }}Try-it is disabled for POST /holds/{id}/confirm
This operation writes data. Running it from these docs would target the production API and change a real record. Copy the request sample and run it against staging first.
Authorization
BearerApiKey API key with the pk_live_ prefix. pk_test_ keys are refused (403 TEST_MODE_UNAVAILABLE) until a sandbox exists.
In: header
Path Parameters
uuidHeader Parameters
Optional UUID for request deduplication. Same key + same body returns the cached response. Same key + different body returns 409 CONFLICT. Keys are scoped to your organisation and endpoint, and expire after 24 hours.
uuidRequest Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/holds/497f6eca-6276-4993-bfeb-53cbbbba6f08/confirm" \ -H "Content-Type: application/json" \ -d '{ "patient_id": "123e4567-e89b-12d3-a456-426614174000", "attendee_name": "Sarah Johnson", "attendee_email": "sarah.johnson@example.com", "notes": "First visit" }'{ "data": { "id": "123e4567-e89b-12d3-a456-426614174006", "patient_id": "P-100428", "clinician_profile_id": "123e4567-e89b-12d3-a456-426614174007", "title": "Ms", "description": "Example description", "start_time": "2026-10-12T18:45:00+01:00", "end_time": "2026-10-12T19:15:00+01:00", "timezone": "Europe/London", "booking_mode": "slot", "status": "pending_approval", "appointment_type_id": "123e4567-e89b-12d3-a456-426614174008", "location_id": "123e4567-e89b-12d3-a456-426614174009", "is_remote": true, "remote_channel": "video", "video_link": "Example video link", "video_provider": "Example video provider", "duration_minutes": 1, "attendee_email": "Example attendee email", "attendee_name": "Example attendee name", "meeting_url": "https://example.com/resource", "meeting_started_at": "2026-01-15T10:30:00Z", "meeting_ended_at": "2026-01-15T10:30:00Z", "attendance_status": "Example attendance status", "arrived_at": "2026-01-15T10:30:00Z", "sent_in": "2026-01-15T10:30:00Z", "left_at": "2026-01-15T10:30:00Z", "cancellation_reason": "Example cancellation reason", "rejection_reason": "Example rejection reason", "reschedule_reason": "Example reschedule reason", "booking_source": "api", "is_guest_booking": true, "guest_first_name": "Example guest first name", "guest_last_name": "Example guest last name", "guest_dob": "2026-01-15", "guest_mobile": "Example guest mobile", "guest_relationship": "Example guest relationship", "is_team_booking": true, "is_untimed": false, "list_position": null, "patient_time_display": null, "arrival_time": null, "episode_id": "123e4567-e89b-12d3-a456-426614174010", "patient_timezone": "Example patient timezone", "booked_by_patient_id": "123e4567-e89b-12d3-a456-426614174011", "created_at": "2026-01-15T10:30:00Z", "updated_at": "2026-01-15T10:30:00Z" }, "meta": { "request_id": "req_abc123" }}