Universal Commerce Protocol (UCP)
The developer-preview REST contract for signed agent carts, Mobile Money checkout, and consent-scoped order reads.
UCP is Cimplify's developer-preview REST surface for agent-operated commerce. The implementation currently supports product discovery, agent-session-owned carts, guest checkout through the PurchaseIntent door, and consent-scoped order reads. REST is the only implemented transport.
Availability and rollout
UCP has two fail-closed deployment controls:
ucp.enabledis the service-wide UCP switch. When it is off, UCP resources are not exposed.native_checkout.purchase_intents_enabledis the deployment-wide checkout emergency switch.
Checkout is a platform capability for every eligible merchant; there is no merchant preference or opt-out. The manifest advertises checkout as enabled when the deployment switch is on, the merchant is active, and at least one UCP-supported payment method is configured. Payment-method configuration is executable tender authority, not a checkout opt-out. Discovery and order reads do not depend on the checkout switches, but every UCP route remains subject to the service-wide UCP switch.
REST routes
The manifest route includes the /manifest suffix. The bare
/ucp/v1/{business_handle} path is not a UCP route.
| Method | Route | Current authorization |
|---|---|---|
GET | /ucp/v1/{business_handle}/manifest | Public discovery |
POST | /ucp/v1/{business_handle}/products | Public product search |
GET | /ucp/v1/{business_handle}/products/{product_id} | Public product read |
POST | /ucp/v1/{business_handle}/cart | Signed agent; add_to_cart, purchase, or full_access consent |
GET | /ucp/v1/{business_handle}/cart/{cart_id} | Signed owner agent |
POST | /ucp/v1/{business_handle}/cart/{cart_id}/items | Signed owner agent; add_to_cart, purchase, or full_access consent |
POST | /ucp/v1/{business_handle}/checkout | Signed owner agent; purchase or full_access consent; Idempotency-Key |
GET | /ucp/v1/{business_handle}/orders?customer_id={customer_id} | Signed agent; view_orders or full_access consent |
GET | /ucp/v1/{business_handle}/orders/{order_id} | Signed agent; view_orders or full_access consent |
GET | /ucp/v1/{business_handle}/orders/{order_id}/tracking | Signed agent; view_orders or full_access consent |
The manifest and product-discovery routes do not currently invoke the agent authenticator. Do not assume that preview behavior will remain the public-access policy at GA.
Manifest
Fetch the REST manifest at:
GET https://api.cimplify.io/ucp/v1/sweet-bakery/manifestThe response advertises only implemented REST bindings. A checkout-enabled business with Mobile Money may return a shape like:
{
"version": "1.0",
"business": {
"id": "biz_sweet_bakery",
"name": "Sweet Bakery",
"handle": "sweet-bakery",
"category": "food",
"currency": "GHS",
"contact": {
"email": "hello@sweetbakery.test",
"phone": "+233244000000"
}
},
"capabilities": {
"product_discovery": {
"enabled": true,
"binding": "rest",
"endpoint": "https://api.cimplify.io/ucp/v1/sweet-bakery/products",
"features": ["search", "browse", "filter"]
},
"checkout": {
"enabled": true,
"binding": "rest",
"endpoint": "https://api.cimplify.io/ucp/v1/sweet-bakery/checkout",
"payment_handlers": ["mobile_money"],
"supported_methods": ["mobile_money"],
"features": ["guest_checkout"]
},
"order_management": {
"enabled": true,
"binding": "rest",
"endpoint": "https://api.cimplify.io/ucp/v1/sweet-bakery/orders",
"features": ["tracking", "history"]
}
},
"shipping": {
"regions": ["GH"],
"methods": ["standard", "pickup"],
"same_day_available": false,
"pickup_available": true
},
"extensions": {
"loyalty": false,
"discounts": true,
"gift_cards": false,
"subscriptions": false,
"reservations": false
}
}Business fields are derived from current configuration. Shipping advertises
only standard, plus pickup when the business supports it; regions is
omitted when the business has no canonical country metadata instead of
inventing a default. Reservations remain false because there is no UCP
reservation route. Disabled checkout is represented by checkout.enabled: false with empty payment-handler and supported-method lists. There is no
advertised UCP identity-linking route.
Agent identity and request signatures
Authenticated routes require UCP-Agent. Its value is a compact JSON object:
UCP-Agent: {"provider":"openai","agent_id":"shopping-agent-42","version":"1.0","capabilities":["commerce"]}provider and agent_id are required. version and capabilities are
optional. The header does not carry a principal or a client-selected session
ID. provider and agent_id must be non-empty canonical values with no
surrounding whitespace or control characters; their limits are 100 and 200
bytes respectively.
When ucp.require_signatures is enabled (the default), authenticated requests
also require an HMAC-SHA256 signature:
X-Request-Signature: t=1787500000,v2=<hex_hmac_sha256>
X-Body-Hash: <hex_sha256_of_exact_request_body>Hash the exact UTF-8 UCP-Agent header value, including its JSON formatting:
agent_hash = hex(SHA256(exact_ucp_agent_header_bytes))
evidence = timestamp.method.path_and_query.body_hash.agent_hashFor a bodyless read, body_hash is the empty string; the separators remain in
the evidence. For POST, PUT, and PATCH, X-Body-Hash is required,
including for an empty-body POST, and is the lowercase hex SHA256 of the exact
bytes sent. Use the exact HTTP method and path including its query string.
Domain-separate and length-frame the HMAC input:
domain = "cimplify.ucp.request-signature.v2"
message = u64be(domain_length) || domain || u64be(evidence_length) || evidence
signature = hex(HMAC-SHA256(configured_ucp_secret, message))Only v2 is accepted; v1 signatures are rejected. The default timestamp
tolerance is 300 seconds and is deployment-configurable. Because the exact
agent header is signed, changing provider, agent_id, optional fields, or
JSON formatting after signing invalidates the request.
Consent proofs
Consent is a separate signed value carried in X-Consent-Proof. Send compact
JSON as the header value rather than as a checkout request-body member.
X-Consent-Proof: {"token":"consent_01","action":"purchase","granted_at":"2026-08-23T12:00:00Z","expires_at":"2026-08-23T13:00:00Z","scope":{"business_id":"biz_sweet_bakery","max_amount":"120.00","currency":"GHS","product_ids":["prod_sourdough"]},"signature":"<hex_hmac_sha256>"}The current proof schema is:
token: opaque consent identity;action:browse,add_to_cart,purchase,view_orders,link_identity, orfull_access;granted_atandexpires_at: an active, ordered UTC validity window;scope.business_id: the internal business ID, not its handle;- optional
scope.customer_id,scope.max_amount,scope.currency, andscope.product_ids; signature: lowercase hex HMAC-SHA256.
The proof signature uses the configured UCP secret and the domain
cimplify.ucp.consent-signature.v2. Its signed evidence includes the token,
action, Unix grant and expiry timestamps, and the complete scope. Changing an
amount, currency, customer, product list, action, or validity timestamp after
issuance invalidates the signature.
Action enforcement is route-specific:
- Cart creation and item addition require
add_to_cart; the broaderpurchaseandfull_accessactions are also accepted. - Checkout requires
purchaseorfull_access. - Order history, order detail, and tracking require
view_ordersorfull_access. A customer-scopedview_ordersproof can read only that customer's orders.full_accessis treated as business-wide for these reads, so do not use it as a customer-limited grant.
Purchase is currently guest-only. A purchase or full_access proof with
scope.customer_id fails closed because no production delegated
Customer-to-Account issuer exists yet. Customer-scoped view_orders remains
supported. For purchase consent, max_amount and currency must be supplied
together; the server checks the signed amount, currency, and optional product
allowlist against the immutable PurchaseIntent terms before confirmation.
AgentSession cart ownership
POST /cart creates an opaque AgentSession and binds the new Cart to it. The
session ID is server-owned and is not selected by the caller. Subsequent Cart
reads, item additions, and checkout resolve the session from the Cart and
require an exact active tuple:
session + business + cart + UCP-Agent.provider + UCP-Agent.agent_idExpired, completed, abandoned, or revoked sessions cannot authorize the Cart.
Session lifetime defaults to 24 hours and is configurable up to 30 days. The
same session becomes the PurchaseIntent's AgentSession owner, so a public Cart
or order ID is not replay authority. Continue sending the same provider and
agent_id; storing a consent on the session does not replace the required
per-request consent header.
Checkout
Checkout requires a canonical Idempotency-Key: it must be non-empty, have no
leading or trailing whitespace or control characters, and be at most 200
bytes. The server domain-separates it with the exact session, business, and
Cart before minting and confirming the PurchaseIntent. Reuse the same key and
request to recover the same operation; do not generate a new key merely because
provider completion is still pending.
The current UCP tender is Mobile Money only:
- accepted
payment.methodvalues aremobile_money,momo,mtn_momo,vodafone_cash, andairtel_money; payment.data, when present, must be an object containing onlyphone_numberandprovider;- generic
mobile_money/momorequirespayment.data.provider; a provider-specific method supplies its corresponding network when omitted; - canonical network values are
mtn,telecel, andairtel; - card, Google Pay, Apple Pay, PayPal, cash, and offline collection are not UCP tenders.
The phone number may come from payment.data.phone_number, top-level phone,
or shipping_address.phone. UCP accepts no provider token or reusable payment
credential. The requested network is a hint into immutable terms: confirmation
selects the exact eligible server-issued Mobile Money payment option matching
that network, and that option determines the provider command.
POST /ucp/v1/sweet-bakery/checkout
UCP-Agent: {"provider":"openai","agent_id":"shopping-agent-42"}
X-Request-Signature: t=1787500000,v2=<signature>
X-Body-Hash: <body_hash>
X-Consent-Proof: <compact_purchase_consent_json>
Idempotency-Key: checkout-cart-01
Content-Type: application/json{
"cart_id": "cart_01",
"shipping_address": {
"name": "Ama Mensah",
"line1": "1 High Street",
"city": "Accra",
"state": "Greater Accra",
"postal_code": "GA-100",
"country": "GH",
"phone": "+233241234567"
},
"shipping_method": "standard",
"payment": {
"method": "mobile_money",
"data": {
"phone_number": "+233241234567",
"provider": "mtn"
}
},
"email": "ama@example.test"
}Checkout outcomes
The UCP response preserves the exact PurchaseIntent confirmation result:
{
"data": {
"outcome": {
"status": "collection_processing",
"intent_id": "pi_01",
"attempt_id": "pia_01",
"order_id": "ord_01"
},
"order": {
"order_id": "ord_01",
"status": "pending",
"totals": {
"subtotal": "100.00",
"tax": "0.00",
"shipping": null,
"discount": "0.00",
"total": "100.00",
"currency": "GHS"
},
"created_at": "2026-08-23T12:00:00Z"
}
},
"meta": {
"timestamp": "2026-08-23T12:00:00Z"
},
"status": {
"code": 202,
"success": true
}
}Agents must branch on data.outcome.status. The closed result set is
order_created, collection_processing, payment_outcome_unknown,
payment_failed, redirect_required, card_popup_required,
authorization_required, terms_changed, selection_unavailable,
offer_ended, and source_unavailable.
order is present when an Order was committed, including pending,
action-required, outcome-unknown, and failed collection attempts. Its presence
and HTTP 202 do not mean payment succeeded. Pre-commit outcomes such as
terms_changed and selection_unavailable have no fabricated Order and return
HTTP 200. Checkout responses use Cache-Control: no-store.
The preview REST surface does not yet expose a separate intent-action or intent-poll endpoint. Preserve the exact checkout idempotency key for recovery and treat action-required or unknown outcomes as non-terminal unless the returned outcome says otherwise.
Developer-preview limitations
UCP is not GA. The implemented transport is REST only, purchase principal is guest-only, checkout tender is Mobile Money only, and the consent issuer is the configured HMAC integration. The manifest, authentication policy, and response shapes may change before GA. Integrations must respect the manifest and kill switches at runtime instead of assuming a capability is enabled.
Appearance API
Cimplify checkout accepts an `appearance` object that controls theme, accent color, font, and corner radius. The same shape works for hosted Pay and embedded checkout.
CheckoutElement
The unified checkout iframe. It renders contact capture, saved details, address, payment, provider authorization, and submit. Shopper verification happens in-panel; the parent SDK handles session persistence and identity binding.