Bazaar
Open your store to AI agents. Elixir SDK for UCP and ACP.
Bazaar helps you build commerce APIs in Elixir/Phoenix that work with both Google Shopping agents (UCP) and OpenAI/Stripe agents (ACP) from a single handler.
[!TIP] See bazaar-merchant for a complete Phoenix app demonstrating a dual-protocol merchant.
Supported Protocols
| Protocol | Used By | Spec |
|---|---|---|
| UCP (Universal Commerce Protocol) | Google Shopping agents | ucp.dev |
| ACP (Agentic Commerce Protocol) | OpenAI Operator, Stripe | GitHub |
Both protocols enable AI agents to discover what your store offers, create and manage shopping carts, complete checkouts, and track orders.
UCP was announced by Google at NRF 2026, co-developed with Shopify, Walmart, Etsy, and Target. ACP is backed by OpenAI and Stripe.
Features
- Dual Protocol Support: Serve both UCP and ACP clients from one handler with automatic request/response translation
- Generated UCP Schemas: Smelter-generated Ecto schemas from official UCP JSON Schemas
- ACP Schema Validation: JSON Schema validation for ACP checkout sessions and delegate payment, plus an Ecto schema for the OpenAI product feed
- Phoenix Router Macro: Mount UCP and ACP routes with a single line each
- Handler Behaviour: Write commerce logic once, serve both protocols
- Built-in Plugs: Request validation, idempotency, and UCP headers
- Auto-generated Discovery:
/.well-known/ucpendpoint from your handler - Protocol Transformer: Automatic field/status mapping between UCP and ACP formats
- Business Logic Helpers: Currency conversion, message factories, order creation
How It Works
Bazaar uses UCP as its internal format. Your handler always works with UCP field names and status values, regardless of which protocol the client uses:
UCP Request → Bazaar Router → Your Handler → UCP Response
ACP Request → [transform to UCP] → Your Handler → [transform to ACP] → ACP ResponseBazaar handles the HTTP/JSON plumbing. You write the commerce logic.
| Bazaar | You |
|---|---|
| Routes requests from UCP and ACP agents | Write business logic |
| Transforms between protocol formats | Query your database |
| Validates request/response structure | Calculate prices, tax, shipping |
| Handles UCP headers and discovery | Integrate with payment/fulfillment |
Architecture
lib/bazaar/
├── schemas/
│ ├── ucp/ # Generated UCP schemas (from Smelter)
│ │ ├── shopping/ # Checkout, Order, Payment types
│ │ ├── capability/# Capability definitions
│ │ └── ucp/ # Discovery profile, response types
│ └── acp/ # ACP schemas: OpenAI product feed
├── protocol.ex # UCP/ACP status mappings
├── protocol/
│ └── transformer.ex # Request/response translation between protocols
├── validator.ex # Schema validation (UCP via JSV, ACP via JSV/$defs, product feed via Ecto)
├── checkout.ex # Business logic: currency helpers
├── order.ex # Business logic: from_checkout helper
├── message.ex # Business logic: error/warning/info factories
├── fulfillment.ex # Business logic: field definitions
├── handler.ex # Handler behaviour
├── phoenix/ # Router and controller
├── plugs/ # Request validation, headers, idempotency
└── webhook/ # Event delivery with signatures and retriesInstallation
Add bazaar to your dependencies in mix.exs:
def deps do
[
{:bazaar, "~> 0.1.0"}
]
endQuick Start
Step 1: Create a Handler
The handler defines your store's capabilities and commerce logic:
defmodule MyApp.CommerceHandler do
use Bazaar.Handler
@impl true
def capabilities, do: [:checkout, :orders]
@impl true
def business_profile do
%{
"name" => "My Awesome Store",
"description" => "We sell amazing products"
}
end
@impl true
def create_checkout(params, _conn) do
# params already validated by Bazaar
{:ok, %{"id" => "chk_123", "status" => "incomplete", ...}}
end
@impl true
def get_checkout(id, _conn) do
{:ok, checkout} or {:error, :not_found}
end
# ... other callbacks: update_checkout, cancel_checkout, get_order, cancel_order
endStep 2: Mount Routes
Add UCP and ACP routes to your Phoenix router:
defmodule MyAppWeb.Router do
use Phoenix.Router
use Bazaar.Phoenix.Router
pipeline :api do
plug :accepts, ["json"]
end
scope "/" do
pipe_through :api
# UCP routes (Google agents)
bazaar_routes "/", MyApp.CommerceHandler
# ACP routes (OpenAI/Stripe agents)
bazaar_routes "/acp", MyApp.CommerceHandler, protocol: :acp
end
endUCP endpoints:
| Method | Path | Description |
|---|---|---|
| GET | /.well-known/ucp | Discovery endpoint |
| POST | /checkout-sessions | Create checkout |
| GET | /checkout-sessions/:id | Get checkout |
| PATCH | /checkout-sessions/:id | Update checkout |
| DELETE | /checkout-sessions/:id | Cancel checkout |
| GET | /orders/:id | Get order |
| POST | /orders/:id/actions/cancel | Cancel order |
| POST | /webhooks/ucp | Receive webhooks |
ACP endpoints:
| Method | Path | Description |
|---|---|---|
| POST | /acp/checkout_sessions | Create checkout |
| GET | /acp/checkout_sessions/:id | Get checkout |
| POST | /acp/checkout_sessions/:id | Update checkout |
| POST | /acp/checkout_sessions/:id/complete | Complete checkout |
| POST | /acp/checkout_sessions/:id/cancel | Cancel checkout |
Step 3: Test It
# UCP discovery
curl http://localhost:4000/.well-known/ucp
# Create a checkout via UCP
curl -X POST http://localhost:4000/checkout-sessions \
-H "Content-Type: application/json" -d '{"items": [...]}'
# Create a checkout via ACP
curl -X POST http://localhost:4000/acp/checkout_sessions \
-H "Content-Type: application/json" -d '{"line_items": [...]}'Protocol Differences
Bazaar automatically handles the differences between UCP and ACP. Your handler code stays the same:
| Aspect | UCP | ACP |
|---|---|---|
| URL style | /checkout-sessions | /checkout_sessions |
| Update method | PATCH | POST |
| Cancel method | DELETE | POST /cancel |
| Discovery | /.well-known/ucp | None |
| Status: incomplete | incomplete | not_ready_for_payment |
| Status: ready | ready_for_complete | ready_for_payment |
| Address: street | street_address | line_one |
| Address: city | address_locality | city |
| Items key | items | line_items |
Validation
Bazaar bundles schema validation for both protocols:
# UCP schemas (via JSV against bundled JSON Schemas)
Bazaar.Validator.validate(data, :checkout)
Bazaar.Validator.validate(data, :order)
Bazaar.Validator.validate(data, :profile)
# ACP schemas (via JSV against bundled JSON Schemas with $defs)
Bazaar.Validator.validate(data, :checkout_session)
Bazaar.Validator.validate(data, :checkout_create_req)
Bazaar.Validator.validate(data, :checkout_complete_req)
Bazaar.Validator.validate(data, :delegate_payment_req)
Bazaar.Validator.validate(data, :delegate_payment_resp)
# OpenAI product feed (via Ecto embedded schema)
Bazaar.Validator.validate(data, :openai_product_feed)
# List all available schemas
Bazaar.Validator.available_schemas()
# => %{ucp: [:checkout, :order, :profile], acp: [:checkout_session, ...]}
UCP schemas track the UCP spec (currently 2026-01-23). ACP schemas track the open ACP repo (currently 2026-01-30).
Capabilities
| Capability | Description | Callbacks |
|---|---|---|
:checkout | Shopping cart management | create_checkout, get_checkout, update_checkout, cancel_checkout |
:orders | Order tracking | get_order, cancel_order |
:fulfillment | Shipping and pickup | Extends checkout/order with fulfillment options |
:identity | User identity linking | link_identity |
:catalog | Product discovery | list_products, get_product, search_products |
:discount | Discount codes | Extends checkout with discount support |
Schemas
UCP schemas are generated from official JSON Schemas using Smelter:
# Validate checkout response
changeset = Bazaar.Schemas.Shopping.CheckoutResp.new(params)
# Create order params from checkout
order_params = Bazaar.Order.from_checkout(checkout, "order_123", "https://shop.com/orders/123")
# Currency helpers
cents = Bazaar.Checkout.to_minor_units(19.99) # => 1999
dollars = Bazaar.Checkout.to_major_units(1999) # => 19.99
# Message factories
error = Bazaar.Message.error(%{"code" => "out_of_stock", "content" => "Item unavailable"})Regenerate UCP schemas if the spec is updated:
mix bazaar.gen.schemas priv/ucp_schemas/2026-01-23Plugs
Optional plugs for production use:
pipeline :ucp do
plug Bazaar.Plugs.UCPHeaders # Extract UCP headers
plug Bazaar.Plugs.ValidateRequest # Validate request body
plug Bazaar.Plugs.Idempotency # Handle retry safety
endGuides
- Getting Started: Build your first merchant
- Protocols: Support both UCP and ACP
- Handlers: Implement commerce logic
- Schemas: Validate checkout and order data
- Plugs: Add validation, idempotency, and headers
- Testing: Test your implementation
Related Protocols
- Agent2Agent (A2A): Agent communication
- Model Context Protocol (MCP): AI model integration
- Agent Payments Protocol (AP2): Secure payments
License
Apache 2.0