API Reference
The Sellforge REST API gives you programmatic access to everything you can do in the admin — orders, products, customers, inventory, accounting, marketplace, affiliates. Build integrations, custom reports, or replace the admin UI entirely.
Introduction
All requests use JSON bodies with Content-Type: application/json. All responses are JSON and use snake_case field names. Currency amounts are always integers in the smallest unit (cents), never floats. Dates are ISO-8601 in UTC.
The API is organized around resources grouped by domain (Orders, Products, Vendors, etc.). Most endpoints follow REST conventions: GET for read, POST to create, PATCH to update, DELETE to remove.
Authentication
All authenticated endpoints use JWT bearer tokens passed in the Authorization header. Tokens are issued via POST /api/auth/login and carry a role (admin, partner, vendor, affiliate, or customer) that gates which endpoints you can call.
Exchange email + password for a JWT.
| Field | Type | Description |
|---|---|---|
| email required | string | User email |
| password required | string | User password |
| twofa_code optional | string | 6-digit TOTP code if 2FA is enabled |
// Request curl -X POST https://api.sellforge.ai/api/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"mara@shop.at","password":"…"}' // Response { "token": "eyJhbGciOiJIUzI1NiI…", "user": { "user_id": "usr_3kF92", "email": "mara@shop.at", "role": "admin" } }
Register a new customer account. Returns a JWT immediately. Rate-limited to 5 attempts per IP per 15 minutes.
Accept an admin or vendor invitation. Body: { token, password, display_name? }.
Using a token
// All authenticated requests curl https://api.sellforge.ai/api/admin/orders \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiI…"
Two-factor authentication
2FA endpoints under /api/twofa/* let you enroll (TOTP setup with QR), verify, and disable 2FA for the current user. Enrollment generates 10 backup codes returned once at activation time.
Storefront public key (pk_sf_…)
The public Storefront API under /api/store/* (products, cart, checkout, order lookup) is addressed with a publishable key that identifies your shop. Generate and rotate it in your shop settings under Public API / Storefront-Key. Pass it in the Authorization header:
// Storefront request — the key selects your shop (tenant) curl https://api.sellforge.ai/api/store/products \ -H "Authorization: Bearer pk_sf_…"
The key is not a secret — it ships in your browser storefront and only exposes published, shop-scoped catalog/cart/checkout data. Each key is bound to exactly one shop, so it can never read another shop's data. Rotating a key invalidates the previous one immediately.
Domain allowlist (recommended for go-live): in the same settings screen you can restrict the key to your storefront domain(s). When set, requests from other origins are rejected with 403. Leaving it empty allows any origin — convenient during development, but set it before going live.
Errors
Sellforge uses standard HTTP status codes. Error responses share a common shape:
{
"error": "validation_error",
"message": "customer_email must be a valid email",
"details": { "field": "customer_email" }
}| Code | Meaning |
|---|---|
400 | Validation or business-logic error (see error field for type) |
401 | Missing or invalid token / Storefront public key |
403 | Token role lacks permission, or request origin not in the key's domain allowlist |
404 | Resource not found or not visible to your role |
409 | Conflict (duplicate slug, race condition, idempotency) |
422 | Body parses but fails schema validation |
429 | Rate limit exceeded — check Retry-After header |
500 | Internal error — already captured in Sentry, see x-request-id in response |
Pagination
List endpoints accept limit (default 50, max 200) and offset query parameters. The response always includes count (total matching), limit, and offset.
GET /api/admin/orders?limit=50&offset=0
{
"orders": [ … ],
"count": 1247,
"limit": 50,
"offset": 0
}Rate limits
Authenticated endpoints allow 600 requests per minute per token. Public POST endpoints (registration, cooperation, review submission) are limited per IP. Limits are not yet enforced on read-only endpoints but may be in the future.
| Endpoint group | Limit |
|---|---|
| Authenticated reads | 600 / min / token |
| Authenticated writes | 120 / min / token |
| Public POST (newsletter, cooperation, reviews) | 3–5 / 15min / IP |
| Login attempts | 10 / min / IP |
| Image uploads | 10 / 15min / IP |
Idempotency
For endpoints that create resources with side-effects (place order, create invoice, send webhook), you can pass an Idempotency-Key header (any unique string up to 255 chars). If the same key is replayed within 24h, the original response is returned without re-executing the operation.
Versioning
The current API is v1 and is stable. Breaking changes require a new version path (/api/v2/…). Additive changes (new optional fields, new endpoints) ship to v1 without notice. The x-api-version response header indicates the version that served the request.
Orders
An order represents a customer purchase. Orders progress through pending → paid → shipped → completed with branches for cancelled and refunded. Each order automatically gets a SevDesk invoice on creation (if configured) and the invoice number appears in the Stripe PaymentIntent descriptor for auto-reconciliation.
Order object
{
"id": "ord_8aG2k…",
"display_id": 3812,
"customer_id": "usr_3kF92",
"customer_email": "anja.k@protonmail.com",
"customer_name": "Anja Kovacs",
"status": "paid",
"source": "ONLINE",
"subtotal_cents": 17700,
"tax_cents": 3150,
"shipping_cents": 1200,
"discount_cents": 0,
"total_cents": 18900,
"currency": "EUR",
"shipping_address": { … },
"billing_address": { … },
"stripe_payment_intent_id": "pi_3O…8aG",
"tracking_number": "00340…0042",
"tracking_url": "https://nolp.dhl…",
"sevdesk_invoice_id": "sdi_4012",
"sevdesk_invoice_number": "RE-2026-001247",
"sevdesk_sync_status": "synced",
"created_at": "2026-05-13T14:08:02Z",
"paid_at": "2026-05-13T14:08:14Z"
}List orders. Supports ?q=email, ?status=paid, limit, offset.
Retrieve a single order with line items.
Create a manual order (off-platform sale, POS, phone order). Skips cart/payment-session flow.
Update mutable fields: status, tracking_number, tracking_url, notes.
Hand the order to Sendcloud, optionally request a label. Body: { request_label?: boolean }. Returns parcel ID + tracking URL.
All audit events scoped to this order — sevdesk syncs, payments, status changes, refunds. Returns up to 200 entries.
Returns a presigned download URL (5 min validity) for the order's SevDesk invoice PDF stored in R2.
{ "url": "https://r2.../invoices/2026/RE-2026-001247.pdf?X-Amz-Signature=…", "expires_in": 300 }Manually retry SevDesk sync for an order with sevdesk_sync_status: "failed".
Mirror an order cancellation in SevDesk (creates a cancellation invoice).
Customer-facing
Anonymous order lookup with email + display ID for guest customers (e.g. order-status page).
Customer downloads their own invoice PDF. Requires customer JWT.
Products
Products represent sellable items. Each product can have variants (size, color), media (images, PDFs), category, tax class, and per-vendor ownership. Types: STANDARD, ASSEMBLY (calculated from BOM), INTERNAL (not sellable, used in production).
List all products (admin sees everything; partner role sees only own). Filters: status, type, category_id, q.
Get a single product with all variants, media, prices, cost-calculation cache.
Create a product. Body includes name, slug?, sku?, type, base_price_cents, tax_class_id, plus optional variants[] and images[].
{
"name": "Cassette 11-46T",
"slug": "cassette-11-46",
"sku": "SLT-CAS-1146",
"type": "STANDARD",
"status": "published",
"base_price_cents": 5900,
"cost_price_cents": 2240,
"tax_class_id": "tax_20",
"weight_grams": 340
}Partial update. Any field from the create body. Variants are managed separately via /variants sub-routes.
Soft-delete (sets deleted_at). Hard-delete only if no orders reference the product.
Upload an image for a product (multipart). Auto-generates WebP variants (thumb 200, small 480, medium 960, large 1600) and lossless WebP original.
Customers
Customers can be registered users or guests (only email + addresses on the order). Lifecycle data, audit trail, and order history are tracked even for guests where possible.
List customers. Search via ?q=email-or-name.
Customer detail incl. addresses (when available).
All orders by this customer, newest first.
UNION of: actions the user did + actions performed on the user + actions on the user's orders. Categorized timeline.
Categories
Hierarchical product categories with arbitrary nesting depth.
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/categories | Public | Full tree, published only |
GET | /api/admin/categories | Admin | Full tree incl. drafts |
POST | /api/admin/categories | Admin | Create |
PATCH | /api/admin/categories/:id | Admin | Update (incl. reparent, reorder) |
DELETE | /api/admin/categories/:id | Admin | Delete (children become orphans) |
Tax classes
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/tax-classes | Admin | List (e.g. "Standard 20%", "Reduced 10%") |
POST | /api/admin/tax-classes | Admin | Create |
PATCH | /api/admin/tax-classes/:id | Admin | Update rate or name |
DELETE | /api/admin/tax-classes/:id | Admin | Delete (refused if referenced) |
Discounts
Discount codes with three types: fixed (cents off), percent, or free_shipping. Optional max uses, expiry, min order value.
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/discounts | Admin | List |
POST | /api/admin/discounts | Admin | Create |
PATCH | /api/admin/discounts/:id | Admin | Update |
DELETE | /api/admin/discounts/:id | Admin | Delete |
Warehouses
Physical storage locations (and POS counters) with optional sub-locations (bin / shelf / aisle). Stock is tracked per warehouse-location, not per warehouse aggregate.
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/warehouses | Admin | List |
POST | /api/admin/warehouses | Admin | Create |
GET | /api/admin/warehouses/:id | Admin | Detail incl. locations |
PATCH | /api/admin/warehouses/:id | Admin | Update |
POST | /api/admin/warehouses/:id/locations | Admin | Add sub-location |
PATCH | /api/admin/warehouse-locations/:id | Admin | Update / move |
Inventory
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/inventory/by-warehouse/:warehouseId | Admin | Stock levels per location in a warehouse |
POST | /api/admin/inventory/adjust | Admin | Adjust stock (with mandatory reason, audit-logged) |
POST /api/admin/inventory/adjust
{
"variant_id": "var_8aG2k",
"location_id": "loc_WH-AT-A3",
"delta": -3,
"reason": "DAMAGED",
"notes": "Bent during pick"
}Fulfillments
Pick-list-driven fulfillment workflow. An order becomes one or more fulfillments (when items split across warehouses). Each fulfillment moves through requested → picking → packed → shipped → delivered.
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/fulfillments | Admin | List active fulfillments |
GET | /api/admin/fulfillments/:id | Admin | Detail incl. line items + pick list |
POST | /api/admin/fulfillments/:id/start-picking | Admin | Generate pick list, lock items |
POST | /api/admin/pick-list-items/:id/pick | Admin | Mark a pick line as picked |
POST | /api/admin/fulfillments/:id/complete-picking | Admin | Move to "packed" state |
POST | /api/admin/fulfillments/:id/packages | Admin | Define parcel dimensions + weight |
POST | /api/admin/fulfillments/:id/ship | Admin | Create Sendcloud parcel + label |
POST | /api/admin/fulfillments/:id/cancel | Admin | Cancel (restocks reserved stock) |
Returns
RMA workflow with explicit states: requested → approved → received → inspected → refunded (or rejected at any point).
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/returns | Admin | List |
POST | /api/admin/returns | Admin | Create RMA |
POST | /api/returns | Customer | Customer self-service return request |
GET | /api/admin/returns/:id | Admin | Detail |
POST | /api/admin/returns/:id/approve | Admin | Approve request, send return label |
POST | /api/admin/returns/:id/receive | Admin | Mark items as received |
POST | /api/admin/returns/:id/inspect | Admin | Inspection result per item (restock / scrap / damaged) |
POST | /api/admin/returns/:id/refund | Admin | Issue Stripe refund + mirror in SevDesk |
POST | /api/admin/returns/:id/reject | Admin | Reject return |
Transfers
Internal stock movements between warehouses or locations.
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/transfers | Admin | List |
POST | /api/admin/transfers | Admin | Create (draft) |
GET | /api/admin/transfers/:id | Admin | Detail |
POST | /api/admin/transfers/:id/send | Admin | Mark as in-transit |
POST | /api/admin/transfers/:id/cancel | Admin | Cancel |
Suppliers
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/suppliers | Admin | List |
POST | /api/admin/suppliers | Admin | Create |
GET | /api/admin/suppliers/:id | Admin | Detail incl. purchase history |
PATCH | /api/admin/suppliers/:id | Admin | Update |
Invoices
Customer-facing invoices. By default Sellforge uses SevDesk as the source of truth — invoices are created there and the PDF is archived in your R2 bucket. The endpoints below also support a built-in PDF generator (legacy / fallback when SevDesk is not configured).
List. Filters: order_id, customer_id, status, from, to.
Single invoice with line items.
Stream the PDF (from local cache or SevDesk).
Manually create an invoice for an order (idempotent).
Finalize a draft invoice (immutable afterward, retention starts).
Resend the invoice email to the customer (with PDF attachment from R2).
Create a credit note (Storno) for an invoice.
Credit notes
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/credit-notes | Admin | List |
POST | /api/admin/credit-notes/:id/finalize | Admin | Finalize draft (immutable, retention 10 years) |
SevDesk integration
SevDesk is wired in directly. On order creation, Sellforge creates a SevDesk contact (if needed), an invoice, and embeds the invoice number in the Stripe PaymentIntent descriptor (e.g. "Rechnung RE-2026-001247") so bank transactions auto-match in your SevDesk inbox.
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/admin/credentials/test/sevdesk | Admin | Test API token + list SevUser |
POST | /api/admin/orders/:id/sevdesk-retry | Admin | Retry sync for a failed order |
POST | /api/admin/orders/:id/sevdesk-cancel | Admin | Mirror cancel in SevDesk |
GET | /api/admin/orders/:id/invoice-url | Admin | Presigned R2 URL for the SevDesk PDF |
Settings keys
| Key | Description |
|---|---|
sevdesk.api_token | 32-char hex token (secret) |
sevdesk.api_base_url | Default https://my.sevdesk.de/api/v1 |
sevdesk.user_id | SevUser ID (invoice owner) |
sevdesk.tax_rule_id | Default tax rule |
sevdesk.tax_rule.<ISO2> | Per-country override (e.g. sevdesk.tax_rule.DE = "8" for innergemeinschaftlich) |
sevdesk.payment_account_id | CheckAccount used for bookings |
Vendors
Marketplace mode. Vendors operate as sub-tenants with their own product catalog. Sellforge handles split payments via Stripe Connect, per-vendor earnings tracking, and ledger-style statements.
Admin endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/vendors | Admin | List all vendors |
GET | /api/admin/vendors/:id | Admin | Detail incl. Stripe Connect status |
POST | /api/admin/vendors/invite | Admin | Create invitation (email + token) |
GET | /api/admin/vendors/invitations | Admin | List open invitations |
POST | /api/admin/vendors/invitations/:id/revoke | Admin | Revoke an open invitation |
POST | /api/admin/vendors/:id/commission-rule | Admin | Set commission rate (per-vendor override of global) |
GET | /api/admin/vendor-analytics | Admin | Aggregated revenue + earnings per vendor |
Vendor earnings
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/vendor-earnings | Admin | List with filters |
POST | /api/admin/vendor-earnings/release | Admin | Release earnings (move from "pending" to "available") |
Vendor payouts
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/vendor-payouts | Admin | List payouts |
POST | /api/admin/vendor-payouts/:id/process | Admin | Trigger Stripe Connect transfer |
POST | /api/admin/vendor-payouts/:id/mark-paid | Admin | Manual mark for off-platform payouts |
Vendor statements
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/vendor-statements | Admin | List |
GET | /api/admin/vendor-statements/:id | Admin | Detail (PDF) |
POST | /api/admin/vendor-statements/:id/finalize | Admin | Finalize (immutable) |
GET | /api/admin/vendor-invoice-extracts | Admin | Per-vendor extract from customer invoices |
GET | /api/admin/vendor-invoice-extracts/:id/pdf | Admin | Extract PDF |
Vendor self-service portal
Endpoints called by the vendor's own app. Requires role: "vendor" JWT.
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/vendor/me | Vendor | Current vendor profile |
GET | /api/vendor/dashboard | Vendor | Today's revenue + open orders |
GET | /api/vendor/orders | Vendor | Orders touching this vendor's products |
GET | /api/vendor/products | Vendor | Own catalog |
POST | /api/vendor/products | Vendor | Create product (subject to admin approval) |
PATCH | /api/vendor/products/:id | Vendor | Update own product |
GET | /api/vendor/earnings | Vendor | Own earnings (pending + available) |
GET | /api/vendor/ledger | Vendor | Transaction ledger |
GET | /api/vendor/payouts | Vendor | Payout history |
GET | /api/vendor/statements | Vendor | Statements list |
GET | /api/vendor/invoice-extracts | Vendor | Per-invoice extracts |
GET | /api/vendor/invoice-extracts/:id/pdf | Vendor | Extract PDF |
POST | /api/vendor/me/connect/onboarding-link | Vendor | Generate Stripe Connect onboarding URL |
POST | /api/vendor/me/connect/dashboard-link | Vendor | Generate Stripe Express dashboard URL |
POST | /api/vendor/invite/check | Public | Validate invitation token |
POST | /api/vendor/invite/accept | Public | Accept invitation (sets password) |
Affiliates
Affiliate program with referral tracking. Affiliates earn a percentage on orders that originate from their tracked link (cookie + first-touch attribution, configurable window).
Admin endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/affiliates | Admin | List |
GET | /api/admin/affiliates/:id | Admin | Detail |
GET | /api/admin/affiliate-content | Admin | List uploaded creative assets |
POST | /api/admin/affiliate-content | Admin | Approve / reject content |
DELETE | /api/admin/affiliate-content/:id | Admin | Remove |
Earnings & payouts
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/affiliate-earnings | Admin | List |
POST | /api/admin/affiliate-earnings/release | Admin | Release pending earnings |
GET | /api/admin/affiliate-payouts | Admin | List payouts |
Affiliate self-service portal
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/affiliate/me | Affiliate | Profile + tracking link |
GET | /api/affiliate/earnings | Affiliate | Earnings breakdown |
GET | /api/affiliate/payouts | Affiliate | Payout history |
GET | /api/affiliate/content | Affiliate | Approved creative assets (banners, links) |
Production orders
For shops that manufacture their own goods (3D printing, assembly, etc.). A production order consumes raw materials, runs a process, and yields finished products that decrement BOM stock and increment finished-good stock.
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/production-orders | Admin | List |
POST | /api/admin/production-orders | Admin | Create (auto-calculates material needs) |
GET | /api/admin/production-orders/:id | Admin | Detail |
GET | /api/admin/production-orders/:id/material-plan | Admin | BOM + availability check |
POST | /api/admin/production-orders/:id/start | Admin | Reserve materials, move to "in-progress" |
POST | /api/admin/production-orders/:id/complete | Admin | Consume materials, add finished goods to stock |
POST | /api/admin/production-orders/:id/cancel | Admin | Release reserved materials |
Purchase orders
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/purchase-orders | Admin | List POs to suppliers |
POST | /api/admin/purchase-orders | Admin | Create |
POST | /api/admin/purchase-orders/:id/receive | Admin | Mark line items as received, increments stock + updates cost-of-goods |
Settings & credentials
All app settings (Stripe, SevDesk, R2, Resend, Sendcloud credentials) live in a unified key-value table with optional encryption-at-rest for secrets. Changes are audit-logged and invalidate the relevant service-client caches automatically.
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/credentials | Admin | List all settings (secrets masked as "abcd••••wxyz") |
GET | /api/admin/credentials/:key/reveal | Admin | One-shot reveal of plaintext (logged in audit trail) |
PUT | /api/admin/credentials/:key | Admin | Set a value (with is_secret flag) |
POST | /api/admin/credentials/test/stripe | Admin | Connectivity test (balance retrieve) |
POST | /api/admin/credentials/test/sevdesk | Admin | Connectivity test (list SevUser) |
POST | /api/admin/credentials/test/r2 | Admin | HeadBucket on both R2 buckets |
POST | /api/admin/credentials/mode/stripe | Admin | Switch between Stripe test and live mode |
GET | /api/admin/settings | Admin | Generic settings (non-credential) |
PUT | /api/admin/settings/:key | Admin | Set generic setting |
GET | /api/admin/shipping-countries | Admin | Country tax rates + shipping rules |
POST | /api/admin/shipping-methods | Admin | Create shipping method (zone + price) |
Jobs queue
Background job queue implemented in Postgres with FOR UPDATE SKIP LOCKED. Used for emails, invoice generation, SevDesk syncs, CSV imports.
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/jobs | Admin | List recent jobs with status |
GET | /api/admin/jobs/:id | Admin | Detail incl. payload + last error |
POST | /api/admin/jobs/:id/retry | Admin | Re-queue a failed job |
Audit log
Append-only log of every mutation. Auto-derived category from action prefix (sevdesk.invoice.created → "sevdesk"). Filterable by user, action, category, entity type, entity ID.
List audit entries. Query params: user_id, action, category, entity_type, entity_id, limit.
{
"entries": [{
"id": "aud_8aG…",
"action": "sevdesk.invoice.created",
"category": "sevdesk",
"entity_type": "orders",
"entity_id": "ord_3812",
"user_id": null,
"metadata": { "invoice_number": "RE-2026-001247" },
"created_at": "2026-05-13T14:08:22Z"
}]
}Categories with counts — feeds filter dropdowns.
Audit scoped to one order.
Union of user actions, target-of-actions, and actions on user's orders.
CSV import / export
RFC-4180-compliant CSV with UTF-8 BOM (Excel-safe). Imports run as background jobs with per-row error tracking. Exports stream directly.
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/products/template.csv | Admin | Empty template with headers |
GET | /api/admin/products/export.csv | Admin | Export all products |
POST | /api/admin/products/import | Admin | Multipart upload, slug-upsert, async via job queue |
GET | /api/admin/customers/template.csv | Admin | Empty template |
GET | /api/admin/customers/export.csv | Admin | Export all customers |
POST | /api/admin/customers/import?invite=1 | Admin | Email-upsert, optional invite mail |
GET | /api/admin/orders/export.csv | Admin | Orders export (no import — too error-prone) |
GET | /api/admin/imports/:jobId/status | Admin | Poll progress + summary + per-row errors |
{
"id": "job_8aG…",
"type": "csv.import.products",
"status": "done",
"progress": { "processed": 412, "total": 412 },
"summary": {
"total": 412,
"created": 298,
"updated": 112,
"skipped": 2,
"errors": [{ "row": 14, "field": "base_price_cents", "message": "Preis nicht parsbar" }]
}
}Media uploads
All image uploads go through Sharp pipeline → WebP variants (thumb 200×200 cover, small 480, medium 960, large 1600) + lossless WebP original. Animated GIFs preserved as animated WebP. Files stored in R2 (or local /static/uploads in dev).
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/uploads/image | Admin / Partner | Generic image upload |
POST | /api/products/:id/images | Admin / Partner | Image attached to a product |
Users & partners
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/users | Admin | All staff users (admin + partner roles) |
POST | /api/admin/users | Admin | Invite new staff user |
PATCH | /api/admin/users/:id | Admin | Update role / status |
GET | /api/admin/partners | Admin | Retail partners (sub-set of users) |
CMS Pages
Simple page CMS for static content (Imprint, Privacy, About, blog posts). Markdown body + meta fields + draft/published state.
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/pages | Public | Published pages list (slug + title) |
GET | /api/pages/:slug | Public | Single page by slug |
GET | /api/admin/cms-pages | Admin | All pages incl. drafts |
POST | /api/admin/cms-pages | Admin | Create |
PATCH | /api/admin/cms-pages/:id | Admin | Update body / meta / status |
DELETE | /api/admin/cms-pages/:id | Admin | Delete |
Newsletter & subscribers
Double-opt-in newsletter list, scoped to your shop. The same address can subscribe to several shops independently. Subscribers move through pending → active → unsubscribed (plus bounced).
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/newsletter/subscribers | Admin | List. Filters: status, q, limit |
GET | /api/admin/newsletter/subscribers/export | Admin | CSV export (UTF-8 BOM) |
PATCH | /api/admin/newsletter/subscribers/:id | Admin | Set status (active / unsubscribed / bounced) |
GET | /api/admin/newsletter/stats | Admin | Subscriber + campaign KPIs |
POST | /store/newsletter | Public | Storefront signup (double-opt-in, pk_sf_ key) |
GET | /store/newsletter/unsubscribe?token= | Public | One-click unsubscribe (link in every mail) |
Campaigns
A campaign is composed once and sent to an audience via the background job queue — one job per recipient, so it is retryable and never blocks the request. It renders either a built-in template (template_slug + content) or your own raw HTML body (see Custom emails). For opt-in recipients, open/click tracking and an unsubscribe link are added automatically.
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/admin/newsletter/campaigns | Admin | List |
GET | /api/admin/newsletter/campaigns/:id | Admin | Detail |
POST | /api/admin/newsletter/campaigns | Admin | Create (template or custom HTML + attachments + audience) |
PUT | /api/admin/newsletter/campaigns/:id | Admin | Update |
DELETE | /api/admin/newsletter/campaigns/:id | Admin | Delete |
POST | /api/admin/newsletter/campaigns/:id/send | Admin | Resolve the audience and enqueue the send |
GET | /api/admin/newsletter/campaigns/:id/stats | Admin | Opens / clicks / rates (from tracking events) |
GET | /api/admin/newsletter/templates | Admin | Built-in campaign + automatic templates |
POST | /api/admin/newsletter/render | Admin | Live HTML preview of a template + content |
Custom emails & attachments
Beyond the built-in templates, a campaign can carry a raw HTML body_html and file attachments, sent to one of three audiences. When body_html is set the template is ignored. The sender is the platform default address; an unsubscribe footer is appended automatically for opt-in subscribers only (free lists and “all customers” are sent as-is).
Audiences
The audience is stored on the campaign as audience_filter and resolved at send time (always scoped to your shop).
audience_filter.type | Recipients |
|---|---|
subscribers | Active newsletter opt-ins of the shop. Gets tracking + unsubscribe footer. |
list | A free list of addresses in emails[] (trimmed, lower-cased, de-duplicated, invalid ones dropped). |
all_customers | Every registered customer of the shop, unfiltered. Requires confirm_all_customers: true on send. Obtaining a lawful basis / consent for the mailing remains the sender's responsibility. |
Create a custom-HTML campaign. body_html up to 512 KB. attachments[] reference objects returned by the attachment upload below.
| Field | Type | Description |
|---|---|---|
| name required | string | Internal campaign name |
| subject required | string | Email subject |
| template_slug required | string | Template id, or any value (e.g. "custom") when body_html is set |
| body_html optional | string | Raw HTML body. If present, the template is ignored |
| audience_filter optional | object | { type, emails? } — see Audiences. Empty = subscribers |
| attachments optional | array | { key, filename, size, contentType }[] — keys must belong to your shop |
// custom HTML for a free email list, with one attachment { "name": "Summer sale", "template_slug": "custom", "subject": "20% off this weekend", "body_html": "<h1>Summer sale</h1><p>20% off this weekend…</p>", "audience_filter": { "type": "list", "emails": ["a@example.com", "b@example.com"] }, "attachments": [{ "key": "sellforge/tenant/…/documents/newsletter/att_…-flyer.pdf", "filename": "flyer.pdf", "size": 20480, "contentType": "application/pdf" }] }
Resolve the campaign's audience and enqueue one send job per recipient. A non-subscribers audience requires body_html to be set. Sending to all_customers requires the confirmation flag, otherwise the request returns 400.
// Request body (only needed for all_customers) { "confirm_all_customers": true } // Response { "ok": true, "recipients": 128, "audience": "list" }
Multipart upload of a single file (field file) → stored in R2 under your shop's tenant prefix. Max 10 MB. Allowed types: PDF, PNG/JPEG/GIF/WebP, TXT/CSV, ZIP, Word, Excel. Returns a descriptor to place into a campaign's attachments[].
// Response { "key": "sellforge/tenant/…/documents/newsletter/att_…-flyer.pdf", "filename": "flyer.pdf", "size": 20480, "contentType": "application/pdf" }
Public catalog
Read-only endpoints meant for the storefront frontend or any public consumer. No authentication required. Cache-friendly with Cache-Control: public, max-age=60.
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/store/products | Public | Published products with filtering: ?category, ?fitment, ?q, ?sort |
GET | /api/products/:id | Public | Single product detail (by id or slug) |
GET | /api/products/:id/media | Public | All media assets for a product |
GET | /api/categories | Public | Category tree |
POST | /api/store/discounts/validate | Public | Validate a discount code against a cart total |
Vehicle fitment
For shops with vehicle-specific products (motorcycle parts, bicycle parts). Tree: Make → Model → Year-Group → fitted products.
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/vehicle-makes | Public | All makes |
GET | /api/vehicle-makes/:makeId/models | Public | Models for a make |
GET | /api/vehicle-models/:modelId/year-groups | Public | Year groups for a model |
GET | /api/vehicle-fitment?make=&model=&year= | Public | Products fitting a specific vehicle |
GET | /api/units | Public | Units of measurement (cm, kg, etc.) |
Shipping
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/shipping-countries | Public | Allowed destination countries with VAT rates |
GET | /api/shipping-methods?country=AT&total_cents=18900 | Public | Available methods + prices for a cart |
Tracking
Anonymous tracking lookup via the parcel number on the order. Returns normalized status events from Sendcloud.
{
"status": "in_transit",
"carrier": "dhl",
"events": [
{ "at": "2026-05-13T17:22Z", "location": "Wien Hub", "description": "Eingang im Versandzentrum" }
],
"tracking_url": "https://nolp.dhl…"
}Outgoing webhooks
Sellforge can deliver event webhooks to your server. Subscribe by configuring an endpoint URL + secret in /api/admin/settings. Events are signed with HMAC-SHA256 over the raw body using your endpoint secret. Retries: 5 attempts with exponential backoff.
changelog for additions.
Event format
POST https://your-server.example/webhooks/sellforge
X-Sellforge-Signature: t=1620000000,v1=5257a869…
X-Sellforge-Event-Id: evt_8aG2k…
Content-Type: application/json
{
"id": "evt_8aG2k…",
"type": "order.paid",
"created_at": "2026-05-13T14:08:14Z",
"data": { /* full order object */ }
}Event types
| Type | When |
|---|---|
order.created | Customer placed order (pre-payment) |
order.paid | Stripe confirmed payment |
order.shipped | Tracking number assigned |
order.delivered | Carrier confirmed delivery |
order.cancelled | Cancelled (manual or payment-failed) |
order.refunded | Refund processed (full or partial) |
customer.created | New customer registered |
customer.email_verified | Email confirmed |
product.created | New product published |
product.stock_low | Variant fell below threshold |
invoice.finalized | SevDesk invoice ready |
invoice.sent | Customer email delivered |
return.requested | RMA opened |
return.refunded | Refund issued for a return |
vendor.payout_processed | Stripe Connect transfer completed |
affiliate.earning_released | Earning moved to "available" |
Signature verification (Node.js example)
import crypto from "node:crypto"; function verify(rawBody, header, secret) { const [ts, sig] = header.split(",").map(s => s.split("=")[1]); const payload = `${ts}.${rawBody}`; const expected = crypto.createHmac("sha256", secret).update(payload).digest("hex"); if (Math.abs(Date.now()/1000 - +ts) > 300) throw new Error("stale"); return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)); }
Incoming receivers
Endpoints where third-party services notify Sellforge. You shouldn't call these directly — they're listed for completeness.
| Method | Path | From | Description |
|---|---|---|---|
POST | /api/webhooks/stripe | Stripe | Payment intents, refunds, disputes, Connect events |
POST | /api/webhooks/sendcloud | Sendcloud | Parcel status updates (in transit, delivered, returned) |
Changelog
2026-07-26 — v1.5
- Custom email campaigns. Newsletter campaigns can now carry a raw HTML
body_htmland file attachments instead of only a built-in template. Send to a selectable audience (audience_filter.type):subscribers(newsletter opt-ins),list(a free email list), orall_customers(every registered customer — requires an explicitconfirm_all_customers: true; obtaining consent stays the sender's responsibility). An unsubscribe footer is auto-appended for opt-in recipients only. New endpoint:POST /admin/newsletter/attachments(multipart → R2, max 10 MB). Sending is fan-out via the job queue (newsletter.send_custom_one); audit actionnewsletter.custom_send. See Custom emails & attachments.
2026-05-13 — v1.4
- SevDesk integration shipped. Auto-invoicing, R2 PDF archive (10-year retention, GoBD-ready), Stripe descriptor matching for auto-reconciliation, per-country tax-rule mapping, retry scheduler. New endpoints:
/orders/:id/sevdesk-retry,/orders/:id/sevdesk-cancel,/orders/:id/invoice-url,/credentials/test/sevdesk. - Cloudflare R2 native storage. Settings UI for credentials + bucket configuration. Helper:
uploadInvoicePdfToR2. Public-bucket CDN URLs + presigned download URLs for private documents. - Image pipeline: WebP-only. Originals re-encoded as lossless WebP. Animated GIF support preserved (animated WebP output). Disk-binary fallback removed.
- CSV import / export. Products, customers, orders. RFC-4180 with UTF-8 BOM. Slug-upsert (products), email-upsert (customers, optional invite mail). Job-queue-async with per-row error tracking. New routes under
/admin/{entity}/{template,export,import}.csv+/admin/imports/:jobId/status. - Audit log: categories + scopes. Auto-derived category from action prefix. Color-coded pills in admin UI. New filter
?category=. New endpoints:/admin/audit-log/categories,/admin/orders/:id/audit-log,/admin/customers/:id/audit-log.
2026-04-22 — v1.3
- Manufacturing module: production orders, BOM, material-plan endpoints, finished-goods stock updates on completion.
- Pick list workflow with per-line picking, package definition, ship from pick list.
- Returns RMA lifecycle endpoints.
2026-03-15 — v1.2
- Marketplace mode: vendors, Stripe Connect onboarding, per-vendor earnings, payouts, statements.
- Affiliate program: tracking links, earnings, payouts, creative content approval.
2026-02-10 — v1.1
- Multi-warehouse inventory with sub-locations.
- Atomic stock reservation in checkout.
- Tax classes + shipping-country VAT.
2026-01-12 — v1.0
- Initial release — orders, products, customers, cart, checkout, Stripe payments, Sendcloud labels, base invoicing.
SDKs
Official SDKs are planned for v2. Until then, the API is plain REST + JSON — any HTTP client works.
| Language | Status | Note |
|---|---|---|
| JavaScript / TypeScript | Planned Q3 2026 | Type definitions ship alongside the API as @sellforge/types |
| Python | Planned Q4 2026 | — |
| PHP | Community | Contribute on GitHub |
| Go | Community | Contribute on GitHub |
Generated OpenAPI spec
The OpenAPI 3.1 spec is available at https://api.sellforge.ai/openapi.json. Use it to generate clients in your language of choice with openapi-generator or Stainless.
Support
Got a question that isn't answered here?
- Email hello@sellforge.ai — typical response within 12h on Studio/Atelier, 1h on Forge tier
- Status page: status.sellforge.ai
- Changelog RSS: sellforge.ai/changelog.rss
- Discord community (Atelier+): discord.gg/sellforge