ai.commercestudio.b2b.approval
Draft, version 2026-10-19. Extends dev.ucp.shopping.checkout (UCP 2026-08-25). Schema: https://b2b.commercestudio.ai/ucp/schemas/approval.json.
In a company, the person who asks for an order isn't always allowed to place it. A junior buyer's orders, or orders over a limit, wait for a manager to approve them. That person may approve in their own agent, on a web page, or in the commerce platform's buyer portal.
This extension lets a checkout wait for that decision without leaving UCP's checkout model. The agent then knows the order is waiting, who must approve it, until when, and where they can review it.
Principles
- No new status. UCP's checkout statuses already fit:
- Complete Checkout returns
complete_in_progresswhile the order waits; - it ends
completedonce an approver places it; - it ends
canceledif the order is rejected or the request expires.
- Complete Checkout returns
- An Action says why it's waiting. While approval is pending, the checkout carries an Action of type
ai.commercestudio.b2b.approval. Its config gives the rule, who may approve, the deadline and a review page. - The requester's agent never approves. It shows the buyer what happened and checks back with Get Checkout. Deciding takes a separate, explicitly granted permission held by approvers.
- Nothing is bought until an approver acts. The business never places the order on the requester's behalf before then.
- Agents that don't negotiate the extension get the same
complete_in_progressstatus and messages, without the Action.
When approval is needed
The business evaluates its rules at Create Checkout, and again at Complete Checkout. Before completion, a checkout that will need approval carries an approval_required info message, so the agent can tell the buyer before they commit.
| Rule | Applies when |
|---|---|
| Purchase permission | The buyer can't purchase (b2b_account.buyer.can_purchase: false, e.g. a junior buyer), at any amount |
| Amount rules | The total is strictly greater than a company rule's threshold, e.g. "orders over $5,000 need a purchasing manager" |
| Spend limit | The total is over the buyer's own spend_limit (account extension) |
| Agent guardrails | The order trips one of the company's limits on AI agents: per-agent order limit, monthly cap on agent orders, or an unusually large quantity. See below |
- Several rules. When more than one applies, the strictest (lowest threshold) decides, and guardrails add their approver roles to it.
- The buyer's own role. A buyer who already holds the approver role isn't asked to approve their own order, except for guardrails.
Action: ai.commercestudio.b2b.approval
While an approval is pending, actions["ai.commercestudio.b2b.approval"] holds one entry. Its id is the approval id. Its config is:
| Field | Meaning |
|---|---|
state |
pending (the Action is only present while pending) |
rule |
{ id, description }: why approval is needed, e.g. purchase_permission, order_over_5000, spend_limit, agent_guardrail |
approver_role |
The role that approves |
approver_roles |
All roles that may decide, when more than one can, e.g. ["admin", "senior_buyer"] |
expires_at |
RFC 3339. After this the request expires and the checkout is cancelled |
review_url |
A page where a person in an approver role signs in, reviews the order and decides. The agent may share it; it never needs to open it |
reference |
What people call the request, e.g. a shopping list number in the platform |
The checkout's own expires_at is extended to cover the approval window, so a platform polling Get Checkout doesn't give up early.
Lifecycle
ready_for_complete ──Complete Checkout──▶ complete_in_progress ──approved──▶ completed (order)
│
├─ rejected ──────────▶ canceled
└─ expired ───────────▶ canceled
- Approved. The approver places the order. When the requester can't purchase, the order is placed in the approver's name, with the requester as the shipping contact.
- Stock and credit are checked again first. If either no longer fits, the checkout is cancelled with the reason (
out_of_stock,insufficient_credit) and nothing is ordered. - A temporary failure leaves it
complete_in_progress. The next Get Checkout retries, and placement never creates a second order.
- Stock and credit are checked again first. If either no longer fits, the checkout is cancelled with the reason (
- Rejected or expired. The checkout is cancelled with
approval_rejectedorapproval_expired. - The requester's agent polls Get Checkout. When it's
completed,ordercarries the order id and label.
Messages
| Code | Type | When |
|---|---|---|
approval_required |
info | Before Complete Checkout: this order will need approval, and why |
agent_guardrail |
info | The order trips an agent guardrail; a person approves it after Complete Checkout |
submitted_for_approval |
info | Complete Checkout submitted it: an authoritative confirmation ("Submitted for approval. Not ordered yet…") for the agent to show the buyer |
approval_pending |
info | Still waiting: who approves, where, and until when |
approved_placing |
info | Approved; the order is being placed |
order_placed |
info | Placed: the authoritative confirmation with the order number |
approval_rejected, approval_expired |
error (unrecoverable) | The checkout was cancelled |
not_ordered |
info | The authoritative confirmation that nothing was ordered, with the reason |
Agent guardrails
A company can limit what AI agents order without a person looking:
- a per-agent order limit, keyed by the agent's profile, with a default for any agent;
- a monthly cap on all agent orders for the company;
- an unusual quantity check: a line more than N× the company's largest past quantity of that item. Items never bought before are ignored.
A trip shows as agent_guardrail on the cart and checkout, and becomes the approval rule (or part of it) at Complete Checkout. Unlike other rules, a guardrail always needs someone other than the requester, even if the requester holds an approver role. That's because it guards the agent, not the person.
Deciding (Commerce Studio operations, not UCP)
An approver is someone who holds one of approver_roles, can purchase, and isn't the requester. They can decide in any of these ways:
- Their own agent. With the
ai.commercestudio.b2b.approval:managescope:list_pending_approvalslists what's waiting;get_approvalshows the lines with contract against list prices, the totals, shipping, PO, ship-to, credit before and after, and past purchases;decide_approvalapproves or rejects, and only when the person explicitly says so.
- REST:
POST /api/b2b/approvals/{id}/decisionwith{ "decision": "approved" | "rejected" }, using the same scope. - The review page at
review_url, after signing in. - The commerce platform, where it has its own approval surface (see below).
Every request, decision and placed order is recorded in the company's audit trail. Approvers see it through agent_activity.
Callisto on BigCommerce B2B Edition
- Approval is native. B2B Edition roles decide who must ask: a Junior Buyer has no purchase permission, and Senior Buyers and Admins approve. Amount rules and spend limits are optional Callisto policy. The Wildleaf demo company Bushwood Country Club has none.
- B2B Edition has no order approval API, only role-based shopping-list approval. So Complete Checkout creates a shopping list "ready for approval" (status 40) in the requester's name. The approver sees it in the Buyer Portal, and B2B Edition can email them.
- Decisions from anywhere.
- Callisto's own decisions are mirrored onto the list: approve sets it to 0; reject sets it back to draft (30) with a note.
- A decision made in the Buyer Portal is picked up on the next Get Checkout or approvals read, because B2B Edition has no shopping-list webhooks.
- The placed order is in the approver's name, with the requester as the shipping contact. The staff note names the requester, the agent, the approver, any quote, and any guardrails cleared.
- Verified B2B Edition limits:
- the server API accepts shopping-list status 0, 30 and 40 only;
- an update that includes the approver's user id is refused;
- list descriptions are capped at 200 characters.
- Verified on Wildleaf: Spaulding Smails (Junior Buyer) requested quote QN000008's order through his agent. Judge Elihu Smails (Admin) approved it, and order #113 was placed in Judge's name.