How Universal Commerce Protocol Achieves Dynamic Discovery via /.well-known/ucp

Universal Commerce Protocol enables permissionless platform onboarding by publishing a JSON discovery document at https://<business-domain>/.well-known/ucp, allowing any platform to dynamically discover services, capabilities, and security credentials without pre-configuration.

The Universal Commerce Protocol (UCP) eliminates manual integration setup by mandating that every business host a machine-readable discovery profile at a standardized well-known URL. According to the Universal-Commerce-Protocol/ucp source code, this design allows platforms to fetch service endpoints, negotiate protocol versions, and retrieve public signing keys through a single HTTP request.

The Discovery Document Structure

Every UCP-compliant business must serve a business discovery profile at /.well-known/ucp that conforms to the JSON-Schema defined in source/discovery/profile_schema.json.

Required Fields and Schema

The document structure follows the top-level definitions in source/schemas/ucp.json and contains these critical sections:

  • ucp.version – The protocol version supported by the business
  • services – Advertised services (REST, MCP, A2A, Embedded) with their endpoint URLs and transport types
  • capabilities – Names, versions, and optional extensions available for negotiation
  • payment_handlers – Optional registry of supported payment handlers
  • signing_keys – JWK public keys used for HTTP-Message-Signature verification and key discovery

The Dynamic Discovery Flow

When initiating UCP operations, platforms must fetch and process the business profile following the sequence documented in docs/specification/overview.md.

Fetch and Validation Steps

  1. Fetch the document via GET https://<business-domain>/.well-known/ucp using HTTPS only (redirects forbidden)
  2. Validate the response against profile_schema.json to ensure schema compliance
  3. Cache the document respecting the Cache-Control: public, max-age>=60 header (the spec mandates a minimum 60-second TTL)
  4. Extract endpoint URLs from services[<service-id>][transport].endpoint for subsequent API calls
  5. Compute capability intersection between the platform's advertised features and the business's capabilities list using the Intersection Algorithm
  6. Compose request/response schemas by fetching base schemas and any active extensions found in the negotiated intersection

Versioning and Extension Mechanisms

The discovery mechanism supports graceful protocol evolution through version-specific profiles and capability extensions.

Version-Specific Profiles

Businesses may expose multiple dated profiles at paths like /.well-known/ucp/2026-01-11 and advertise them in the supported_versions map. This allows platforms to request exactly the version they understand, ensuring backward compatibility while enabling protocol upgrades.

Extension-Driven Evolution

New capabilities are added via the extends field within the capabilities object. Platforms only load extensions whose parent capabilities appear in the negotiated intersection, preventing breaking changes while supporting feature expansion.

Security and Caching Requirements

The specification enforces strict security controls for the discovery process:

  • HTTPS-only transport with no redirects permitted
  • Mandatory caching directives requiring Cache-Control: public with a minimum max-age of 60 seconds
  • In-band key discovery via the signing_keys array, enabling platforms to verify HTTP-Message-Signatures using the same profile
  • Graceful error handling with defined error codes profile_unreachable and profile_malformed when discovery fails

Implementation Example

Below is a complete Python implementation demonstrating profile fetching, validation, and endpoint extraction:

import requests
import json
import jsonschema

# URL of the discovery document (must be HTTPS)

DISCOVERY_URL = "https://example-business.com/.well-known/ucp"

# 1. Fetch the profile

resp = requests.get(DISCOVERY_URL, timeout=5)
resp.raise_for_status()                # 2xx → OK

profile = resp.json()

# 3. Validate against the JSON-Schema (downloaded once, cached)

schema_url = "https://raw.githubusercontent.com/Universal-Commerce-Protocol/ucp/main/source/discovery/profile_schema.json"
schema = requests.get(schema_url).json()
jsonschema.validate(instance=profile, schema=schema)

# 4. Extract endpoint for the Shopping REST service

shopping = profile["ucp"]["services"]["dev.ucp.shopping"]
rest_entry = next(s for s in shopping if s["transport"] == "rest")
api_base = rest_entry["endpoint"]       # e.g. https://biz.example.com/api/v2

# 5. Use the endpoint to call a Checkout operation

checkout_url = f"{api_base}/checkout-sessions"
checkout_resp = requests.post(
    checkout_url,
    json={"line_items": [{"price_id": "sku_123", "quantity": 1}]},
    headers={"UCP-Agent": f'profile="{DISCOVERY_URL}"'}
)
checkout_resp.raise_for_status()
print(json.dumps(checkout_resp.json(), indent=2))

Sample Discovery Document

The following JSON illustrates the structure defined in source/schemas/ucp.json and source/discovery/profile_schema.json:

{
  "ucp": {
    "version": "2026-01-23",
    "services": {
      "dev.ucp.shopping": [
        {
          "version": "2026-01-23",
          "transport": "rest",
          "endpoint": "https://biz.example.com/api/v2",
          "schema": "https://ucp.dev/2026-01-23/services/shopping/rest.openapi.json"
        },
        {
          "version": "2026-01-23",
          "transport": "mcp",
          "endpoint": "https://biz.example.com/mcp",
          "schema": "https://ucp.dev/2026-01-23/services/shopping/mcp.openrpc.json"
        }
      ]
    },
    "capabilities": {
      "dev.ucp.shopping.checkout": [{ "version": "2026-01-23" }],
      "dev.ucp.shopping.fulfillment": [{ "version": "2026-01-23", "extends": "dev.ucp.shopping.checkout" }]
    },
    "signing_keys": [{ "kid": "biz2025", "kty": "EC", "crv": "P-256", "...": "..." }]
  }
}

Summary

  • Dynamic discovery occurs via GET /.well-known/ucp, returning a JSON document defined by source/discovery/profile_schema.json
  • No pre-configuration is required—platforms learn service endpoints, protocol versions, and signing keys directly from the business domain
  • Capability negotiation uses the Intersection Algorithm to determine compatible features between platform and business
  • Mandatory caching requires Cache-Control: public, max-age>=60 to reduce load while maintaining freshness
  • Security combines HTTPS-only transport with in-band key discovery via the signing_keys array for HTTP-Message-Signature verification

Frequently Asked Questions

What is the exact URL path for UCP discovery?

The discovery document must be hosted at /.well-known/ucp on the business's primary domain, accessed via https://<business-domain>/.well-known/ucp. The specification mandates HTTPS-only transport with no redirects permitted, as documented in docs/specification/overview.md and demonstrated in docs/specification/playground.md.

How does UCP handle protocol version negotiation?

Businesses advertise supported versions in the supported_versions map and may host version-specific profiles at dated paths like /.well-known/ucp/2026-01-11. Platforms compute the intersection of their supported versions with the business's advertised versions, then fetch the appropriate schema definitions from the endpoints specified in the discovery document.

What security mechanisms protect the discovery process?

UCP enforces HTTPS-only transport with strict Cache-Control requirements (public, minimum 60-second TTL). The signing_keys array within the discovery profile provides JWK public keys for HTTP-Message-Signature verification, enabling platforms to authenticate subsequent API calls using credentials discovered in-band. The specification also defines error codes profile_unreachable and profile_malformed for handling discovery failures gracefully.

How long should platforms cache the discovery document?

The specification mandates a minimum cache duration of 60 seconds via the Cache-Control: public, max-age>=60 header. Platforms should respect the actual max-age value provided by the server while maintaining a local cache to avoid repeated requests during high-frequency operations, as detailed in docs/specification/overview.md.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →