# Verdure > Plant-based protein, considered — twenty small-batch products (pea and rice isolates, seed blends, bars, ready-to-drink) sold direct from a quiet paper-and-cream storefront. Verdure is agent-friendly: products are discoverable from a sitemap and a plain JSON catalog, and human checkout runs on Cybersource Unified Checkout. Verdure sells a curated line of vegan protein — isolates, multi-source blends, slow-baked bars, and chilled ready-to-drink. Products, pricing, and checkout are available both to humans (storefront) and to agents (the endpoints below). Prices are in USD; order amounts are always computed server-side. ## Agent & commerce endpoints - [Sitemap](https://verdure-v2.kaylinhiatt.com/sitemap.xml): every product page + core pages — the primary way to discover products. - [Catalog API](https://verdure-v2.kaylinhiatt.com/api/catalog): product list as JSON (id, name, flavor, category, price, tags, macros, image). - [Product API](https://verdure-v2.kaylinhiatt.com/api/catalog/:id): one product by id (e.g. /api/catalog/peaflora-vanilla). ## Product discovery - [Sitemap](https://verdure-v2.kaylinhiatt.com/sitemap.xml): every product URL (the product id is the last path segment, e.g. `/products/peaflora-vanilla.html` → id `peaflora-vanilla`). - Then fetch product data from the [Catalog API](https://verdure-v2.kaylinhiatt.com/api/catalog) (all) or https://verdure-v2.kaylinhiatt.com/api/catalog/ (one). Use the product "id" in the cart. - Every product includes an `apiUrl`, `url` (storefront page), price, macros, ingredients, and image URLs. ## Checkout & payments - Human checkout: Cybersource Unified Checkout — hosted card entry + Click to Pay (Visa, Mastercard, Amex, Discover, JCB, Diners). - Card networks accepted: Visa, Mastercard, Amex, Discover, JCB, Diners Club. - Payment credentials (PAN, wallet tokens) are collected by Cybersource's hosted widget and returned to the merchant as an opaque transient token; the merchant never sees the PAN. ## When the store asks who's behind you This applies however you shop: the JSON API or the web pages. You never need a special tool, a wallet or a token from anyone. The payment calls (POST /api/capture-context and POST /api/checkout-complete) answer 401 with code "KYA_REQUIRED" when they don't know the verified person behind you. The body has verify_url, status_url, expires_in (seconds), message_for_person and link_text. 1. Send your person message_for_person as is. If your chat shows markdown, you can send a markdown link instead: [link_text](verify_url). Never open the link or finish it yourself: only your person can. 2. Check status_url every few seconds. "pending" means keep waiting. "verified" comes with a token, shown once: keep it. "failed" or "expired" means stop and retry checkout to get a new link. 3. Retry the same call with the header Authorization: Bearer . Use the same token for the rest of this checkout. Before you pay, show your person the cart and total and get their OK. ## Agent checkout (server-priced cart + Unified Checkout capture context) The merchant prices the cart and returns a stable `reference` + total. The agent then requests a capture context, hands it to the Unified Checkout widget in the browser, and the widget returns a transient token that the merchant exchanges for an authorization. Card credentials never touch the merchant server. 1. Find the item and its id: GET https://verdure-v2.kaylinhiatt.com/sitemap.xml → discover product page URLs (id is the last path segment) GET https://verdure-v2.kaylinhiatt.com/api/catalog → product list (JSON); or GET https://verdure-v2.kaylinhiatt.com/api/catalog/ → one product. Use the product "id" in the cart. 2. Price the cart (merchant prices server-side; does NOT charge): POST https://verdure-v2.kaylinhiatt.com/api/checkout/price body: { "cart": [ { "slug": "", "qty": 1 } ] } → { "reference", "currency", "subtotal", "shipping", "tax", "total", "lines": [...] } 3. Request a Unified Checkout capture context (JWT signed by Cybersource): POST https://verdure-v2.kaylinhiatt.com/api/capture-context (needs Authorization: Bearer ; without one: 401 KYA_REQUIRED, see "When the store asks who's behind you") body: { "amount": "", "currency": "USD" } → { "jwt", "clientVersion" } The `jwt` is passed to Cybersource's `Accept(jwt).unifiedPayments().show(...)` widget in the browser; the widget resolves with a **transient token** once the user has entered card / wallet details. 4. Finalize the payment (server exchanges the transient token for an authorization): POST https://verdure-v2.kaylinhiatt.com/api/checkout-complete (needs Authorization: Bearer ; without one: 401 KYA_REQUIRED, see "When the store asks who's behind you") body: { "transientToken": "", "cart": [ { "slug", "qty" } ], "amount": "" } → { "status": "AUTHORIZED", "id": "", "reconciliationId", "submitTimeUtc" } ## Know Your Agent (Baselayer KYA): bring your own KYA credential Agent calls to the payment endpoints (`/api/capture-context`, `/api/checkout-complete`) must present a store access token obtained by exchanging a Baselayer KYA credential. Browsing, the catalog, and `/api/checkout/price` stay open. Without a store token, the payment endpoints answer 401 `KYA_REQUIRED`. The only other way through is a person in a browser who passes the Cloudflare check on the checkout page. In a browser, checkout shows a popup before the card form. People shopping for themselves pass the Cloudflare check. Agents choose "Verify with Baselayer" and send their person the link, then press "I've verified, continue" once the person is done. Agents follow "When the store asks who's behind you" above. 1. Without a token you get `401` with header `KYA-Disclosure-Scope: #checkout` and JSON `code: KYA_REQUIRED`, with `verify_url`, `status_url` and `expires_in`. Follow "When the store asks who's behind you" above. That is all most agents need. The steps below are for agents that bring their own KYA credential. 2. Read the disclosure profile: GET https://verdure-v2.kaylinhiatt.com/.well-known/kya-profile.json - scope `checkout`: pairwise subject only — no personal fields. Default. - scope `checkout-shipping`: may disclose only listed paths such as `given_name`, `email`, `address.*`, `hashes.email` (see Asking for Fields). Anything else is refused as `SCOPE_VIOLATION`. 3. Have your principal mint a credential (`POST https://api.baselayer.com/issuance/credentials` with `type: "INDIVIDUAL"`, `individual_ref` from `individual.individual_ref` in the onboarding results, and `audience: "verdure-v2.kaylinhiatt.com"`) bound to your Ed25519 agent key. Use only the fields listed in the selected scope. 4. Get a single-use nonce: POST https://verdure-v2.kaylinhiatt.com/nonces → `{ "nonce", "audience", "expires_at" }` (~10 min). 5. Sign a Key Binding JWT (`typ: kb+jwt`, `alg: EdDSA`, payload `{ aud, nonce, iat, sd_hash }`) with your agent key and append it after the credential's trailing `~`. `aud` must match the nonce response exactly. 6. Exchange the presentation for this store's access token: POST https://verdure-v2.kaylinhiatt.com/oauth/token body (JSON or form): `grant_type=urn:ietf:params:oauth:grant-type:token-exchange`, `subject_token_type=urn:baselayer:oauth:token-type:kya-credential`, `subject_token=`, `scope=#checkout` → `{ "access_token", "token_type": "Bearer", "expires_in", "scope" }` 7. Send `Authorization: Bearer ` on protected calls. Responses include `kya: { status, sub, level, returning_customer, visits }` and header `KYA-Customer: new|returning`. Returning customers are recognized by the credential's pairwise DID (`sub`). - Dry run without ordering: POST https://verdure-v2.kaylinhiatt.com/api/kya/verify with a presentation (returns claims + an access_token). - Failure codes (token route puts them in `kya_error`): KYA_REQUIRED, NO_CREDENTIAL_PRESENTED, UNKNOWN_DISCLOSURE_SCOPE, MALFORMED_PRESENTATION, UNSUPPORTED_CREDENTIAL_TYPE, UNTRUSTED_ISSUER, SANDBOX_ISSUER, UNKNOWN_SIGNING_KEY, ISSUER_SIGNATURE_INVALID, CREDENTIAL_EXPIRED, CREDENTIAL_REVOKED, AUDIENCE_MISMATCH, DISCLOSURE_DIGEST_MISMATCH, SCOPE_VIOLATION, KEY_BINDING_MISSING, KEY_BINDING_INVALID, NONCE_NOT_ISSUED, NONCE_REPLAYED, ISSUER_RESOLUTION_FAILED, STATUS_LIST_RESOLUTION_FAILED. - Recent decisions: https://verdure-v2.kaylinhiatt.com/kya - Spec: https://docs.baselayer.com/docs/presenting-and-verifying-a-credential ## Data shape Product (example): { "id": "peaflora-vanilla", "name": "Peaflora Isolate", "flavor": "Vanilla Bean", "category": "isolate", "categoryLabel": "Pea Isolate", "price": { "amount": "42.00", "currency": "USD" }, "priceNote": "907g · 30 servings", "tagline": "A clean 25g of pea isolate with a whisper of Madagascar vanilla.", "badges": ["Vegan", "Gluten-free", "Non-GMO"], "tags": ["Pea", "Vanilla", "Isolate"], "macros": { "protein": "25g", "calories": "115", "carbs": "3g", "fat": "1.5g", "sugar": "1g", "servingSize": "30g scoop" }, "ingredients": "Yellow pea protein isolate, natural vanilla bean, ...", "image": "https://images.unsplash.com/...", "imageThumb": "https://images.unsplash.com/...", "url": "https://verdure-v2.kaylinhiatt.com/products/peaflora-vanilla.html", "apiUrl": "https://verdure-v2.kaylinhiatt.com/api/catalog/peaflora-vanilla", "inStock": true } Notes for agents: - All product ids are stable slugs (kebab-case, e.g. `sowen-wild-cocoa`). - Every product is 100% plant-based. No whey, casein, collagen, or meat is sold on this site. - Prices are all USD. Tax is estimated at 8% and shipping is free over $60 (flat $6 otherwise). - Rate-limiting is generous but not unlimited — please cache `/api/catalog` for at least 60 seconds. - CORS is open (`Access-Control-Allow-Origin: *`) on all `/api/*` routes. ## Contact - General & care: care@verdure.co - Wholesale: wholesale@verdure.co - Press: press@verdure.co