Skip to content

Availability

Capability: dev.usp-protocol.services.availability

The availability capability lets platforms query when services are available and, optionally, hold slots to prevent double-booking during the booking flow.

Feature Flags

Flag Type Default Description
holds boolean false When true, the business supports Hold Slot and Release Slot operations. Platforms MUST NOT call hold/release endpoints unless the business advertises "holds": true.

Businesses declare feature flags inside the capability entry in their profile:

"dev.usp-protocol.services.availability": [
  {
    "version": "2026-08-20",
    "holds": true
  }
]

When holds is false or absent, the booking flow proceeds directly from slot query to booking creation without an intermediate hold step.


Time Slot

A time slot represents a specific, bookable combination of a time window and assigned resources, computed dynamically by the business from schedules, resource calendars, and existing bookings.

One Slot Per Resource Combination

If the same time window is available with multiple resource options (e.g., three stylists are all free at 3 pm), the business MUST return a separate slot for each option. Each slot's resources array carries exactly the resources assigned to that slot. Picking a slot is equivalent to picking both the time and the resource.

Non-transactional

Availability responses are not transactional commitments. A slot returned as available reflects the business's state at query time; by the time create_booking is called the slot may have been taken. Platforms MUST NOT assume that an available slot will remain bookable. The optional hold mechanism provides a short-lived, best-effort reservation to reduce this race window.

Time Slot Schema

Field Type Required Description
id string Yes Unique slot identifier, opaque to the platform.
service_id string Yes The service this slot belongs to.
start string Yes RFC 3339 start time of the slot.
end string Yes RFC 3339 end time of the slot.
duration string Yes ISO 8601 duration of the slot (e.g., PT60M).
state string Yes Availability state. See state values below.
capacity object No {total, remaining, held, waitlist} -- present for group and reservation types. waitlist is a boolean; a slot MUST NOT carry state waitlist unless it is true.
resources Array[object] No {id, type, name} -- the specific resources assigned to this slot.
location object No {id, name} -- the specific location for this slot.
pricing object No {amount, currency, label} -- slot-specific pricing that overrides service-level pricing.

Slot State Values

State Description
available The slot has capacity for new bookings. For appointment types, the slot is open. For group/reservation types, capacity.remaining > 0.
limited Low remaining capacity. Businesses SHOULD return limited when remaining capacity drops below 20% of total or fewer than 3 spots remain.
waitlist Fully booked but the service has waitlist enabled (capacity.waitlist: true). Platform MAY allow the buyer to join the waitlist. Businesses MUST NOT return waitlist unless the dev.usp-protocol.services.waitlist capability is supported.

Hold

Feature Flag Required

This section applies only when the business advertises "holds": true in its dev.usp-protocol.services.availability capability entry.

A hold is a temporary reservation of a time slot that prevents double-booking during the booking flow. Holds have a short TTL and are automatically released when they expire, are explicitly released, or are converted to a booking.

Hold Schema

Field Type Required Description
id string Yes Unique hold identifier.
slot_id string Yes The held slot.
service_id string Yes The service.
spots integer No Number of spots held. Default: 1.
expires_at string Yes RFC 3339 expiration time. Businesses SHOULD set hold TTL between 5 and 10 minutes.
status string Yes active, expired, released, or converted.

Concurrent Hold Rules

The business MUST enforce hold concurrency rules matching the service's capacity model:

Service Type Concurrency Rule
appointment MUST NOT accept more than one active hold per slot. Second request returns slot_unavailable.
group / reservation Multiple concurrent holds permitted up to remaining capacity. Exceeding capacity returns slot_unavailable.
rental Overlapping time holds on the same resource MUST be rejected with slot_unavailable.

Hold Conversion

A hold exists to make the gap between slot selection and booking safe. That is only true if converting it cannot lose the reservation, so conversion is specified rather than left to each implementation.

Capacity accounting. Hold.spots and the booking request's party_size count the same capacity units from opposite ends of the flow: spots is what was reserved, party_size is what is being booked.

  • With a hold_id, party_size (default 1) MUST NOT exceed the hold's spots (default 1). Exceeding it returns capacity_exceeded, because the surplus was never reserved and another buyer may hold it.
  • A party_size lower than spots is allowed; unused spots MUST return to capacity.remaining on conversion.
  • Without a hold_id, party_size is checked directly against capacity.remaining at booking time.

Conversion is atomic. Either the booking is created and the hold moves to converted, or neither happens. A hold MUST NOT be left converted with no booking, and a booking MUST NOT be created from a hold left active where it could convert again.

Hold state at conversion Slot still bookable? Code Hold outcome
active Yes (success) converted
expired Yes hold_expired stays expired
expired No slot_unavailable stays expired
released Yes hold_expired stays released
released No slot_unavailable stays released
converted n/a (success, idempotent replay) stays converted
active, party_size exceeds spots n/a capacity_exceeded stays active

Two rows are deliberate. A converted hold replayed with the same request returns the existing booking rather than an error, which is what makes hold_id usable as an idempotency key. And capacity_exceeded leaves the hold active, so the platform can retry with a party_size that fits instead of losing the reservation.

An unrecognized hold_id returns validation_error, not hold_expired: the platform needs to tell a bad identifier from a lapsed reservation.


Operations

Query Availability -- POST /availability/query

Returns available time slots for a service within a date range. Use the Availability Hint on the service entity to narrow the date range before querying.

Request Fields:

Field Type Required Description
service_id string Yes The service to query.
start_date string Yes Start of range (RFC 3339 date or datetime).
end_date string Yes End of range (RFC 3339 date or datetime).
timezone string No IANA timezone. Defaults to business timezone.
resource_id string No Preferred resource. Only matching slots are returned.
party_size integer No Number of participants. Default: 1.
location_id string No Location filter for multi-location businesses.
locale string No BCP 47 language tag for localized content.
cursor string No Opaque pagination cursor from a previous response.

Date Range Guidance

Platforms SHOULD query at most 7 calendar days per request. Businesses MAY reject queries spanning more than their configured maximum by returning HTTP 422 with error code range_too_wide.

Single-Service Design

Each query targets exactly one service. For multi-service scenarios, platforms SHOULD issue separate queries per service and correlate results client-side.

{
  "service_id": "svc_haircut_001",
  "start_date": "2026-03-15",
  "end_date": "2026-03-21",
  "timezone": "America/New_York",
  "resource_id": "staff_jane",
  "location_id": "loc_main",
  "locale": "en-US"
}
{
  "usp": {
    "version": "2026-08-20",
    "capabilities": {
      "dev.usp-protocol.services.availability": [
        { "version": "2026-08-20" }
      ]
    }
  },
  "service_id": "svc_haircut_001",
  "slots": [
    {
      "id": "slot_20260315_0900",
      "service_id": "svc_haircut_001",
      "start": "2026-03-15T09:00:00-04:00",
      "end": "2026-03-15T10:00:00-04:00",
      "duration": "PT60M",
      "state": "available",
      "resources": [
        { "id": "staff_jane", "type": "staff", "name": "Jane Smith" }
      ],
      "location": { "id": "loc_main", "name": "Downtown Studio" }
    },
    {
      "id": "slot_20260315_1030",
      "service_id": "svc_haircut_001",
      "start": "2026-03-15T10:30:00-04:00",
      "end": "2026-03-15T11:30:00-04:00",
      "duration": "PT60M",
      "state": "available",
      "resources": [
        { "id": "staff_jane", "type": "staff", "name": "Jane Smith" }
      ],
      "location": { "id": "loc_main", "name": "Downtown Studio" }
    }
  ],
  "opening_hours": [
    {
      "day_of_week": ["monday", "tuesday", "wednesday", "thursday", "friday"],
      "opens": "09:00",
      "closes": "18:00"
    },
    {
      "day_of_week": ["saturday"],
      "opens": "10:00",
      "closes": "16:00"
    }
  ],
  "pagination": { "cursor": null, "has_more": false }
}

Response Fields:

Field Type Required Description
service_id string Yes Echoes the queried service identifier.
slots array Yes List of available time slots. Empty array when no slots match.
opening_hours array No Regular business hours for the queried period.
messages array No Optional informational or warning messages about the result set.
pagination object No Pagination state: cursor and has_more.

opening_hours[] Fields:

Field Type Required Description
day_of_week Array[string] Yes Days this entry applies to (lowercase English day names).
opens string Yes Opening time in HH:MM 24-hour format (local business time).
closes string Yes Closing time in HH:MM 24-hour format.

Slots are returned in ascending start order.


Hold Slot -- POST /availability/holds

Requires Feature Flag

Platforms MUST NOT call this endpoint unless the business profile advertises "holds": true.

Creates a temporary hold on a time slot to prevent double-booking while the buyer completes the booking flow.

{
  "slot_id": "slot_20260315_0900",
  "service_id": "svc_haircut_001",
  "spots": 1
}
{
  "usp": {
    "version": "2026-08-20",
    "capabilities": {
      "dev.usp-protocol.services.availability": [
        { "version": "2026-08-20" }
      ]
    }
  },
  "hold": {
    "id": "hold_abc123",
    "slot_id": "slot_20260315_0900",
    "service_id": "svc_haircut_001",
    "spots": 1,
    "expires_at": "2026-03-15T08:10:00-04:00",
    "status": "active"
  }
}
{
  "usp": {
    "version": "2026-08-20",
    "capabilities": {}
  },
  "messages": [
    {
      "type": "error",
      "code": "slot_unavailable",
      "content": "The requested slot is no longer available.",
      "severity": "recoverable"
    }
  ]
}

Release Slot -- DELETE /availability/holds/{hold_id}

Requires Feature Flag

Requires "holds": true on the dev.usp-protocol.services.availability capability.

Explicitly releases a hold before it expires, freeing the slot for other buyers.

DELETE /availability/holds/hold_abc123
{
  "usp": {
    "version": "2026-08-20",
    "capabilities": {
      "dev.usp-protocol.services.availability": [
        { "version": "2026-08-20" }
      ]
    }
  },
  "hold": {
    "id": "hold_abc123",
    "slot_id": "slot_20260315_0900",
    "service_id": "svc_haircut_001",
    "spots": 1,
    "expires_at": "2026-03-15T08:10:00-04:00",
    "status": "released"
  }
}

Caching Strategy

Availability data has an inverse relationship between freshness and usefulness: near-term slots are the most actionable but change the fastest, while far-out availability is stable but less immediately useful. Platforms SHOULD use a tiered caching strategy:

Tier Source Date Range Recommended TTL Use Case
Hint availability_hint (including slot_bitmaps when published) General / near-term Cached with catalog; honor producer valid_until or registry validity policy Agent pre-filtering: "which date range should I even query?"
Select slot query 1-2 specific days 30-60 seconds Time picker: "what times are available on Tuesday?"
Commit (optional) Hold Single slot Real-time (no cache) Slot hold before booking. Only when "holds": true.

This creates a natural funnel that balances user experience with data freshness:

graph TD
    H["1. Availability Hint (catalog-cached; valid_until cutout)"] -- "Agent narrows date range" --> S
    S["2. Slot Query (slot-level, short cache)"] --> D["Agent picks a slot"]
    D --> E{"3. Holds supported?"}
    E -- "Yes" --> F["Hold Slot (real-time)"]
    F --> G["4. Create Booking"]
    E -- "No" --> G

When Holds Are Not Supported

When holds are not supported, the flow skips directly from slot selection to booking creation. The platform should handle the possibility of the slot being taken between query and booking.