REST Binding¶
The REST binding uses HTTP/1.1 (or higher) with JSON request/response bodies. All examples in the USP specification use the REST binding.
| Property | Value |
|---|---|
| Schema format | OpenAPI 3.x (JSON) |
| Content type | application/json |
| Schema reference | openapi/usp-rest.json |
| Data shapes | Normative JSON Schema definitions under schemas/ ($defs per file) |
Schema resolution
The machine-readable openapi/usp-rest.json references schema documents with relative JSON Pointer URIs (e.g., ../schemas/catalog.json#/$defs/Service) and is not self-contained unless bundled. Implementations and tools MUST resolve external $refs against the repository layout or use a pre-bundled copy.
Capability Negotiation¶
The platform advertises its profile URI via the USP-Agent header using Dictionary Structured Field syntax (RFC 8941):
POST /services/list HTTP/1.1
Host: business.example.com
USP-Agent: profile="https://agent.example/profiles/scheduling-agent.json"
Content-Type: application/json
{"filters": {"type": "appointment"}}
Error Responses¶
USP uses RFC 9457 Problem Details for HTTP API error responses and distinguishes between protocol errors and business outcome errors.
Business Outcome Errors¶
Business outcome errors (e.g., slot unavailable, hold expired, capacity exceeded) return HTTP 200 with a messages[] array on the response object. Each message has type, code, content, optional content_type, optional severity, and an optional path field.
Note
The messages[] array is available on all USP response envelopes, including catalog responses (/services/list, /services/{service_id}, /services/feed), not only state-modifying operations. This enables partial-success signalling, filter feedback, service-level warnings, and deprecation notices.
Protocol Errors¶
Protocol errors use standard HTTP status codes with RFC 9457 Problem Details:
| HTTP Status | USP Meaning |
|---|---|
200 OK | Operation succeeded, or business outcome error (check messages[]) |
201 Created | Resource created (bookings, holds, registry entries, waitlist entries, feed subscriptions) |
400 Bad Request | Malformed JSON, missing required fields, invalid profile URL |
401 Unauthorized | Authentication required or invalid credentials |
403 Forbidden | Platform profile not in business allowlist |
422 Unprocessable Entity | Syntactically valid but structurally invalid request |
424 Failed Dependency | Business profile unreachable |
429 Too Many Requests | Rate limited; retry after Retry-After header |
500 Internal Server Error | Unexpected server failure |
503 Service Unavailable | Business temporarily unable to handle requests |
Idempotency¶
State-modifying operations (booking creation, cancellation, rescheduling, hold creation, confirm-payment) SHOULD support idempotency via the Idempotency-Key header, consistent with draft-ietf-httpapi-idempotency-key-header.
Behavior¶
- The platform SHOULD send an
Idempotency-Keyheader (UUID v4 recommended) with all state-modifying requests. - The business MUST store the idempotency key with the operation result for at least 24 hours.
- If the business receives a request with a previously seen key and the same parameters, it MUST return the cached result without re-executing.
- If the business receives a request with a previously seen key but different parameters, it MUST return
409 Conflict.
Why idempotency matters
Idempotency is critical for booking operations where network retries could create duplicate reservations. For read-only operations (GET, POST /services/list, POST /availability/query), idempotency keys are not required.
Example¶
POST /bookings HTTP/1.1
Host: business.example.com
USP-Agent: profile="https://agent.example/profiles/scheduling-agent.json"
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{"service_id": "svc_haircut_001", "slot_id": "slot_20260315_0900", ...}
sequenceDiagram
participant P as Platform
participant B as Business
P->>B: POST /bookings (Idempotency-Key: abc-123)
B->>B: Store key + result
B-->>P: 201 Created (booking)
Note over P,B: Network retry (same key, same params)
P->>B: POST /bookings (Idempotency-Key: abc-123)
B->>B: Key found, same params
B-->>P: 201 Created (cached result)
Note over P,B: Conflicting retry (same key, different params)
P->>B: POST /bookings (Idempotency-Key: abc-123, different body)
B->>B: Key found, different params
B-->>P: 409 Conflict Pagination¶
Several USP operations return paginated result sets. All paginated operations use cursor-based pagination.
Request Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
cursor | string | No | Opaque cursor from the previous response's pagination.cursor. Omit on first request. |
limit | integer | No | Requested page size. Businesses MAY apply a lower or upper cap. |
Response Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
pagination.cursor | string | null | Yes | Opaque cursor for the next request. null when no more pages. |
pagination.has_more | boolean | Yes | true if additional pages exist; false on the last page. |
Semantics¶
- Cursors are opaque strings -- platforms MUST NOT parse or construct them.
- Businesses SHOULD honor a cursor for at least 60 seconds after it is issued.
- Platforms that retry after cursor expiry MAY receive a
cursor_expirederror and SHOULD restart from the first page. - Default page sizes: 50 items for slot queries, 20 items for service lists.
Feed endpoint exception
The GET /services/feed endpoint uses a timestamp-based cursor named next_cursor (not cursor) because its pagination semantics are tied to the RPDE incremental-sync model. All other paginated USP operations use the cursor/has_more pattern.
Example¶
sequenceDiagram
participant P as Platform
participant B as Business
P->>B: POST /availability/query (no cursor)
B-->>P: slots[], pagination: {cursor: "abc", has_more: true}
P->>B: POST /availability/query (cursor: "abc")
B-->>P: slots[], pagination: {cursor: "def", has_more: true}
P->>B: POST /availability/query (cursor: "def")
B-->>P: slots[], pagination: {cursor: null, has_more: false} Discovery¶
Platforms discover a business's REST endpoints through the business profile published at /.well-known/usp. The profile's usp.services array lists supported USP operations with their base URLs and transport type. Platforms MUST filter for entries where transport is "rest" to locate REST endpoints.
On each request, the platform identifies itself by sending the USP-Agent header:
The business resolves the platform profile to perform capability negotiation. For UCP-Native deployments, discovery is inherited from /.well-known/ucp.
sequenceDiagram
participant P as Platform
participant B as Business
P->>B: GET /.well-known/usp
B-->>P: Business profile (services, capabilities, transport: "rest")
P->>B: POST /services/list<br/>USP-Agent: profile="https://agent.example/..."
B->>B: Resolve platform profile, negotiate capabilities
B-->>P: Service catalog Request Signing¶
State-modifying REST requests (POST, PUT, DELETE on bookings, holds, waitlist, registry) SHOULD be signed using HTTP Message Signatures (RFC 9421) to ensure integrity and authenticity.
Signed Components¶
The signature MUST cover at minimum:
content-digestcontent-type@method@target-uri@created
The usp-agent and idempotency-key headers SHOULD also be included when present.
Platform Signing Keys¶
The platform's signing keys are published in the platform profile via the signing_keys array. Businesses that enforce request verification MUST advertise this requirement in their business profile.
Example¶
POST /bookings HTTP/1.1
Host: business.example.com
Content-Type: application/json
USP-Agent: profile="https://agent.example/profiles/scheduling-agent.json"
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Digest: sha-256=:RK/0qy18MlBSVnWgjwz6lZEWjP/lF5HF9bvEF8FabDg=:
Signature-Input: sig1=("@method" "@target-uri" "content-digest" "content-type" "usp-agent" "idempotency-key");keyid="platform-2026";created=1711036800
Signature: sig1=:MEUCIQDXyK9N3p5Rt...:
{"service_id": "svc_haircut_001", "slot_id": "slot_20260315_0900", ...}
MCP note
For the MCP binding, request integrity is ensured by the underlying MCP transport layer (stdio pipe or HTTP-SSE with TLS). The _meta.usp.profile field identifies the platform without transport-level signing.
Conformance Requirements¶
MUST¶
A conforming REST binding implementation MUST:
- Serve all endpoints over HTTPS (TLS 1.2 or later).
- Accept and return
application/jsonon all endpoints. - Return RFC 9457 Problem Details for protocol errors.
- Return business outcome errors as HTTP 200 with a
messages[]array on the response object. - Support the
USP-Agentheader on all requests. - Return
201 Createdfor resource creation operations (bookings, holds, registry entries, waitlist entries, feed subscriptions). - Implement webhook signing per the security specification.
SHOULD¶
A conforming REST binding implementation SHOULD:
- Support the
Idempotency-Keyheader on state-modifying operations. - Sign state-modifying requests using HTTP Message Signatures (RFC 9421).
- Support cursor-based pagination.