@spree/docs 0.1.92 → 0.1.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/api-reference/admin-api/authentication.md +34 -18
  2. package/dist/api-reference/admin-api/endpoints.md +348 -0
  3. package/dist/api-reference/admin-api/errors.md +2 -0
  4. package/dist/api-reference/admin-api/introduction.md +11 -0
  5. package/dist/api-reference/store.yaml +243 -232
  6. package/dist/developer/agentic/overview.md +1 -1
  7. package/dist/developer/cli/admin-api.md +146 -0
  8. package/dist/developer/cli/quickstart.md +40 -5
  9. package/dist/developer/core-concepts/addresses.md +32 -16
  10. package/dist/developer/core-concepts/adjustments.md +11 -5
  11. package/dist/developer/core-concepts/architecture.md +8 -8
  12. package/dist/developer/core-concepts/calculators.md +31 -51
  13. package/dist/developer/core-concepts/channels.md +13 -6
  14. package/dist/developer/core-concepts/customers.md +47 -23
  15. package/dist/developer/core-concepts/events.md +22 -17
  16. package/dist/developer/core-concepts/imports-exports.md +69 -14
  17. package/dist/developer/core-concepts/inventory.md +79 -1
  18. package/dist/developer/core-concepts/markets.md +64 -20
  19. package/dist/developer/core-concepts/media.md +43 -6
  20. package/dist/developer/core-concepts/metafields.md +76 -13
  21. package/dist/developer/core-concepts/orders.md +95 -17
  22. package/dist/developer/core-concepts/payments.md +14 -13
  23. package/dist/developer/core-concepts/pricing.md +95 -9
  24. package/dist/developer/core-concepts/products.md +192 -26
  25. package/dist/developer/core-concepts/promotions.md +61 -4
  26. package/dist/developer/core-concepts/reports.md +4 -2
  27. package/dist/developer/core-concepts/search-filtering.md +82 -32
  28. package/dist/developer/core-concepts/shipments.md +16 -13
  29. package/dist/developer/core-concepts/slugs.md +20 -11
  30. package/dist/developer/core-concepts/staff-roles.md +51 -1
  31. package/dist/developer/core-concepts/store-credits-gift-cards.md +90 -9
  32. package/dist/developer/core-concepts/stores.md +16 -14
  33. package/dist/developer/core-concepts/taxes.md +28 -0
  34. package/dist/developer/core-concepts/translations.md +16 -7
  35. package/dist/developer/core-concepts/users.md +13 -9
  36. package/dist/developer/core-concepts/webhooks.md +95 -64
  37. package/dist/developer/how-to/custom-api-authentication.md +103 -23
  38. package/dist/developer/multi-store/quickstart.md +1 -1
  39. package/dist/developer/sdk/admin/authentication.md +1 -1
  40. package/dist/developer/sdk/admin/resources.md +2 -0
  41. package/dist/developer/upgrades/5.3-to-5.4.md +1 -1
  42. package/dist/developer/upgrades/5.4-to-5.5.md +1 -1
  43. package/dist/integrations/integrations.md +0 -7
  44. package/package.json +1 -1
  45. package/dist/integrations/sso-mfa-social-login/admin-dashboard.md +0 -57
  46. package/dist/integrations/sso-mfa-social-login/storefront.md +0 -56
@@ -194,7 +194,7 @@ Limits the promotion to orders in a specific currency.
194
194
  Requires products with specific option values (e.g., size, color) to be in the order.
195
195
 
196
196
  **Configuration:**
197
- - `preferred_eligible_values` - Array of eligible variant IDs
197
+ - `preferred_eligible_values` - Array of `Spree::OptionValueVariant` IDs (the option-value↔variant join records the rule matches against the order's variants), not variant IDs
198
198
 
199
199
  **Use case:** "10% off all red items", "Discount on size XL".
200
200
 
@@ -255,6 +255,61 @@ The action checks stock availability before adding items. Items are not automati
255
255
 
256
256
  **Use case:** "Free gift with purchase", "Buy 2 get 1 free", "Spend $100 get free sample".
257
257
 
258
+ ## Managing Promotions
259
+
260
+ Create and manage promotions via the [Admin API](../../api-reference/admin-api/introduction.md). Rules and actions can be supplied inline on create — each is a `{ type, preferences }` draft using the rule/action types described above:
261
+
262
+
263
+ ```typescript Admin SDK
264
+ import { createAdminClient } from '@spree/admin-sdk'
265
+
266
+ const client = createAdminClient({
267
+ baseUrl: 'https://store.example.com',
268
+ secretKey: 'sk_xxx',
269
+ })
270
+
271
+ // "20% off orders over $100 with code SUMMER20"
272
+ const promotion = await client.promotions.create({
273
+ name: 'Summer Sale',
274
+ code: 'SUMMER20',
275
+ kind: 'coupon_code',
276
+ rules: [{ type: 'item_total', preferences: { amount_min: 100 } }],
277
+ actions: [
278
+ {
279
+ type: 'create_adjustment',
280
+ calculator: { type: 'flat_percent_item_total', preferences: { flat_percent: 20 } },
281
+ },
282
+ ],
283
+ })
284
+ ```
285
+
286
+ ```bash CLI
287
+ spree api post /promotions -d '{
288
+ "name": "Summer Sale",
289
+ "code": "SUMMER20",
290
+ "kind": "coupon_code",
291
+ "rules": [{ "type": "item_total", "preferences": { "amount_min": 100 } }]
292
+ }'
293
+ ```
294
+
295
+
296
+ You can also add rules and actions to an existing promotion, or update and remove the promotion itself:
297
+
298
+
299
+ ```typescript Admin SDK
300
+ await client.promotions.rules.create('promo_xxx', {
301
+ type: 'first_order',
302
+ })
303
+ await client.promotions.update('promo_xxx', { expires_at: '2026-09-01T00:00:00Z' })
304
+ await client.promotions.delete('promo_xxx')
305
+ ```
306
+
307
+ ```bash CLI
308
+ spree api post /promotions/promo_xxx/promotion_rules -d '{"type": "first_order"}'
309
+ spree api patch /promotions/promo_xxx -d '{"expires_at": "2026-09-01T00:00:00Z"}'
310
+ ```
311
+
312
+
258
313
  ## Coupon Codes
259
314
 
260
315
  Coupon codes track promotion usage and can be single-use or multi-use.
@@ -274,10 +329,10 @@ For bulk-generated codes, each code tracks its usage state:
274
329
 
275
330
  ## Applying Coupon Codes via Store API
276
331
 
277
- Customers apply coupon codes during checkout via the Store API:
332
+ Customers [apply coupon codes during checkout](../sdk/store/cart-checkout.md) via the Store API:
278
333
 
279
334
 
280
- ```typescript SDK
335
+ ```typescript Store SDK
281
336
  // Apply a discount code to the cart
282
337
  const cart = await client.carts.discountCodes.apply('cart_abc123', 'SUMMER20', {
283
338
  spreeToken: '<token>',
@@ -291,7 +346,7 @@ await client.carts.discountCodes.remove('cart_abc123', 'SUMMER20', {
291
346
 
292
347
  ```bash cURL
293
348
  # Apply a coupon code
294
- curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_abc123/coupon_codes' \
349
+ curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_abc123/discount_codes' \
295
350
  -H 'X-Spree-API-Key: pk_xxx' \
296
351
  -H 'X-Spree-Token: <token>' \
297
352
  -H 'Content-Type: application/json' \
@@ -320,3 +375,5 @@ How promotions are evaluated and applied:
320
375
  - [Calculators](calculators.md) - Learn about promotion calculators
321
376
  - [Orders](orders.md) - Understanding order processing
322
377
  - [Customization Quickstart](../customization/quickstart.md) - Overview of customization options
378
+ - [Admin SDK Resources](../sdk/admin/resources.md) - The `client.promotions.*` resource-client pattern used in the examples above
379
+ - [Spree CLI Admin API](../cli/admin-api.md) - The `spree api` command reference for the CLI examples above
@@ -100,7 +100,7 @@ The Products Performance report aggregates sales metrics by product for the spec
100
100
  ### Generation Flow
101
101
 
102
102
  1. **User creates report** via admin UI with date range, currency, and optional vendor
103
- 2. **Report is saved** to database with status tracking
103
+ 2. **Report is saved** to the database
104
104
  3. **`report.created` event fires** via `publishes_lifecycle_events` concern
105
105
  4. **`Spree::ReportSubscriber` catches event** and enqueues `GenerateJob`
106
106
  5. **Background job runs `report.generate`**:
@@ -184,7 +184,7 @@ Spree::Admin::RuntimeConfig.reports_line_items_limit = 100
184
184
 
185
185
  ### Background Job Queue
186
186
 
187
- Reports use a dedicated queue. Configure your job processor:
187
+ By default, report jobs run on the `:default` queue. To route them to a dedicated queue, configure your job processor:
188
188
 
189
189
  ```ruby
190
190
  # Sidekiq example
@@ -204,3 +204,5 @@ can :manage, Spree::Report
204
204
 
205
205
  - [Build a Custom Report](../how-to/custom-report.md) - Step-by-step guide to creating custom reports
206
206
  - [Events](events.md) - How report generation uses the events system
207
+ - [Imports & Exports](imports-exports.md) - The broader async-export, ActiveStorage-attachment pattern reports share
208
+ - [Admin API Endpoints](../../api-reference/admin-api/endpoints.md) - Endpoints for creating and downloading reports from an integration
@@ -9,7 +9,7 @@ import { Since } from '/snippets/since.mdx';
9
9
 
10
10
  Spree provides powerful search, filtering, and sorting capabilities for products and other resources. The Store API supports:
11
11
 
12
- - Full-text search across product names, descriptions, and SKUs
12
+ - Full-text search across product names and SKUs
13
13
  - Attribute-based filtering (price range, availability, stock status)
14
14
  - Category and taxon filtering
15
15
  - Faceted search with filter counts
@@ -20,13 +20,20 @@ Spree provides powerful search, filtering, and sorting capabilities for products
20
20
  Use the `search` parameter for full-text search across product fields:
21
21
 
22
22
 
23
- ```typescript SDK
23
+ ```typescript Store SDK
24
24
  const { data: products } = await client.products.list({
25
25
  search: 'tote bag',
26
26
  limit: 12,
27
27
  })
28
28
  ```
29
29
 
30
+ ```typescript Admin SDK
31
+ const { data: products } = await adminClient.products.list({
32
+ search: 'tote bag',
33
+ limit: 12,
34
+ })
35
+ ```
36
+
30
37
  ```bash cURL
31
38
  curl 'https://api.mystore.com/api/v3/store/products?q[search]=tote+bag&limit=12' \
32
39
  -H 'X-Spree-API-Key: pk_xxx'
@@ -38,13 +45,20 @@ curl 'https://api.mystore.com/api/v3/store/products?q[search]=tote+bag&limit=12'
38
45
  ### By Price Range
39
46
 
40
47
 
41
- ```typescript SDK
48
+ ```typescript Store SDK
42
49
  const { data: products } = await client.products.list({
43
50
  price_gte: 20,
44
51
  price_lte: 100,
45
52
  })
46
53
  ```
47
54
 
55
+ ```typescript Admin SDK
56
+ const { data: products } = await adminClient.products.list({
57
+ price_gte: 20,
58
+ price_lte: 100,
59
+ })
60
+ ```
61
+
48
62
  ```bash cURL
49
63
  curl 'https://api.mystore.com/api/v3/store/products?q[price_gte]=20&q[price_lte]=100' \
50
64
  -H 'X-Spree-API-Key: pk_xxx'
@@ -54,12 +68,18 @@ curl 'https://api.mystore.com/api/v3/store/products?q[price_gte]=20&q[price_lte]
54
68
  ### By Availability
55
69
 
56
70
 
57
- ```typescript SDK
71
+ ```typescript Store SDK
58
72
  const { data: products } = await client.products.list({
59
73
  in_stock: true,
60
74
  })
61
75
  ```
62
76
 
77
+ ```typescript Admin SDK
78
+ const { data: products } = await adminClient.products.list({
79
+ in_stock: true,
80
+ })
81
+ ```
82
+
63
83
  ```bash cURL
64
84
  curl 'https://api.mystore.com/api/v3/store/products?q[in_stock]=true' \
65
85
  -H 'X-Spree-API-Key: pk_xxx'
@@ -69,13 +89,8 @@ curl 'https://api.mystore.com/api/v3/store/products?q[in_stock]=true' \
69
89
  ### By Category
70
90
 
71
91
 
72
- ```typescript SDK
73
- // Products in a specific category
74
- const { data: products } = await client.categories.products.list('clothing/shirts', {
75
- limit: 12,
76
- })
77
-
78
- // Or filter by category ID (includes descendants)
92
+ ```typescript Store SDK
93
+ // Filter by category ID (includes descendants)
79
94
  const { data: products } = await client.products.list({
80
95
  in_category: 'ctg_xxx',
81
96
  })
@@ -86,11 +101,19 @@ const { data: products } = await client.products.list({
86
101
  })
87
102
  ```
88
103
 
89
- ```bash cURL
90
- # Products in a category
91
- curl 'https://api.mystore.com/api/v3/store/categories/clothing/shirts/products?limit=12' \
92
- -H 'X-Spree-API-Key: pk_xxx'
104
+ ```typescript Admin SDK
105
+ // Filter by category ID (includes descendants)
106
+ const { data: products } = await adminClient.products.list({
107
+ in_category: 'ctg_xxx',
108
+ })
93
109
 
110
+ // Multiple categories (OR logic)
111
+ const { data: products } = await adminClient.products.list({
112
+ in_categories: ['ctg_shirts', 'ctg_pants'],
113
+ })
114
+ ```
115
+
116
+ ```bash cURL
94
117
  # Filter by category ID (includes descendants)
95
118
  curl 'https://api.mystore.com/api/v3/store/products?q[in_category]=ctg_xxx' \
96
119
  -H 'X-Spree-API-Key: pk_xxx'
@@ -104,14 +127,20 @@ curl 'https://api.mystore.com/api/v3/store/products?q[in_categories][]=ctg_shirt
104
127
  ### By Tags
105
128
 
106
129
 
107
- ```typescript SDK
130
+ ```typescript Store SDK
108
131
  const { data: products } = await client.products.list({
109
- tags_cont: 'sale',
132
+ tags_name_cont: 'sale',
133
+ })
134
+ ```
135
+
136
+ ```typescript Admin SDK
137
+ const { data: products } = await adminClient.products.list({
138
+ tags_name_cont: 'sale',
110
139
  })
111
140
  ```
112
141
 
113
142
  ```bash cURL
114
- curl 'https://api.mystore.com/api/v3/store/products?q[tags_cont]=sale' \
143
+ curl 'https://api.mystore.com/api/v3/store/products?q[tags_name_cont]=sale' \
115
144
  -H 'X-Spree-API-Key: pk_xxx'
116
145
  ```
117
146
 
@@ -119,33 +148,45 @@ curl 'https://api.mystore.com/api/v3/store/products?q[tags_cont]=sale' \
119
148
  ## Sorting
120
149
 
121
150
 
122
- ```typescript SDK
151
+ ```typescript Store SDK
123
152
  // Sort by price (low to high)
124
153
  const sorted = await client.products.list({
125
- sort: 'price_low_to_high',
154
+ sort: 'price',
126
155
  })
127
156
 
128
- // Available sort options:
129
- // price_low_to_high, price_high_to_low, newest, name_a_z, name_z_a
157
+ // Available sort options (prefix '-' for descending; use '-available_on' for newest):
158
+ // price, -price, name, -name, available_on, -available_on, best_selling
159
+ ```
160
+
161
+ ```typescript Admin SDK
162
+ const { data: products } = await adminClient.products.list({
163
+ sort: 'price',
164
+ })
130
165
  ```
131
166
 
132
167
  ```bash cURL
133
- curl 'https://api.mystore.com/api/v3/store/products?sort=price_low_to_high' \
168
+ curl 'https://api.mystore.com/api/v3/store/products?sort=price' \
134
169
  -H 'X-Spree-API-Key: pk_xxx'
135
170
  ```
136
171
 
137
172
 
138
173
  ## Product Filters (Faceted Search)
139
174
 
140
- Get available filter options for building a faceted search UI. Returns option values, price ranges, and categories with counts:
175
+ Get available filter options for building a faceted search UI. Returns an array of typed filter objects (price range, availability, option types, categories) with counts, plus sort and pagination metadata:
141
176
 
142
177
 
143
- ```typescript SDK
178
+ ```typescript Store SDK
144
179
  const filters = await client.products.filters()
145
180
  // {
146
- // option_types: [{ name: "size", option_values: [{ name: "Small", count: 12 }, ...] }],
147
- // price_range: { min: 9.99, max: 199.99 },
148
- // categories: [{ id: "ctg_xxx", name: "Clothing", count: 45 }],
181
+ // filters: [
182
+ // { id: 'price', type: 'price_range', min: 9.99, max: 199.99, currency: 'USD' },
183
+ // { id: 'availability', type: 'availability', options: [{ id: 'in_stock', count: 42 }, { id: 'out_of_stock', count: 3 }] },
184
+ // { id: 'optt_xxx', type: 'option', name: 'size', label: 'Size', kind: 'dropdown', options: [{ id: 'optval_xxx', name: 'Small', label: 'Small', position: 1, color_code: null, image_url: null, count: 12 }] },
185
+ // { id: 'categories', type: 'category', options: [{ id: 'ctg_xxx', name: 'Clothing', permalink: 'clothing', count: 45 }] },
186
+ // ],
187
+ // sort_options: [{ id: 'manual' }, { id: 'price' }, { id: '-price' }],
188
+ // default_sort: 'manual',
189
+ // total_count: 120,
149
190
  // }
150
191
 
151
192
  // Scoped to a specific category
@@ -210,13 +251,20 @@ The Store API uses query parameters prefixed with `q[]` for filtering any resour
210
251
  All list endpoints support pagination:
211
252
 
212
253
 
213
- ```typescript SDK
254
+ ```typescript Store SDK
214
255
  const { data: products, meta } = await client.products.list({
215
256
  page: 1,
216
257
  limit: 24,
217
258
  })
218
- // meta.total_count => 150
219
- // meta.total_pages => 7
259
+ // meta.count => 150
260
+ // meta.pages => 7
261
+ ```
262
+
263
+ ```typescript Admin SDK
264
+ const { data: products, meta } = await adminClient.products.list({
265
+ page: 1,
266
+ limit: 24,
267
+ })
220
268
  ```
221
269
 
222
270
  ```bash cURL
@@ -243,6 +291,8 @@ See [Build a Custom Search Provider](../how-to/custom-search-provider.md) for ar
243
291
  ## Related Documentation
244
292
 
245
293
  - [Products](products.md) — Product catalog and listing
246
- - [Querying](../../api-reference/store-api/querying.md) — API filtering, sorting, and pagination reference
294
+ - [Store SDK Products](../sdk/store/products.md) — `client.products.list()` options and methods
295
+ - [Querying](../../api-reference/store-api/querying.md) — Store API filtering, sorting, and pagination reference
296
+ - [Admin API querying](../../api-reference/admin-api/querying.md) — Admin API filtering, sorting, and pagination reference
247
297
  - [Build a Custom Search Provider](../how-to/custom-search-provider.md) — Step-by-step guide
248
298
  - [Meilisearch Integration](../../integrations/search/meilisearch.md) — Setup guide
@@ -61,9 +61,9 @@ The Store API/SDK exposes shipments as `fulfillments` with these attributes:
61
61
  | `fulfillment_type` | `shipping` or `digital` | `shipping` |
62
62
  | `cost` | Delivery cost | `9.99` |
63
63
  | `fulfilled_at` | When the fulfillment was shipped | `2025-07-21T14:36:00Z` |
64
- | `stock_location` | Where items ship from | `Warehouse NYC` |
64
+ | `stock_location` | Where items ship from | `{ id: "sloc_xxx", name: "Warehouse NYC" }` |
65
65
  | `delivery_method` | The selected delivery method | `{ id: "dm_xxx", name: "UPS Ground" }` |
66
- | `delivery_rates` | Available rates for the customer to pick from | `[{ id: "rate_xxx", cost: "9.99", selected: true, ... }]` |
66
+ | `delivery_rates` | Available rates for the customer to pick from | `[{ id: "dr_xxx", cost: "9.99", selected: true, ... }]` |
67
67
 
68
68
  ## Shipment States
69
69
 
@@ -86,28 +86,30 @@ The shipment was canceled. All items are restocked.
86
86
 
87
87
  ## Selecting Shipping Rates
88
88
 
89
- During checkout, after the customer provides a shipping address, Spree calculates available shipping rates for each shipment. The customer must select a rate before proceeding.
89
+ During checkout, after the customer provides a shipping address, Spree calculates available shipping rates for each shipment. The customer must select a rate before proceeding. See the [Cart & Checkout SDK](../sdk/store/cart-checkout.md) guide for the full storefront cart and fulfillment flow.
90
90
 
91
91
 
92
- ```typescript SDK
93
- // Get fulfillments with available delivery rates
92
+ ```typescript Store SDK
93
+ // Get the cart with its fulfillments and available delivery rates
94
94
  // (the Store API/SDK exposes shipments as `fulfillments`)
95
- const order = await client.orders.get(orderId, {
96
- expand: ['fulfillments'],
97
- })
95
+ const cart = await client.carts.get(cartId)
98
96
 
99
97
  // Each fulfillment has available delivery rates
100
- order.fulfillments?.forEach(fulfillment => {
98
+ cart.fulfillments?.forEach(fulfillment => {
101
99
  console.log(fulfillment.number) // "H12345678901"
102
- console.log(fulfillment.delivery_rates) // [{ id: "rate_xxx", name: "UPS Ground", cost: "9.99", selected: true }, ...]
100
+ console.log(fulfillment.delivery_rates) // [{ id: "dr_xxx", name: "UPS Ground", cost: "9.99", selected: true }, ...]
103
101
  })
104
102
 
105
103
  // Select a delivery rate
106
104
  await client.carts.fulfillments.update(cartId, fulfillment.id, {
107
- selected_delivery_rate_id: 'rate_xxx',
105
+ selected_delivery_rate_id: 'dr_xxx',
108
106
  })
109
107
  ```
110
108
 
109
+ ```typescript Admin SDK
110
+ const order = await adminClient.orders.get(orderId)
111
+ ```
112
+
111
113
  ```bash cURL
112
114
  # Get fulfillments
113
115
  curl 'https://api.mystore.com/api/v3/store/carts/cart_xxx?expand=fulfillments' \
@@ -119,7 +121,7 @@ curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx/fulfillments/
119
121
  -H 'X-Spree-API-Key: pk_xxx' \
120
122
  -H 'X-Spree-Token: abc123' \
121
123
  -H 'Content-Type: application/json' \
122
- -d '{ "selected_delivery_rate_id": "rate_xxx" }'
124
+ -d '{ "selected_delivery_rate_id": "dr_xxx" }'
123
125
  ```
124
126
 
125
127
 
@@ -195,7 +197,7 @@ A channel can also override the routing **strategy** entirely — useful when on
195
197
 
196
198
  ### When it fires
197
199
 
198
- Routing fires once, when the order transitions from `cart` to `address` (the start of checkout). It produces one or more `Shipment`s, each tied to a chosen stock location. The decision is sticky — once the shipments are created, their locations stay fixed unless the merchant edits them in the admin or the cart is cleared and re-routed.
200
+ Routing fires once, when the order transitions into the `delivery` step of checkout (from `address`). The `cart`→`address` transition only assigns default addresses; routing runs on the next step. It produces one or more `Shipment`s, each tied to a chosen stock location. The decision is sticky — once the shipments are created, their locations stay fixed unless the merchant edits them in the admin or the cart is cleared and re-routed.
199
201
 
200
202
  Routing happens **after** stock reservations: by the time routing runs, the cart has already reserved the units it needs. Reservations and routing make their decisions at different layers — reservations protect the variant's total inventory across all locations, routing picks which location ships. They coexist correctly today, with a small inefficiency around location-pinning that's planned to be tightened in 6.0.
201
203
 
@@ -302,3 +304,4 @@ A store shipping from 2 locations (New York, Los Angeles) with 3 carriers and 3
302
304
  - [Addresses](addresses.md) — Shipping address and zones
303
305
  - [Build Custom Order Routing](../how-to/custom-order-routing.md) — Custom rules and strategies for choosing the fulfillment location
304
306
  - [Events](events.md) — Subscribe to shipment events (e.g., `shipment.shipped`)
307
+ - [Admin API](../../api-reference/admin-api/introduction.md) — Inspect and manage shipments, shipping methods, and categories from the back office
@@ -7,10 +7,10 @@ description: How Spree generates SEO-friendly URL slugs for products, categories
7
7
 
8
8
  Spree generates SEO-friendly URL slugs for resources like products, categories, and stores. Instead of accessing resources by ID, you can use clean, readable URLs based on resource names.
9
9
 
10
- Both the Store API and Admin API accept slugs or IDs interchangeably for resource lookups.
10
+ The Store API accepts slugs or prefixed IDs interchangeably for resource lookups. The Admin API resolves resources by prefixed ID only — slugs are not accepted.
11
11
 
12
12
 
13
- ```typescript SDK
13
+ ```typescript Store SDK
14
14
  // Both work — slug or ID
15
15
  const product = await client.products.get('spree-tote')
16
16
  const product = await client.products.get('prod_86Rf07xd4z')
@@ -19,6 +19,11 @@ const product = await client.products.get('prod_86Rf07xd4z')
19
19
  const category = await client.categories.get('clothing/shirts')
20
20
  ```
21
21
 
22
+ ```typescript Admin SDK
23
+ // Admin SDK accepts prefixed IDs only — not slugs
24
+ const product = await adminClient.products.get('prod_86Rf07xd4z')
25
+ ```
26
+
22
27
  ```bash cURL
23
28
  # By slug
24
29
  curl 'https://api.mystore.com/api/v3/store/products/spree-tote' \
@@ -49,7 +54,6 @@ If a slug already exists, Spree appends the SKU or a unique identifier to ensure
49
54
  | Product | `slug` | Yes | No |
50
55
  | Category | `permalink` | Yes | Yes |
51
56
  | Store | `code` | No | No |
52
- | Post | `slug` | Yes | No |
53
57
 
54
58
  ### Category Permalinks
55
59
 
@@ -65,27 +69,31 @@ When a parent category is renamed, all child permalinks update automatically.
65
69
 
66
70
  ## Slug History
67
71
 
68
- When a slug changes (e.g., a product is renamed), Spree preserves the old slug. Requests using old slugs still find the resource, enabling:
72
+ When a slug changes (e.g., a product is renamed), Spree records the old slug in its history table (`friendly_id_slugs`).
69
73
 
70
- - **SEO continuity**no broken links when names change
71
- - **Bookmark compatibility** — saved URLs keep working
72
- - **Graceful URL migration** — old links resolve automatically
74
+ Note: the Store API product and category endpoints look up the *current* slug only (`find_by!` on the slug/permalink column) and do not automatically resolve retired slugs a request using an old slug returns `404`. To preserve SEO and avoid broken links, issue HTTP 301 redirects from old slugs to the current one in your storefront or application layer.
73
75
 
74
76
  ## Internationalization
75
77
 
76
- Products and categories support localized slugs — a different slug per locale:
78
+ Products and categories support localized slugs — a different slug per locale. The locale is resolved from the request's [`X-Spree-Locale` header](../../api-reference/store-api/localization.md):
77
79
 
78
80
 
79
- ```typescript SDK
81
+ ```typescript Store SDK
80
82
  // English
81
83
  const product = await client.products.get('red-shoes')
82
84
 
83
- // French (with locale header)
84
- const product = await client.products.get('chaussures-rouges', {
85
+ // French locale goes in the third (request options) argument,
86
+ // which sends the X-Spree-Locale header
87
+ const product = await client.products.get('chaussures-rouges', undefined, {
85
88
  locale: 'fr',
86
89
  })
87
90
  ```
88
91
 
92
+ ```typescript Admin SDK
93
+ // Admin SDK accepts prefixed IDs only — not slugs
94
+ const product = await adminClient.products.get('prod_86Rf07xd4z')
95
+ ```
96
+
89
97
  ```bash cURL
90
98
  # English
91
99
  curl 'https://api.mystore.com/api/v3/store/products/red-shoes' \
@@ -109,3 +117,4 @@ Spree prevents certain words from being used as slugs to avoid route conflicts:
109
117
  - [Products](products.md) — Product catalog
110
118
  - [Translations](translations.md) — Multi-language content
111
119
  - [Querying](../../api-reference/store-api/querying.md) — API resource lookup
120
+ - [Store SDK Products](../sdk/store/products.md) — `client.products.get()` slug/ID lookup
@@ -63,6 +63,35 @@ spree user create --email admin@example.com --password secret123
63
63
 
64
64
  The created user gets the `admin` role on the default store.
65
65
 
66
+ ## Authentication & identity providers
67
+
68
+ Staff authenticate against the Admin API, which issues a short-lived JWT used for subsequent requests. How a staff member proves who they are is **pluggable** — Spree ships email/password out of the box, and you can plug in any external identity provider (Okta, Microsoft Entra ID, Google Workspace, a custom JWT issuer, SAML, etc.) without changing the rest of the API.
69
+
70
+ ### How admin login works
71
+
72
+ A staff member logs in via the `POST /api/v3/admin/auth/login` endpoint (see [Admin API Authentication](../../api-reference/admin-api/authentication.md)). The request's `provider` field selects a registered **authentication strategy**. When `provider` is omitted it defaults to `email`, the built-in email/password strategy (which you can also disable and restrict the admin to your preferred SSO provider).
73
+
74
+ ```mermaid
75
+ flowchart TB
76
+ A["POST /api/v3/admin/auth/login"] --> B{"provider field"}
77
+ B -->|"omitted or email"| C["Built-in EmailPasswordStrategy"]
78
+ B -->|"okta, saml, ..."| D["Your custom strategy"]
79
+ C --> E["Strategy authenticates, returns the staff user"]
80
+ D --> E
81
+ E --> F["Spree issues a JWT (aud=admin_api) + HttpOnly refresh cookie"]
82
+ ```
83
+
84
+ Whichever strategy authenticates the request, Spree issues the **same** credentials in return, so downstream code and the admin SPA never need to know which provider was used:
85
+
86
+ - a JWT access token (`aud: admin_api`), short-lived by design;
87
+ - a rotating refresh token, set as an `HttpOnly` cookie scoped to `/api/v3/admin/auth` (the admin flow keeps it out of the response body — see [Admin Auth & Cookie Refresh](../../api-reference/admin-api/authentication.md)).
88
+
89
+ The same strategy registry exists on the customer side, so storefront sign-in is pluggable the same way — the only difference is the user class and that the Store API returns the refresh token in the body rather than a cookie.
90
+
91
+ ### Registering a custom identity provider
92
+
93
+ Follow the [Custom API Authentication how-to](../how-to/custom-api-authentication.md) for details how to create a custom authentication strategy and register it with the admin API. Once registered, you can use it from the admin SPA or any API client by passing its name in the `provider` field of the login request.
94
+
66
95
  ## Inviting Admin Users
67
96
 
68
97
  You can invite new admins through the Admin Panel or programmatically.
@@ -74,7 +103,25 @@ You can invite new admins through the Admin Panel or programmatically.
74
103
  3. Enter the email address and select a role
75
104
  4. Click **Send Invitation**
76
105
 
77
- The invitee receives an email with an invitation link. If they already have an account, they log in to accept. Otherwise, they create an account first.
106
+ **Programmatically:**
107
+
108
+ Using the [Admin SDK](../sdk/admin/quickstart.md), call `client.invitations.create`:
109
+
110
+ ```typescript
111
+ import { createAdminClient } from '@spree/admin-sdk'
112
+
113
+ const client = createAdminClient({
114
+ baseUrl: 'https://store.example.com',
115
+ secretKey: 'sk_xxx',
116
+ })
117
+
118
+ const invitation = await client.invitations.create({
119
+ email: 'new-admin@example.com',
120
+ role_id: 'role_xxx',
121
+ })
122
+ ```
123
+
124
+ Creating an invitation publishes `invitation.created`, which sends the email. Either way, the invitee receives an email with an invitation link. If they already have an account, they log in to accept. Otherwise, they create an account first.
78
125
 
79
126
  ```mermaid
80
127
  flowchart TB
@@ -117,6 +164,9 @@ See the [Customize Permissions guide](../customization/permissions.md) for detai
117
164
 
118
165
  ## Related Documentation
119
166
 
167
+ - [Admin SDK](../sdk/admin/quickstart.md) — the TypeScript client for back-office automation
168
+ - [Custom API Authentication](../how-to/custom-api-authentication.md) — implement a custom identity-provider strategy (the full guide)
169
+ - [Admin API Authentication](../../api-reference/admin-api/authentication.md) — keys, JWTs, scopes, and the refresh-cookie flow
120
170
  - [Customers](customers.md) — Customer accounts and authentication
121
171
  - [Stores](stores.md) — Multi-store setup
122
172
  - [Permissions](../customization/permissions.md) — Roles and authorization