Skip to content

Schema Authoring Guide

This guide documents conventions for authoring UCP JSON schemas: metadata fields, the registry pattern, schema variants, and versioning.

UCP schemas use standard JSON Schema fields plus UCP-specific metadata:

FieldStandardPurposeRequired For
$schemaJSON SchemaDeclares JSON Schema draft version (SHOULD use draft/2020-12)All schemas
$idJSON SchemaSchema’s canonical URI for $ref resolutionAll schemas
titleJSON SchemaHuman-readable display nameAll schemas
descriptionJSON SchemaSchema purpose and usageAll schemas
nameUCPReverse-domain identifier; doubles as registry keyCapabilities, services, handlers
versionUCPEntity version (YYYY-MM-DD format)Capabilities, services, payment handlers
idUCPInstance identifier for multiple configurationsPayment handlers only

Capability schemas must be self-describing: when a platform fetches a schema, it should determine exactly what capability and version it represents without cross-referencing other documents. This matters because:

  1. Independent versioning: Capabilities may version independently. The schema must declare its version explicitly—you can’t infer it from the URL.

  2. Validation: Validators can cross-check that a capability declaration’s schema URL points to a schema whose embedded name/version match the declaration. Mismatches are authoring errors caught at build time.

  3. Developer experience: When reading a schema file, integrators immediately see what capability it defines without reverse-engineering the $id URL.

  4. Compact namespace: The name field provides a standardized reverse-domain identifier (e.g., dev.ucp.shopping.checkout) that’s more compact and semantic than the full $id URL.

FieldRoleFormat
$idJSON Schema primitive for $ref resolution and toolingURI (required by spec)
nameRegistry key and stable identifierReverse-domain

$id must be a valid URI per JSON Schema spec. name is the key used in registries (capabilities, services, payment_handlers) and the wire protocol identifier used in capability negotiation—decoupled from schema hosting so that schema URLs can change as infrastructure evolves.

The reverse-domain format provides namespace governance: domain owners control their namespace (dev.ucp.*, com.shopify.*), avoiding collisions between UCP and vendor entities.

The version field uses date-based versioning (YYYY-MM-DD) to enable:

  • Capability negotiation: Platforms request specific versions they support
  • Breaking change management: New versions get new dates; old versions remain valid and resolvable
  • Independent lifecycles: Extensions can release on their own schedule

UCP schemas fall into six categories based on their role in the protocol.

Define negotiated capabilities that appear in ucp.capabilities{} registries.

  • Top-level fields: $schema, $id, title, description, name, version
  • Variants: platform_schema, business_schema, response_schema

Examples: checkout.json, fulfillment.json, discount.json, order.json

Define transport bindings that appear in ucp.services{} registries. Each transport (REST, MCP, A2A, Embedded) is a separate entry.

  • Top-level fields: $schema, $id, title, description, name, version
  • Variants: platform_schema, business_schema
  • Transport requirements:
    • REST/MCP: endpoint, schema (OpenAPI/OpenRPC URL)
    • A2A: endpoint (Agent Card URL)
    • Embedded: schema (OpenRPC URL)

Define payment handler configurations in ucp.payment_handlers{} registries.

  • Top-level fields: $schema, $id, title, description, name, version, available_instruments
  • Variants: platform_schema, business_schema, response_schema
  • Instance id: Required to distinguish multiple configurations of the same handler
  • available_instruments: Optional. Array of supported instrument types with type-specific constraints (e.g., brands for credit cards). When absent, the handler places no restrictions.

Examples: com.google.pay, dev.shopify.shop_pay, dev.ucp.processor_tokenizer

→ See Payment Handler Guide for detailed guidance on handler structure, config/instrument/credential schemas, and the full specification template.

Data structures embedded within capabilities but not independently negotiated. Do not appear in registries.

  • Top-level fields: $schema, $id, title, description
  • Omit: name, version (not independently versioned)

Examples:

  • schemas/shopping/payment.json — Payment configuration (part of checkout)

Reusable definitions referenced by other schemas. Do not appear in registries.

  • Top-level fields: $schema, $id, title, description
  • Omit: name, version

Examples: types/buyer.json, types/line_item.json, types/postal_address.json

Define protocol structure rather than entity payloads.

  • Top-level fields: $schema, $id, title, description
  • Omit: name, version

Examples: ucp.json (entity base), capability.json, service.json, payment_handler.json

UCP organizes capabilities, services, and handlers in registries—objects keyed by name rather than arrays of objects with name fields.

{
"capabilities": {
"dev.ucp.shopping.checkout": [{"version": "2026-01-11"}],
"dev.ucp.shopping.fulfillment": [{"version": "2026-01-11"}]
},
"services": {
"dev.ucp.shopping": [
{"version": "2026-01-11", "transport": "rest"},
{"version": "2026-01-11", "transport": "mcp"}
]
},
"payment_handlers": {
"com.google.pay": [{"id": "gpay_1234", "version": "2026-01-11", "available_instruments": [{"type": "google_pay_card"}]}]
}
}

The same registry structure appears in three contexts with different field requirements:

ContextLocationRequired Fields
Platform ProfileAdvertised URIversion, spec, schema
Business Profile/.well-known/ucpversion; may add config
API ResponsesCheckout/order payloadsversion (+ id for handlers)

All capabilities, services, and handlers extend a common entity base schema:

FieldTypeDescription
versionstringEntity version (YYYY-MM-DD) — always required
specURIHuman-readable specification
schemaURIJSON Schema URL
idstringInstance identifier (handlers only)
configobjectEntity-specific configuration

Each entity type defines three variants for different contexts:

platform_schema — Full declarations for discovery

{
"dev.ucp.shopping.fulfillment": [{
"version": "2026-01-11",
"spec": "https://ucp.dev/2026-01-11/specification/fulfillment",
"schema": "https://ucp.dev/2026-01-11/schemas/shopping/fulfillment.json",
"config": {
"supports_multi_group": true
}
}]
}

business_schema — Business-specific overrides

{
"dev.ucp.shopping.fulfillment": [{
"version": "2026-01-11",
"config": {
"allows_multi_destination": {"shipping": true}
}
}]
}

response_schema — Minimal references in API responses

{
"ucp": {
"capabilities": {
"dev.ucp.shopping.fulfillment": [{"version": "2026-01-11"}]
}
}
}

Prefer open string vocabularies with documented well-known values over closed enum arrays. Enums are a one-way door: adding a new value is a breaking change for strict validators, and removing one breaks existing producers.

// PREFER: open vocabulary — extensible without schema changes
"type": {
"type": "string",
"description": "Media type. Well-known values: `image`, `video`, `model_3d`."
}
// AVOID: closed enum — adding `audio` requires a schema version bump
"type": {
"type": "string",
"enum": ["image", "video", "model_3d"]
}

Use enum only for provably closed sets where new values would represent a fundamental protocol change (e.g., checkout.status: open | completed | expired).

UCP-authored capabilities version with protocol releases by default. Individual capabilities may version independently when needed.

Capabilities outside dev.ucp.* version fully independently:

{
"name": "com.shopify.loyalty",
"version": "2025-09-01",
"spec": "https://shopify.dev/ucp/loyalty",
"schema": "https://shopify.dev/ucp/schemas/loyalty.json"
}

Vendor schemas follow the same self-describing requirements.

A capability schema defines both payload structure and declaration variants:

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://ucp.dev/2026-01-11/schemas/shopping/checkout.json",
"name": "dev.ucp.shopping.checkout",
"version": "2026-01-11",
"title": "Checkout",
"description": "Base checkout schema. Extensions compose via allOf.",
"$defs": {
"platform_schema": {
"allOf": [{"$ref": "../capability.json#/$defs/platform_schema"}]
},
"business_schema": {
"allOf": [{"$ref": "../capability.json#/$defs/business_schema"}]
},
"response_schema": {
"allOf": [{"$ref": "../capability.json#/$defs/response_schema"}]
}
},
"type": "object",
"required": ["ucp", "id", "line_items", "status", "currency", "totals", "links"],
"properties": {
"ucp": {"$ref": "../ucp.json#/$defs/response_checkout_schema"},
"id": {"type": "string", "description": "Checkout identifier"},
"line_items": {"type": "array", "items": {"$ref": "types/line_item.json"}},
"status": {"type": "string", "enum": ["open", "completed", "expired"]},
"currency": {"type": "string", "pattern": "^[A-Z]{3}$"},
"totals": {"$ref": "types/totals.json"},
"links": {"$ref": "types/links.json"}
}
}

Key points:

  • Top-level name and version make the schema self-describing
  • $defs variants enable validation in different contexts
  • Payload properties define the actual checkout response structure