Skip to Content
GuidesShopping agent (SDK)

Guide: build a shopping agent (SDK)

Goal: you are the platform. You run your own assistant or app that searches, checks out, and completes purchases on behalf of your signed-in buyers — on card or crypto — using the @artos-commerce/ucp-client SDK rather than the hosted bridge.

Who this is for: product teams embedding agentic commerce. If you just want Claude/Cursor to shop, use Connect an assistant — zero code. If you only read the catalog, use Catalog crawler.

What you own vs what the SDK owns

You ownThe SDK owns
A platform API key (get one)The UCP transport + envelope, idempotency
A hosted agent profile at /.well-known/ucpThe two-credential auth model + 401 relay
An AP2 signing key (public JWK published in the profile)AP2 mandate minting + byte-parity canonical JSON
Buyer OAuth 2.1 + PKCE (acquire + refresh the bearer)Merchant-terms verification, rail selection
A CIMD document (only because you run buyer OAuth)Card / crypto / $0 settlement orchestration

Phased checklist

Phase 0 — credentials

  1. Request a platform API key (ck_<id>.<secret>).
  2. Generate an AP2 signing key + profile: artos profile init --url https://your-app.example/.well-known/ucp (see CLI). Host ucp.json publicly; keep private-jwk.json as AGENT_PRIVATE_JWK.
  3. Host a CIMD oauth-client.json (sample below) so buyers can consent.

Phase 1 — verifyartos doctor (your setup) and artos conformance (the deployment’s UCP contract); gate the latter in CI (Conformance & CI).

Phase 2 — flows — search → checkout → confirmPurchase; then buyer OAuth + the account tools; then the crypto rail.

Phase 3 — production — point at https://api.artos.sh, store rotating refresh tokens, relay 401 for silent refresh, never ship the buyer bearer to a browser.

Environment

# .env (server-side only — never expose the buyer bearer to a browser) ARTOS_BASE_URL=https://api.artos.sh UCP_PLATFORM_API_KEY=ck_xxx.secret_xxx # platform key (cross-store) PLATFORM_PROFILE_URL=https://your-app.example/.well-known/ucp AGENT_PRIVATE_JWK={"kty":"EC","crv":"P-256",...} # AP2 signing key (secret) AGENT_KID=your-key-id # matches the public JWK in your profile # Crypto rail only: AGENT_SUI_PRIVATE_KEY=suiprivkey1... # Buyer OAuth (your CIMD client): OAUTH_CLIENT_ID=https://your-app.example/oauth-client.json OAUTH_REDIRECT_URI=https://your-app.example/callback

Bootstrap + buy

Crypto symbols live off the root barrel so card-only builds never load @mysten/sui. Import them from the /crypto and /sui subpaths.

import { UcpClient, Ap2Signer, createCheckoutHandlers, } from '@artos-commerce/ucp-client'; import { resolveCryptoDeps } from '@artos-commerce/ucp-client/crypto'; // crypto subpath const client = new UcpClient( { artosBaseUrl: process.env.ARTOS_BASE_URL!, platformApiKey: process.env.UCP_PLATFORM_API_KEY!, platformProfileUrl: process.env.PLATFORM_PROFILE_URL!, }, { buyerToken: session.buyerBearer, // server-side; omit for card-only/anonymous onAuthChallenge: (wwwAuthenticate) => respondWith401(wwwAuthenticate), }, ); const signer = new Ap2Signer( JSON.parse(process.env.AGENT_PRIVATE_JWK!), process.env.AGENT_KID!, ); // Optional crypto rail — returns undefined when no Sui key is set. const crypto = resolveCryptoDeps({ agentSuiPrivateKey: process.env.AGENT_SUI_PRIVATE_KEY, suiNetwork: 'mainnet', agentMaxSpendAmount: 50_000, // optional client-side cap (minor units) }); const shop = createCheckoutHandlers({ client, signer, crypto }); const results = await shop.searchProducts({ query: 'running shoes', filters: { price: { max: 150, currency: 'USD' } }, // MAJOR units }); const checkout = await shop.createCheckout({ storeSlug: 'YOUR_STORE_SLUG', // from each result's metadata.artos_seller.slug items: [{ id: 'variant_123', quantity: 1 }], shippingAddress: { country: 'US', postal_code: '94016' }, }); const outcome = await shop.confirmPurchase({ storeSlug: 'YOUR_STORE_SLUG', checkoutId: checkout.id as string, paymentMethod: 'artos.card', // omit on a multi-rail store to be asked });

Handle the outcome

confirmPurchase never throws for a business outcome — it resolves to a CheckoutOutcome you switch on:

switch (outcome.status) { case 'completed': return renderOrder(outcome.checkout); case 'escalation_required': return redirect(outcome.continueUrl); // 3-DS / hosted payment step case 'payment_selection_required': return askBuyerToPickRail(outcome.rails); // retry with paymentMethod case 'error': // code: crypto_not_configured | authentication_required | // merchant_authorization_invalid | spend_cap_exceeded | payment_execution_failed | … return showError(outcome.code, outcome.message); }

Transport failures (network / non-2xx / JSON-RPC) still throw UcpClientError; an expired buyer bearer throws UcpAuthError carrying the WWW-Authenticate challenge (relayed via onAuthChallenge for silent refresh).

Buyer OAuth (you own this)

The SDK forwards and relays the buyer bearer but does not acquire it. Use OAuthClient + createPkce:

import { OAuthClient, createPkce, DEFAULT_BUYER_SCOPE } from '@artos-commerce/ucp-client/oauth'; const oauth = new OAuthClient({ artosBaseUrl: process.env.ARTOS_BASE_URL! }); await oauth.discover(); // 1. Start consent. const pkce = createPkce(); const url = oauth.authorizeUrl({ clientId: process.env.OAUTH_CLIENT_ID!, // your CIMD URL redirectUri: process.env.OAUTH_REDIRECT_URI!, codeChallenge: pkce.challenge, scope: DEFAULT_BUYER_SCOPE, // "purchase:complete offline_access" }); // redirect the buyer to `url`; stash pkce.verifier in the session. // 2. On the callback, exchange the code. const tokens = await oauth.exchangeCode({ code, codeVerifier: pkce.verifier, clientId: process.env.OAUTH_CLIENT_ID!, redirectUri: process.env.OAUTH_REDIRECT_URI!, }); // store tokens.refresh_token (rotated, single-use) and use tokens.access_token as buyerToken.

Minimal CIMD document

Host this at the URL you use as client_id. List every redirect URI you use (a callback not in the document is rejected). See Authentication → CIMD.

{ "client_id": "https://your-app.example/oauth-client.json", "client_name": "Your App", "client_uri": "https://your-app.example", "redirect_uris": ["https://your-app.example/callback"], "token_endpoint_auth_method": "none" }

The crypto rail

To settle on-chain, pass resolveCryptoDeps(...) into createCheckoutHandlers and confirm with paymentMethod: 'artos.crypto'. The SDK prepares the payment intent (buyer bearer only), re-prices, re-verifies the merchant authorization, mints the checkout_mandate and payment_mandate, signs/submits the Sui PTB via SuiSigner, then completes with the tx digest.

Crypto is buyer-bound and has two hard requirements beyond OAuth consent:

  • @mysten/sui installed and AGENT_SUI_PRIVATE_KEY set (else outcome.code === 'crypto_not_configured').
  • The buyer authorized your agent under My Artos → Agents with a spend cap (else authentication_required / spend_cap_exceeded). See Authentication → spend caps.

Buyer-account tools

With a buyer bearer set, reach the account surface through createAccountHandlers (buyer bearer only — never the platform key):

import { createAccountHandlers } from '@artos-commerce/ucp-client/account'; const account = createAccountHandlers({ client }); const ctx = await account.getBuyerContext(); // default address, spend caps const orders = await account.listOrders(); // cross-store history const order = await account.getOrder('order_789'); // sends { order_id }

See Buyer account for the full tool set.

Next steps

Last updated on