# Sellbase — full documentation for AI agents
Sellbase # Sellbase **Open source commerce that lives inside your project, run by your AI agent.** Catalog, checkout, orders, inventory, abandoned carts, discounts, appointments, emails, a complete admin, an API and an MCP server, all installed in **your repo** and **your Supabase** with one command. [Quick start](#quick-start) · [Features](#features) · [Docs](#documentation) · [Deploy](docs/guides/deploy.md) · [Contributing](CONTRIBUTING.md) · [Español](#en-español) ![Sellbase admin](docs/images/admin-home.png)
## Why Sellbase - **Yours, end to end.** Your data lives in your Postgres, your money goes to your Stripe account and your code stays in your repo. There is no platform fee, no lock-in and no Sellbase servers in between. MIT licensed. - **AI-first.** `sellbase init` writes `CLAUDE.md`/`AGENTS.md`, skills and an MCP server. Tell your agent "set up my store" and it creates products, connects payments and runs a test purchase. Money and destructive actions always ask the owner first, and everything is logged. - **Works on any site.** - **Next.js and Vite + React:** React components. - **Plain HTML, WordPress, Webflow, Vue, Svelte, Astro, Angular:** `` web components (13 KB gzipped) and a static admin. - **An admin that is a pleasure to use.** Products with drag and drop photos and variants, inventory, orders with packing slips and refunds, abandoned cart recovery, customers, discounts and settings for every connection. Also: - a command palette (⌘K) to search and act; - an AI copilot on Home; - built for store owners, not developers. - **Safe by default.** - Money stored as integers. - RLS on every table, with tests. - Orders only come from verified payment webhooks. - Idempotent writes. - Third-party secrets in Supabase Vault. - Signed outbound webhooks that never reach private networks. ## Quick start Requirements: Node 20+ and a Supabase project (or the local stack with Docker). ```bash npx supabase start # local stack (or pass --supabase-url … for a hosted project) npx sellbase init --yes # schema, Edge Functions, store, owner, admin, components, agent files ``` Then: - **Admin:** open `/admin` and sign in. Locally, the owner's email and password are in `.env.sellbase`. - **Setup:** follow the setup guide on Home. - **Or let your agent do it:** open it in the project and say **"Configura mi tienda con Sellbase"** / **"Set up my store with Sellbase"**. It uses the `sellbase` MCP tools until a test purchase succeeds. ```bash npx sellbase doctor # checklist with the next step for anything missing npx sellbase upgrade # new versions: dry run, backup, migrations, functions, components ``` ### Start from a template ```bash npx sellbase create my-store --template nextjs # Next.js store with the admin at /admin npx sellbase create my-landing --template html # plain HTML landing page, no build step ``` Both are in [`examples/`](examples) with Vercel and Netlify deploy buttons. ### With Claude Code ``` /plugin marketplace add jlgavel011/sellbase /plugin install sellbase@sellbase /sellbase:setup I sell handmade candles, $250 MXN each, shipping $120 ``` The plugin brings the Sellbase skills and MCP server to every project. `/sellbase:setup` installs Sellbase, creates your products, connects Stripe in test mode and runs a test purchase. Any other MCP client can use `npx sellbase mcp` (listed in the MCP registry as `io.github.jlgavel011/sellbase`). ### With any agent (Cursor, Codex, Copilot, Gemini CLI, Windsurf…) ```bash npx skills add jlgavel011/sellbase # the Sellbase skills, including add-ecommerce ``` Then ask your agent to "add a store to this project". The `add-ecommerce` skill installs Sellbase and sets it up. ### With shadcn The storefront components are also a shadcn-compatible registry: ```bash npx shadcn add https://jlgavel011.github.io/sellbase/r/store.json # everything npx shadcn add https://jlgavel011.github.io/sellbase/r/product-grid.json # or one component ``` They still need the backend: `npx sellbase init --yes`. The index is at [`/r/registry.json`](https://jlgavel011.github.io/sellbase/r/registry.json). Going live: [deploy guide](docs/guides/deploy.md) and [live payments with Stripe](docs/guides/stripe-live.md). ## Features | | | | --------------------------------------------------------- | ------------------------------------------------------- | | ![Products](docs/images/admin-products.png) | ![Product editor](docs/images/admin-product-editor.png) | | ![Orders](docs/images/admin-orders.png) | ![Order](docs/images/admin-order.png) | | ![Command palette](docs/images/admin-command-palette.png) | ![Sign in](docs/images/admin-login.png) | **Catalog:** - physical products, digital downloads and services with appointments; - up to 3 options per product (size, color…) with a variant matrix, compare-at prices and SEO; - collections, tags and stock per variant; - CSV import (including Shopify exports) and export; - bulk price changes with a preview. **Checkout:** - Stripe Checkout (cards, Apple Pay, Google Pay; test and live mode); - shipping rates, free shipping thresholds and local pickup; - inclusive or exclusive taxes; - discount codes and automatic discounts; - deposits for services; - a required consent checkbox (e.g. 18+ for alcohol) enforced by the server and stored with the order. **Orders:** - payment and fulfillment statuses; - shipping with carrier and tracking; - full or partial refunds; - cancellations with restock; - a timeline with comments; - printable packing slips; - manual orders (cash, transfer, WhatsApp) and payment links. **Growth:** - abandoned checkouts with one-click or automatic recovery emails that restore the cart; - customers with their history; - sales metrics and best sellers. **Services:** - resources with weekly hours and exceptions; - availability; - bookings with reminders (24 h and 2 h before) and calendar invites. **Emails (Resend):** order confirmation with download links, shipping updates, refunds, cancellations, appointment notices and cart recovery. All of them carry your logo and brand color. **Team and AI:** - staff roles and invitations; - API tokens with scopes for agents (money and data export scopes are opt-in); - an activity log; - signed outbound webhooks with retries for your ERP, spreadsheets or Zapier. **Storefront:** - React components copied into your repo (edit freely); - headless hooks; - web components for any site; - SEO helpers (JSON-LD, sitemap); - order lookup and download pages; - accessible markup (checked with axe). ![Storefront](docs/images/storefront.jpg) ## How it works ``` your site ──(@sellbase/react or web components)──┐ /admin (@sellbase/admin) ─────────────────────────────────────┤ your AI agent ──(sellbase MCP server)─────────────────────────┤ ▼ Supabase: Edge Functions (sellbase-api, -webhooks, -jobs) Postgres schema `sellbase` (RLS) · Vault · Storage · pg_cron │ Stripe · Resend · your webhooks ``` - **One API contract.** Every route is declared once with Zod in `@sellbase/core`. The Hono server validates with it, the OpenAPI spec and the typed SDK come from it, and the MCP tools reuse it. - **Snapshots.** Orders keep a copy of prices, titles and addresses, so later catalog changes never alter past orders. - **The outbox.** Business events are written in the same transaction as the change; jobs deliver emails and webhooks from it. | Package | What it is | | ----------------------------------- | --------------------------------------------------------------- | | [`sellbase`](packages/cli) | CLI: `init`, `doctor`, `upgrade`, `add`, `seed`, `token`, `mcp` | | [`@sellbase/admin`](packages/admin) | The admin (React), also shipped as a static page | | [`@sellbase/react`](packages/react) | Provider and hooks for storefronts | | [`@sellbase/web`](packages/web) | Web components for any site | | [`@sellbase/sdk`](packages/sdk) | Typed API client and SEO helpers | | [`@sellbase/mcp`](packages/mcp) | MCP server for AI agents | | [`@sellbase/core`](packages/core) | Schemas, API contracts, pricing, state machines | ## Documentation - [Getting started (español)](docs/guides/getting-started.md) - [Add an ecommerce store to a Next.js app with Supabase](docs/guides/nextjs-supabase-ecommerce.md) ([en español](docs/guides/tienda-nextjs-supabase.md)) - [Deploy to production](docs/guides/deploy.md) - [Customize the admin](docs/guides/customize-admin.md) - [Live payments with Stripe (español)](docs/guides/stripe-live.md) - [API reference](docs/reference/api.md), plus [`llms.txt`](docs/llms.txt) and [`llms-full.txt`](docs/llms-full.txt) for agents - [Specification](SPEC.md) and [architecture decisions](docs/decisions/) - [Changelog](CHANGELOG.md) ## Development ```bash pnpm i pnpm exec supabase start && node scripts/seed-demo.mjs pnpm dev # playground on http://localhost:3100 (admin at /admin) pnpm lint && pnpm typecheck && pnpm test pnpm test:db # pgTAP pnpm test:integration # API against the local database pnpm test:e2e # Playwright node scripts/acceptance.mjs # clean project → init → agent over MCP → test purchase ``` See [CONTRIBUTING.md](CONTRIBUTING.md). Security issues: [SECURITY.md](SECURITY.md). ## Status Sellbase is **0.3**, young but complete. The full flow is covered by CI on every commit: - pgTAP; - API integration; - Playwright end to end; - a clean-install acceptance test with a real agent over MCP. Next on the roadmap: - an installation guide per hosting provider; - more payment providers (Mercado Pago, OXXO via Stripe); - shipping label integrations; - invoicing (CFDI). ## En español Sellbase es un kit de comercio open source que se instala **dentro de tu proyecto** (tu repo y tu Supabase) y que tu agente de IA puede configurar y operar. **Qué incluye:** - catálogo con fotos y variantes; - checkout con Stripe; - pedidos, inventario y carritos abandonados; - descuentos, citas y correos; - un admin completo en `/admin`; - una API y un servidor MCP. Funciona en Next.js, Vite + React y en cualquier sitio (HTML puro, WordPress, Webflow…) con componentes web. ```bash npx supabase start npx sellbase init --yes ``` Después entra a `/admin` (en local, el usuario y la contraseña quedan en `.env.sellbase`) o dile a tu agente: **"Configura mi tienda con Sellbase"**. Guías: [primeros pasos](docs/guides/getting-started.md), [cobrar de verdad con Stripe](docs/guides/stripe-live.md) y [despliegue](docs/guides/deploy.md). ## License [MIT](LICENSE) # Customize the admin The admin is a package (`@sellbase/admin`), not code copied into your project, so updates never overwrite your changes. Customize it through its `config`: theme, logo, texts, named slots and extra pages. ```tsx // app/admin/[[...path]]/page.tsx (Next.js) or src/sellbase/admin-page.tsx (Vite) , 'order.detail.sidebar': ({ orderId }) => , }, pages: [{ path: 'reportes', label: 'Reportes', icon: 'chart', render: () => }], }} /> ``` On sites without React, the same options go in `admin/config.js` (`window.SellbaseAdminConfig`). Only values can be set there: no slots or pages. ## Slots | Slot | Where | | ------------------------- | -------------------------------------------------- | | `home.top`, `home.bottom` | Home, above and below the setup guide and metrics | | `orders.list.top` | Orders list | | `order.detail.sidebar` | Order page, right column (receives `orderId`) | | `products.list.top` | Products list | | `product.form.bottom` | Product editor (receives `productId` when editing) | | `settings.bottom` | Settings → General | | `sidebar.bottom` | Side menu, above Settings | Every slot receives `sellbase` (the typed API client with the signed-in staff session) and `navigate(path)`. ## Extra pages Each entry in `pages` adds an item to the side menu and a route under the admin. - `icon`: one of the admin icon names (`chart`, `store`, `mail`, `users`, `tag`, `file`, …) or a short text. - `render`: receives the same context as slots. ## Theme - `theme.primary` colors the primary buttons. - The rest of the admin uses Sellbase's neutral design tokens, so any brand color looks right. - The admin's styles are scoped to `.sb-admin` and never touch your site's CSS. ## Going further Sellbase is open source (MIT). If you need something the config cannot do, open an issue or a pull request, since other stores probably need it too. Forking `@sellbase/admin` works, but you lose automatic updates. # Deploy to production Sellbase has no servers of its own. In production it runs in **your** Supabase project (database, Auth, Storage, Edge Functions, cron) and your site runs wherever it runs today (Vercel, Netlify, Cloudflare Pages, a VPS, WordPress hosting…). ## 1. Create the Supabase project 1. Create a project at [supabase.com](https://supabase.com/dashboard). 2. From **Project Settings → API**, copy the project URL, the anon key and the service role key. From **Database → Connect**, copy the connection string (session pooler). 3. Link your repo to it: ```bash npx supabase login npx supabase link --project-ref ``` ## 2. Install Sellbase against it ```bash npx sellbase init --yes \ --store-name "My store" --currency MXN --country MX \ --owner-email you@yourstore.com \ --supabase-url https://.supabase.co \ --anon-key \ --service-role-key \ --db-url "postgresql://…" ``` What `init` does: - **Migrations:** applies them with `supabase db push`. - **Edge Functions:** deploys `sellbase-api`, `sellbase-webhooks` and `sellbase-jobs`. - **Store and jobs:** creates the store and schedules the jobs (pg_cron, every minute). - **Owner:** emails the owner an invitation to set their password. - **Project files:** writes the public config (`.env.local`, or `sellbase/config.js` for sites without React) and the agent files. The service role key is used only during `init`. It is not written to any file. ## 3. Deploy your site - **Next.js / Vite:** set the public variables from `.env.local` in your hosting (`NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, `NEXT_PUBLIC_SELLBASE_URL`, or the `VITE_` versions) and deploy as usual. The admin lives at `/admin`. - **Plain HTML, WordPress or any other site:** upload the `sellbase/` and `admin/` folders with the rest of the site. The admin works at `/admin/` with no server rewrites (it uses `#/` routes). ## 4. Finish setup in the admin Sign in at `/admin` and follow the **setup guide** on Home. Every step can also be done by your AI agent over MCP. 1. **Settings → General:** logo, brand color, contact email. 2. **Settings → Payments:** paste your Stripe secret key. It is stored in Supabase Vault. On a deployed project the Stripe webhook is created for you. Start with `sk_test_…`; switch to live keys when you are ready ([live payments guide](stripe-live.md)). 3. **Settings → Notifications:** connect Resend with a verified domain so customers get order, shipping and download emails. Send yourself a test email. 4. **Settings → Checkout:** - your store URL (used by abandoned checkout emails and payment links); - a required checkbox if you sell age-restricted products; - automatic recovery emails. 5. **Settings → Shipping and Taxes.** 6. Make a test purchase and check that the order appears in **Orders**. ## 5. Check and keep it healthy ```bash npx sellbase doctor # every check with the next step when something is missing npx sellbase upgrade # new versions: dry run, backup, migrations, functions, components ``` The doctor fails if `SELLBASE_WEBHOOKS_ALLOW_PRIVATE` is on in a deployed project (it is only for the local stack). **Backups:** Supabase takes daily backups on paid plans. `sellbase upgrade` also dumps the `sellbase` schema before applying migrations. ## Checklist before selling - [ ] Stripe live keys connected and the live check passed (Settings → Payments) - [ ] Resend connected with your domain; test email received - [ ] Store URL, contact email, shipping and taxes set - [ ] A real purchase with your own card, then refunded from the order page - [ ] Team members invited with the right role (Settings → Team) - [ ] AI agent tokens only have the scopes they need (Settings → AI agents) # Primeros pasos Sellbase se instala dentro de tu proyecto: tu repo y tu Supabase. No hay servidores de Sellbase de por medio. Funciona en **cualquier sitio**: - Next.js o Vite + React: usa componentes de React. - HTML puro, WordPress, Webflow, Vue, Svelte, Astro, Angular…: usa etiquetas `` que se agregan con un solo ` ``` 2. Place the elements where they belong in the existing design: | Element | Where | | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | `` | Next to each product (price, options, quantity, button; services show times) | | `` | Inline price anywhere | | `` | One product page for every product (`producto.html?p=`); set `productUrl: '/producto.html?p={slug}'` | | `` | Catalog sections | | `` | Header or nav. The cart drawer is added automatically | | `` | A checkout page. Use `consent` for required confirmations such as "Confirmo que soy mayor de 18 años" (alcohol) | | `` | The thank-you page (`success-url`) | | `` | "Mi pedido" page | | `` | Downloads page (`?token=`). Then set `download_page_url` to `https:///descargas.html?token={token}` | 3. **Theme with CSS variables** on `:root` or on the element: `--sellbase-primary`, `--sellbase-primary-text`, `--sellbase-text`, `--sellbase-muted`, `--sellbase-bg`, `--sellbase-surface`, `--sellbase-border`, `--sellbase-radius`, `--sellbase-danger`, `--sellbase-success`. Fonts and text color are inherited. Fine-tune with `::part(button)`, `::part(input)`, `::part(panel)`. The page's own CSS cannot break them (Shadow DOM). 4. **Links:** in `sellbase/config.js` set `productUrl` (e.g. `/productos/{slug}.html`), `checkoutUrl` and `successUrl`; `locale: 'en'` for English. 5. **Your own buttons:** `window.Sellbase.cart.add(variantId, qty)`, `Sellbase.cart.open()` and `Sellbase.onChange(fn)`. 6. **Admin:** the site serves it at `/admin/`, with `#/` routes, so it needs no rewrites. Edit `admin/config.js` for theme and logo. 7. Verify: add to cart → checkout reaches Stripe; then `test_purchase`. ## React sites (Next.js, Vite) Start with `storefront_scaffold` and the owner's intent (e.g. "tienda de playeras", "citas para mi consultorio"). It returns the components to add and the `npx sellbase add …` command. Components are copied into this repo, and you can edit them freely: - Next.js: `components/sellbase/` - Vite: `src/components/sellbase/` They read everything from the API through `@sellbase/react`. Never hardcode products or prices. ## 1. Provider and theme - Wrap the site once in `SellbaseStoreProvider` (`…/components/sellbase/provider.tsx`): - **Next.js:** in `app/layout.tsx`, around `{children}`. - **Vite:** in `src/main.tsx`, around ``. - Import `…/components/sellbase/theme.css` once in the global CSS. Set its variables to the site's colors, radius and fonts. - Tailwind v4: if the components folder is outside what Tailwind scans, add `@source "../components/sellbase";`. ## 2. Components and pages | Page | Component | Notes | | -------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | Home, landing | ``, `` | Carousel scrolls with arrows or touch | | Collection `/collections/[slug]` | `` | Title and description: `useCollection(slug)` | | Product `/products/[slug]` | `` | Includes `variant-picker` (sold-out values crossed out) and, for services, `booking-picker` | | Header and layout | ``, `` | The drawer traps focus; Escape closes it | | Cart `/carrito` | `` | Lines, `discount-input`, totals | | Checkout `/checkout` | `` | Redirects to pay | | Thank you `/gracias` | `` (order-status) | Shows "Confirmando tu pago…" until the webhook arrives, then the order. Clears the cart | | My order `/pedido` | `` (order-status) | Order number + email → status, tracking, appointments | | Downloads `/descargas/[token]` | `` | Then set `settings.download_page_url` to `https:///descargas/{token}` (`store_update_settings`) so emails link there | Routes are suggestions. Components take `hrefFor`, `productHref`, `checkoutHref` and `cartHref` to match yours; the default product URL is `/products/`. ## 3. SEO - **Product pages:** render `` (`product-seo`). For the title and Open Graph tags, use `productMetadata(product, { url, siteName })`: in Next.js return it from `generateMetadata`, loading the product on the server with `createSellbase(...).products.get(slug)`. - **Sitemap:** `catalogSitemap(sellbase, { baseUrl })` lists every product and collection. Next.js: return it from `app/sitemap.ts`. Vite: write `sitemapXml(entries)` to `public/sitemap.xml` in a build script. ## 4. Admin at /admin - **Next.js:** already mounted in `app/admin/[[...path]]/page.tsx`. - **Vite:** mount `src/sellbase/admin-page.tsx` for every path under `/admin`: - with React Router, `} />`; - without a router, render it instead of `` in `src/main.tsx` when `location.pathname.startsWith('/admin')`. The host must serve `index.html` for `/admin/*`: a SPA fallback, such as Vercel or Netlify rewrites. ## 5. Match the design and verify - Adapt markup and classes to the site. - Keep the data flow (hooks), the loading, error and sold-out states, and the accessibility attributes (labels, `aria-live`, radiogroups, focus handling). - In the browser: browse → add to cart → checkout reaches Stripe. Then run `test_purchase`. # Configure payments and email ## Stripe (test mode first) 1. Ask the owner for their Stripe **test** secret key (`sk_test_…`, at dashboard.stripe.com/test/apikeys). Prefer that they paste it into `.env.sellbase` themselves (`STRIPE_SECRET_KEY=`) instead of the chat, then read it from there. 2. Webhooks: - **Deployed** (public https Supabase URL): call `integration_connect` without `webhook_secret`. Sellbase creates the Stripe webhook endpoint and stores its secret. - **Local**: run `stripe listen --forward-to /functions/v1/sellbase-webhooks/stripe` and keep it running. Pass the `whsec_…` it prints as `webhook_secret`. 3. `integration_connect` with `provider: "stripe"`. Follow `next_steps` in the response. 4. Run `test_purchase`. Then check `store_status`: `payments` warns "test mode" until you go live, which is expected. ## Going live (real money) Only when the owner explicitly says the store is ready to sell. Full guide: `docs/guides/stripe-live.md`. 1. The Stripe account must be activated (business details and bank account). `store_status` fails `payments` while live charges are disabled. 2. **The owner connects the live key themselves** in the admin (Settings → Payments), so it goes straight to Supabase Vault. Never ask for it in chat or put it in a file. Use a restricted `rk_live_…` key if they prefer, with write access to Checkout Sessions, Payment Intents, Refunds and Webhook Endpoints. 3. After they connect it, check `store_status`: `payments` shows the live account, and deployed projects get the Stripe webhook automatically. (`integration_connect` with `confirm: true` also accepts live keys, but prefer the admin.) 4. `payments_live_check` with `confirm: true`: give the owner the URL. They pay the minimum (10 MXN or 0.50 USD) with a real card, and it is refunded automatically. `store_status` then shows "Live payment check" ok. Stripe keeps its small fee. 5. `test_purchase` never runs with live keys; it tells you to use the live check. Never echo keys back. Never switch back to test keys on a live store without asking: pending checkouts would fail. ## Resend (email) Without Resend, order emails are only printed to the function logs. Ask for an API key (`re_…`, resend.com/api-keys), call `integration_connect` with `provider: "resend"`, then `integration_test`. Until a domain is verified in Resend, emails only reach the account owner's address. # Configure shipping Sellbase ships with **manual shipping**: the store decides the price, and ships with any carrier. A carrier aggregator (automatic labels and quotes) comes later. ## Options (`store_update_settings`) All amounts are integers in minor units: $99.00 MXN is `9900`. ```json { "settings": { "shipping": { "flat_rate_amount": 9900, "free_over_amount": 99900, "label": "Envío estándar", "estimated_days": { "min": 2, "max": 5 }, "pickup": { "enabled": true, "label": "Recoger en tienda" } } } } ``` - `flat_rate_amount`: price of a normal shipment. - `free_over_amount`: subtotal from which shipping is free. Use `null` to never make it free. - `pickup.enabled`: offers store pickup at no cost. The buyer then gives no address. - Only **physical** products need shipping. Digital products and services never ask for an address. - For weight-based pricing in the future, fill `variants[].physical` (`weight_g`, `length_cm`, `width_cm`, `height_cm`) with `product_upsert` from the start. Check the result: `store_status`, then add a physical product to a cart. `GET /storefront/carts/:token/shipping-rates` lists the options the buyer will see. ## Shipping an order 1. `orders_search` with `fulfillment_status: "unfulfilled"` lists what to ship today. 2. `order_action` with `action: "fulfill"`, and `carrier`, `tracking_number` and `tracking_url` when there is a guide. The buyer gets the "tu pedido va en camino" email unless `notify_customer: false`. 3. For partial shipments, send `items` with the order item ids and quantities. ## Tax on shipping If the store charges VAT on shipping (the default in Mexico), leave `settings.tax.applies_to_shipping: true`. With tax-inclusive prices, the shipping price already includes VAT. # Manage the catalog - **One call per product**: `product_upsert` with the whole product. Prices in minor units (`price_amount: 34900` = $349.00). Every product needs ≥1 variant; use a single variant titled "Default" when there are no options. - **Publish** with `status: "active"`; drafts are invisible in the storefront. - **Options** (Talla, Color): `options: [{ name: "Talla", values: ["M", "L"] }]` and one variant per combination with `option_values: { "Talla": "M" }`. - **Physical**: `variants[].physical` (weight_g and dimensions) and `variants[].inventory.on_hand`. Stock with policy `deny` never oversells. - **Digital**: create the product, then `product_file_upload` with the variant id and a local file path. Buyers get an expiring link by email. - **Images**: `media_add` with a URL or a local path; the first image is the one shown in listings. - **Updates**: send `id` (and each variant `id`) to update in place. Variants left out are archived, not deleted; orders keep their snapshots. - **Stock changes**: `inventory_adjust` with a reason. - Use `dry_run: true` when unsure; check results with `product_get`. - **Collections** ("Lo más vendido", "Regalos"): `collection_upsert` with `title` and `product_ids` in display order. The storefront filters with `?collection=`. Remove one with `delete: { id }`; products are kept. - **Discounts**: `discount_upsert`. `kind: "percent"` uses basis points (`value: 1000` = 10%), `"fixed"` uses minor units, `"free_shipping"` uses `value: 0`. Codes are uppercase; `code: null` makes an automatic discount. Limit with `min_subtotal_amount`, `usage_limit`, `per_customer_limit`, `starts_at`/`ends_at`, or `applies_to: { type: "collections", collection_ids: [...] }`. Call it with no `discount` to list the current ones. Used discounts cannot be deleted, only paused (`status: "disabled"`). - **Many products at once**: `products_import` with a CSV file path (our template, Spanish headers or a Shopify export). Always run `dry_run: true` first and show the owner the summary and row errors. `products_bulk` publishes, drafts or archives a list of products; for prices it returns a preview, and you send `confirm: true` only after the owner approves it. # Operate orders - "How is the store doing?": `report_summary` returns sales today, last 7 and 30 days (paid minus refunded; test purchases excluded), a daily series, best sellers, orders waiting to ship and today's appointments. - Customers: `customers_search` with `q` (email, name or phone) for totals; pass `id` for addresses, orders and appointments. - "What do I need to ship?": `orders_search` with `fulfillment_status: "unfulfilled"` (and `"partially_fulfilled"` for mixed orders). - A specific order: `orders_search` with `q: "#1001"` or the customer email, then `order_get` for items, payments and timeline. - Orders flagged `metadata.test_purchase: true` come from `test_purchase`; leave them out of sales reports. - Amounts are minor units; format them for the owner (19990 → $199.90). - Sales outside the store (in person, WhatsApp): `order_create`. With `payment.mode: "paid"` (cash, spei, card terminal) confirm the total with the owner and send `confirm: true`. With `payment.mode: "link"` you get a Stripe link to send; the order opens when it is paid. Orders filter by `channel` (e.g. `whatsapp`). # Report to Sellbase Sellbase is open source and improves through what agents run into. Every report helps the next store. ## When to report - **Bug:** - an error that does not match the docs; - a `hint` that does not work; - `sellbase doctor` or `upgrade` failing; - a component that breaks; - a migration error; - something you had to patch or work around. - **Idea:** something the owner asked for that Sellbase could do natively. - **Extension:** you built something on top of Sellbase for this store, such as a custom field in `metadata`, an integration (shipping, invoicing, WhatsApp), a report, or an admin page with `config.pages`. Describe it so the maintainers can consider shipping it for everyone. Do not report problems in the owner's own code, or questions that the docs answer (`docs_search`). ## How 1. **Collect the facts:** - what you did (tool or command); - what you expected; - what happened (error `code`, `message` and `hint`); - the Sellbase version (`npx sellbase --version`); - the project type. For extensions: what the owner needed and a short outline of what you built. 2. **Draft it** with the MCP tool `feedback_draft` or the CLI: ```bash npx sellbase feedback --kind bug \ --title "product_upsert rejects …" \ --summary "…" --steps "1. … 2. …" --expected "…" \ --details-file ./error.txt --area api --agent "Claude Code" ``` Secrets (API keys, tokens, connection strings) and personal data (emails, phones) are redacted automatically. 3. **Ask the owner:** show the draft and ask _"¿Quieres compartir este reporte con el equipo de Sellbase para que lo mejoren?"_ / _"Do you want to share this report with the Sellbase team?"_ 4. **If they agree,** give them the link (`submit_url`). They review and submit the issue on GitHub. **Never submit it yourself and never send anything without their yes.** ## Never include - customer names, emails, phones or addresses; - order contents or amounts from real sales; - API keys, tokens, webhook secrets or database URLs; - the owner's private code beyond the few lines needed to reproduce the problem. When in doubt, leave it out and describe it in words. # Services and bookings 1. **The service**: `product_upsert` with `type: "service"` and, per variant, `service: { duration_min, location_type: "in_person" | "online", online_meeting_url?, capacity? (group classes), deposit_amount? (minor units), buffer_before_min?, buffer_after_min?, min_notice_min?, booking_window_days? }`. Price in minor units as usual. Set `status: "active"`. 2. **Who and when**: `service_setup` with the resource name, its weekly `hours` (`weekday` 0 = Sunday … 6 = Saturday, `"09:00"`–`"18:00"`, local store time) and `service_product_ids`. Block vacations with `closed`. 3. **Check**: `availability_get` with the variant id must list times. If it is empty, the resource has no hours or is not assigned to the service. 4. **Storefront**: the product page shows `` automatically for services (from `product-detail`). Checkout offers "pay deposit / pay total" when the service has `deposit_amount`. 5. **Verify**: `test_purchase` with the service `variant_ids` books the first free time end to end (payment, confirmation email with a calendar invitation) and frees it again. ## Operating the agenda - Today's appointments: `bookings_search` with today's range (store time zone). - `booking_action`: `complete`, `no_show`, `reschedule` (pick `starts_at` from `availability_get`), `cancel` (reason, `confirm: true`; `refund: true` needs refunds permission). Customers are emailed on reschedule and cancel; reminders go out 24 h and 2 h before automatically. - Deposits: the balance of a deposit order is collected with `order_action` `payment_link`; send the URL to the customer. - Times are instants (UTC) in the API: always present them to the owner in the store time zone. # Set up the store Goal: a successful `test_purchase` (order + email + download) with as few questions to the owner as possible. 1. Call `store_status`. Fix every `fail` item first, then `warn` items the owner cares about. 2. **Store basics** (`store_update_settings`): name, contact email, currency, tax (`settings.tax.rate_bps`: 1600 = 16% IVA, `mode: "inclusive"` in Mexico) and shipping (`settings.shipping.flat_rate_amount`, `free_over_amount`, `pickup.enabled`). Ask only for what you cannot infer from the site. 3. **Payments**: follow the `configure-payments` skill. Use Stripe _test_ keys. 4. **Catalog**: follow `manage-catalog`. Create at least one active product; if the store sells files, also a digital product with its file attached. 5. **Verify**: run `test_purchase`. If a step fails, apply its `hint` and run it again. 6. **Storefront**: follow `add-storefront` so the site can sell. 7. Finish by summarizing to the owner: what is live, what is in test mode, and what remains (e.g. switching Stripe to live keys, connecting Resend). - **Connect other systems** (ERP, sheets, Zapier/Make/n8n): `webhook_setup` with `action: "create"`, the receiving URL and the events it needs. It needs the `webhooks:write` scope, which agent tokens only have if the owner granted it, and `confirm: true` after the owner approved that URL: customer and order data will be sent there. Give the owner the signing secret once so they store it in the receiver, then run `action: "test"`. # Upgrade Sellbase 1. **Read what changed.** Check the changelog of the new `sellbase` package (`node_modules/sellbase/assets/CHANGELOG.md`), especially **"What your agent should review"**. 2. **Dry run first.** Run `npx sellbase upgrade --dry-run`. It lists pending migrations, applies them inside a transaction that is rolled back, and reports errors without touching data. It also lists which files would change. 3. **Apply.** Run `npx sellbase upgrade`. It: - saves a backup of the `sellbase` schema in `.sellbase/backups/` (with `pg_dump` when available); - applies the migrations; - redeploys the Sellbase functions (hosted projects); - refreshes skills, rules and the `CLAUDE.md` section; - runs the doctor. 4. **Components the owner edited are never overwritten.** For each one, `upgrade` writes `.sellbase/updates/.diff` with the upstream change. Merge those diffs by hand, keeping the site's design, then delete them. 5. **Verify.** Run `store_status` and `test_purchase` (test mode). If something fails, follow the hints. To roll back, restore the backup: `psql "$DB_URL" -f .sellbase/backups/.sql`. Then tell the owner. Never edit the Sellbase migrations (listed in `.sellbase/manifest.json`) or `supabase/functions/sellbase-*` by hand: upgrades replace them. # Sellbase API reference Generated from the route contracts (`packages/core/src/api/contracts.ts`). Base URL: `/functions/v1/sellbase-api/v1`. Full schemas: `GET /openapi.json`. Auth: public routes need no token; the rest need `Authorization: Bearer ` with the scope shown. Money is an integer in minor units. Errors have `code`, `message` and `hint`. ## storefront - **GET /storefront/products** (public): List active products. - **GET /storefront/products/:slug** (public): Get an active product with variants and media. - **POST /storefront/carts** (public): Create a cart; keep the returned token in a cookie or localStorage. - **GET /storefront/carts/:token** (public): Get a cart with server-computed totals. - **GET /storefront/availability** (public): Free start times for a service variant. Times are instants (ISO 8601, UTC); show them in `timezone`. Defaults to the next 14 days; at most 62 days per request. - **POST /storefront/carts/:token/items** (public): Add a variant to the cart (adds to the quantity if already present). Services need `booking_slot` with a start time from GET /storefront/availability; each booking is its own line with quantity 1. - **PATCH /storefront/carts/:token/items/:item_id** (public): Set the quantity of a cart line. - **DELETE /storefront/carts/:token/items/:item_id** (public): Remove a cart line. - **POST /storefront/carts/:token/discounts** (public): Apply a discount code. - **DELETE /storefront/carts/:token/discounts/:code** (public): Remove a discount code. - **POST /storefront/carts/:token/shipping-rates** (public): Quote shipping options for the cart. - **POST /storefront/checkout** (public): Start checkout: reserves stock and returns where to pay. Totals are recomputed on the server. Stock is held for 15 minutes. The order is created only when the payment provider confirms payment. - **GET /storefront/checkout/:id** (public): Status of a checkout, for the page the buyer returns to after paying. The success_url gets ?sellbase_checkout=. The buyer may arrive before or after the payment webhook: show "confirming payment" while status is pending and poll every few seconds. - **GET /storefront/collections** (public): Collections with at least one active product, for navigation. - **GET /storefront/collections/:slug** (public): One collection; list its products with GET /storefront/products?collection=. - **POST /storefront/orders/lookup** (public): A buyer checks their order with its number and email. Any mismatch answers NOT_FOUND (it never tells which part was wrong). Limited to 10 attempts per minute per IP. - **GET /storefront/downloads/:grant_token/info** (public): What a download link gives, and whether it still works. - **GET /storefront/store** (public): Store name, logo, currency and the consent checkout requires. - **GET /storefront/downloads/:grant_token** (public): Redirect to a short-lived signed URL for a purchased file. ## catalog - **GET /products** (scope: catalog:read): Search products. - **POST /products/import** (scope: catalog:write): Import products from CSV (our template, Spanish headers or a Shopify export). Rows with the same handle are variants of one product. Existing products (same handle/slug) are updated; variants match by SKU. Use dry_run first to see what would change. - **POST /products/bulk** (scope: catalog:write): Publish, unpublish, archive or reprice many products at once. Price changes are a money action: without confirm=true the response is only a preview of old and new prices. - **GET /products/:id** (scope: catalog:read): Get a product with variants, inventory and specs. - **POST /products** (scope: catalog:write): Create or update a full product of any type in one call. Send `id` to update. Variants with `id` are updated, without are created, missing ones are archived. - **DELETE /products/:id** (scope: catalog:write): Archive a product (orders keep their snapshots). - **POST /products/:id/media** (scope: catalog:write): Add an image to a product from a URL or an uploaded file. Send `url` to reference an existing image, or `file_name` + `content_base64` (max 5 MB) to upload it to the public media bucket. - **PATCH /products/:id/media** (scope: catalog:write): Reorder product images and edit their alt text. The images listed come first, in this order; the first one is the main image. Omitted images keep their relative order after them. - **DELETE /products/:id/media/:media_id** (scope: catalog:write): Remove an image from a product (uploaded files are deleted too). - **POST /variants/:id/digital-assets** (scope: catalog:write): Upload the file buyers receive for a digital variant. The file is stored in a private bucket and only reachable through short-lived signed links issued after payment. Max 10 MB per call. - **GET /inventory** (scope: catalog:read): Stock per variant, with low and out of stock filters. - **POST /inventory/adjust** (scope: catalog:write): Adjust stock by a delta with a reason. - **GET /resources** (scope: catalog:read): People, rooms or equipment that deliver services, with their weekly hours. - **POST /resources** (scope: catalog:write): Create or update a resource, its weekly hours and the services it delivers. rules replace the weekly hours (weekday 0 = Sunday … 6 = Saturday, local times in `timezone`); product_ids replace the services it delivers. - **POST /resources/:id/exceptions** (scope: catalog:write): Block time off (closed) or add extra hours (open) for a resource. - **DELETE /resources/:id/exceptions/:exception_id** (scope: catalog:write): Remove a time-off or extra-hours exception. - **GET /collections** (scope: catalog:read): List collections with their product ids. - **POST /collections** (scope: catalog:write): Create or update a collection and set its products. - **DELETE /collections/:id** (scope: catalog:write): Delete a collection (products are kept). ## orders - **GET /orders** (scope: orders:read): Search orders. - **POST /orders** (scope: orders:write): Record a manual order (sale outside the storefront). payment.mode "paid": money was received in cash, transfer or terminal; needs confirm=true. payment.mode "link": the order waits in pending_payment and the response has a Stripe payment link. Stock is taken now either way. Services must be booked from the storefront. - **GET /orders/:id** (scope: orders:read): Get an order with items, payments and timeline. - **POST /test-purchase** (scope: orders:write): Run a real end-to-end purchase in test mode and report every step. Uses the payment provider test mode (never live keys): cart, checkout with stock reservation, payment, order, digital delivery, confirmation email and download link. Stock is restored afterwards and the order is flagged as a test. - **POST /orders/:id/fulfillments** (scope: orders:write): Mark items as shipped (manual shipping) with carrier and tracking. Omit `items` to ship everything still pending. The customer gets a "your order is on its way" email unless notify_customer is false. - **POST /orders/:id/cancel** (scope: orders:write): Cancel an order, optionally refunding it and restocking items. Money action: requires confirm=true. refund=true also needs the refunds:write scope. Download links are revoked. - **POST /orders/:id/refunds** (scope: refunds:write): Refund all or part of an order through the payment provider. Money action: requires confirm=true and the refunds:write scope. Omit amount to refund everything still refundable. - **POST /orders/:id/payment-link** (scope: orders:write): Create a payment link for the balance still owed on an order. For orders paid with a deposit. Send the URL to the customer; when paid, the order becomes "paid". - **POST /orders/:id/notes** (scope: orders:write): Add an internal note to the order timeline. - **POST /orders/:id/notifications** (scope: orders:write): Send an order email again (confirmation or shipment). - **GET /checkouts/abandoned** (scope: orders:read): Checkouts started with an email but not paid (abandoned carts). One row per cart (its latest checkout). "recovered" means the cart was bought afterwards. - **POST /checkouts/:id/recovery-email** (scope: orders:write): Email the buyer a link that restores their cart. Needs settings.site_url (the storefront) so the link can open the cart. Sends at most once per checkout unless resend is true. - **GET /bookings** (scope: orders:read): Appointments in a date range (the agenda). - **POST /bookings/:id/complete** (scope: orders:write): Mark an appointment as done. - **POST /bookings/:id/no-show** (scope: orders:write): Mark that the customer did not come. - **POST /bookings/:id/cancel** (scope: orders:write): Cancel an appointment, optionally refunding it. refund=true refunds the booked line through the payment provider (needs refunds:write). Requires confirm=true. - **POST /bookings/:id/reschedule** (scope: orders:write): Move an appointment to another free time. Creates a new confirmed booking linked to the old one (status "rescheduled"). ## customers - **GET /customers** (scope: customers:read): Search customers by email or name. - **GET /customers/:id** (scope: customers:read): Get a customer with addresses, orders and bookings. ## discounts - **GET /discounts** (scope: any staff or token): List discounts (codes and automatic). - **POST /discounts** (scope: discounts:write): Create or update a discount. percent: value in basis points (1000 = 10%). fixed: minor units. free_shipping: value 0. code null = automatic discount. - **DELETE /discounts/:id** (scope: discounts:write): Delete an unused discount; used ones are disabled so orders keep their history. ## reports - **GET /reports/summary** (scope: orders:read): Sales today / 7 / 30 days, daily series, top products and pending work. ## team - **GET /team** (scope: settings:write): Team members and their roles. - **POST /team** (scope: settings:write): Invite someone by email (they get a link to set a password). Existing users are added right away. Only owners can add owners. redirect_to must be an allowed redirect URL in Supabase Auth. - **PATCH /team/:user_id** (scope: settings:write): Change the role of a team member. - **DELETE /team/:user_id** (scope: settings:write): Remove someone from the team (the store always keeps one owner). - **GET /tokens** (scope: settings:write): API tokens (for AI agents and integrations), without their secret. - **POST /tokens** (scope: settings:write): Create an API token; the full token is shown only in this response. Defaults to the agent scopes (everything except refunds:write and webhooks:write). A token can only create tokens with scopes it has. - **DELETE /tokens/:id** (scope: settings:write): Revoke an API token right away. - **GET /audit** (scope: settings:write): Activity log: who (staff, AI agents, webhooks) changed what. ## webhooks - **GET /webhooks** (scope: webhooks:write): Outbound webhook endpoints with delivery stats. - **POST /webhooks** (scope: webhooks:write): Subscribe a URL to store events; the signing secret is shown only here. Needs webhooks:write; API tokens must also send confirm=true. Each POST carries Sellbase-Signature: t=,v1=.">. Failed deliveries retry for about a day. - **PATCH /webhooks/:id** (scope: webhooks:write): Change the URL, events or pause an endpoint. - **DELETE /webhooks/:id** (scope: webhooks:write): Delete an endpoint and its secret. - **POST /webhooks/:id/test** (scope: webhooks:write): Send a signed webhook.test event now and report the response. - **GET /webhooks/:id/deliveries** (scope: webhooks:write): Last 50 deliveries of an endpoint. ## store - **GET /store** (scope: any staff or token): Get store settings. - **PATCH /store** (scope: settings:write): Update store settings. - **POST /store/logo** (scope: settings:write): Upload the store logo (PNG, JPG, WebP or SVG, max 2 MB). Used by the admin, emails and GET /storefront/store. ## integrations - **GET /integrations** (scope: integrations:write): List integrations and their status. - **POST /integrations/:provider/connect** (scope: integrations:write): Connect a provider with API keys (stored in Vault) or get a connect URL. Stripe: when the project has a public https URL and no webhook_secret is sent, the webhook endpoint is created in Stripe automatically and its secret stored in Vault. Locally, run `stripe listen --forward-to ` and send its whsec_ as webhook_secret. - **POST /integrations/:provider/test** (scope: integrations:write): Test a provider connection and explain any error. - **POST /notifications/test** (scope: settings:write): Send a sample order email to check the email provider. - **POST /integrations/stripe/live-check** (scope: integrations:write): Real-money check: a minimum charge the owner pays, refunded automatically. Live keys only. Returns a Stripe Checkout URL for the smallest amount Stripe allows (10 MXN, 0.50 USD). When its webhook arrives the charge is refunded and the integration is marked verified. Stripe keeps its fee. Needs confirm=true. ## system - **GET /doctor** (scope: any staff or token): Setup checklist with a hint for each pending item.