Open Commerce Protocol (OCP)

Spec Version Status License
1.0.0-rc.1 🚧 Pre-Release Candidate MIT
Pre-Release Notice
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.

OpenAPI Specification The complete, machine-readable OpenAPI 3.0 specification is available on GitHub: spec.yaml

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

GET /capabilities
Always call first to see what the server supports

List Vendors

GET /stores
Get available stores/vendors

Get Catalog

GET /catalogs/{id}
Fetch products from a catalog

Create Cart

POST /carts
Start a new shopping session

Add to Cart

POST /carts/{id}/items
Add products to cart

Place Order

POST /orders
Checkout and create order

Track Order

GET /orders/{id}/updates
Real-time SSE updates

Get Order

GET /orders/{id}
Fetch current order state

Cancel Order

POST /orders/{id}/cancel
Request to cancel an 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.

Core Principle The base API should be so simple that you can understand it in 10 minutes. Everything else should be discoverable and optional.

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.

The OCP Promise Write your client once, work with any vendor. Write your server once, work with any client. That's the power of a minimal, extensible standard.

What Makes OCP Different

So why does OCP succeed where other standards fail? A few key decisions:

1. Minimal Core
The base API is tiny. You can implement a basic OCP server in an afternoon. This means people actually adopt it.
2. Discoverable Extensions
Instead of version hell, features are discoverable. Clients adapt to what servers support. No breaking changes, no API versions to juggle.
3. Structured Metadata
Metadata isn't a dumping ground. It's structured, validated, and linked to schemas. This prevents the chaos of "just put it in metadata."
4. Real-Time by Default
SSE for order updates is built in, not bolted on. Real-time isn't a premium feature—it's how the API works.
5. Web3-Native Payments
X402 integration means instant, low-fee payments with stablecoins. Perfect for micropayments, AI agents, and global commerce.
6. Implementation Freedom
OCP doesn't dictate your business logic. It's a transport layer, not a framework. Use any database, any auth system, any payment processor.

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

1
Review the Specification

Read the OpenAPI spec for the full API details. The specification defines all endpoints, schemas, and behaviors.

2
Implement Core Endpoints

Start with discovery (/capabilities, /stores, /catalogs) and cart/order flows.

3
Add Capabilities

Implement optional features like variants or tracking via standard schemas.

4
Build Clients

Use capability discovery for adaptive apps compatible with any OCP server.

Key Features

💳
Unified Payment Protocol
One Protocol, Any Payment Rail. OCP leverages the x402 protocol's extensible "schemes" to handle any payment type. Use the exact scheme for instant, low-fee crypto settlements and the fiat_intent scheme for secure, compliant processing of traditional payments through providers like Stripe and PayPal.
🎛️
Server-Driven UI Logic
Radically Simplified Client Logic. Stop hardcoding payment provider logic. The OCP server tells your client exactly what payment options are available, how to display them (with names and icons), and what to do next. Your app becomes future-proof by default.
🔍
Dynamic Capability Discovery
An API That Adapts to Any Business. OCP is more than payments. A powerful discovery mechanism allows your client to dynamically adapt to any feature the server supports—from product variants and customizations to real-time delivery tracking.
🛒
For Any Checkout Flow
One size doesn't fit all. OCP provides a robust, server-side cart for complex shopping journeys. For quick purchases, it also supports a stateless "Buy Now" flow. Your app can discover which models a server supports and dynamically provide the perfect UI for every product.
🔗
Standards Alignment
Built for the Web Ecosystem. OCP integrates seamlessly with Hydra for hypermedia semantics and Interledger/Open Payments for multi-rail payment interoperability—while maintaining its simple, developer-friendly JSON-first approach.

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:

  1. 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.
  2. 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.

🏪
E-commerce Marketplaces
Building a large e-commerce marketplace? Use the core, server-side Cart to manage complex sessions, validate promotions, and calculate shipping and taxes with confidence.
Quick Digital Purchases
Selling access to a single digital article or a SaaS subscription? Enable the Direct Order capability to provide a frictionless, "Buy Now" experience that takes users from discovery to purchase in a single step.
🎯
Adaptive Client Apps
The power of OCP is that your client application can support all of these models through one consistent and predictable API. Discover capabilities at runtime and render the perfect UI for every product.

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.

Key Principle Instead of bloating core schemas with every possible field, OCP uses structured metadata with discoverable schemas. This keeps the API minimal while supporting unlimited extensibility.

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.

Client Version Negotiation Clients negotiate versions via the Accept-OCP-Capabilities header (comma-separated list of capability IDs with versions, e.g., dev.ocp.product.variants@2.0). If omitted, servers use default versions. This makes evolution explicit and allows servers to innovate without breaking existing clients.

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.

Core Principle: A client should only need to know a single, fixed entry point (e.g., 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.

🔗
1. No More Brittle URLs
If a server needs to evolve its URL structure (e.g., from /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.
🔍
2. A Self-Discovering API
Your application learns the API as it uses it. The presence of a link or an action is the signal that a feature is available. This removes guesswork and the need to constantly consult documentation for every endpoint.
🎯
3. Dynamic, Context-Aware UIs
The API provides the logic for your UI. Instead of your client code deciding when to show a "Write a Review" button, the API makes the decision. If the action is present in the response, you show the button. If not, you don't.
🛡️
4. True Server Freedom
OCP doesn't dictate a server's internal architecture. Your returns system can live on a completely different microservice with a different URL structure, and as long as you provide the correct links, any OCP client can use it seamlessly.

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 the signInUrl provided.
  • How to search: It doesn't need to guess the query parameter is q; it uses the urlTemplate to 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

  1. A user views a recently delivered order. Your client makes a GET /orders/order_123.
  2. The server checks its business logic. Because the order is within the 30-day return window, it includes an initiate_return action in the response:
{
  "id": "order_123",
  "status": "completed",
  "actions": [
    {
      "id": "initiate_return",
      "href": "https://api.example.com/v2/order/order_123/return-requests"
    }
  ],
  "items": [...]
}
  1. 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 cancel action only appears if an order is cancellable.
  • Writing Reviews: The add_review action only appears on a CatalogItem if the logged-in user has purchased it.
  • Ratings: The rate_order action appears on completed orders if dev.ocp.order.ratings@1.0 is supported.
  • Returns: The initiate_return action appears if dev.ocp.order.returns@1.0 is supported and the order is eligible.
The Result By embracing this hypermedia-driven approach, the Open Commerce Protocol provides a blueprint for a truly modern, resilient, and intelligent commerce ecosystem.

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.

GET /capabilities
Response:
{
  "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"
    }
  ]
}
Client Insight: "Okay, this server supports stateful shopping carts (dev.ocp.cart@1.0), products with variants, shipment tracking, order ratings, and returns workflows. I can now enable the UI for these features."

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
        }
      ]
    }
  }
}
Client Insight: The client sees the dev.ocp.product.variants key. It now knows it can confidently parse the data within to build a size/color selection UI and disable the "White" option because its stock is 0.

Step 3: Place the Order

The user adds var_large_black to the cart. During checkout, the client places the order.

POST /orders
Request:
{
  "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.

GET /orders/{orderId}/updates
Response Stream:
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}}]
Client Insight: The client's UI updates the status to "Shipped." Because it saw the dev.ocp.order.shipment_tracking@1.0 capability in Step 1, it knows to look for the dev.ocp.order.shipment_tracking key in the metadata and can now display a "Track Your Shipment" button that links to the trackingUrl. If ratings are supported, it can also show a "Rate Your Order" button.

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.

GET /user/profile
Response:
{
  "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
}
Client Logic: Populate address dropdowns with savedAddresses. Pre-select defaultBillingAddress for billing. Use preferences to set UI language and currency. Check consentGiven before collecting additional data.

Guest Checkout with Temporary Customer Info

For anonymous users, dev.ocp.cart.guest@1.0 allows attaching temporary customer information to carts.

POST /carts
Request (Guest Cart):
{
  "storeId": "store_123",
  "guestInfo": {
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "+1-555-0123"
  }
}
Client Insight: Guest carts can include basic contact info without requiring account creation. This enables checkout flows for anonymous users while maintaining data for order fulfillment and communication.

Order with Billing Address and Custom Fields

Enhanced order requests can include separate billing addresses and custom fields for international commerce.

POST /orders
Request:
{
  "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"
    }
  }
}
Client Logic: Include billingAddress when different from delivery. Use customer_fields for tax IDs, customs info, and B2B details. This supports international shipping and compliance requirements.

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
}
Client Logic: When fulfillmentType is "hybrid", check fulfillmentComponents. If any component is "physical", require deliveryAddress. Show appropriate UI for each component type. Note that all components marked as "required": true must be fulfilled together.

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" }
        }
      ]
    }
  }
}
Client Implementation: For service scheduling, display available time slots as a calendar/picker. When user selects a slot, include it in the cart item metadata. Handle booking conflicts gracefully.

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
        }
      ]
    }
  }
}
Client Implementation: Content localization is automatic - the server handles translation. Clients only need to request their preferred language and render the received strings.

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);
  }
});
Server Requirements: When advertising dev.ocp.i18n@1.0, servers must implement proper locale negotiation, provide accurate formatting rules, and ensure all user-facing content is translated according to the negotiated locale.
Visual Example: Imagine the same e-commerce app UI displayed side-by-side: on the left in English (LTR) showing "$1,299.50", and on the right in Arabic (RTL) showing "ر.س ١٬٢٩٩.٥٠" with right-aligned text and RTL layout. This demonstrates how OCP i18n enables truly global commerce experiences.
Global-Ready by Design: OCP internationalization enables truly global commerce. Any client can provide a fully localized experience in any supported language, with culturally correct formatting and proper UI layout for RTL languages.

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"
    }
  ]
}
Multi-Rail Interoperability: This response is valid OCP, valid JSON-LD, and can settle via x402, Interledger/Open Payments, or traditional payment processors—all through the same elegant HTTP 402 flow.

📋 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

GET /capabilities Public

Discover server features and metadata schemas.

GET /stores Public

List available vendors/stores.

GET /catalogs Public

List available catalogs.

GET /catalogs/{id} Public

Get a full product catalog.

Cart Management

POST /carts Auth Required

Create a new shopping cart.

GET /carts/{id} Auth Required

Get a cart by ID.

POST /carts/{id}/items Auth Required

Add an item to the cart.

PATCH /carts/{id}/items/{itemId} Auth Required

Update an item in the cart.

DELETE /carts/{id}/items/{itemId} Auth Required

Remove an item from the cart.

POST /carts/{id}/promotions Auth Required

Apply or validate a promotion on a cart.

Ordering

POST /orders Auth Required

Place an order from a cart. Order type is inferred from cart contents. May return 402 if payment is required.

GET /orders/{id} Auth Required

Get the current state of an order.

GET /orders/{id}/updates Auth Required

Subscribe to real-time order updates via Server-Sent Events (SSE).

POST /orders/{id}/cancel Auth Required

Request to cancel an order.

POST /orders/{id}/ratings Auth Required

Submit ratings for a completed order.

POST /returns Auth Required

Initiate a return for items from an order.

GET /returns/{id} Auth Required

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

GET /webhooks Auth Required

List all active webhook subscriptions for the authenticated user or system (paginated).

POST /webhooks Auth Required

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 /webhooks/{id} Auth Required

Get details of a specific webhook subscription by ID.

DELETE /webhooks/{id} Auth Required

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:

JWT (JSON Web Tokens)
Stateless tokens containing encoded claims, signed by the server. Ideal for distributed systems.
API Keys
Simple, long-lived tokens for server-to-server communication or developer access.
OAuth 2.0
Industry-standard authorization framework for delegated access and third-party integrations.
SIWE (Sign-In With Ethereum)
Web3-native authentication using blockchain wallet signatures, no passwords required.

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.

Future Direction Standard authentication capabilities are planned, such as dev.ocp.auth.oauth2.pkce@1.0 or dev.ocp.auth.siwe@1.0. When implemented, clients will be able to discover authentication flows dynamically via GET /capabilities.

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
Specification Reference For complete details on the security scheme and authentication flow, see the securitySchemes section in the OpenAPI spec.

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.0
Note This content type is used throughout the OpenAPI specification for all request and response bodies.

Core 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.

Philosophy Errors are part of the API experience, not an afterthought. A great API tells you not just what went wrong, but why it happened and how to fix it—before you even make the request.

1. Proactive Error Prevention

Before making requests, clients can discover server constraints and business rules to prevent errors entirely.

1
Discover Store Constraints

Check if the server supports dev.ocp.store.constraints@1.0 in the capabilities response. If available, fetch store metadata to understand business rules.

2
Adapt UI Based on 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.

🤖
Machine-Readable Codes
Specific error codes for programmatic handling and recovery logic.
👨‍💻
Developer Context
Detailed technical messages and structured validation/business logic issues.
👤
User-Friendly Messages
Localization-ready messages with parameters for clean UI display.
🛠️
Recovery Actions
Suggested next steps with endpoint URLs to resolve the error.

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.
Result With OCP error handling, your application can prevent most errors proactively and handle the rest gracefully, creating a smooth, professional user experience.

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.

Get Involved Join the community in building the future of open commerce. Your feedback and contributions help make OCP better for everyone.

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.