API (api-v2)
Storefront, internal & ops
Beyond the dashboard and the till, api-v2 serves merchant-run websites, internal tooling and one-off ops jobs. It also manages per-shop app installs.
Storefront
For merchant-run websites. Shoppers authenticate as Customer-type users.
Table: shop_cart_item (a server-side cart)
Routes, all under /storefront/v1/:
| Group | Routes |
|---|---|
| Auth | send-otp, login, refresh-token, logout, handoff, handoff/exchange |
| Catalogue | get-shop, get-categories, get-products, get-product, search-products, search-product-facet-values, get-product-filters, get-sections, get-section-display-types |
| Cart & checkout | save-cart-item, delete-cart-item, get-cart, place-order |
| Account | get-orders, get-order, get-customer |
- Orders are placed as
pending. There's no online payment gateway. - Pickup, or delivery at the shop's flat shipping rate.
- Promo codes are supported. Orders are auto-routed to a location, with preorder availability status.
requireShopCustomerAccessguards shopper-scoped data.- Swagger at
/storefront/docs. Generate the spec withnpm run docs:openapi:storefront.
Internal API
Mounted only when INTERNAL_SECRET_KEY is set. Docs are public at /internal/docs.
| Route | Purpose |
|---|---|
/internal/shop/v1/search-shops, get-shop-statuses | Shop admin |
/internal/product/v1/search-products, get-product-statuses | Catalogue admin |
/internal/order/v1/search-orders | Order admin |
/internal/user/v1/search-customers | Customer admin |
/internal/merchant/* | The whole merchant API, re-pathed and reusing the merchant handlers, acting as owner (require-internal-acting-as.ts) |
Owner authority over every shop
The internal key grants owner-level access to every shop through /internal/merchant/* (docs/openapi/AUTH.md). That was accepted only for a non-production deployment. See Known gaps.
Ops: catalogue import
| Route | Auth |
|---|---|
POST /internal/ops/v1/import-book-catalogue | Bearer OPS_TOKEN (routes don't exist without it) |
GET job status | Bearer OPS_TOKEN |
- Also available as a script:
scripts/import-book-catalogue.ts. - Dry run by default. Idempotent: products are matched on barcode/ISBN.
- Writes only through the owning services, so cache, search and the ledger stay consistent.
- Skipped rows and warnings are reported.
- Production runs imported 4,356 books (shop 2) and 23,870 (shop 3).
- Companion scripts:
upload-catalogue-to-cloudinary.mjslink-product-images.mjs: SKU-named images go to Cloudinary and are attached to products
Apps
Tables: app, shop_app_install
| Route | Guard |
|---|---|
get-apps, get-installed-apps | any view code |
install-app, uninstall-app | owner |
- Today's apps are
reports,jardeandshelves. - Installation is per shop and is not a plan entitlement.
requireAppInstalledreturnsapp_not_installed(403), and the dashboard hides app nav items untilget-installed-appsconfirms them.
Scripts & seeds
| Kind | Scripts |
|---|---|
| Repair & backfill | backfill-order-payments.mjs (missing ledger payment entries), backfill-search.ts, reindex-products.ts (Algolia), backfill-contributor-roles.ts, module-boundaries-baseline.ts |
| Dev seeds | seed-pos-demo.mjs, seed-dashboard-demo.mjs, seed-pos-sections.mjs, seed-dev-plans.mjs |
Client data (run against prod via .env.prod) | seed-cafe-modifiers.mjs (--assign attaches groups to drinks), seed-cafe-sections.mjs, seed-book-sections.mjs, fix-cafe-drink-products.mjs, add-cafe-missing-drinks.mjs |
| Tooling | reset-test-db.mjs, dump-schema.mjs, wait-for-postgres.mjs, kill-port.sh, OpenAPI generators → docs/openapi/{merchant,storefront,internal}-api.json |
All seed scripts are dry-run by default and write only with --apply.
backfill-search.ts is a hand-kept copy
backfill-search.ts duplicates the search indexer's logic. Change both in the same commit.