@spree/docs 0.1.229 → 0.1.231

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 (45) hide show
  1. package/dist/api-reference/store.yaml +78 -78
  2. package/dist/developer/agentic/agent-skills.md +1 -1
  3. package/dist/developer/cli/quickstart.md +2 -2
  4. package/dist/developer/core-concepts/addresses.md +13 -0
  5. package/dist/developer/core-concepts/calculators.md +6 -1
  6. package/dist/developer/core-concepts/carts.md +19 -1
  7. package/dist/developer/core-concepts/catalogs.md +51 -2
  8. package/dist/developer/core-concepts/channels.md +20 -1
  9. package/dist/developer/core-concepts/commissions.md +70 -1
  10. package/dist/developer/core-concepts/companies.md +74 -0
  11. package/dist/developer/core-concepts/customers.md +17 -5
  12. package/dist/developer/core-concepts/data-privacy.md +20 -7
  13. package/dist/developer/core-concepts/delivery-setup.md +57 -0
  14. package/dist/developer/core-concepts/discounts.md +7 -0
  15. package/dist/developer/core-concepts/events.md +13 -7
  16. package/dist/developer/core-concepts/fees.md +20 -0
  17. package/dist/developer/core-concepts/fulfillments.md +3 -3
  18. package/dist/developer/core-concepts/imports-exports.md +21 -5
  19. package/dist/developer/core-concepts/inventory.md +73 -1
  20. package/dist/developer/core-concepts/markets.md +5 -6
  21. package/dist/developer/core-concepts/media.md +66 -1
  22. package/dist/developer/core-concepts/metafields.md +1 -1
  23. package/dist/developer/core-concepts/order-totals.md +18 -1
  24. package/dist/developer/core-concepts/orders.md +2 -1
  25. package/dist/developer/core-concepts/payments.md +100 -20
  26. package/dist/developer/core-concepts/pricing.md +13 -8
  27. package/dist/developer/core-concepts/products.md +52 -1
  28. package/dist/developer/core-concepts/reporting.md +5 -5
  29. package/dist/developer/core-concepts/returns-exchanges-claims.md +41 -2
  30. package/dist/developer/core-concepts/search-filtering.md +1 -2
  31. package/dist/developer/core-concepts/sellers.md +109 -2
  32. package/dist/developer/core-concepts/slugs.md +1 -1
  33. package/dist/developer/core-concepts/staff-roles.md +23 -3
  34. package/dist/developer/core-concepts/stores.md +21 -0
  35. package/dist/developer/core-concepts/taxes.md +43 -0
  36. package/dist/developer/core-concepts/translations.md +9 -0
  37. package/dist/developer/core-concepts/webhooks.md +45 -3
  38. package/dist/developer/create-spree-app/quickstart.md +10 -10
  39. package/dist/developer/dashboard/deployment.md +4 -4
  40. package/dist/developer/dashboard/recipes/attribute-end-to-end.md +1 -1
  41. package/dist/developer/deployment/docker.md +2 -2
  42. package/dist/developer/how-to/custom-promotion.md +33 -13
  43. package/dist/developer/multi-tenant/quickstart.md +6 -6
  44. package/dist/developer/storefront/nextjs/testing.md +2 -2
  45. package/package.json +1 -1
@@ -9,7 +9,7 @@ Spree has a highly flexible payments model which allows multiple payment methods
9
9
 
10
10
  Payment methods typically represent a payment gateway. Gateways will process card payments, online bank transfers, buy-now-pay-later, wallet payments, and other types of payments. Spree also comes with a Check option for offline processing.
11
11
 
12
- The `Payment` model in Spree tracks payments against [Orders](orders). Payments relate to a `source` which indicates how the payment was made, and a `PaymentMethod`, indicating the processor used for this payment.
12
+ The `Payment` model in Spree tracks payments against [Orders](orders.md). Payments relate to a `source` which indicates how the payment was made, and a `PaymentMethod`, indicating the processor used for this payment.
13
13
 
14
14
  When a payment is created, it is given a unique, 8-character identifier. This is used when sending the payment details to the payment processor. Without this identifier, some payment gateways mistakenly reported duplicate payments.
15
15
 
@@ -153,7 +153,8 @@ Payment methods where `session_required?` returns `false` don't need a payment s
153
153
 
154
154
  For these methods, the Store API allows creating a payment directly without going through the payment session flow:
155
155
 
156
- ```typescript
156
+
157
+ ```typescript Store SDK
157
158
  const options = { spreeToken: cart.token }
158
159
 
159
160
  // Create a payment for a non-session payment method
@@ -166,6 +167,19 @@ const payment = await client.carts.payments.create(cart.id, {
166
167
  }, options)
167
168
  ```
168
169
 
170
+ ```bash cURL
171
+ curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_xxx/payments' \
172
+ -H 'X-Spree-API-Key: pk_xxx' \
173
+ -H 'X-Spree-Token: CART_TOKEN' \
174
+ -H 'Content-Type: application/json' \
175
+ -d '{
176
+ "payment_method_id": "pm_xyz789",
177
+ "amount": "99.99",
178
+ "metadata": { "purchase_order_number": "PO-12345" }
179
+ }'
180
+ ```
181
+
182
+
169
183
  The payment is created in `checkout` state. When the order transitions to `complete`, Spree calls `process_payments!` which runs the payment method's `authorize` (or `purchase` if auto-capture is enabled). For manual payment methods like Check, this is a no-op that succeeds immediately — the payment moves to `pending` (or `completed` with auto-capture), allowing the order to complete.
170
184
 
171
185
  The merchant can later capture or void the payment from the Admin Panel once the actual payment is received (e.g., check arrives, bank transfer clears, cash is collected on delivery).
@@ -349,7 +363,8 @@ stateDiagram-v2
349
363
 
350
364
  **Create a Payment Session:**
351
365
 
352
- ```typescript
366
+
367
+ ```typescript Store SDK
353
368
  const options = { spreeToken: cart.token }
354
369
 
355
370
  // Create a payment session for the selected payment method
@@ -364,9 +379,18 @@ console.log(session.external_id) // e.g. 'pi_3ABC123' (Stripe Payme
364
379
  console.log(session.external_data.client_secret) // Use with Stripe.js or Adyen Drop-in
365
380
  ```
366
381
 
382
+ ```bash cURL
383
+ curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_xxx/payment_sessions' \
384
+ -H 'X-Spree-API-Key: pk_xxx' \
385
+ -H 'X-Spree-Token: CART_TOKEN' \
386
+ -H 'Content-Type: application/json' \
387
+ -d '{ "payment_method_id": "pm_xyz789", "amount": "99.99", "external_data": {} }'
388
+ ```
389
+
390
+
367
391
  **Response shape (`StorePaymentSession`):**
368
392
 
369
- ```json
393
+ ```json Response
370
394
  {
371
395
  "id": "ps_abc123",
372
396
  "status": "pending",
@@ -386,7 +410,8 @@ console.log(session.external_data.client_secret) // Use with Stripe.js or Adyen
386
410
 
387
411
  **Update a Payment Session** (e.g., after order total changes):
388
412
 
389
- ```typescript
413
+
414
+ ```typescript Store SDK
390
415
  const updated = await client.carts.paymentSessions.update(
391
416
  cart.id, session.id,
392
417
  { amount: '149.99' },
@@ -394,9 +419,19 @@ const updated = await client.carts.paymentSessions.update(
394
419
  )
395
420
  ```
396
421
 
422
+ ```bash cURL
423
+ curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx/payment_sessions/ps_xxx' \
424
+ -H 'X-Spree-API-Key: pk_xxx' \
425
+ -H 'X-Spree-Token: CART_TOKEN' \
426
+ -H 'Content-Type: application/json' \
427
+ -d '{ "amount": "149.99" }'
428
+ ```
429
+
430
+
397
431
  **Complete a Payment Session** (after customer confirms payment on the frontend):
398
432
 
399
- ```typescript
433
+
434
+ ```typescript Store SDK
400
435
  const completed = await client.carts.paymentSessions.complete(
401
436
  cart.id, session.id,
402
437
  { session_result: '...', external_data: {} },
@@ -405,14 +440,31 @@ const completed = await client.carts.paymentSessions.complete(
405
440
  console.log(completed.status) // 'completed'
406
441
  ```
407
442
 
443
+ ```bash cURL
444
+ curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_xxx/payment_sessions/ps_xxx/complete' \
445
+ -H 'X-Spree-API-Key: pk_xxx' \
446
+ -H 'X-Spree-Token: CART_TOKEN' \
447
+ -H 'Content-Type: application/json' \
448
+ -d '{ "session_result": "...", "external_data": {} }'
449
+ ```
450
+
451
+
408
452
  > **WARNING:** Completing a payment session does **not** complete the order. You must call `POST /carts/:id/complete` separately after the session is completed. This separation prevents race conditions between the frontend and payment webhooks.
409
453
 
410
454
  **Complete the Order** (after the payment session is completed):
411
455
 
412
- ```typescript
456
+
457
+ ```typescript Store SDK
413
458
  const order = await client.carts.complete(cart.id, options)
414
459
  ```
415
460
 
461
+ ```bash cURL
462
+ curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_xxx/complete' \
463
+ -H 'X-Spree-API-Key: pk_xxx' \
464
+ -H 'X-Spree-Token: CART_TOKEN'
465
+ ```
466
+
467
+
416
468
  ### Payment Webhooks
417
469
 
418
470
  Spree provides a generic webhook endpoint at `POST /api/v3/webhooks/payments/:payment_method_id` that payment gateway extensions can use. When a payment provider sends a webhook (e.g., Stripe `payment_intent.succeeded`), Spree:
@@ -427,7 +479,7 @@ The background job creates/updates the Payment record, marks the session as comp
427
479
 
428
480
  Gateway extensions implement `parse_webhook_event` to normalize provider-specific payloads:
429
481
 
430
- ```ruby
482
+ ```ruby server/app/models/my_gateway.rb
431
483
  class MyGateway < Spree::Gateway
432
484
  def parse_webhook_event(raw_body, headers)
433
485
  # Verify signature — raise WebhookSignatureError if invalid
@@ -591,7 +643,8 @@ sequenceDiagram
591
643
 
592
644
  **Create a Payment Setup Session:**
593
645
 
594
- ```typescript
646
+
647
+ ```typescript Store SDK
595
648
  const options = { token: jwtToken }
596
649
 
597
650
  // Create a setup session for saving a payment method
@@ -604,9 +657,18 @@ const setupSession = await client.customer.paymentSetupSessions.create({
604
657
  console.log(setupSession.external_client_secret) // e.g. 'seti_ABC123_secret_xyz'
605
658
  ```
606
659
 
660
+ ```bash cURL
661
+ curl -X POST 'https://api.mystore.com/api/v3/store/customers/me/payment_setup_sessions' \
662
+ -H 'X-Spree-API-Key: pk_xxx' \
663
+ -H 'Authorization: Bearer $CUSTOMER_JWT' \
664
+ -H 'Content-Type: application/json' \
665
+ -d '{ "payment_method_id": "pm_xyz789", "external_data": {} }'
666
+ ```
667
+
668
+
607
669
  **Response shape (`StorePaymentSetupSession`):**
608
670
 
609
- ```json
671
+ ```json Response
610
672
  {
611
673
  "id": "pss_abc123",
612
674
  "status": "pending",
@@ -622,13 +684,22 @@ console.log(setupSession.external_client_secret) // e.g. 'seti_ABC123_secret_xyz
622
684
 
623
685
  **Get a Payment Setup Session:**
624
686
 
625
- ```typescript
687
+
688
+ ```typescript Store SDK
626
689
  const session = await client.customer.paymentSetupSessions.get('pss_abc123', options)
627
690
  ```
628
691
 
692
+ ```bash cURL
693
+ curl 'https://api.mystore.com/api/v3/store/customers/me/payment_setup_sessions/pss_abc123' \
694
+ -H 'X-Spree-API-Key: pk_xxx' \
695
+ -H 'Authorization: Bearer $CUSTOMER_JWT'
696
+ ```
697
+
698
+
629
699
  **Complete a Payment Setup Session** (after the customer completes setup on the frontend using the gateway SDK and `external_client_secret`):
630
700
 
631
- ```typescript
701
+
702
+ ```typescript Store SDK
632
703
  const completed = await client.customer.paymentSetupSessions.complete(
633
704
  'pss_abc123',
634
705
  { external_data: {} },
@@ -638,6 +709,15 @@ console.log(completed.status) // 'completed'
638
709
  console.log(completed.payment_source_id) // e.g. 'card_xyz789' - the saved payment method
639
710
  ```
640
711
 
712
+ ```bash cURL
713
+ curl -X POST 'https://api.mystore.com/api/v3/store/customers/me/payment_setup_sessions/pss_abc123/complete' \
714
+ -H 'X-Spree-API-Key: pk_xxx' \
715
+ -H 'Authorization: Bearer $CUSTOMER_JWT' \
716
+ -H 'Content-Type: application/json' \
717
+ -d '{ "external_data": {} }'
718
+ ```
719
+
720
+
641
721
  Spree will verify the result with the provider and create a saved payment source (e.g., `Spree::CreditCard`) that can be used for future payments.
642
722
 
643
723
  ## Supported Gateways
@@ -678,14 +758,6 @@ Spree publishes events throughout the payment lifecycle that you can subscribe t
678
758
 
679
759
  See [Events](events.md) for more details on subscribing to events.
680
760
 
681
- ## Related Documentation
682
-
683
- - [Payments (Store SDK)](../sdk/store/payments.md) - SDK how-to for payment sessions, payments, and setup sessions
684
- - [Build a Custom Payment Method](../how-to/custom-payment-method.md) - Step-by-step guide to creating your own payment gateway integration
685
- - [Orders](orders.md) - Order lifecycle, payment and fulfillment status
686
- - [Checkout Customization](carts.md) - Customizing the checkout flow
687
- - [Events](events.md) - Subscribe to payment events
688
-
689
761
  ## Two paths to a completed order
690
762
 
691
763
  A payment can finish in two places, and both have to end at the same result.
@@ -697,3 +769,11 @@ Whichever arrives first completes the order; the other finds the work already do
697
769
  > **WARNING:** Never treat the browser returning from a redirect as proof of payment. The provider's webhook is the authoritative signal — a customer can close the tab, and a browser response can be forged.
698
770
 
699
771
  Both paths can be replaced if your integration needs different behaviour — see [Dependencies](../customization/dependencies.md).
772
+
773
+ ## Related Documentation
774
+
775
+ - [Payments (Store SDK)](../sdk/store/payments.md) - SDK how-to for payment sessions, payments, and setup sessions
776
+ - [Build a Custom Payment Method](../how-to/custom-payment-method.md) - Step-by-step guide to creating your own payment gateway integration
777
+ - [Orders](orders.md) - Order lifecycle, payment and fulfillment status
778
+ - [Checkout Customization](carts.md) - Customizing the checkout flow
779
+ - [Events](events.md) - Subscribe to payment events
@@ -268,7 +268,7 @@ curl 'https://api.mystore.com/api/v3/store/products/spree-tote?expand=prior_pric
268
268
 
269
269
  The response includes a `prior_price` object when expanded:
270
270
 
271
- ```json
271
+ ```json Response
272
272
  {
273
273
  "id": "prod_xxx",
274
274
  "name": "Spree Tote",
@@ -287,15 +287,20 @@ The response includes a `prior_price` object when expanded:
287
287
 
288
288
  Price history tracking is enabled by default. To disable it (e.g., for non-EU stores):
289
289
 
290
- ```ruby
291
- # config/initializers/spree.rb
292
- Spree.config do |config|
293
- config.track_price_history = false
294
- config.price_history_retention_days = 30 # default
295
- end
290
+
291
+ ```typescript Admin SDK
292
+ await client.store.update({ preferred_track_price_history: false })
293
+ ```
294
+
295
+ ```bash cURL
296
+ curl -X PATCH 'https://api.mystore.com/api/v3/admin/store' \
297
+ -H 'X-Spree-API-Key: sk_xxx' \
298
+ -H 'Content-Type: application/json' \
299
+ -d '{ "preferred_track_price_history": false }'
296
300
  ```
297
301
 
298
- Price history retention defaults to 30 days and can be configured globally. A Rake task is provided for cleanup:
302
+
303
+ Retention defaults to 30 days, set per store with the `price_history_retention_days` preference. A Rake task prunes anything older:
299
304
 
300
305
 
301
306
  ```bash Spree CLI (Docker)
@@ -213,6 +213,7 @@ spree api post /products -d '{
213
213
 
214
214
  For something with no options at all, send prices directly and skip the variants array — Spree forwards them to the product's single variant:
215
215
 
216
+
216
217
  ```typescript Admin SDK
217
218
  await adminClient.products.create({
218
219
  name: 'The Spree Handbook',
@@ -221,6 +222,18 @@ await adminClient.products.create({
221
222
  })
222
223
  ```
223
224
 
225
+ ```bash cURL
226
+ curl -X POST 'https://api.mystore.com/api/v3/admin/products' \
227
+ -H 'X-Spree-API-Key: sk_xxx' \
228
+ -H 'Content-Type: application/json' \
229
+ -d '{
230
+ "name": "The Spree Handbook",
231
+ "status": "active",
232
+ "prices": [{ "currency": "USD", "amount": "19.99" }]
233
+ }'
234
+ ```
235
+
236
+
224
237
  Don't pass both.
225
238
 
226
239
  Update, clone, or archive a product (deleting soft-deletes it):
@@ -346,7 +359,7 @@ await client.products.approve('prod_xxx')
346
359
  await client.products.reject('prod_xxx', { reason: 'Please add a photo showing scale.' })
347
360
 
348
361
  // The review queue
349
- const pending = await client.products.list({ filter: { status_eq: 'proposed' } })
362
+ const pending = await client.products.list({ status_eq: 'proposed' })
350
363
 
351
364
  // Who decided, and when
352
365
  const product = await client.products.get('prod_xxx', { expand: ['submission'] })
@@ -448,6 +461,7 @@ There's no such thing as a product without one. A book with no size or colour st
448
461
 
449
462
  `default_variant_id` names the variant that represents the product — the price shown on a listing page, and what "add to cart" means before anyone picks anything.
450
463
 
464
+
451
465
  ```typescript Store SDK
452
466
  const product = await client.products.get('spree-tote')
453
467
 
@@ -456,6 +470,12 @@ product.price // that variant's price
456
470
  product.variant_count // 6
457
471
  ```
458
472
 
473
+ ```bash cURL
474
+ curl 'https://api.mystore.com/api/v3/store/products/spree-tote' \
475
+ -H 'X-Spree-API-Key: pk_xxx'
476
+ ```
477
+
478
+
459
479
  For a single-variant product that's the only variant. For a product with options it's the first one, unless you say otherwise. If the default is ever removed, another is promoted automatically — a product is never left without one.
460
480
 
461
481
  Products can also carry `buy_box_variant_id`, naming the variant a marketplace has chosen to feature when several sellers offer the same listing.
@@ -583,6 +603,7 @@ erDiagram
583
603
  }
584
604
  ```
585
605
 
606
+
586
607
  ```typescript Admin SDK
587
608
  const shoes = await adminClient.productTypes.create({
588
609
  name: 'Footwear',
@@ -601,6 +622,19 @@ await adminClient.products.create({
601
622
  })
602
623
  ```
603
624
 
625
+ ```bash cURL
626
+ curl -X POST 'https://api.mystore.com/api/v3/admin/product_types' \
627
+ -H 'X-Spree-API-Key: sk_xxx' \
628
+ -H 'Content-Type: application/json' \
629
+ -d '{
630
+ "name": "Footwear",
631
+ "option_type_ids": ["optt_size", "optt_color"],
632
+ "category_ids": ["ctg_footwear"],
633
+ "delivery_profile_id": "dp_standard"
634
+ }'
635
+ ```
636
+
637
+
604
638
  ### A type is a template, not a controller
605
639
 
606
640
  This is the part that determines how you should think about them.
@@ -620,10 +654,17 @@ Seeding is also **additive** — it adds what's missing and never removes what a
620
654
 
621
655
  If you *do* want an edited type to reach the products already using it, that's an explicit action:
622
656
 
657
+
623
658
  ```typescript Admin SDK
624
659
  const { products_count } = await adminClient.productTypes.applyToProducts('pt_xxx')
625
660
  ```
626
661
 
662
+ ```bash cURL
663
+ curl -X POST 'https://api.mystore.com/api/v3/admin/product_types/pt_xxx/apply_to_products' \
664
+ -H 'X-Spree-API-Key: sk_xxx'
665
+ ```
666
+
667
+
627
668
  It runs in the background and is additive like the rest — never a side effect of saving a type.
628
669
 
629
670
  > **INFO:** `required` on a type's custom field is **advisory** — it marks the field in the dashboard but isn't enforced on write, since Spree saves the product and its fields in two steps. Validate in your own tooling if you need it enforced.
@@ -878,6 +919,7 @@ curl 'https://api.mystore.com/api/v3/store/categories/clothing/shirts/products?l
878
919
 
879
920
  Collections work the same way from a storefront's point of view:
880
921
 
922
+
881
923
  ```typescript Store SDK
882
924
  const { data: collections } = await client.collections.list()
883
925
 
@@ -886,6 +928,15 @@ const { data: products } = await client.collections.products.list('summer-2025',
886
928
  })
887
929
  ```
888
930
 
931
+ ```bash cURL
932
+ curl 'https://api.mystore.com/api/v3/store/collections' \
933
+ -H 'X-Spree-API-Key: pk_xxx'
934
+
935
+ curl 'https://api.mystore.com/api/v3/store/collections/summer-2025/products?limit=12' \
936
+ -H 'X-Spree-API-Key: pk_xxx'
937
+ ```
938
+
939
+
889
940
  A rule-based collection is defined once and maintains itself — set it to match everything tagged `sale` and under $50, and products appear and disappear as those facts change. Ordering can be manual or by a rule such as newest first.
890
941
 
891
942
  ## Publications and Sales Channels
@@ -28,7 +28,7 @@ flowchart LR
28
28
 
29
29
  A query names what to measure and how to slice it. This is the whole surface:
30
30
 
31
- ```json
31
+ ```json Query
32
32
  {
33
33
  "metrics": ["total_sales", "orders"],
34
34
  "dimensions": [{ "name": "completed_at", "grain": "week" }],
@@ -76,7 +76,7 @@ different clocks, so a payment total beside a units-received count is two
76
76
  reports wearing one table — the query refuses the mix rather than inventing a
77
77
  join between grains that have no honest relationship.
78
78
 
79
- ```ruby
79
+ ```ruby server/config/initializers/spree.rb
80
80
  Spree.reporting.metric :total_sales,
81
81
  sql: 'SUM(%{orders}.total)', base: :orders, format: :money
82
82
  ```
@@ -118,7 +118,7 @@ row and inflate every other metric in the same query.
118
118
  Core's sales metrics follow the sequence merchants and accountants already use,
119
119
  so a Spree figure means what the same word means everywhere else:
120
120
 
121
- ```
121
+ ```text
122
122
  gross_sales − discounts − returns = net_sales
123
123
  net_sales + shipping + duties + fees + taxes = total_sales
124
124
  ```
@@ -137,7 +137,7 @@ zero and margin over an incompletely-costed catalogue reads high.
137
137
  A dimension groups and filters. Its definition owns every behaviour keyed off
138
138
  it, which is what lets an extension-registered dimension work end to end:
139
139
 
140
- ```ruby
140
+ ```ruby server/config/initializers/spree.rb
141
141
  Spree.reporting.dimension :channel, base: :orders, column: :channel_id,
142
142
  lookup: :channel,
143
143
  subject: -> { Spree::Channel }, key_scope: 'read_channels',
@@ -179,7 +179,7 @@ fulfill", "Open returns" and "Low stock" are the state of the store right now,
179
179
  with no time range and no currency. The registry holds these as **counters**
180
180
  beside metrics and dimensions, and `GET /dashboard/counters` evaluates them:
181
181
 
182
- ```json
182
+ ```json Response
183
183
  {
184
184
  "channel_id": null,
185
185
  "counters": [
@@ -163,6 +163,7 @@ how postage records work in general.
163
163
 
164
164
  What to do about a claim is decided when you resolve it, not when the customer opens it — merchants usually decide once they've seen the photos.
165
165
 
166
+
166
167
  ```typescript Admin SDK
167
168
  await adminClient.orders.claims.approve('or_xxx', 'clm_xxx')
168
169
 
@@ -173,32 +174,70 @@ await adminClient.orders.claims.resolve('or_xxx', 'clm_xxx', {
173
174
  })
174
175
  ```
175
176
 
177
+ ```bash cURL
178
+ curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/claims/clm_xxx/approve' \
179
+ -H 'X-Spree-API-Key: sk_xxx'
180
+
181
+ curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/claims/clm_xxx/resolve' \
182
+ -H 'X-Spree-API-Key: sk_xxx' \
183
+ -H 'Content-Type: application/json' \
184
+ -d '{
185
+ "resolution": "refund_and_replacement",
186
+ "refund_method": "store_credit",
187
+ "replacement_line_item_ids": ["li_xxx"]
188
+ }'
189
+ ```
190
+
191
+
176
192
  A replacement creates a new [fulfillment](fulfillments.md) on the original order, so the customer doesn't have to place a second one.
177
193
 
178
194
  ## Exchanges
179
195
 
180
196
  An exchange works like a return, but ends by sending different items instead of refunding:
181
197
 
198
+
182
199
  ```typescript Admin SDK
183
200
  await adminClient.orders.exchanges.approve('or_xxx', 'exch_xxx')
184
201
  await adminClient.orders.exchanges.receive('or_xxx', 'exch_xxx')
185
202
  await adminClient.orders.exchanges.fulfill('or_xxx', 'exch_xxx')
186
203
  ```
187
204
 
205
+ ```bash cURL
206
+ curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/exchanges/exch_xxx/approve' \
207
+ -H 'X-Spree-API-Key: sk_xxx'
208
+
209
+ curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/exchanges/exch_xxx/receive' \
210
+ -H 'X-Spree-API-Key: sk_xxx'
211
+
212
+ curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/exchanges/exch_xxx/fulfill' \
213
+ -H 'X-Spree-API-Key: sk_xxx'
214
+ ```
215
+
216
+
188
217
  ## Reporting
189
218
 
190
219
  Because each one is its own record, you can query them directly:
191
220
 
221
+
192
222
  ```typescript Admin SDK
193
223
  // Everything awaiting action
194
- const pending = await adminClient.returns.list({ filter: { status_eq: 'approved' } })
224
+ const pending = await adminClient.returns.list({ status_eq: 'approved' })
195
225
 
196
226
  // Claims opened this month
197
227
  const claims = await adminClient.claims.list({
198
- filter: { created_at_gt: '2026-08-01' },
228
+ created_at_gt: '2026-08-01',
199
229
  })
200
230
  ```
201
231
 
232
+ ```bash cURL
233
+ curl 'https://api.mystore.com/api/v3/admin/returns?q[status_eq]=approved' \
234
+ -H 'X-Spree-API-Key: sk_xxx'
235
+
236
+ curl 'https://api.mystore.com/api/v3/admin/claims?q[created_at_gt]=2026-08-01' \
237
+ -H 'X-Spree-API-Key: sk_xxx'
238
+ ```
239
+
240
+
202
241
  ## Return policy
203
242
 
204
243
  Spree ships no built-in return window. Whether a customer may open a return is store policy, and it's checked when the return is created — so you can hold customers to a 30-day window while letting staff make an exception for a good customer, and vary the rule by market where local law requires it.
@@ -278,8 +278,7 @@ See [Querying](../../api-reference/store-api/querying.md) for the full list of f
278
278
 
279
279
  Spree uses a pluggable search provider architecture. The default provider uses SQL (ILIKE + Ransack). For production catalogs with 1,000+ products, we recommend switching to [Meilisearch](../../integrations/search/meilisearch.md) for typo tolerance, relevance ranking, and faster faceted search.
280
280
 
281
- ```ruby
282
- # config/initializers/spree.rb
281
+ ```ruby server/config/initializers/spree.rb
283
282
  Spree.search_provider = 'Spree::SearchProvider::Meilisearch'
284
283
  ```
285
284