Open Commerce Protocol (OCP)
| Spec Version | Status | License |
|---|---|---|
| 1.0.0-rc.1 | 🚧 Pre-Release Candidate | MIT |
This is a living pre-release specification awaiting implementation and feedback. The specification is stable enough for experimental implementations but may undergo minor changes based on real-world usage before the final 1.0 release.
The Open Commerce Protocol (OCP) is an open, extensible API specification for digital commerce, enabling universal clients to interact with any compliant server—from simple product catalogs to complex e-commerce platforms. By leveraging HTTP semantics, capability discovery, and structured metadata, OCP supports physical goods, digital services, and in-store pickup while prioritizing implementer freedom. OCP provides a unified API for any commerce model, whether you need a multi-item shopping cart for your online store or a streamlined, one-click checkout for your digital service. It integrates web3-native payments via the x402 Protocol for instant, low-fee blockchain transactions.
Overview & Common Questions
What is OCP?
An open, minimal, and extensible standard for digital commerce. It defines a universal HTTP API for the entire transaction lifecycle: product discovery, cart management, ordering, and real-time updates—supporting physical goods, digital services, and in-store pickup.
Why OCP?
Traditional e-commerce APIs are fragmented and vendor-specific. OCP provides a consistent, extensible framework that adapts to any business model—from simple catalogs to food delivery platforms to complex apparel stores—without dictating internal logic. It prioritizes implementer freedom while enabling universal, adaptive clients.
How does OCP work?
Clients begin by discovering server capabilities (GET /capabilities), then browse catalogs, manage carts, and place orders. Real-time updates stream via Server-Sent Events. Complexity is delegated to structured metadata linked to JSON Schemas, allowing clients to adapt dynamically without hardcoding features.
Why X402 for payments?
OCP integrates the x402 Protocol for flexible, modern payments. X402 is payment-method agnostic—it works with traditional payment processors like Stripe AND blockchain-based payments with stablecoins like USDC. This means you can start with familiar payment methods and optionally add crypto support later, or use both simultaneously. Unlike being locked into one payment provider, x402 gives you the freedom to choose what works best for your business.
How does authentication work?
OCP uses Bearer token authentication (Authorization: Bearer <token>). Token formats (e.g., JWT, API keys) are implementation-specific and may be discoverable via capabilities. Discovery endpoints are public; others require auth.
Who is OCP for?
API providers monetizing digital commerce, client developers building universal shopping apps, and AI agents requiring autonomous transactions.
How do promotions work?
Promotions are handled through discoverable capabilities. Servers advertise available promotions via dev.ocp.promotions.discoverable@1.0, clients validate them using POST /carts/{id}/promotions, and applied promotions are recorded in order metadata via dev.ocp.order.applied_promotions@1.0. This flexible system supports discounts, BOGO deals, free shipping, and more.
Quick Reference
A condensed cheat sheet for the most common OCP operations. Perfect for quick lookups while building.
Essential Endpoints
Discover Features
List Vendors
Get Catalog
Create Cart
Add to Cart
Place Order
Track Order
Get Order
Cancel Order
Quick Start Code
// Complete OCP workflow in one snippet
const API_BASE = 'https://api.example.com';
const AUTH_TOKEN = 'your_token';
// 1. Discover capabilities
const caps = await fetch(`${API_BASE}/capabilities`).then(r => r.json());
// 2. Get catalog
const catalog = await fetch(`${API_BASE}/catalogs/catalog_123`).then(r => r.json());
// 3. Create cart
const cart = await fetch(`${API_BASE}/carts`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AUTH_TOKEN}`,
'Content-Type': 'application/ocp+json; version=1.0'
},
body: JSON.stringify({ storeId: 'store_123' })
}).then(r => r.json());
// 4. Add item
await fetch(`${API_BASE}/carts/${cart.id}/items`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AUTH_TOKEN}`,
'Content-Type': 'application/ocp+json; version=1.0',
'Idempotency-Key': crypto.randomUUID()
},
body: JSON.stringify({ itemId: 'item_123', quantity: 1 })
});
// 5. Place order
const order = await fetch(`${API_BASE}/orders`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AUTH_TOKEN}`,
'Content-Type': 'application/ocp+json; version=1.0',
'Idempotency-Key': crypto.randomUUID()
},
body: JSON.stringify({ cartId: cart.id })
}).then(r => r.json());
// 6. Track order
const events = new EventSource(`${API_BASE}/orders/${order.id}/updates`);
events.addEventListener('order.patch', (e) => {
console.log('Order updated:', JSON.parse(e.data));
});Required Headers
| Header | Value | When Required |
|---|---|---|
| Accept | application/ocp+json; version=1.0 | All requests |
| Content-Type | application/ocp+json; version=1.0 | POST, PATCH requests |
| Authorization | Bearer <token> | Protected endpoints |
| Idempotency-Key | UUID | State-changing requests |
Common Patterns
Check for Variants
const hasVariants = item.metadata?.['dev.ocp.product.variants'];
if (hasVariants) {
// Show size/color picker
const variants = hasVariants.variants;
}Handle 402 Payment Required
const response = await fetch(`${API_BASE}/orders`, { /* ... */ });
if (response.status === 402) {
const paymentInfo = await response.json();
// Handle x402 payment flow
}Subscribe to Order Updates
const eventSource = new EventSource(`${API_BASE}/orders/${orderId}/updates`);
eventSource.addEventListener('order.patch', (event) => {
const patches = JSON.parse(event.data);
patches.forEach(patch => {
if (patch.path === '/status') {
console.log('New status:', patch.value);
}
});
});Understanding OCP: The Philosophy Behind a Minimal Commerce Standard
Let's talk about why OCP exists and why it works the way it does. If you've ever tried to integrate with multiple e-commerce APIs, you know the pain: every platform has its own way of doing things, its own quirks, its own special fields. Building a universal shopping app? Good luck—you'll be writing custom code for every single vendor.
The Problem: Commerce APIs Are a Mess
Here's the thing: commerce is actually pretty simple at its core. You have products, customers put them in a cart, and then they place an order. That's it. But somehow, every API makes this complicated in its own special way.
Some APIs have 47 fields on a product object "just in case." Others require you to make 6 API calls just to figure out if something is in stock. And don't even get me started on trying to build a client that works with multiple vendors—it's a nightmare of if-statements and special cases.
The OCP Philosophy: Minimal by Default, Complex by Choice
OCP takes a different approach. Instead of trying to predict every possible feature you might need and cramming it all into the core API, we keep the core ridiculously simple. Then we give you a way to discover and extend it when you need more.
This means:
- A product is just a product—name, price, availability. That's it.
- Need variants (sizes, colors)? That's in metadata, and you discover it via capabilities.
- Need shipping tracking? Also in metadata, also discoverable.
- Building a simple client? You can ignore all the fancy stuff and just work with the basics.
- Building a sophisticated client? You can discover and adapt to every feature the server supports.
Let's Build Something: Your First OCP Client
Enough theory. Let's write some code. I'm going to walk you through building a simple client that browses a catalog and places an order. We'll use plain JavaScript and fetch—no frameworks, no magic.
Step 1: Discover What the Server Can Do
Every OCP interaction starts the same way: ask the server what it supports. This is like walking into a store and asking for the catalog before you order.
// First, let's see what this server can do
const API_BASE = 'https://api.example.com';
const AUTH_TOKEN = 'your_token_here'; // You'd get this from your auth flow
async function discoverCapabilities() {
const response = await fetch(`${API_BASE}/capabilities`, {
headers: {
'Accept': 'application/ocp+json; version=1.0'
}
});
const data = await response.json();
console.log('Server capabilities:', data.capabilities);
return data.capabilities;
}
// Let's call it
const capabilities = await discoverCapabilities();
// Output might look like:
// [
// { id: "dev.ocp.product.variants@1.0", schemaUrl: "..." },
// { id: "dev.ocp.order.shipment_tracking@1.0", schemaUrl: "..." }
// ]See what we did there? We now know this server supports product variants and shipment tracking. We can use this information to enable features in our UI. No hardcoding, no guessing.
Step 2: Browse the Catalog
Now let's get the actual products. In OCP, products live in "catalogs".
// First, let's see what catalogs are available
async function getCatalogs() {
const response = await fetch(`${API_BASE}/catalogs`, {
headers: {
'Accept': 'application/ocp+json; version=1.0'
}
});
return await response.json();
}
// Get the first catalog's details
async function getCatalog(catalogId) {
const response = await fetch(`${API_BASE}/catalogs/${catalogId}`, {
headers: {
'Accept': 'application/ocp+json; version=1.0'
}
});
return await response.json();
}
// Let's use it
const catalogs = await getCatalogs();
const catalog = await getCatalog(catalogs[0].id);
console.log('Products:', catalog.items);
// Each item looks like:
// {
// id: "item_123",
// name: "Classic T-Shirt",
// price: { amount: "25.00", currency: "USD" },
// available: true,
// fulfillmentType: "physical"
// }Notice how clean this is? A product is just a product. Name, price, availability. If you're building a simple client, you're done—just show these to the user.
Step 3: Understanding Items and Metadata
But what if a product has variants, like different sizes? This is where OCP gets clever. Instead of adding a variants field to every product (even ones that don't need it), we use metadata.
// A product with variants looks like this:
const tshirt = {
id: "prod_123",
name: "Classic T-Shirt",
price: { amount: "25.00", currency: "USD" },
available: true,
fulfillmentType: "physical",
metadata: {
// This key matches the base ID from the capability we discovered earlier!
"dev.ocp.product.variants": {
"_version": "1.0",
options: ["Size", "Color"],
variants: [
{
id: "var_large_black",
values: ["Large", "Black"],
price: { amount: "25.00", currency: "USD" },
stock: 42
},
{
id: "var_medium_white",
values: ["Medium", "White"],
price: { amount: "25.00", currency: "USD" },
stock: 15
}
]
}
}
};
// In your client, you check if the capability exists:
function hasVariants(item, capabilities) {
const variantCapability = capabilities.find(
cap => cap.id === "dev.ocp.product.variants@1.0"
);
return variantCapability &&
item.metadata?.["dev.ocp.product.variants"];
}
// Then render accordingly:
if (hasVariants(tshirt, capabilities)) {
// Show size/color picker
const variants = tshirt.metadata["dev.ocp.product.variants"];
console.log('Available options:', variants.options);
console.log('Available variants:', variants.variants);
}
// Then render accordingly:
if (hasVariants(tshirt, capabilities)) {
// Show size/color picker
const variants = tshirt.metadata["dev.ocp.product.variants@1.0"];
console.log('Available options:', variants.options);
console.log('Available variants:', variants.variants);
} else {
// Just show a simple "Add to Cart" button
console.log('Simple product, no variants');
}This is the magic of OCP. Your client adapts to what the server supports. If variants exist, you show them. If not, you don't. No special cases, no brittle code.
Step 4: Creating a Cart and Adding Items
Alright, the user wants to buy something. Let's create a cart and add an item to it.
// Create a new cart
async function createCart() {
const response = await fetch(`${API_BASE}/carts`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AUTH_TOKEN}`,
'Content-Type': 'application/ocp+json; version=1.0',
'Accept': 'application/ocp+json; version=1.0'
},
body: JSON.stringify({
storeId: 'store_123' // Which store/vendor
})
});
return await response.json();
}
// Add an item to the cart
async function addToCart(cartId, itemId, quantity = 1, variantId = null) {
const body = {
itemId: itemId,
quantity: quantity
};
// If the user selected a variant, include it
if (variantId) {
body.variantId = variantId;
}
const response = await fetch(`${API_BASE}/carts/${cartId}/items`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AUTH_TOKEN}`,
'Content-Type': 'application/ocp+json; version=1.0',
'Accept': 'application/ocp+json; version=1.0',
'Idempotency-Key': crypto.randomUUID() // Prevent duplicate adds
},
body: JSON.stringify(body)
});
return await response.json();
}
// Let's use it
const cart = await createCart();
console.log('Created cart:', cart.id);
// Add a specific variant to the cart
await addToCart(cart.id, 'prod_123', 1, 'var_large_black');
console.log('Added item to cart!');Notice the Idempotency-Key header? This is important. If the network hiccups and you retry the request, the server won't add the item twice. This is the kind of detail OCP gets right.
Step 5: Placing an Order
The user is ready to check out. Let's place the order.
// Place an order
async function placeOrder(cartId, deliveryAddress = null) {
const body = {
cartId: cartId
};
// Include delivery address if provided (required for physical items)
if (deliveryAddress) {
body.deliveryAddress = deliveryAddress;
}
const response = await fetch(`${API_BASE}/orders`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AUTH_TOKEN}`,
'Content-Type': 'application/ocp+json; version=1.0',
'Accept': 'application/ocp+json; version=1.0',
'Idempotency-Key': crypto.randomUUID()
},
body: JSON.stringify(body)
});
// Check if payment is required
if (response.status === 402) {
// Server needs payment via x402 protocol
const paymentInfo = await response.json();
console.log('Payment required:', paymentInfo);
// Handle x402 payment flow here...
return null;
}
return await response.json();
}
// Place an order (type inferred from cart contents)
const order = await placeOrder(cart.id, {
address: '123 Main St, City, State 12345',
latitude: 37.7749,
longitude: -122.4194
});
console.log('Order placed!', order);
// {
// id: "order_123",
// status: "pending",
// items: [...],
// total: { amount: "25.00", currency: "USD" },
// createdAt: "2025-01-15T10:30:00Z"
// }Step 6: Tracking the Order in Real-Time
Here's where it gets cool. OCP supports real-time order updates via Server-Sent Events (SSE). This means you can show the user live updates as their order progresses.
// Subscribe to order updates
function subscribeToOrderUpdates(orderId, onUpdate) {
const eventSource = new EventSource(
`${API_BASE}/orders/${orderId}/updates`,
{
headers: {
'Authorization': `Bearer ${AUTH_TOKEN}`
}
}
);
eventSource.addEventListener('order.patch', (event) => {
// The server sends JSON Patch operations
const patches = JSON.parse(event.data);
console.log('Order updated:', patches);
// Apply the patches to your local order object
patches.forEach(patch => {
if (patch.op === 'replace' && patch.path === '/status') {
console.log('New status:', patch.value);
onUpdate({ status: patch.value });
}
// Check for tracking info
if (patch.path.includes('shipment_tracking')) {
console.log('Tracking info available!', patch.value);
onUpdate({ tracking: patch.value });
}
});
});
eventSource.onerror = (error) => {
console.error('SSE error:', error);
eventSource.close();
};
return eventSource;
}
// Use it
const subscription = subscribeToOrderUpdates(order.id, (update) => {
if (update.status) {
console.log(`Order is now: ${update.status}`);
// Update your UI: "Your order is being prepared!"
}
if (update.tracking) {
console.log('Track your package:', update.tracking);
// Show tracking link in UI
}
});
// When you're done, clean up
// subscription.close();Why This Approach Works
Let's step back and think about what we just built. With less than 200 lines of code, we created a client that can:
- Discover what any OCP server supports
- Browse products with or without variants
- Manage a shopping cart
- Place orders for physical, digital, or pickup items
- Track orders in real-time
And here's the kicker: this same code works with any OCP-compliant server. A grocery store, a clothing store, a digital goods marketplace—doesn't matter. The API is the same.
What Makes OCP Different
So why does OCP succeed where other standards fail? A few key decisions:
The Bottom Line
OCP exists because commerce APIs shouldn't be hard. The core flow—browse, cart, order—is universal. The details—variants, tracking, fulfillment—should be discoverable and optional.
By keeping the core minimal and making extensions discoverable, OCP gives you the best of both worlds: simplicity when you need it, power when you want it.
Now go build something cool. The API is simple, the docs are here, and the community is growing. Welcome to open commerce.
Getting Started
Read the OpenAPI spec for the full API details. The specification defines all endpoints, schemas, and behaviors.
Start with discovery (/capabilities, /stores, /catalogs) and cart/order flows.
Implement optional features like variants or tracking via standard schemas.
Use capability discovery for adaptive apps compatible with any OCP server.
Key Features
How It Works: The x402 Payment Flow
Here's the "aha!" moment for technical users. The x402 protocol handles payments through a simple request/retry cycle, with extensible schemes that support any payment method.
The Payment Flow
Client POST /orders → Server
Server → 402 Payment Required → Client
Path A (Fiat): Client uses clientSecret → Stripe.js → Gets paymentIntentId
Path B (Crypto): Client uses Wallet → Signs Message → Gets signature
Paths converge:
Client POST /orders (with X-PAYMENT header) → Server
Server → 201 Created → Client
The Server's 402 Response: A Blueprint for Your UI
The server's 402 response is the most powerful piece of code to show. It tells the client everything it needs to know—no hardcoding required!
// The server tells the client everything it needs to know.
// No hardcoding required!
{
"x402Version": 1,
"accepts": [
{
// Option 1: A traditional payment via Stripe
"scheme": "fiat_intent",
"network": "stripe",
"extra": {
// The secret key for the Stripe.js SDK
"clientSecret": "pi_3Pq..._secret_XYZ...",
// The text for the button in your app
"displayName": "Credit / Debit Card",
// The text for the final submit button in the Stripe UI
"inputAction": "Pay $25.00"
}
},
{
// Option 2: A web3-native payment
"scheme": "exact",
"network": "base-sepolia",
"extra": {
// The text for the other button in your app
"displayName": "Pay with Crypto"
}
}
]
}A Unified Payment Protocol: x402
OCP leverages the x402 Protocol to provide a single, unified layer for all payment processing. The core design principle of x402 is its payment-rail agnosticism. It is an HTTP-native protocol that can orchestrate any payment type, from traditional credit cards via Stripe to web3-native assets like USDC.
This flexibility is achieved through extensible payment schemes, which act as "drivers" for different financial networks. OCP specifies two primary schemes:
- fiat_intent (For Traditional Payments): This scheme is designed to work with modern processors like Stripe and PayPal. It orchestrates the secure, token-based "Payment Intent" flow, where sensitive details are never exposed to the OCP server, ensuring security and compliance.
- exact (For Web3 Payments): This scheme uses cryptographic signatures (EIP-712) to authorize transactions directly on a blockchain, enabling instant settlement, true micropayments, and autonomous payments by AI agents.
This dual-scheme architecture means you are never locked into a single payment world. You can start with the system that makes sense for your business today and expand effortlessly tomorrow.
The OCP Advantage: One Protocol, Two Worlds
Think of OCP as a universal adapter for digital commerce. By building on one simple protocol, you gain the benefits of both traditional and web3 finance.
| Feature | Traditional Payments (Standalone) | OCP with x402 (Unified) |
|---|---|---|
| Payment Methods | Credit cards, bank transfers. | Best of both worlds: Credit cards, bank transfers, plus crypto and stablecoins. |
| Transaction Fees | 2-3% + processing costs. | 2-3% for cards, near-zero for crypto. |
| Settlement Time | Days. | Days for cards, seconds for crypto. |
| Micropayments | Not economically viable. | Fully enabled by the low cost of crypto rails. |
| Programmability | Requires complex integrations and human oversight. | Enables true autonomous payments for AI agents and machine-to-machine commerce via crypto. |
| Provider Independence | Locked into one provider's specific API flow. | A single, unified flow that can support any provider now or in the future. |
Your Commerce, Your Choice: A Strategic Growth Path
The power of the OCP payment model lies in its flexibility. You can adopt it incrementally without rewriting your business logic.
Imagine you're launching a new service. You can start with a simple, familiar integration by enabling the fiat_intent scheme with Stripe. Your customers will pay with credit cards, and the experience will be seamless and secure.
Months later, you want to tap into a global market or serve an AI-driven use case. You simply enable the exact scheme in your OCP server configuration. Your application will now automatically present crypto as an additional payment option to your users—no rebuilding of your checkout flow is required. This allows you to meet your customers where they are, offering the best payment method for their needs, all through one consistent and elegant protocol.
From Online Stores to Digital Subscriptions
OCP is built for the entire spectrum of digital commerce.
Core Concepts
OCP's power lies in delegating complexity to metadata and making it discoverable.
Core schemas stay lean—no endless optional fields for variants, sizes, or weights. Domain-specific data resides in a generic metadata object.
To avoid chaos, GET /capabilities provides a "table of contents" for metadata, linking to standardized JSON Schemas. Clients adapt dynamically, creating a system that's simple by default, complex by choice.
Versioning Rules
OCP uses semantic versioning for capabilities and the overall standard to ensure smooth evolution without breaking existing clients.
PATCH Updates (1.0 → 1.1)
Add optional fields or minor enhancements with no breaking changes. Clients can safely upgrade without modifications.
MINOR Updates (1.x → 2.0)
Breaking changes are allowed but must include a migration guide. Servers announce deprecation 3 months in advance.
Deprecation Policy
Servers must support old versions for at least 12 months after sunset announcement to give clients time to adapt.
The OCP Hypermedia Engine: An API That Guides You
In many APIs, the developer's first step is to read the documentation and hardcode a long list of URL paths into their application. The Open Commerce Protocol (OCP) is designed on a more powerful and resilient principle: Hypermedia as the Engine of Application State (HATEOAS).
This sounds complex, but the idea is simple: instead of memorizing paths, your client "discovers" them. The API itself tells you what you can do next and provides the exact URLs needed to do it.
https://api.example.com). Every other action and resource should be discoverable by following links and actions provided by the API in its responses.
Why Work This Way?
This isn't just an academic exercise; it creates a fundamentally better and more robust integration.
/v1/returns to /v2/return-requests), a client that follows links will never break. A client with hardcoded URLs would fail instantly. This makes the entire ecosystem more resilient.How It Works: Two Types of Discovery
OCP uses two primary mechanisms for this hypermedia-driven discovery.
1. Global Discovery: The /capabilities Endpoint
The first thing your client does is ask the server what it's capable of. This is for server-wide features that aren't tied to a specific product or order.
Example: Discovering Authentication and Search
A client makes a GET /capabilities request and receives the "table of contents" for the API:
{
"capabilities": [
{
"id": "dev.ocp.auth.flows@1.0",
"metadata": {
"signInUrl": "https://auth.example.com/login",
"profileUrl": "https://api.example.com/users/me"
}
},
{
"id": "dev.ocp.product.search@1.0",
"metadata": {
"urlTemplate": "https://api.example.com/search?q={query}"
}
}
]
}From this single response, your client now knows:
- How to sign in: It doesn't need to know the path is
/login; it just uses thesignInUrlprovided. - How to search: It doesn't need to guess the query parameter is
q; it uses theurlTemplateto construct a valid search request.
2. Contextual Discovery: The actions Array
This is the most powerful feature. Core resources like Order and CatalogItem will tell you what actions you can perform on them in their current state.
Example: A Discoverable Returns Workflow
- A user views a recently delivered order. Your client makes a
GET /orders/order_123. - The server checks its business logic. Because the order is within the 30-day return window, it includes an
initiate_returnaction in the response:
{
"id": "order_123",
"status": "completed",
"actions": [
{
"id": "initiate_return",
"href": "https://api.example.com/v2/order/order_123/return-requests"
}
],
"items": [...]
}- Your client's logic is now incredibly simple:
// Pseudo-code for a UI component
const returnAction = order.actions.find(action => action.id === 'initiate_return');
if (returnAction) {
// The action is present, so we show the button
showReturnButton({
// We tell the button exactly which URL to POST to
onClick: () => postTo(returnAction.href)
});
}If the same order were 60 days old, the server would simply omit the initiate_return action from the actions array. The exact same client code would run, find nothing, and the "Return Items" button would never be rendered. The business logic lives on the server, where it belongs, and the client intelligently adapts.
This same pattern is used for:
- Cancellations: The
cancelaction only appears if an order is cancellable. - Writing Reviews: The
add_reviewaction only appears on aCatalogItemif the logged-in user has purchased it. - Ratings: The
rate_orderaction appears on completed orders ifdev.ocp.order.ratings@1.0is supported. - Returns: The
initiate_returnaction appears ifdev.ocp.order.returns@1.0is supported and the order is eligible.
Example: A Universal Client Buys a T-Shirt
This narrative demonstrates how a client uses the full power of OCP to interact with an apparel store.
Step 1: Discover Server Capabilities
The client's first action is to understand the server's features.
{
"capabilities": [
{
"id": "dev.ocp.cart@1.0",
"schemaUrl": "https://schemas.OCP.dev/cart/v1.json"
},
{
"id": "dev.ocp.product.variants@1.0",
"schemaUrl": "https://schemas.OCP.dev/product/variants/v1.json"
},
{
"id": "dev.ocp.order.shipment_tracking@1.0",
"schemaUrl": "https://schemas.OCP.dev/order/shipment_tracking/v1.json"
},
{
"id": "dev.ocp.order.ratings@1.0"
},
{
"id": "dev.ocp.order.returns@1.0"
}
]
}Step 2: View a Product with Structured Metadata
The client fetches a catalog (/catalogs/{id}) and gets a product (CatalogItem).
{
"id": "prod_123",
"name": "Classic OCP Tee",
"price": { "amount": "25.00", "currency": "USD" },
"fulfillmentType": "physical",
"available": true,
"metadata": {
"x-vendor-style-code": "FW25-TEE-01",
"dev.ocp.product.variants": {
"_version": "1.0",
"options": ["Size", "Color"],
"variants": [
{
"id": "var_large_black",
"values": ["Large", "Black"],
"price": { "amount": "25.00", "currency": "USD" },
"stock": 42
},
{
"id": "var_large_white",
"values": ["Large", "White"],
"price": { "amount": "25.00", "currency": "USD" },
"stock": 0
}
]
}
}
}Step 3: Place the Order
The user adds var_large_black to the cart. During checkout, the client places the order.
{
"cartId": "cart_abc",
"deliveryAddress": {
"address": "123 Main St, City, State 12345",
"latitude": 37.7749,
"longitude": -122.4194
},
"billingAddress": {
"address": "456 Billing Ave, City, State 67890"
}
}Step 4: Track the Order in Real-Time
The client immediately opens a connection to the SSE endpoint.
event: order.patch
data: [
{"op": "replace", "path": "/status", "value": "confirmed"},
{"op": "add", "path": "/metadata/dev.ocp.order.detailed_status", "value": {
"_version": "1.0",
"title": "Order Confirmed",
"description": "Your order has been confirmed and will be processed soon."
}}
]
event: order.patch
data: [
{"op": "replace", "path": "/status", "value": "in_transit"},
{"op": "replace", "path": "/metadata/dev.ocp.order.detailed_status/title", "value": "Out for Delivery"},
{"op": "add", "path": "/metadata/dev.ocp.order.delivery_tracking", "value": {
"_version": "1.0",
"driver": { "name": "Jane D.", "vehicle": "Blue Sedan" },
"status": "en_route_to_customer",
"liveLocation": { "latitude": 40.7135, "longitude": -74.0050 },
"estimatedArrivalAt": "2025-10-22T19:30:00Z"
}}
]
event: order.patch
data: [{"op": "replace", "path": "/metadata/dev.ocp.order.delivery_tracking/liveLocation", "value": {"latitude": 40.7140, "longitude": -74.0045}}]Customer Information Examples
These examples demonstrate how clients use the new customer information capabilities to enhance the user experience with saved profiles, guest checkouts, and billing addresses.
Using Saved User Profiles for Address Pre-filling
When dev.ocp.user.profile@1.0 is supported, clients can fetch and use saved addresses to streamline checkout.
{
"savedAddresses": [
{
"label": "Home",
"address": {
"streetAddress": "123 Main St",
"addressLocality": "Anytown",
"addressRegion": "CA",
"postalCode": "12345",
"addressCountry": "US"
}
},
{
"label": "Work",
"address": {
"streetAddress": "456 Office Blvd",
"addressLocality": "Business City",
"addressRegion": "CA",
"postalCode": "67890",
"addressCountry": "US"
}
}
],
"defaultBillingAddress": {
"streetAddress": "123 Main St",
"addressLocality": "Anytown",
"addressRegion": "CA",
"postalCode": "12345",
"addressCountry": "US"
},
"preferences": {
"language": "en-US",
"notifications": true,
"currency": "USD"
},
"consentGiven": true
}Guest Checkout with Temporary Customer Info
For anonymous users, dev.ocp.cart.guest@1.0 allows attaching temporary customer information to carts.
{
"storeId": "store_123",
"guestInfo": {
"email": "guest@example.com",
"name": "John Doe",
"phone": "+1-555-0123"
}
}Order with Billing Address and Custom Fields
Enhanced order requests can include separate billing addresses and custom fields for international commerce.
{
"cartId": "cart_abc",
"deliveryAddress": {
"address": "123 Main St, Anytown, CA 12345"
},
"billingAddress": {
"address": "456 Billing Rd, Business City, CA 67890"
},
"metadata": {
"dev.ocp.order.customer_fields": {
"_version": "1.0",
"taxId": "US123456789",
"customsDeclaration": {
"description": "Apparel",
"value": 25.00,
"countryOfOrigin": "US"
},
"companyName": "Example Corp"
}
}
}Advanced Commerce Scenarios
OCP handles complex commerce scenarios through discoverable capabilities. Here are examples of how clients adapt to advanced features like hybrid fulfillment, subscriptions, pre-orders, and service scheduling.
Hybrid Fulfillment: Physical + Digital Bundles
Some products combine multiple fulfillment types. A book with a digital audiobook companion requires both shipping and digital access.
{
"id": "bundle_book_audiobook",
"name": "Complete Package: Book + Audiobook",
"price": { "amount": "29.99", "currency": "USD" },
"fulfillmentType": "hybrid",
"fulfillmentComponents": [
{
"type": "physical",
"description": "Hardcover book shipped to your address",
"required": true
},
{
"type": "digital",
"description": "MP3 audiobook download",
"required": true
}
],
"available": true
}Subscription Orders: Recurring Billing
Subscription orders use metadata to define billing cycles and management options. Clients can discover available actions like pause, resume, and cancel based on the subscription status.
{
"id": "order_sub_123",
"status": "confirmed",
"metadata": {
"dev.ocp.order.subscription": {
"_version": "1.0",
"frequency": "monthly",
"nextBillingDate": "2025-11-15T00:00:00Z",
"status": "active",
"cancellationPolicy": {
"canCancelAnytime": true
}
}
},
"actions": [
{
"id": "pause_subscription",
"href": "/orders/order_sub_123/subscription/pause"
},
{
"id": "cancel_subscription",
"href": "/orders/order_sub_123/subscription/cancel"
}
]
}Pre-Orders: Future Availability
Pre-order items show release information and payment timing options.
{
"id": "game_nextgen",
"name": "Next-Gen Console",
"price": { "amount": "499.99", "currency": "USD" },
"fulfillmentType": "physical",
"available": true,
"metadata": {
"dev.ocp.order.preorder": {
"_version": "1.0",
"releaseDate": "2025-11-10T00:00:00Z",
"paymentTiming": "immediate"
}
}
}Service Scheduling: Appointments & Reservations
Service-based items include time slot availability and booking constraints.
{
"id": "haircut_service",
"name": "Premium Haircut",
"fulfillmentType": "pickup",
"available": true,
"metadata": {
"dev.ocp.service.scheduling": {
"_version": "1.0",
"duration": 45,
"location": "Main Street Salon",
"timeSlots": [
{
"startTime": "2025-10-25T10:00:00Z",
"endTime": "2025-10-25T10:45:00Z",
"available": true,
"price": { "amount": "75.00", "currency": "USD" }
},
{
"startTime": "2025-10-25T14:00:00Z",
"endTime": "2025-10-25T14:45:00Z",
"available": false,
"price": { "amount": "75.00", "currency": "USD" }
}
]
}
}
}Error Handling: Item Availability Changes
When items become unavailable during checkout, OCP provides structured error responses with recovery options.
{
"code": "order_items_unavailable",
"message": "Some items are no longer available",
"details": [
{
"type": "business_logic",
"resourceId": "prod_123",
"reason": "Item has 0 stock remaining. Previously had 5.",
"availableQuantity": 0
}
],
"userMessage": {
"localizationKey": "error.cart.items_unavailable"
},
"nextActions": [
{
"id": "review_cart",
"href": "/carts/cart_123",
"method": "GET"
},
{
"id": "update_quantities",
"href": "/carts/cart_123/items/item_456",
"method": "PATCH"
}
]
}Internationalization (i18n)
OCP provides comprehensive internationalization support, allowing clients to deliver culturally appropriate experiences in any language. The system follows OCP principles of server-driven discovery and client adaptability.
Locale Discovery
Clients discover a server's i18n capabilities through the standard capabilities endpoint.
{
"capabilities": [
{
"id": "dev.ocp.i18n@1.0",
"schemaUrl": "https://schemas.OCP.dev/i18n/v1.json",
"metadata": {
"_version": "1.0",
"defaultLocale": "en-US",
"supportedLocales": [
{
"code": "en-US",
"isRtl": false,
"numberFormat": {
"decimalSeparator": ".",
"groupingSeparator": ","
},
"currencyFormat": {
"symbol": "$",
"position": "before"
},
"dateFormat": {
"shortDate": "MM/dd/yyyy",
"longDate": "MMMM dd, yyyy",
"timeFormat": "h:mm a"
}
},
{
"code": "fr-FR",
"isRtl": false,
"numberFormat": {
"decimalSeparator": ",",
"groupingSeparator": " "
},
"currencyFormat": {
"symbol": "€",
"position": "after"
},
"dateFormat": {
"shortDate": "dd/MM/yyyy",
"longDate": "dd MMMM yyyy",
"timeFormat": "HH:mm"
}
},
{
"code": "ar-SA",
"isRtl": true,
"numberFormat": {
"decimalSeparator": ".",
"groupingSeparator": ","
},
"currencyFormat": {
"symbol": "ر.س",
"position": "before"
},
"dateFormat": {
"shortDate": "dd/MM/yyyy",
"longDate": "dd MMMM yyyy",
"timeFormat": "HH:mm"
}
}
]
}
}
]
}Language Negotiation
OCP uses standard HTTP headers for language negotiation, following RFC 7231.
// Client request with language preferences
GET /catalogs/catalog_123 HTTP/1.1
Accept-Language: fr-FR, fr;q=0.9, en;q=0.8
Accept: application/ocp+json; version=1.0
// Server response with selected language
HTTP/1.1 200 OK
Content-Language: fr-FR
Content-Type: application/ocp+json; version=1.0
{
"id": "catalog_123",
"name": "Électronique Grand Public",
"description": "Découvrez notre sélection d'appareils électroniques",
"items": [
{
"id": "prod_456",
"name": "Téléphone Intelligent",
"price": { "amount": "599.99", "currency": "EUR" }
}
]
}Localized Content
All user-facing strings in API responses are automatically localized based on the negotiated language.
{
"id": "prod_123",
"name": "Classic T-Shirt", // English
"description": "A comfortable cotton t-shirt",
"metadata": {
"dev.ocp.product.variants": {
"_version": "1.0",
"options": ["Size", "Color"], // Localized option names
"variants": [
{
"id": "var_large_black",
"values": ["Large", "Black"] // Localized values
}
]
}
}
}Error Message Localization
Error messages use localization keys for client-side translation.
{
"type": "https://schemas.OCP.dev/errors/cart-expired",
"title": "Cart Expired",
"status": 410,
"detail": "Cart expired after 3600 seconds of inactivity",
"instance": "https://api.example.com/carts/123",
"timestamp": "2023-10-23T12:00:00Z",
"localizationKey": "error.cart.expired",
"nextActions": [
{
"id": "create_new_cart",
"href": "/carts",
"method": "POST",
"title": "Create New Cart"
}
]
}Formatting Rules
Clients use the formatting rules from the i18n capability to display numbers, currencies, and dates correctly.
// Example: Formatting currency based on locale rules
function formatCurrency(amount, currency, localeRules) {
const { symbol, position } = localeRules.currencyFormat;
const formattedAmount = formatNumber(amount, localeRules.numberFormat);
if (position === 'before') {
return `${symbol}${formattedAmount}`;
} else {
return `${formattedAmount} ${symbol}`;
}
}
// For en-US: "$1,299.50"
// For fr-FR: "1 299,50 €"
// For ar-SA: "ر.س 1,299.50"Right-to-Left Language Support
The i18n capability includes RTL flags for proper UI layout.
// RTL language detection and UI adaptation
const locale = selectedLocale; // From i18n capability
if (locale.isRtl) {
document.documentElement.setAttribute('dir', 'rtl');
// Apply RTL-specific styles and layout adjustments
} else {
document.documentElement.setAttribute('dir', 'ltr');
}Server Implementation
Servers implementing i18n support need to handle locale negotiation and content translation.
// Example server-side locale negotiation
function negotiateLocale(acceptLanguage, supportedLocales, defaultLocale) {
if (!acceptLanguage) return defaultLocale;
// Parse Accept-Language header (simplified)
const requestedLocales = acceptLanguage.split(',').map(lang => {
const [code, q] = lang.trim().split(';q=');
return { code: code.trim(), q: parseFloat(q) || 1.0 };
}).sort((a, b) => b.q - a.q);
// Find best match
for (const request of requestedLocales) {
// Exact match
const exact = supportedLocales.find(loc => loc.code === request.code);
if (exact) return exact.code;
// Language match (e.g., 'fr-CA' matches 'fr-FR')
const languageMatch = supportedLocales.find(loc =>
loc.code.startsWith(request.code.split('-')[0] + '-')
);
if (languageMatch) return languageMatch.code;
// Base language match
const baseMatch = supportedLocales.find(loc =>
loc.code.split('-')[0] === request.code.split('-')[0]
);
if (baseMatch) return baseMatch.code;
}
return defaultLocale;
}
// Usage in API handler
app.get('/catalogs/:id', (req, res) => {
const capabilities = getServerCapabilities();
const i18n = capabilities.find(cap => cap.id === 'dev.ocp.i18n@1.0');
if (i18n) {
const selectedLocale = negotiateLocale(
req.headers['accept-language'],
i18n.metadata.supportedLocales,
i18n.metadata.defaultLocale
);
// Fetch localized content
const catalog = getLocalizedCatalog(req.params.id, selectedLocale);
res.set('Content-Language', selectedLocale);
res.json(catalog);
} else {
// Fallback to default behavior
const catalog = getCatalog(req.params.id);
res.json(catalog);
}
});Standards Alignment
OCP is designed to integrate seamlessly with the broader web standards ecosystem while maintaining its core principles: HTTP-native, JSON-first, and developer-friendly. This section outlines how OCP aligns with adjacent standards for enhanced interoperability.
🔗 Hypermedia Semantics (Hydra)
OCP enhances its hypermedia capabilities by providing optional fields that map cleanly to Hydra's link and operation semantics, as well as standard HTTP Link headers.
Optional rel and href Fields
Actions and capabilities in OCP now support two optional metadata fields:
- rel: Link relation type that mirrors HTTP Link rel and Hydra relation (e.g., self, cancel, create)
- href: Target URI for the action or linked resource
{
"id": "cancel",
"href": "/orders/123/cancel",
"rel": "cancel",
"method": "POST"
}HTTP Link Headers
Servers may optionally include equivalent HTTP Link headers for standard HTTP clients:
Link: </orders/123/cancel>; rel="cancel"JSON-LD Context
For Semantic Web compatibility, OCP provides an optional JSON-LD context at schemas/context.jsonld. Servers may include a top-level @context field in responses:
{
"@context": "https://schemas.OCP.dev/context.jsonld",
"capabilities": [
{
"id": "dev.ocp.cart@1.0",
"rel": "self",
"href": "/cart"
}
]
}JSON-only clients can safely ignore @context, while JSON-LD-aware clients can parse it semantically for integration with Hydra, RDF, and Linked Data tools.
💸 Payment Interoperability (Interledger / Open Payments)
OCP preserves its elegant HTTP 402 payment flow while enabling multi-rail interoperability through optional Interledger/Open Payments metadata.
Enhanced Payment Requirements
The X402PaymentRequirements schema now includes an optional interledger field:
{
"scheme": "fiat_intent",
"network": "stripe",
"maxAmountRequired": "2500",
"asset": "USD",
"interledger": {
"incoming_payment": "https://pay.example.com/incoming-payments/123",
"asset_code": "USD",
"asset_scale": 2
}
}Mapping to Open Payments
OCP payment fields map cleanly to Interledger/Open Payments concepts:
| OCP Field | Open Payments Equivalent | Notes |
|---|---|---|
| interledger.incoming_payment | incoming-payment or quote URL | Open Payments endpoint |
| maxAmountRequired | incoming-amount.value | Transfer amount |
| interledger.asset_code | incoming-amount.assetCode | ISO 4217 or token symbol |
| network | Connector / ledger ID | e.g., stripe, ilp, ethereum |
Unified Payment Flow
With these enhancements, a single OCP response can support multiple payment rails:
{
"@context": "https://schemas.OCP.dev/context.jsonld",
"x402Version": 1,
"accepts": [
{
"scheme": "fiat_intent",
"network": "stripe",
"asset": "USD",
"maxAmountRequired": "2500",
"interledger": {
"incoming_payment": "https://pay.example.com/incoming-payments/123",
"asset_code": "USD",
"asset_scale": 2
}
},
{
"scheme": "exact",
"network": "base-sepolia",
"asset": "0x...",
"maxAmountRequired": "25000000"
}
]
}📋 Implementation Status
All standards alignment features are:
- Optional: No breaking changes to existing implementations
- Backward Compatible: Existing clients continue to work without modification
- Progressive Enhancement: Servers can adopt features incrementally
For detailed implementation guidelines, see the Standards Alignment Roadmap documentation.
Capabilities Reference
Capabilities are the heart of OCP's extensibility. The following standard capabilities are defined. For implementation details on the capabilities endpoint and response format, see the /capabilities endpoint in the spec.
| Capability ID | Applies To | Description | |
|---|---|---|---|
| dev.ocp.cart@1.0 | Server-wide | Indicates support for stateful, server-side shopping carts with configurable lifecycle policies (expiration, limits, persistence). | [cart/v1.json](./schemas/cart/v1.json) |
| dev.ocp.order.direct@1.0 | Server-wide | Indicates support for cart-less "Buy Now" orders created directly from items, bypassing the cart entirely. | [order/direct/v1.json](./schemas/order/direct/v1.json) |
| dev.ocp.product.variants@1.0 | Product (CatalogItem) | Defines user-selectable options (e.g., size, color) and their corresponding variants, each with its own ID, price, and stock. | [product/variants/v1.json](./schemas/product/variants/v1.json) |
| dev.ocp.product.customization@1.0 | Product (CatalogItem) | Defines complex, selectable modifiers for a menu item (e.g., toppings, sides, customizations). | |
| dev.ocp.product.addons@1.0 | Product (CatalogItem) | Defines simple, selectable add-ons for a product, such as gift wrapping, extended warranty, or side dishes. | |
| dev.ocp.product.physical_properties@1.0 | Product (CatalogItem) | Defines physical properties like weight and dimensions, primarily for shipping estimation. | |
| dev.ocp.restaurant.profile@1.0 | Store | Adds restaurant-specific data like cuisine, hours, price range, and ratings. | |
| dev.ocp.order.kitchen_status@1.0 | Order | Provides real-time updates on an order's status within the kitchen. | |
| dev.ocp.order.delivery_tracking@1.0 | Order | Provides a real-time data structure for tracking a delivery, including driver info, GPS, and ETA. | |
| dev.ocp.order.shipment_tracking@1.0 | Order | Provides an array of shipment objects, each with a carrier, tracking number, and URL to track the package. | |
| dev.ocp.order.tipping@1.0 | Order | Allows for adding tips to the order. | [order/tipping/v1.json](./schemas/order/tipping/v1.json) |
| dev.ocp.order.ratings@1.0 | Order | Allows customers to submit ratings and feedback for completed orders. | N/A |
| dev.ocp.order.digital_access@1.0 | Order | Provides an array of objects containing access details for digital goods, such as download URLs or license keys. | |
| dev.ocp.order.detailed_status@1.0 | Order | Provides a rich, human-readable status (title, description, progress) for display in a UI, augmenting the core status field. | |
| dev.ocp.order.cancellation@1.0 | Order (Action) | Enables the workflow for cancelling an order via a dedicated endpoint, discoverable through the Order.actions field. | |
| dev.ocp.order.returns@1.0 | Order (Action) | Enables a discoverable workflow for item returns, initiated via an action on the Order and navigated via hypermedia links. | N/A (Workflow) |
| dev.ocp.order.refunds@1.0 | Order (Metadata) | Provides a standardized, auditable record of all monetary refunds associated with an order. | [order/refunds/v1.json](./schemas/order/refunds/v1.json) |
| dev.ocp.promotions.discoverable@1.0 | Store, Catalog | Allows a server to advertise publicly available promotions to clients. | [promotions/discoverable/v1.json](./schemas/promotions/discoverable/v1.json) |
| dev.ocp.promotions.policies@1.0 | Server-wide | Defines promotion stacking rules, validation policies, and exclusion rules to help clients provide better UX and prevent promotion conflicts. | [promotions/policies/v1.json](./schemas/promotions/policies/v1.json) |
| dev.ocp.order.applied_promotions@1.0 | Order | Provides a final, authoritative record of all value modifications on the completed order. | [order/applied_promotions/v1.json](./schemas/order/applied_promotions/v1.json) |
| dev.ocp.order.fulfillment_intent@1.0 | CreateOrderRequest | Allows a client to specify a precise fulfillment plan for the items in a cart, supporting mixed fulfillment and split orders. | [order/fulfillment_intent/v1.json](./schemas/order/fulfillment_intent/v1.json) |
| dev.ocp.store.constraints@1.0 | Store | Advertises server-side business rules (e.g., promotion policies, return windows) to help clients prevent errors. | [store/constraints/v1.json](./schemas/store/constraints/v1.json) |
| dev.ocp.payment.x402_fiat@1.0 | Server-wide | Advertises support for fiat payments via the x402 fiat_intent scheme and provides public keys for payment providers. | [payment/x402_fiat/v1.json](./schemas/payment/x402_fiat/v1.json) |
| dev.ocp.auth.flows@1.0 | Server-wide | Provides URLs for authentication flows (sign-in, sign-out, profile, registration). | [auth/flows/v1.json](./schemas/auth/flows/v1.json) |
| dev.ocp.product.search@1.0 | Server-wide | Provides a URL template for product search with supported sort options. | |
| dev.ocp.product.categorization@1.0 | Product (CatalogItem) | Provides an ordered category path (breadcrumb) for navigation. | |
| dev.ocp.product.relations@1.0 | Product (CatalogItem) | Provides related products (recommendations, accessories, alternatives). | |
| dev.ocp.order.subscription@1.0 | Order | Defines recurring subscription orders with frequency, billing cycles, and cancellation policies. | [order/subscription/v1.json](./schemas/order/subscription/v1.json) |
| dev.ocp.order.preorder@1.0 | Product (CatalogItem) | Defines preorder information for items not yet available, including release dates and payment timing. | [order/preorder/v1.json](./schemas/order/preorder/v1.json) |
| dev.ocp.service.scheduling@1.0 | Product (CatalogItem) | Defines scheduling information for service-based items like appointments, including time slots and booking constraints. | [service/scheduling/v1.json](./schemas/service/scheduling/v1.json) |
| dev.ocp.i18n@1.0 | Server-wide | Provides internationalization support including supported locales, default locale, and formatting rules for numbers, currencies, and dates. | [i18n/v1.json](./schemas/i18n/v1.json) |
| dev.ocp.store.info@1.0 | Store | Provides general store information including location, hours, website, and contact details, referencing schema.org LocalBusiness properties. | [store/info/v1.json](./schemas/store/info/v1.json) |
| dev.ocp.store.policies@1.0 | Store | Provides URLs to store policies such as return policy, privacy policy, terms of service, etc. | [store/policies/v1.json](./schemas/store/policies/v1.json) |
| dev.ocp.store.shipping@1.0 | Store | Defines comprehensive shipping policies including delivery areas, pricing calculations, processing times, shipping options, and providers. | [store/shipping/v1.json](./schemas/store/shipping/v1.json) |
| dev.ocp.user.profile@1.0 | Server-wide | Provides user-specific data like saved addresses and preferences via a discoverable profile endpoint. | [user/profile/v1.json](./schemas/user/profile/v1.json) |
| dev.ocp.cart.guest@1.0 | Server-wide | Allows carts to include temporary customer information for anonymous guest checkouts. | N/A (Extends Cart schema) |
| dev.ocp.order.customer_fields@1.0 | Order | Defines common custom fields for international commerce, tax, and customs purposes. | [order/customer_fields/v1.json](./schemas/order/customer_fields/v1.json) |
| dev.ocp.server.webhooks@1.0 | Server-wide | Enables server-to-server event notifications via webhooks. Servers advertise supported events and provide subscription management endpoints for asynchronous backend system integrations (inventory, shipping, ERP, AI agents, etc.). | [server/webhooks/v1.json](./schemas/server/webhooks/v1.json) |
API Endpoints
This section provides an overview of the core OCP endpoints. For complete details including request/response schemas, parameters, and error codes, refer to the OpenAPI specification.
Discovery Endpoints
Discover server features and metadata schemas.
List available vendors/stores.
List available catalogs.
Get a full product catalog.
Cart Management
Create a new shopping cart.
Get a cart by ID.
Add an item to the cart.
Update an item in the cart.
Remove an item from the cart.
Apply or validate a promotion on a cart.
Ordering
Place an order from a cart. Order type is inferred from cart contents. May return 402 if payment is required.
Get the current state of an order.
Subscribe to real-time order updates via Server-Sent Events (SSE).
Request to cancel an order.
Submit ratings for a completed order.
Initiate a return for items from an order.
Get the status of a specific return.
Webhooks
Webhooks enable server-to-server event notifications, allowing external systems to receive real-time updates when events occur in your OCP implementation. Unlike client-pull mechanisms (like actions) or client-initiated streams (like SSE), webhooks are server-push: your OCP server proactively sends HTTP POST requests to registered URLs when specific events happen.
Why Webhooks?
Webhooks solve integration problems that are impossible or inefficient with polling:
- Inventory Management: A centralized inventory system needs instant notifications when orders are placed to decrement stock across multiple stores. Polling every few seconds would be inefficient and create race conditions.
- Shipping & Fulfillment: Third-party logistics (3PL) providers need to know immediately when an order is confirmed so they can start picking and packing. A webhook triggers their fulfillment workflow automatically.
- Accounting Systems: When an order completes or a refund is issued, your accounting software (QuickBooks, Xero, etc.) needs to create invoices and ledger entries. Webhooks eliminate manual data entry and sync delays.
- Agentic Checkout (AI Agents): AI shopping agents (like OpenAI's agent framework) need to be notified when order status changes to shipped or delivered—long after the initial checkout conversation ended. Webhooks wake up the agent's backend to take action.
- Fraud Detection: Security systems can receive order creation events in real-time to perform risk analysis and automatically cancel suspicious orders before fulfillment begins.
- Customer Notifications: External notification services (SendGrid, Twilio) can be triggered to send order confirmations, shipping updates, and delivery alerts via email or SMS.
- Analytics & Business Intelligence: Data warehouses and analytics platforms can ingest events as they happen, ensuring dashboards and reports always reflect current state without polling delays.
Security: All webhook payloads are signed with HMAC-SHA256. Subscribers verify the signature using a shared secret to ensure requests genuinely come from your OCP server and haven't been tampered with.
Webhooks vs. Other Communication Patterns
| Feature | actions (Hypermedia) | SSE (/orders/{id}/updates) | Webhooks |
|---|---|---|---|
| Direction | Server → Client | Server → Client | Server → Server |
| Initiator | Client (by making request) | Client (by opening connection) | Server (by pushing event) |
| Primary Use Case | Interactive UIs & agents navigating the API | Real-time UI updates for a single resource | Backend system integration |
| State | Synchronous | Asynchronous (Streaming) | Asynchronous (Event-based) |
| Example | "Order is confirmed. You can cancel it by POSTing to this URL." | "Order 123's status changed to shipped." | "Hey, Shipping System! Order 456 was just created. Process it." |
Webhook Management Endpoints
List all active webhook subscriptions for the authenticated user or system (paginated).
Create a new webhook subscription for specified events. Server will send HTTP POST requests to the provided URL when events occur. Returns a shared secret for HMAC-SHA256 signature verification.
Get details of a specific webhook subscription by ID.
Delete a webhook subscription. No further events will be sent to the subscription URL.
Authentication
OCP uses Bearer token authentication for all protected endpoints. This provides a flexible, standardized way to secure API resources while allowing implementers to choose their preferred authentication method.
How It Works
Authenticated requests must include the Authorization header with a Bearer token:
Authorization: Bearer <token>Token Format Flexibility
OCP doesn't mandate a specific token format. The authentication method is determined by the server implementation and may include:
Public vs Protected Endpoints
Not all OCP endpoints require authentication. Discovery endpoints are intentionally public to allow clients to explore available products and services before authenticating.
| Endpoint Type | Authentication | Examples |
|---|---|---|
| Public (No Auth) | Not required | /capabilities, /stores, /catalogs, /catalogs/{id} |
| Protected (Auth Required) | Bearer token required | /carts, /orders, /orders/{id}/updates |
Example: Making an Authenticated Request
// Example: Creating a cart with authentication
const API_BASE = 'https://api.example.com';
const AUTH_TOKEN = 'your_bearer_token_here';
const response = await fetch(`${API_BASE}/carts`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AUTH_TOKEN}`,
'Content-Type': 'application/ocp+json; version=1.0',
'Accept': 'application/ocp+json; version=1.0'
},
body: JSON.stringify({
storeId: 'store_123'
})
});
if (response.status === 401) {
console.error('Authentication failed - token invalid or expired');
} else if (response.status === 403) {
console.error('Forbidden - insufficient permissions');
} else {
const cart = await response.json();
console.log('Cart created:', cart);
}Error Responses
401 Unauthorized
Returned when the token is missing, invalid, or expired. The client should prompt the user to re-authenticate.
{
"code": "unauthorized",
"message": "Authentication credentials are required and are missing or invalid"
}403 Forbidden
Returned when the token is valid but the user lacks permission to perform the requested action.
{
"code": "forbidden",
"message": "The client is not authorized to perform this action"
}Discoverable Authentication Methods
As OCP evolves, servers will be able to advertise their supported authentication methods via the capabilities endpoint. This allows clients to automatically adapt to different authentication requirements.
Best Practices
- Secure token storage: Store tokens securely using platform-appropriate mechanisms (keychain, secure storage, etc.)
- Token expiration: Handle token expiration gracefully with automatic refresh flows when possible
- HTTPS only: Always use HTTPS in production to prevent token interception
- Minimal scope: Request only the permissions needed for your application
- Token rotation: Implement token rotation strategies for long-lived applications
Content Type
All API requests and responses use the custom media type application/ocp+json; version=1.0. Clients should set the Accept and Content-Type headers accordingly:
Accept: application/ocp+json; version=1.0
Content-Type: application/ocp+json; version=1.0Core Schemas
OCP defines several core data structures used throughout the API. Below are examples of the most important schemas. For complete schema definitions including all fields, validation rules, and examples, consult the components/schemas section of the OpenAPI spec.
Money
{
"amount": "12.99",
"currency": "USD"
}Location
{
"address": "123 Main St, City, State 12345",
"latitude": 37.7749,
"longitude": -122.4194
}CatalogItem
{
"id": "item_123",
"name": "Product Name",
"description": "Product description",
"price": { "amount": "29.99", "currency": "USD" },
"available": true,
"fulfillmentType": "physical",
"metadata": {}
}Order
{
"id": "order_123",
"status": "confirmed",
"items": [...],
"total": { "amount": "59.98", "currency": "USD" },
"deliveryAddress": {...},
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-01-15T10:35:00Z",
"metadata": {}
}Implementation Notes
Always discover capabilities first
Call GET /capabilities to adapt dynamically—never hardcode features.
Use full Capability IDs as metadata keys
E.g., "dev.ocp.product.variants@1.0" to link to schemas.
Handle missing capabilities gracefully
Fall back to basic representations if optional features aren't supported.
Require idempotency
Include Idempotency-Key in all state-changing requests to prevent duplicates.
Support all fulfillment types
Design checkout for physical (shipping), digital, and pickup items in any cart.
Best Practices
- Start with capabilities: Always call /capabilities before making assumptions about server features.
- Use idempotency keys: Include unique Idempotency-Key headers for all POST, PATCH, and DELETE requests.
- Handle SSE gracefully: Implement reconnection logic for Server-Sent Events connections.
- Validate metadata schemas: Use the provided JSON Schema URLs to validate metadata structure.
- Support mixed carts: Allow users to purchase physical, digital, and pickup items in a single order.
- Implement proper error handling: Handle all standard HTTP error codes and OCP-specific error responses.
- Cache capabilities: Cache the capabilities response to reduce unnecessary API calls.
Error Handling
The OCP error handling system is designed as a first-class communication platform that serves three distinct audiences: the machine (for programmatic recovery), the developer (for debugging), and the end-user (for clear, localized guidance). It combines proactive error prevention with rich, contextual error responses.
1. Proactive Error Prevention
Before making requests, clients can discover server constraints and business rules to prevent errors entirely.
Check if the server supports dev.ocp.store.constraints@1.0 in the capabilities response. If available, fetch store metadata to understand business rules.
Use constraint data to disable invalid options, show appropriate warnings, and guide user behavior.
// Example: Prevent invalid cart sizes
async function getStoreConstraints(storeId) {
const store = await fetch(`${API_BASE}/stores/${storeId}`).then(r => r.json());
return store.metadata?.['dev.ocp.store.constraints@1.0'];
}
const constraints = await getStoreConstraints('store_123');
if (constraints?.maxCartItems) {
// Disable "Add to Cart" when limit reached
addToCartButton.disabled = cart.items.length >= constraints.maxCartItems;
}2. Rich Error Responses
When errors do occur, OCP provides structured responses with multiple layers of information.
Error Response Structure
{
"code": "promotion_stacking_not_allowed",
"message": "Multiple promotions cannot be applied to the same cart",
"details": [
{
"type": "business_logic",
"resourceId": "cart_abc",
"reason": "Cart already has promotion 'SUMMER10' applied"
}
],
"userMessage": {
"localizationKey": "error.promotion.multiple_not_allowed",
"params": {
"existingCode": "SUMMER10"
}
},
"nextActions": [
{
"id": "remove_promotion",
"href": "/carts/cart_abc/promotions/SUMMER10",
"method": "DELETE"
}
]
}Handling Different Error Types
Validation Errors
Client-side input issues with specific field pointers.
{
"code": "bad_request",
"details": [
{
"type": "validation",
"field": "/deliveryAddress/latitude",
"value": "invalid_coords",
"reason": "Must be a valid latitude between -90 and 90"
}
]
}Business Logic Errors
Server-side rule violations with resource context.
{
"code": "product_unavailable",
"details": [
{
"type": "business_logic",
"resourceId": "prod_123",
"reason": "Product has 0 stock remaining"
}
]
}Common E-commerce Errors
Frequently encountered errors in online commerce scenarios.
// Insufficient stock
{
"code": "insufficient_stock",
"message": "Only 3 units available, requested 5",
"userMessage": {
"localizationKey": "error.stock.insufficient",
"params": { "available": 3, "requested": 5 }
},
"nextActions": [
{
"id": "reduce_quantity",
"href": "/carts/cart_123/items/item_456",
"method": "PATCH"
}
]
}
// Geographic restriction
{
"code": "geographic_restriction",
"message": "Item not available for delivery to Hawaii",
"userMessage": {
"localizationKey": "error.geographic.restricted",
"params": { "location": "Hawaii" }
}
}
// Minimum order value
{
"code": "minimum_order_value",
"message": "Order total $45.00 is below minimum $50.00",
"userMessage": {
"localizationKey": "error.order.minimum_not_met",
"params": { "current": "45.00", "minimum": "50.00" }
},
"nextActions": [
{
"id": "add_more_items",
"href": "/carts/cart_123/items",
"method": "POST"
}
]
}Common Error Codes
| Code | HTTP Status | Description | Recovery Strategy |
|---|---|---|---|
| product_unavailable | 400 | Requested item is out of stock | Remove from cart or suggest alternatives |
| promotion_invalid | 400 | Invalid promo code | Prompt user to enter a different code |
| delivery_address_required | 400 | Physical items need delivery address | Show address form |
| payment_failed | 400 | Payment processing failed | Retry payment or use different method |
| cancellation_window_expired | 403 | Order too old to cancel | Show return options instead |
| cart_expired | 410 | Cart deleted due to inactivity | Create new cart and add items |
| order_items_unavailable | 409 | Some cart items became unavailable | Remove unavailable items and retry |
| insufficient_stock | 409 | Requested quantity exceeds available inventory | Reduce quantity or try again later |
| invalid_quantity | 400 | Quantity is negative, zero, or too large | Provide a valid quantity |
| geographic_restriction | 403 | Item not available in customer's location | Choose different item or location |
| minimum_order_value | 400 | Order total below minimum required | Add more items to meet minimum |
| service_unavailable | 503 | Service temporarily down for maintenance | Wait and retry later |
| payment_method_unsupported | 400 | Selected payment method not accepted | Choose different payment method |
Implementation Best Practices
- Check constraints first: Always fetch store constraints before building cart/checkout UIs.
- Handle RFC 9457 fields: Use title for error type, detail for specifics, and status for HTTP code.
- Use extensions: Leverage localizationKey for i18n and nextActions for recovery flows.
- Automate recovery: Use nextActions with requestSchema and responseSchema for programmatic error resolution.
- Log with instance: Use instance and timestamp for debugging and tracing.
Future Direction
OCP is a living standard. The future direction includes:
- A Richer Capability Library: Developing more standard schemas for concepts like product.reviews, order.returns, and service.bookings.
- Alternative Transport Layers: Exploring implementations over transports beyond HTTP, such as asynchronous message queues or other protocols.
- Tooling and SDKs: Fostering a community to build server middleware and client libraries that make implementing OCP trivial.
- Authentication Capabilities: Defining standard capabilities for advertising supported authentication methods (e.g., dev.ocp.auth.oauth2.pkce@1.0).
Contributing
This is an open standard, and contributions are welcome. Please open an issue or pull request to suggest changes, propose new standard capability modules, or report any issues.
License
The Open Commerce Protocol is open-source and available under the MIT License.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software.