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

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 Response

Bazaar 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 retries

Installation

Add bazaar to your dependencies in mix.exs:

def deps do
  [
    {:bazaar, "~> 0.1.0"}
  ]
end

Quick 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
end

Step 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
end

UCP 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 PATCHPOST
Cancel method DELETEPOST /cancel
Discovery /.well-known/ucp None
Status: incomplete incompletenot_ready_for_payment
Status: ready ready_for_completeready_for_payment
Address: street street_addressline_one
Address: city address_localitycity
Items key itemsline_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-23

Plugs

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
end

Guides

Related Protocols

License

Apache 2.0