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.

API v1 · stableLast updated · 2026-07-26Base URL · https://api.sellforge.ai

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.

Scope of this document. This reference covers all endpoints intended for external integrations — admin API, public catalog, customer-facing endpoints, vendor + affiliate portals, and webhook delivery. Internal endpoints used only between the hosted shop frontend and the Sellforge backend (cart, checkout-session, real-time notifications) are not documented here; they are implementation details and may change without notice.

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.

POST/api/auth/loginPublic

Exchange email + password for a JWT.

FieldTypeDescription
email requiredstringUser email
password requiredstringUser password
twofa_code optionalstring6-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"
  }
}
POST/api/auth/registerPublic

Register a new customer account. Returns a JWT immediately. Rate-limited to 5 attempts per IP per 15 minutes.

POST/api/auth/invite/acceptPublic

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" }
}
CodeMeaning
400Validation or business-logic error (see error field for type)
401Missing or invalid token / Storefront public key
403Token role lacks permission, or request origin not in the key's domain allowlist
404Resource not found or not visible to your role
409Conflict (duplicate slug, race condition, idempotency)
422Body parses but fails schema validation
429Rate limit exceeded — check Retry-After header
500Internal 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 groupLimit
Authenticated reads600 / min / token
Authenticated writes120 / min / token
Public POST (newsletter, cooperation, reviews)3–5 / 15min / IP
Login attempts10 / min / IP
Image uploads10 / 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"
}
GET/api/admin/ordersAdmin

List orders. Supports ?q=email, ?status=paid, limit, offset.

GET/api/admin/orders/:idAdmin

Retrieve a single order with line items.

POST/api/admin/ordersAdmin

Create a manual order (off-platform sale, POS, phone order). Skips cart/payment-session flow.

PATCH/api/admin/orders/:idAdmin

Update mutable fields: status, tracking_number, tracking_url, notes.

POST/api/admin/orders/:id/shipAdmin

Hand the order to Sendcloud, optionally request a label. Body: { request_label?: boolean }. Returns parcel ID + tracking URL.

GET/api/admin/orders/:id/audit-logAdmin

All audit events scoped to this order — sevdesk syncs, payments, status changes, refunds. Returns up to 200 entries.

GET/api/admin/orders/:id/invoice-urlAdmin

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 }
POST/api/admin/orders/:id/sevdesk-retryAdmin

Manually retry SevDesk sync for an order with sevdesk_sync_status: "failed".

POST/api/admin/orders/:id/sevdesk-cancelAdmin

Mirror an order cancellation in SevDesk (creates a cancellation invoice).

Customer-facing

GET/api/customer/order-lookup?email=&display_id=Public

Anonymous order lookup with email + display ID for guest customers (e.g. order-status page).

GET/api/orders/:orderId/invoiceCustomer

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).

GET/api/admin/productsAdmin

List all products (admin sees everything; partner role sees only own). Filters: status, type, category_id, q.

GET/api/admin/products/:idAdmin

Get a single product with all variants, media, prices, cost-calculation cache.

POST/api/admin/productsAdmin

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
}
PATCH/api/admin/products/:idAdmin

Partial update. Any field from the create body. Variants are managed separately via /variants sub-routes.

DELETE/api/admin/products/:idAdmin

Soft-delete (sets deleted_at). Hard-delete only if no orders reference the product.

POST/api/admin/products/:id/imagesAdmin

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.

GET/api/admin/customersAdmin

List customers. Search via ?q=email-or-name.

GET/api/admin/customers/:idAdmin

Customer detail incl. addresses (when available).

GET/api/admin/customers/:id/ordersAdmin

All orders by this customer, newest first.

GET/api/admin/customers/:id/audit-logAdmin

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.

MethodPathAuthDescription
GET/api/categoriesPublicFull tree, published only
GET/api/admin/categoriesAdminFull tree incl. drafts
POST/api/admin/categoriesAdminCreate
PATCH/api/admin/categories/:idAdminUpdate (incl. reparent, reorder)
DELETE/api/admin/categories/:idAdminDelete (children become orphans)

Tax classes

MethodPathAuthDescription
GET/api/admin/tax-classesAdminList (e.g. "Standard 20%", "Reduced 10%")
POST/api/admin/tax-classesAdminCreate
PATCH/api/admin/tax-classes/:idAdminUpdate rate or name
DELETE/api/admin/tax-classes/:idAdminDelete (refused if referenced)

Discounts

Discount codes with three types: fixed (cents off), percent, or free_shipping. Optional max uses, expiry, min order value.

MethodPathAuthDescription
GET/api/admin/discountsAdminList
POST/api/admin/discountsAdminCreate
PATCH/api/admin/discounts/:idAdminUpdate
DELETE/api/admin/discounts/:idAdminDelete

Warehouses

Physical storage locations (and POS counters) with optional sub-locations (bin / shelf / aisle). Stock is tracked per warehouse-location, not per warehouse aggregate.

MethodPathAuthDescription
GET/api/admin/warehousesAdminList
POST/api/admin/warehousesAdminCreate
GET/api/admin/warehouses/:idAdminDetail incl. locations
PATCH/api/admin/warehouses/:idAdminUpdate
POST/api/admin/warehouses/:id/locationsAdminAdd sub-location
PATCH/api/admin/warehouse-locations/:idAdminUpdate / move

Inventory

MethodPathAuthDescription
GET/api/admin/inventory/by-warehouse/:warehouseIdAdminStock levels per location in a warehouse
POST/api/admin/inventory/adjustAdminAdjust 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.

MethodPathAuthDescription
GET/api/admin/fulfillmentsAdminList active fulfillments
GET/api/admin/fulfillments/:idAdminDetail incl. line items + pick list
POST/api/admin/fulfillments/:id/start-pickingAdminGenerate pick list, lock items
POST/api/admin/pick-list-items/:id/pickAdminMark a pick line as picked
POST/api/admin/fulfillments/:id/complete-pickingAdminMove to "packed" state
POST/api/admin/fulfillments/:id/packagesAdminDefine parcel dimensions + weight
POST/api/admin/fulfillments/:id/shipAdminCreate Sendcloud parcel + label
POST/api/admin/fulfillments/:id/cancelAdminCancel (restocks reserved stock)

Returns

RMA workflow with explicit states: requested → approved → received → inspected → refunded (or rejected at any point).

MethodPathAuthDescription
GET/api/admin/returnsAdminList
POST/api/admin/returnsAdminCreate RMA
POST/api/returnsCustomerCustomer self-service return request
GET/api/admin/returns/:idAdminDetail
POST/api/admin/returns/:id/approveAdminApprove request, send return label
POST/api/admin/returns/:id/receiveAdminMark items as received
POST/api/admin/returns/:id/inspectAdminInspection result per item (restock / scrap / damaged)
POST/api/admin/returns/:id/refundAdminIssue Stripe refund + mirror in SevDesk
POST/api/admin/returns/:id/rejectAdminReject return

Transfers

Internal stock movements between warehouses or locations.

MethodPathAuthDescription
GET/api/admin/transfersAdminList
POST/api/admin/transfersAdminCreate (draft)
GET/api/admin/transfers/:idAdminDetail
POST/api/admin/transfers/:id/sendAdminMark as in-transit
POST/api/admin/transfers/:id/cancelAdminCancel

Suppliers

MethodPathAuthDescription
GET/api/admin/suppliersAdminList
POST/api/admin/suppliersAdminCreate
GET/api/admin/suppliers/:idAdminDetail incl. purchase history
PATCH/api/admin/suppliers/:idAdminUpdate

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).

GET/api/admin/invoicesAdmin

List. Filters: order_id, customer_id, status, from, to.

GET/api/admin/invoices/:idAdmin

Single invoice with line items.

GET/api/admin/invoices/:id/pdfAdmin

Stream the PDF (from local cache or SevDesk).

POST/api/admin/orders/:orderId/invoiceAdmin

Manually create an invoice for an order (idempotent).

POST/api/admin/invoices/:id/finalizeAdmin

Finalize a draft invoice (immutable afterward, retention starts).

POST/api/admin/invoices/:id/send-emailAdmin

Resend the invoice email to the customer (with PDF attachment from R2).

POST/api/admin/invoices/:id/credit-noteAdmin

Create a credit note (Storno) for an invoice.

Credit notes

MethodPathAuthDescription
GET/api/admin/credit-notesAdminList
POST/api/admin/credit-notes/:id/finalizeAdminFinalize 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.

MethodPathAuthDescription
POST/api/admin/credentials/test/sevdeskAdminTest API token + list SevUser
POST/api/admin/orders/:id/sevdesk-retryAdminRetry sync for a failed order
POST/api/admin/orders/:id/sevdesk-cancelAdminMirror cancel in SevDesk
GET/api/admin/orders/:id/invoice-urlAdminPresigned R2 URL for the SevDesk PDF

Settings keys

KeyDescription
sevdesk.api_token32-char hex token (secret)
sevdesk.api_base_urlDefault https://my.sevdesk.de/api/v1
sevdesk.user_idSevUser ID (invoice owner)
sevdesk.tax_rule_idDefault tax rule
sevdesk.tax_rule.<ISO2>Per-country override (e.g. sevdesk.tax_rule.DE = "8" for innergemeinschaftlich)
sevdesk.payment_account_idCheckAccount used for bookings
Auto-reconciliation. When Stripe pays out to your bank, the transaction memo carries the invoice number → SevDesk matches it automatically. Manual reconciliation only needed for non-Stripe receipts (manual transfers, cash).

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

MethodPathAuthDescription
GET/api/admin/vendorsAdminList all vendors
GET/api/admin/vendors/:idAdminDetail incl. Stripe Connect status
POST/api/admin/vendors/inviteAdminCreate invitation (email + token)
GET/api/admin/vendors/invitationsAdminList open invitations
POST/api/admin/vendors/invitations/:id/revokeAdminRevoke an open invitation
POST/api/admin/vendors/:id/commission-ruleAdminSet commission rate (per-vendor override of global)
GET/api/admin/vendor-analyticsAdminAggregated revenue + earnings per vendor

Vendor earnings

MethodPathAuthDescription
GET/api/admin/vendor-earningsAdminList with filters
POST/api/admin/vendor-earnings/releaseAdminRelease earnings (move from "pending" to "available")

Vendor payouts

MethodPathAuthDescription
GET/api/admin/vendor-payoutsAdminList payouts
POST/api/admin/vendor-payouts/:id/processAdminTrigger Stripe Connect transfer
POST/api/admin/vendor-payouts/:id/mark-paidAdminManual mark for off-platform payouts

Vendor statements

MethodPathAuthDescription
GET/api/admin/vendor-statementsAdminList
GET/api/admin/vendor-statements/:idAdminDetail (PDF)
POST/api/admin/vendor-statements/:id/finalizeAdminFinalize (immutable)
GET/api/admin/vendor-invoice-extractsAdminPer-vendor extract from customer invoices
GET/api/admin/vendor-invoice-extracts/:id/pdfAdminExtract PDF

Vendor self-service portal

Endpoints called by the vendor's own app. Requires role: "vendor" JWT.

MethodPathAuthDescription
GET/api/vendor/meVendorCurrent vendor profile
GET/api/vendor/dashboardVendorToday's revenue + open orders
GET/api/vendor/ordersVendorOrders touching this vendor's products
GET/api/vendor/productsVendorOwn catalog
POST/api/vendor/productsVendorCreate product (subject to admin approval)
PATCH/api/vendor/products/:idVendorUpdate own product
GET/api/vendor/earningsVendorOwn earnings (pending + available)
GET/api/vendor/ledgerVendorTransaction ledger
GET/api/vendor/payoutsVendorPayout history
GET/api/vendor/statementsVendorStatements list
GET/api/vendor/invoice-extractsVendorPer-invoice extracts
GET/api/vendor/invoice-extracts/:id/pdfVendorExtract PDF
POST/api/vendor/me/connect/onboarding-linkVendorGenerate Stripe Connect onboarding URL
POST/api/vendor/me/connect/dashboard-linkVendorGenerate Stripe Express dashboard URL
POST/api/vendor/invite/checkPublicValidate invitation token
POST/api/vendor/invite/acceptPublicAccept 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

MethodPathAuthDescription
GET/api/admin/affiliatesAdminList
GET/api/admin/affiliates/:idAdminDetail
GET/api/admin/affiliate-contentAdminList uploaded creative assets
POST/api/admin/affiliate-contentAdminApprove / reject content
DELETE/api/admin/affiliate-content/:idAdminRemove

Earnings & payouts

MethodPathAuthDescription
GET/api/admin/affiliate-earningsAdminList
POST/api/admin/affiliate-earnings/releaseAdminRelease pending earnings
GET/api/admin/affiliate-payoutsAdminList payouts

Affiliate self-service portal

MethodPathAuthDescription
GET/api/affiliate/meAffiliateProfile + tracking link
GET/api/affiliate/earningsAffiliateEarnings breakdown
GET/api/affiliate/payoutsAffiliatePayout history
GET/api/affiliate/contentAffiliateApproved 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.

MethodPathAuthDescription
GET/api/admin/production-ordersAdminList
POST/api/admin/production-ordersAdminCreate (auto-calculates material needs)
GET/api/admin/production-orders/:idAdminDetail
GET/api/admin/production-orders/:id/material-planAdminBOM + availability check
POST/api/admin/production-orders/:id/startAdminReserve materials, move to "in-progress"
POST/api/admin/production-orders/:id/completeAdminConsume materials, add finished goods to stock
POST/api/admin/production-orders/:id/cancelAdminRelease reserved materials

Purchase orders

MethodPathAuthDescription
GET/api/admin/purchase-ordersAdminList POs to suppliers
POST/api/admin/purchase-ordersAdminCreate
POST/api/admin/purchase-orders/:id/receiveAdminMark 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.

MethodPathAuthDescription
GET/api/admin/credentialsAdminList all settings (secrets masked as "abcd••••wxyz")
GET/api/admin/credentials/:key/revealAdminOne-shot reveal of plaintext (logged in audit trail)
PUT/api/admin/credentials/:keyAdminSet a value (with is_secret flag)
POST/api/admin/credentials/test/stripeAdminConnectivity test (balance retrieve)
POST/api/admin/credentials/test/sevdeskAdminConnectivity test (list SevUser)
POST/api/admin/credentials/test/r2AdminHeadBucket on both R2 buckets
POST/api/admin/credentials/mode/stripeAdminSwitch between Stripe test and live mode
GET/api/admin/settingsAdminGeneric settings (non-credential)
PUT/api/admin/settings/:keyAdminSet generic setting
GET/api/admin/shipping-countriesAdminCountry tax rates + shipping rules
POST/api/admin/shipping-methodsAdminCreate 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.

MethodPathAuthDescription
GET/api/admin/jobsAdminList recent jobs with status
GET/api/admin/jobs/:idAdminDetail incl. payload + last error
POST/api/admin/jobs/:id/retryAdminRe-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.

GET/api/admin/audit-logAdmin

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"
  }]
}
GET/api/admin/audit-log/categoriesAdmin

Categories with counts — feeds filter dropdowns.

GET/api/admin/orders/:id/audit-logAdmin

Audit scoped to one order.

GET/api/admin/customers/:id/audit-logAdmin

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.

MethodPathAuthDescription
GET/api/admin/products/template.csvAdminEmpty template with headers
GET/api/admin/products/export.csvAdminExport all products
POST/api/admin/products/importAdminMultipart upload, slug-upsert, async via job queue
GET/api/admin/customers/template.csvAdminEmpty template
GET/api/admin/customers/export.csvAdminExport all customers
POST/api/admin/customers/import?invite=1AdminEmail-upsert, optional invite mail
GET/api/admin/orders/export.csvAdminOrders export (no import — too error-prone)
GET/api/admin/imports/:jobId/statusAdminPoll 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).

MethodPathAuthDescription
POST/api/uploads/imageAdmin / PartnerGeneric image upload
POST/api/products/:id/imagesAdmin / PartnerImage attached to a product

Users & partners

MethodPathAuthDescription
GET/api/admin/usersAdminAll staff users (admin + partner roles)
POST/api/admin/usersAdminInvite new staff user
PATCH/api/admin/users/:idAdminUpdate role / status
GET/api/admin/partnersAdminRetail 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.

MethodPathAuthDescription
GET/api/pagesPublicPublished pages list (slug + title)
GET/api/pages/:slugPublicSingle page by slug
GET/api/admin/cms-pagesAdminAll pages incl. drafts
POST/api/admin/cms-pagesAdminCreate
PATCH/api/admin/cms-pages/:idAdminUpdate body / meta / status
DELETE/api/admin/cms-pages/:idAdminDelete

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).

MethodPathAuthDescription
GET/api/admin/newsletter/subscribersAdminList. Filters: status, q, limit
GET/api/admin/newsletter/subscribers/exportAdminCSV export (UTF-8 BOM)
PATCH/api/admin/newsletter/subscribers/:idAdminSet status (active / unsubscribed / bounced)
GET/api/admin/newsletter/statsAdminSubscriber + campaign KPIs
POST/store/newsletterPublicStorefront signup (double-opt-in, pk_sf_ key)
GET/store/newsletter/unsubscribe?token=PublicOne-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.

MethodPathAuthDescription
GET/api/admin/newsletter/campaignsAdminList
GET/api/admin/newsletter/campaigns/:idAdminDetail
POST/api/admin/newsletter/campaignsAdminCreate (template or custom HTML + attachments + audience)
PUT/api/admin/newsletter/campaigns/:idAdminUpdate
DELETE/api/admin/newsletter/campaigns/:idAdminDelete
POST/api/admin/newsletter/campaigns/:id/sendAdminResolve the audience and enqueue the send
GET/api/admin/newsletter/campaigns/:id/statsAdminOpens / clicks / rates (from tracking events)
GET/api/admin/newsletter/templatesAdminBuilt-in campaign + automatic templates
POST/api/admin/newsletter/renderAdminLive 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.typeRecipients
subscribersActive newsletter opt-ins of the shop. Gets tracking + unsubscribe footer.
listA free list of addresses in emails[] (trimmed, lower-cased, de-duplicated, invalid ones dropped).
all_customersEvery 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.
POST/api/admin/newsletter/campaignsAdmin

Create a custom-HTML campaign. body_html up to 512 KB. attachments[] reference objects returned by the attachment upload below.

FieldTypeDescription
name requiredstringInternal campaign name
subject requiredstringEmail subject
template_slug requiredstringTemplate id, or any value (e.g. "custom") when body_html is set
body_html optionalstringRaw HTML body. If present, the template is ignored
audience_filter optionalobject{ type, emails? } — see Audiences. Empty = subscribers
attachments optionalarray{ 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" }]
}
POST/api/admin/newsletter/campaigns/:id/sendAdmin

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" }
POST/api/admin/newsletter/attachmentsAdmin

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.

MethodPathAuthDescription
GET/api/store/productsPublicPublished products with filtering: ?category, ?fitment, ?q, ?sort
GET/api/products/:idPublicSingle product detail (by id or slug)
GET/api/products/:id/mediaPublicAll media assets for a product
GET/api/categoriesPublicCategory tree
POST/api/store/discounts/validatePublicValidate 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.

MethodPathAuthDescription
GET/api/vehicle-makesPublicAll makes
GET/api/vehicle-makes/:makeId/modelsPublicModels for a make
GET/api/vehicle-models/:modelId/year-groupsPublicYear groups for a model
GET/api/vehicle-fitment?make=&model=&year=PublicProducts fitting a specific vehicle
GET/api/unitsPublicUnits of measurement (cm, kg, etc.)

Shipping

MethodPathAuthDescription
GET/api/shipping-countriesPublicAllowed destination countries with VAT rates
GET/api/shipping-methods?country=AT&total_cents=18900PublicAvailable methods + prices for a cart

Tracking

GET/api/tracking/:trackingNumberPublic

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.

Status: Beta. Outgoing webhook events ship with v1.1 (planned Q3 2026). Endpoint signature stable; event coverage still expanding. Subscribe to 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

TypeWhen
order.createdCustomer placed order (pre-payment)
order.paidStripe confirmed payment
order.shippedTracking number assigned
order.deliveredCarrier confirmed delivery
order.cancelledCancelled (manual or payment-failed)
order.refundedRefund processed (full or partial)
customer.createdNew customer registered
customer.email_verifiedEmail confirmed
product.createdNew product published
product.stock_lowVariant fell below threshold
invoice.finalizedSevDesk invoice ready
invoice.sentCustomer email delivered
return.requestedRMA opened
return.refundedRefund issued for a return
vendor.payout_processedStripe Connect transfer completed
affiliate.earning_releasedEarning 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.

MethodPathFromDescription
POST/api/webhooks/stripeStripePayment intents, refunds, disputes, Connect events
POST/api/webhooks/sendcloudSendcloudParcel status updates (in transit, delivered, returned)

Changelog

2026-07-26 — v1.5

  • Custom email campaigns. Newsletter campaigns can now carry a raw HTML body_html and 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), or all_customers (every registered customer — requires an explicit confirm_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 action newsletter.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.

LanguageStatusNote
JavaScript / TypeScriptPlanned Q3 2026Type definitions ship alongside the API as @sellforge/types
PythonPlanned Q4 2026
PHPCommunityContribute on GitHub
GoCommunityContribute 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?