Marc’s Inventory API

This document describes authentication, headers, pagination, common responses, and every major endpoint exposed by the backend. Admin endpoints are login‑gated and must be scoped to an account.

Contents

Quickstart

1) Login to obtain a bearer token. 2) Call APIs with Authorization: Bearer <token> and tenant scoping via X-Account-ID or ?account_id=.

# 1) Login (form-encoded)
curl -X POST https://api.example.com/auth/login \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "username=marc@example.com&password=secret&account_id=123"

# => { "access_token": "...", "token_type":"bearer", "expires_in":3600 }

# 2) List products for account 123
curl "https://api.example.com/catalog/products?account_id=123&limit=25" \
  -H "Authorization: Bearer <token>"

Base URL & Versioning

Authentication

Most admin routes require a bearer token obtained via login. On multi‑tenant systems, include an account scope.

MethodPathDescription
POST/auth/loginForm: username, password. Optional ?account_id= to scope login.
GET/auth/meReturns the current user and memberships.
POST/auth/refresh(If enabled) exchange a refresh token for access token.

Response (login)

200 OK
{ "access_token":"<jwt>", "token_type":"bearer", "expires_in":3600 }

Tenant Scoping

Include the tenant scope for account‑specific operations using either:

Headers & Content Types

Pagination

Endpoints that list resources (e.g., /catalog/products) support cursor‑based pagination.

GET /catalog/products?account_id=123&limit=25
{
  "items":[ ... ],
  "next_cursor":"eyJvZmZzZXQiOjI1LCJpZCI6MTAwfQ=="
}

Errors

Errors use standard HTTP codes with a consistent JSON body.

StatusMeaningExample
400Bad request / validation{"detail":"Invalid parameter 'limit'"}
401Unauthorized{"detail":"Not authenticated"}
403Forbidden{"detail":"Insufficient role"}
404Not found{"detail":"Resource not found"}
409Conflict{"detail":"Already exists"}
422Unprocessable entity{"detail":[{"loc":["body","field"],"msg":"required"}]}
429Too many requests{"detail":"Rate limit exceeded"}
500Server error{"detail":"Internal server error"}

Catalog

Public product listing for rendering the store’s catalog or internal stock views.

MethodPathNotes
GET/catalog/productsFilter with q, category; supports limit, cursor.

Request

GET /catalog/products?account_id=123&q=ball&category=Accessories&limit=25

Response

200 OK
{
  "items":[
    {"id":101,"sku":"BALL-5","name":"Size 5 Match Ball","stock_qty":42,"category":"Balls"},
    {"id":102,"sku":"SHIN-GUARD","name":"Shin Guards Small","stock_qty":12,"category":"Protection"}
  ],
  "next_cursor":null,
  "category_facets":{"Balls":18,"Protection":9}
}

Inventory & Orders

MethodPathDescription
POST/admin/inventory/adjustCreate an adjustment movement (positive or negative).
POST/admin/ops/reapply-inventory?order_id=Reapply sale movements for a given order.

Adjust Inventory — Request

POST /admin/inventory/adjust
Headers:
  Authorization: Bearer <token>
  X-Account-ID: 123
Body:
{
  "product_id": 101,
  "delta": -3,
  "reason": "stocktake"   // one of: manual_adjust | stocktake | damage | return
}

Response

200 OK
{
  "ok": true,
  "movement_id": 9876,
  "product_id": 101,
  "new_stock_qty": 39
}

Emails

MethodPathDescription
GET/admin/emails/recipientsList recipients grouped by purpose.
PUT/admin/emails/recipientsReplace recipients for a purpose.
GET/admin/emails/templatesList account/global templates.
GET/admin/emails/templates/{key}Fetch one template (account fallback to global).
PUT/admin/emails/templates/{key}Upsert a template.
GET/admin/emails-uiWeb UI for templates (requires login).

Recipients — PUT Body

{
  "purpose":"ops",
  "emails":["ops@example.com","mgr@example.com"]
}

Template Upsert — PUT Body

{
  "subject":"Your order has shipped",
  "html":"<p>Hello {{ name }}, your order is on the way.</p>",
  "text":"Hello {{ name }}, your order is on the way.",
  "enabled": true,
  "global_template": false
}

Shipping

MethodPathDescription
POST/admin/shipping/labelCreate a label from order data or explicit addresses.
POST/admin/shipping/verify-addressStandardize/validate addresses.

Create Label — Request

POST /admin/shipping/label
Headers:
  Authorization: Bearer <token>
  X-Account-ID: 123
Body:
{
  "order_id": 555,              // optional
  "from_address": { "name":"Shop A", "street1":"123 Main", "city":"Austin","state":"TX","zip":"78701","country":"US" },
  "to_address":   { "name":"Alex", "street1":"77 Park Ave", "city":"New York","state":"NY","zip":"10016","country":"US" },
  "parcels":[{"length":"10","width":"10","height":"10","distance_unit":"in","weight":"2","mass_unit":"lb"}],
  "label_file_type":"PDF"
}

Response

200 OK
{
  "shipment_id":"shp_...",
  "rate_id":"rate_...",
  "label_url":"https://.../label.pdf",
  "tracking_number":"1Z..."
}

Integrations

Secret names/ARNs are stored per account. Supported provider keys: quickbooks, stripe, shippo.

MethodPathDescription
GET/admin/integrations/accounts/{account_id}/{provider}/configRead current secret name.
PUT/admin/integrations/accounts/{account_id}/{provider}/configSet secret name (validated exists).
DELETE/admin/integrations/accounts/{account_id}/{provider}/configClear secret name.
POST/admin/integrations/email/provision-templatesInstall/refresh default SES templates.
GET/admin/quickbooks/connect-urlReturn OAuth2 authorization URL.

PUT Body

{ "secret_name":"arn:aws:secretsmanager:...:secret:quickbooks/prod" }

Payments

MethodPathDescription
POST/admin/payments/intentCreate a payment intent (e.g., Stripe) for an amount.

Request

POST /admin/payments/intent
{
  "account_id":123,
  "amount_cents": 4999,
  "currency": "USD",
  "metadata": {"order_id":555}
}

Response

200 OK
{ "id":"pi_...", "client_secret":"...", "status":"requires_confirmation" }

Feature Flags

MethodPathDescription
GET/admin/flagsList flags (account and global).
GET/admin/flags/{key}Fetch one flag with fallback.
PUT/admin/flags/{key}Upsert flag (set scope to account or global).
DELETE/admin/flags/{key}Soft delete account flag.

PUT Body

{
  "value": true,
  "scope": "account"   // or "global"
}

Webhooks

The system can receive incoming events (e.g., Stripe). Webhooks must be signed and verified.

MethodPathDescription
POST/stripe/webhookStripe events (e.g., payment_intent.succeeded). Verify signature with your endpoint secret.

Example event

POST /stripe/webhook
Headers:
  Stripe-Signature: t=...,v1=...,v0=...
Body:
{
  "id": "evt_...",
  "type": "payment_intent.succeeded",
  "data": { "object": { "id":"pi_...", "amount":4999, "currency":"usd" } }
}

Questions? See the landing page for contact options, or open the Admin page and use the built‑in UI to exercise endpoints after login.