Headless REST API for SilverShop (SilverStripe CMS 6): catalog/cart/order/customer resources, token auth with scopes, pagination, pluggable resources/serializers and order webhooks. A backwards-compatible superset of core's api/v1.
Package info
github.com/silvershop-suite/silvershop-api
Type:silverstripe-vendormodule
pkg:composer/silvershop-suite/api
Requires
- php: ^8.3
- silvershop/core: ^6
- silverstripe/cms: ^6.0
- silverstripe/framework: ^6.0
Requires (Dev)
Suggests
- silvershop-suite/brands: Enriches the product resource with brand fields.
- silvershop-suite/colors: Enriches the product resource with colour swatches.
- silvershop-suite/product-gallery: Enriches the product resource with gallery image URLs.
- silvershop-suite/reviews: Enriches the product resource with aggregate rating fields.
- silvershop-suite/search: Exposes the instant product-search bridge at api/v1/search.
- symbiote/silverstripe-queuedjobs: Deliver webhooks as a background job instead of inline during the request.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-19 19:54:19 UTC
README
A headless REST API for SilverShop (SilverStripe CMS 6): read the catalog, drive the cart, read orders, manage the customer's own profile/addresses, issue scoped access tokens, and emit order/product webhooks — for SPAs, mobile apps, PWAs and ERP/marketplace sync.
It is a backwards-compatible superset of core's api/v1: it takes over the api/v1 route from core's
WebServiceController and keeps the existing products and cart contracts (same fields, CSRF behaviour and JSON/XML
content negotiation), then layers on new resources, pagination, token auth and webhooks. Existing api/v1/products /
api/v1/cart consumers keep working — the product payload only gains fields.
GraphQL is not part of this module. The REST base is deliberately graphql-free (no
silverstripe/graphqldependency, no build step). A GraphQL schema is a separate, optionalsilvershop-suite/api-graphqloverlay.
Requirements
silvershop/core^6,silverstripe/framework^6,silverstripe/cms^6- PHP 8.3 / 8.4
Installation
composer require silvershop-suite/api
Then run dev/build?flush=1. The API is mounted at /api/v1/.
Authentication
Two mechanisms, matching core:
-
Bearer tokens (server-to-server / headless). Create an API token in the CMS under API (or programmatically). The plaintext token is shown once at creation — only its SHA-256 hash is stored. Present it as:
Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxEach token belongs to a
Member(whosecanView()/canEdit()permissions back the resources) and carries one or more scopes:Scope Grants readread catalog, cart, own orders and profile writemodify cart, profile and addresses (implies read)admincreate/update products (implies write)Tokens may carry an optional expiry.
-
Session + CSRF (same-origin storefront JS). Cart writes from a logged-in/same-origin session work exactly as core: a valid
SecurityID(CSRF token) is required. Bearer-token clients skip CSRF (the token is the auth).
Public read: the live catalog is readable without a token by default. Set
SilverShop\Api\Control\ApiController.public_read: false to require a read-scope token for catalog reads too.
Endpoints
All responses negotiate JSON (default) or XML via ?format=json|xml, a .json/.xml suffix, or the Accept
header. List endpoints return a bare array body plus pagination headers (X-Total-Count, X-Page-Start,
X-Page-Limit, X-Total-Pages).
Catalog (read; read scope or public)
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/products |
List published products |
| GET | /api/v1/products/{id|urlSegment} |
A single product |
| GET | /api/v1/products/{id}/variations |
A product's variations |
| GET | /api/v1/categories |
List categories |
| GET | /api/v1/categories/tree |
The nested category tree |
| GET | /api/v1/categories/{id|urlSegment} |
A single category |
| GET | /api/v1/variations / /variations/{id} |
Variations |
| GET | /api/v1/search?q=… |
Product search (requires silvershop-suite/search) |
List params: start, limit, sort (sort=Title, sort=-BasePrice, comma-separated), and
filter[Field]=value (products: Title, InternalItemID, Model, Featured, and category for recursive category
filtering).
Cart
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/cart |
The current cart |
| POST | /api/v1/cart/add |
Add a buyable (ProductID, optional Buyable, quantity) |
| POST | /api/v1/cart/update |
Set a buyable's quantity |
| POST | /api/v1/cart/remove |
Remove a buyable |
| POST | /api/v1/cart/clear |
Empty the cart |
| GET | /api/v1/cart/checkout |
Checkout hand-off: returns the hosted checkoutUrl (no API payment capture) |
Cart writes need the write scope (bearer) or a valid CSRF token (session). A bearer-token cart is bound to the
token's member, so it persists across stateless requests.
Orders & customer (read/write scope; own data only)
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/orders |
The member's placed orders |
| GET | /api/v1/orders/{id} |
A single order the member owns |
| GET | /api/v1/customer |
Profile + address book |
| PUT/PATCH | /api/v1/customer |
Update profile (first name, surname) |
| GET | /api/v1/customer/addresses |
List addresses |
| POST | /api/v1/customer/addresses |
Create an address |
| GET/PUT/PATCH | /api/v1/customer/addresses/{id} |
Read/update an address |
| GET | /api/v1/customer/orders |
Order history |
Admin catalog write (admin scope + canEdit()/canCreate())
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/products |
Create a product (published) |
| PUT/PATCH | /api/v1/products/{id} |
Update a product (published) |
Extending — resources & serializers
Resources are registered in a config-ordered ResourceRegistry (the suite's provider/registry pattern), and each
resource serialises its model through a dedicated serializer that fires an updateSerialised<Resource> extension
hook (generalising core's single updateSerialisedProduct):
SilverShop\Api\Resource\ResourceRegistry: resources: reviews: '%$My\Module\Api\ReviewResource' SilverShop\Api\Serializer\ProductSerializer: extensions: - My\Module\Api\MyProductEnrichment # implements updateSerialisedProduct(&$data, $product)
This module ships guarded enrichers that add fields to the product payload when the sibling module is installed: brand (brands), colours (colors), rating (reviews), gallery (product-gallery), downloadable (downloads).
Webhooks
Manage endpoints in the CMS under API → Webhooks (or via the SilverShop\Api\Webhook\WebhookService.endpoints
config map). Events:
order.placed— a cart becomes an order (onPlaceOrder)order.paid— payment settled (onPaid)order.statuschange— order status transition (onStatusChange)product.published— a product is published
Each delivery is a JSON POST of {event, timestamp, data}. When a Secret is set it is signed with
X-SilverShop-Signature: sha256=<hmac>. Deliveries run as a background job when
symbiote/silverstripe-queuedjobs is installed (suggested), and inline otherwise.
CORS
Off by default. Enable for browser-based headless clients:
SilverShop\Api\Control\ApiController: cors: enabled: true allow_origin: 'https://my-spa.example.com'
Configuration
SilverShop\Api\Control\ApiController: public_read: true # false = catalog reads require a read-scope token default_page_size: 30 max_page_size: 100
License
BSD-3-Clause.
Credits
Built and maintained by Remy Vaartjes (remy@xd.nl) — part of the SilverShop-Suite.