Back to Spree

Admin API

docs/plans/6.0-admin-api.md

5.6.052.3 KB
Original Source

Admin API

Status: In Progress Target: Spree 6.0 Depends on: 6.0-admin-spa.md, 6.0-platform-auth.md Author: damian Last updated: 2026-06-25

Summary

A new REST API namespace (/api/v3/admin/*) that exposes every admin resource and operation currently served by the spree/admin Rails engine. Provides the programmatic backbone for the new React admin SPA, the TypeScript SDK, the CLI, and any other admin-facing tooling. Follows the same conventions as the Store API (flat JSON, prefixed IDs, Ransack filters, Pagy pagination, CanCanCan authorization) with a separate JWT audience and admin-scoped authentication.

Key Decisions (do not deviate without discussion)

  • All controllers inherit from Spree::Api::V3::Admin::ResourceController — no bespoke controllers unless absolutely necessary.
  • RESTful onlyPOST to create, PATCH to update, DELETE to destroy. No custom verbs except for state-machine actions (/cancel, /approve, etc.).
  • Admin serializers extend Store API serializers — inherit the public fields and add admin-only ones. Never duplicate attribute definitions.
  • Flat JSON for nested resources — no Rails nested attributes. Target audience is TypeScript developers, not Rails devs.
  • Prefixed IDs everywhere — requests and responses. Including association IDs.
  • Separate JWT audience (admin_api) — admin tokens MUST NOT be valid on the Store API and vice versa. Uses Spree.admin_user_class.
  • Two auth mechanisms, both already infrastructured:
    1. Secret API keys (spree_sk_xxx) via Authorization: Bearer for server-to-server
    2. JWT tokens via Authorization: Bearer for SPA sessions
  • Stripe-style error responses — same format as Store API (see docs/api-reference/store-api/errors.mdx).
  • Use existing services for business logic — cart create, order update, line item CRUD, etc. Only create new services when direct record.update! would embed non-trivial business rules in the controller.
  • Max 2 levels of nesting — e.g., /orders/:id/line_items/:id is fine, /orders/:id/shipments/:id/rates/:id is not. Flatten with filter params when necessary.
  • Attachments via multipart/form-data — ActiveStorage underneath. Product images use Spree::Asset (ActiveStorage + alt text + ordering + variant scoping).
  • Typelizer generates TypeScript types after every serializer change — part of the PR checklist; never merge a serializer change without regenerating.

Design Details

Controller hierarchy

text
Spree::Api::V3::BaseController           # shared concerns: pagination, Ransack, caching
  └── Spree::Api::V3::Admin::BaseController
        └── Spree::Api::V3::Admin::ResourceController  # default CRUD
              └── Spree::Api::V3::Admin::ProductsController
              └── Spree::Api::V3::Admin::OrdersController
              └── ...

ResourceController provides a default index/show/create/update/destroy implementation. Subclasses override model_class, serializer_class, and optionally scope, permitted_params, find_resource, scope_includes. Most admin controllers should fit in 30–80 lines.

Authentication

  • Spree::Api::V3::AdminAuthentication concern added to Admin::BaseController
  • Tries secret API key first, falls back to JWT with admin audience
  • Refresh tokens via Spree::RefreshToken — rotated on every refresh
  • /auth/login, /auth/refresh live on Admin::AuthController which skips authenticate_admin!
  • Returns { token, refresh_token, user } shape

Response format

  • Flat JSON, no root key wrapping ({ id: ..., name: ... } not { product: { id: ... } })
  • Collections return { data: [...], meta: { total_count, page, pages, limit } }
  • Prefixed IDs everywhere (prod_86Rf07xd4z, variant_k5nR8xLq)
  • ?expand=variants,variants.prices to include associations (max 4 levels)
  • ?q[name_cont]=shirt for Ransack filtering
  • ?sort=-created_at for sorting (JSON:API convention)

Example: product create/update

json
{
  "name": "Test product",
  "tax_category_id": "taxcat_abc1234",
  "taxon_ids": ["taxon_abc123", "taxon_def456"],
  "tags": ["eco", "best-seller"],
  "variants": [
    {
      "option_type": "size",
      "option_value": "small",
      "total_on_hand": 10,
      "track_inventory": true,
      "width": 10.5,
      "height": 5.2,
      "depth": 2.1,
      "weight": 0.5,
      "dimensions_unit": "cm",
      "weight_unit": "kg",
      "prices": [
        { "currency": "USD", "amount": 10.99 },
        { "currency": "EUR", "amount": 9.99 }
      ]
    }
  ]
}

Validation errors return 422 with:

json
{
  "error": {
    "code": "validation_error",
    "message": "Name can't be blank",
    "details": { "name": ["can't be blank"] }
  }
}

Example: order create with line items (one call)

json
{
  "currency": "USD",
  "locale": "en-US",
  "line_items": [
    { "variant_id": "variant_123456789", "quantity": 1 },
    { "variant_id": "variant_987654321", "quantity": 2 }
  ]
}

Permissions / GET /me

GET /api/v3/admin/me returns the current admin user plus a serialized list of their permissions (CanCanCan rules flattened). The SPA consumes this to drive UI permission checks; the server still enforces authorize! on every action.

json
{
  "user": { "id": "admin_xxx", "email": "...", ... },
  "permissions": [
    { "allow": true, "actions": ["manage"], "subjects": ["all"], "has_conditions": false },
    { "allow": false, "actions": ["destroy"], "subjects": ["Spree::Order"], "has_conditions": true }
  ]
}

Per-record conditions (CanCanCan blocks/hashes) are not serialized — only has_conditions: true as a hint. The SPA shows actions optimistically and handles 403 from the API.

Testing

  • Controller specs first; save integration/rswag specs for after the design solidifies per endpoint.
  • Serializer specs for attribute coverage — keep controller specs focused on behavior, not field-by-field assertions.
  • Lean and pragmatic. Cover known edge cases, skip trivial validations.

Migration Path

Infrastructure (done)

  • Base controllers, resource controller, admin authentication, JWT with admin audience, secret API key auth (with scopes), httpOnly refresh-cookie auth, prefixed IDs, error handling, Pagy, Ransack, CanCanCan scoping, per-action bulk-operation helper (Spree::Api::V3::BulkOperations), custom_fieldable route concern, Active Storage direct uploads — all in place.
  • Admin serializers exist for Product, Variant, Price, PriceList, Order, LineItem, Fulfillment, Payment, Refund, Adjustment, Customer, Address, CreditCard, StoreCredit, CustomerGroup, GiftCard, GiftCardBatch, Category, OptionType, OptionValue, Promotion, PromotionAction, PromotionRule, CouponCode, PaymentMethod, Channel, Market, Country, TaxCategory, StockLocation, StockItem, StockReservation, StockTransfer, Store, AdminUser, Role, Invitation, ApiKey, AllowedOrigin, WebhookEndpoint, WebhookDelivery, Export, CustomFieldDefinition, CustomField, Tag, StoreCreditCategory. More are added as endpoints come online.

Endpoint rollout order

  1. Authentication — login, refresh, logout, me ✅
  2. Products + Variants + Media + Options — highest-priority, hardest, nested ✅
  3. Orders + Items + Fulfillments + Payments + Adjustments + Refunds
  4. Customers + Addresses + Credit Cards + Store Credits + Customer Groups + Gift Cards
  5. Promotions + Rules + Actions + Coupon Codes
  6. Stock (Locations, Items, Reservations, Transfers) + Price Lists + Prices ✅ (Transfers lifecycle + Suppliers + Purchase Orders pending — see 6.0-inventory-operations.md)
  7. Categories ✅ (read) — Collections + write/reposition pending
  8. Store settings ✅, Channels ✅, Markets ✅, Tax Categories ✅, Admin users ✅, Roles ✅ (read), Invitations ✅, API keys ✅, Webhooks ✅, Allowed Origins ✅, Exports ✅, Dashboard ✅; Policies pending
  9. Custom Fields + Custom Field Definitions ✅ — Translations (polymorphic) pending
  10. Returns/Exchanges/Claims, Product Types, Collections, Marketplace (Vendors/Commission/Payouts), Delivery Methods/Zones, Tax Rates/Markets-as-tax, Payment Methods ✅ — remaining 6.0 model work; Catalogs/Companies (B2B) deferred to 6.1 per decisions.md 2026-06-16
  11. Reports, Imports, Integrations, Price History, Stock Movements — pending

PR process for each endpoint

  1. Add controller and route
  2. Add/extend admin serializer
  3. Run bundle exec rake typelizer:generate + pnpm generate:zod
  4. Add controller spec (+ serializer spec if new attributes)
  5. Add SDK resource in @spree/admin-sdk
  6. Mark endpoint [x] in this plan
  7. If public-facing: update OpenAPI spec with bundle exec rake rswag:specs:swaggerize

Constraints on Current Work

  • Never add endpoints outside Admin::ResourceController hierarchy without a written exception in this plan.
  • Never expose raw IDs — always prefixed.
  • Never use Rails nested attributes in the API layer.
  • Never duplicate a Store serializer — extend it instead.
  • Never merge a serializer change without running Typelizer.
  • Never create new services unless absolutely necessary; record.update is almost always enough.

Changes from 5.x to 6.0

This plan reflects the 6.0 model/naming landscape. Endpoints removed from 5.x or renamed are tracked in the Removed / Renamed endpoints table at the top so contributors don't accidentally re-implement them.

Removed / renamed endpoints

5.x endpoint6.0 endpointReason
/api/v3/admin/taxonomies/*removedSpree::Taxonomy dropped; Category belongs directly to store. See 6.0-replace-taxons-with-categories.md
/api/v3/admin/taxons/*/api/v3/admin/categories/*Spree::TaxonSpree::Category rename
/api/v3/admin/shipping_methods/*/api/v3/admin/delivery_methods/*ShippingMethodDeliveryMethod rename
/api/v3/admin/orders/:id/shipments/*/api/v3/admin/orders/:id/fulfillments/*ShipmentFulfillment rename
/api/v3/admin/shipping_categories/*removedReplaced by ProductType.fulfillment_types. See 6.0-fulfillment-and-delivery.md
/api/v3/admin/zones/*removedTax zones → direct country/state FKs on TaxRate. Shipping zones → new DeliveryZone. See 6.0-tax-provider.md, 6.0-fulfillment-and-delivery.md
/api/v3/admin/return_authorizations/*/api/v3/admin/returns/*ReturnAuthorizationReturn. See 6.0-returns-exchanges-claims.md
/api/v3/admin/customer_returns/*removedMerged into Return + ReturnItem
/api/v3/admin/orders/:id/reimbursements/*removedReplaced by direct Refund on Returns/Exchanges/Claims
/api/v3/admin/reimbursement_types/*removedConcept dropped
/api/v3/admin/prototypes/*/api/v3/admin/product_types/*PrototypeProductType rename. See 6.0-product-types.md
/api/v3/admin/metafield_definitions/*/api/v3/admin/custom_field_definitions/*MetafieldCustomField rename. See 5.4-6.0-custom-fields-rename.md
/api/v3/admin/orders/:id/adjustments/*/api/v3/admin/orders/:id/tax_lines, discounts, fees (read-only)Polymorphic Adjustment → typed TaxLine, Discount, Fee. See 6.0-split-adjustments.md
?expand=master_variant?expand=default_variantis_master dropped, default_variant_id FK. See 6.0-remove-master-variant.md
?expand=images?expand=mediaImage / AssetMedia. See 5.4-6.0-product-media-system.md

Field renames on request / response bodies

The following field names change across all endpoints. See 5.4-store-api-naming-standardization.md for the full list.

Model5.x6.0
Paymentstatestatus
Fulfillment (was Shipment)statestatus
Fulfillmentshipped_atfulfilled_at
FulfillmentItem (was InventoryUnit)statestatus
Return (was ReturnAuthorization)statestatus
GiftCardstatestatus
Ordershipment_statefulfillment_status
Orderpayment_statepayment_status
Ordershipment_totaldelivery_total
Orderpromo_totaldiscount_total
Orderitem_counttotal_quantity
Orderspecial_instructionscustomer_note
Orderbill_address_idbilling_address_id
Ordership_address_idshipping_address_id
LineItempromo_totaldiscount_total
Addressfirstnamefirst_name
Addresslastnamelast_name
Addresszipcodepostal_code
OptionType, OptionValue, CustomFieldDefinitionpresentationlabel
CreditCardcc_typebrand
CreditCardlast_digitslast4
CustomFieldDefinitiondisplay_on (string)storefront_visible (boolean)
CustomFieldDefinitionmetafield_typefield_type

Prefix ID changes

Also tracked in the SDK type definitions. See each linked plan for rationale.

Model5.x prefix6.0 prefix
Fulfillment (was Shipment)shipment_ful_
DeliveryMethod (was ShippingMethod)dm_
DeliveryRate (was ShippingRate)dr_
FulfillmentItem (was InventoryUnit)fi_
DeliveryZone (new)dz_
StockReservation (new)res_
StockMovementsm_
Return (was ReturnAuthorization)ret_
Exchange (new)exch_
Claim (new)claim_
Channel (new)ch_
Catalog (new)cat_
Company (new)comp_
CompanyLocation (new)cloc_
CompanyContact (new)cc_
ProductPublication (replaces StoreProduct)pp_
Collection (new)coll_
ProductType (was Prototype)proto_pt_
Customer (was User)user_cust_
AdminUseradmin_adm_
CustomField (was Metafield)mf_cf_
CustomFieldDefinition (was MetafieldDefinition)mfdef_cfdef_
Media (was Image/Asset)image_/asset_media_

Endpoint list

Each endpoint below is tracked as [ ] (pending) or [x] (shipped). When you implement one, flip the checkbox in the same PR. Endpoints are grouped by resource. Removed/renamed 5.x endpoints are NOT listed — see the table above.

Authentication

  • POST /api/v3/admin/auth/login — admin user login (JWT + httpOnly refresh cookie)
  • POST /api/v3/admin/auth/refresh — rotate refresh token (read from signed cookie), return new JWT
  • POST /api/v3/admin/auth/logout — invalidate JWT and refresh token, clear cookie
  • GET /api/v3/admin/me — current admin user + serialized permissions
  • PATCH /api/v3/admin/me — self-service profile updates (e.g. the admin's own UI language)
  • GET /api/v3/admin/auth/invitations/:id/lookup — public invitation lookup (prefixed ID + token in URL act as credential)
  • POST /api/v3/admin/auth/invitations/:id/accept — public invitation acceptance (sets password, issues tokens)

5.x → 6.0: Logout shipped as POST (not DELETE); the refresh token moved to an httpOnly signed cookie and is no longer returned in the login response body. See 5.5-admin-auth-cookie-refresh.md, 6.0-platform-auth.md.

Products (⭐ high priority)

  • GET /api/v3/admin/products — list (Ransack, sort, paginate)
  • GET /api/v3/admin/products/:id
  • POST /api/v3/admin/products — with nested variants, prices, category_ids
  • PATCH /api/v3/admin/products/:id
  • DELETE /api/v3/admin/products/:id — soft delete
  • POST /api/v3/admin/products/:id/clone
  • POST /api/v3/admin/products/bulk_status_update — bulk status change
  • POST /api/v3/admin/products/bulk_add_to_categories / bulk_remove_from_categories
  • POST /api/v3/admin/products/bulk_add_to_channels / bulk_remove_from_channels
  • POST /api/v3/admin/products/bulk_add_tags / bulk_remove_tags
  • DELETE /api/v3/admin/products/bulk_destroy

Bulk envelope decision (resolved): per-action bulk routes (not a single /bulk), bodies { ids: [...], ... }, shared via the Spree::Api::V3::BulkOperations concern. Resolves the "Bulk operations envelope" open question.

6.0 field changes: shipping_category_id is dropped (fulfillment types come from ProductType.fulfillment_types). prototype_idproduct_type_id. taxon_idscategory_ids. Product has default_variant_id FK (no more is_master).

Variants (nested under Products)

  • GET /api/v3/admin/products/:product_id/variants
  • GET /api/v3/admin/products/:product_id/variants/:id
  • POST /api/v3/admin/products/:product_id/variants — with nested prices
  • PATCH /api/v3/admin/products/:product_id/variants/:id
  • DELETE /api/v3/admin/products/:product_id/variants/:id

Variants (top-level — search / autocomplete across all products)

  • GET /api/v3/admin/variants — flat variant search (e.g. order line-item picker); supports custom_fields
  • GET /api/v3/admin/variants/:id

Prices

A standalone Spree::Price resource covers both base prices (price_list_id: nil) and price-list overrides, filtered via Ransack on index. (Variant create/update may still set prices inline via nested params, but prices are independently addressable.)

  • GET /api/v3/admin/prices — list (filter by variant, currency, price_list)
  • GET /api/v3/admin/prices/:id
  • POST /api/v3/admin/prices
  • PATCH /api/v3/admin/prices/:id
  • DELETE /api/v3/admin/prices/:id — soft delete
  • POST /api/v3/admin/prices/bulk_upsert — upsert on (variant_id, currency, price_list_id)
  • DELETE /api/v3/admin/prices/bulk_destroy

Product Media (nested under Products, replaces Assets)

  • GET /api/v3/admin/products/:product_id/media — list (filterable by media_type)
  • POST /api/v3/admin/products/:product_id/media — upload via multipart OR source_url param (async download). Params: media_type, alt, position, variant_ids
  • PATCH /api/v3/admin/products/:product_id/media/:id — update alt, position, variant assignment (variant_ids: [])
  • DELETE /api/v3/admin/products/:product_id/media/:id
  • GET|POST /api/v3/admin/products/:product_id/variants/:variant_id/media + PATCH|DELETE .../media/:id — variant-level media (legacy path retained in 5.5)

6.0: Media defaults to viewable_type: 'Spree::Product'; variant↔product-asset linking via the variant_ids array (Spree::VariantMedia join). expand=imagesexpand=media. See 5.4-6.0-product-media-system.md.

Digital Assets (nested under Products)

  • GET /api/v3/admin/products/:product_id/digital_assets
  • POST /api/v3/admin/products/:product_id/digital_assets
  • PATCH /api/v3/admin/products/:product_id/digital_assets/:id
  • DELETE /api/v3/admin/products/:product_id/digital_assets/:id

Option Types

  • GET /api/v3/admin/option_types
  • GET /api/v3/admin/option_types/:id
  • POST /api/v3/admin/option_types
  • PATCH /api/v3/admin/option_types/:id
  • DELETE /api/v3/admin/option_types/:id

Option Values

Managed via nested payload on the Option Type (option_values: [{ id, name, label, position, color_code, image, _destroy }]) — no standalone endpoints. Position drives drag-and-drop reorder; kind (dropdown/color_swatch/buttons) lives on the Option Type and color_code/image on each value. See 5.4-option-type-enhancements.md.

Product Types (renamed from Prototypes)

  • GET /api/v3/admin/product_types
  • GET /api/v3/admin/product_types/:id
  • POST /api/v3/admin/product_types
  • PATCH /api/v3/admin/product_types/:id
  • DELETE /api/v3/admin/product_types/:id

6.0: fulfillment_types JSON array column (['shipping'] default). Product types can require specific CustomFieldDefinitions via nested definition records. See 6.0-product-types.md.

Categories (renamed from Taxons; Taxonomy dropped)

  • GET /api/v3/admin/categories — list with tree metadata
  • GET /api/v3/admin/categories/:id
  • POST /api/v3/admin/categories
  • PATCH /api/v3/admin/categories/:id
  • DELETE /api/v3/admin/categories/:id
  • PATCH /api/v3/admin/categories/:id/reposition — reposition in tree

6.0: Spree::Category belongs directly to Spree::Store (no Taxonomy). ClassificationProductCategory. See 6.0-replace-taxons-with-categories.md.

Product Categories (nested under Categories — product assignments)

  • GET /api/v3/admin/categories/:category_id/products
  • POST /api/v3/admin/categories/:category_id/products — assign
  • PATCH /api/v3/admin/categories/:category_id/products/:product_id — update position
  • DELETE /api/v3/admin/categories/:category_id/products/:product_id — unassign

Collections (new in 6.0)

  • GET /api/v3/admin/collections
  • GET /api/v3/admin/collections/:id
  • POST /api/v3/admin/collections — with nested rules (automatic) or manual product list
  • PATCH /api/v3/admin/collections/:id
  • DELETE /api/v3/admin/collections/:id

6.0: Collections replace the "smart taxon" (automatic: true, rules_match_policy) feature. CollectionRule replaces TaxonRule. See 6.0-replace-taxons-with-categories.md.

Channels (new in 6.0) — shipped 5.5

  • GET /api/v3/admin/channels
  • GET /api/v3/admin/channels/:id
  • POST /api/v3/admin/channels
  • PATCH /api/v3/admin/channels/:id
  • DELETE /api/v3/admin/channels/:id
  • POST /api/v3/admin/channels/:id/add_products — bulk publish (product_ids, optional published_at/unpublished_at)
  • POST /api/v3/admin/channels/:id/remove_products — bulk unpublish (product_ids)

6.0: A Channel is a sales surface (POS, Storefront A, Marketplace, etc.). Channel#default resolves the store's default channel. See 6.0-channels-catalogs-b2b.md, 6.0-order-routing.md.

Product Publications (replaces StoreProduct)

Nested product_publications CRUD was cut — the SPA never used it and there's no industry precedent. Publication is managed through Channel member actions (/channels/:id/add_products, /remove_products) and the product-side bulk routes (/products/bulk_add_to_channels, /products/bulk_remove_from_channels). See 6.0-channels-catalogs-b2b.md.

Catalogs (new in 6.0, B2B)

  • GET /api/v3/admin/catalogs
  • GET /api/v3/admin/catalogs/:id
  • POST /api/v3/admin/catalogs
  • PATCH /api/v3/admin/catalogs/:id
  • DELETE /api/v3/admin/catalogs/:id
  • GET /api/v3/admin/catalogs/:id/products
  • POST /api/v3/admin/catalogs/:id/products — bulk assign
  • DELETE /api/v3/admin/catalogs/:id/products/:product_id
  • POST /api/v3/admin/catalogs/:id/assign — assign catalog to companies/locations

Companies (new in 6.0, B2B)

  • GET /api/v3/admin/companies
  • GET /api/v3/admin/companies/:id
  • POST /api/v3/admin/companies
  • PATCH /api/v3/admin/companies/:id
  • DELETE /api/v3/admin/companies/:id

Company Locations (nested under Companies)

  • GET /api/v3/admin/companies/:company_id/locations
  • POST /api/v3/admin/companies/:company_id/locations
  • GET /api/v3/admin/company_locations/:id
  • PATCH /api/v3/admin/company_locations/:id
  • DELETE /api/v3/admin/company_locations/:id

Company Contacts (nested under Company Locations)

  • GET /api/v3/admin/company_locations/:location_id/contacts
  • POST /api/v3/admin/company_locations/:location_id/contacts
  • PATCH /api/v3/admin/company_contacts/:id
  • DELETE /api/v3/admin/company_contacts/:id

Orders (⭐ high priority)

  • GET /api/v3/admin/orders
  • GET /api/v3/admin/orders/:id
  • POST /api/v3/admin/orders — create draft
  • PATCH /api/v3/admin/orders/:id
  • DELETE /api/v3/admin/orders/:id
  • PATCH /api/v3/admin/orders/:id/complete
  • PATCH /api/v3/admin/orders/:id/cancel
  • PATCH /api/v3/admin/orders/:id/approve
  • PATCH /api/v3/admin/orders/:id/resume
  • POST /api/v3/admin/orders/:id/resend_confirmation

6.0: Order state machine dropped (derived status from line items + fulfillments + payments — see 6.0-cart-order-split.md). PATCH /next and /advance no longer apply and have been removed from the 5.x admin API ahead of the 6.0 cart/order split. Cart and Order are separate models; LineItem.owner is polymorphic. Order gains channel_id, company_location_id, withdrawal_eligible_until FKs.

Items (nested under Orders — matches Store API /carts/:id/items convention)

  • GET /api/v3/admin/orders/:order_id/items
  • GET /api/v3/admin/orders/:order_id/items/:id
  • POST /api/v3/admin/orders/:order_id/items
  • PATCH /api/v3/admin/orders/:order_id/items/:id
  • DELETE /api/v3/admin/orders/:order_id/items/:id

Fulfillments (nested under Orders; replaces Shipments)

  • GET /api/v3/admin/orders/:order_id/fulfillments
  • GET /api/v3/admin/orders/:order_id/fulfillments/:id
  • POST /api/v3/admin/orders/:order_id/fulfillments — manual fulfillment creation (external carrier / 3PL sync): moves not-yet-shipped units out of routed fulfillments, bypassing order routing; optional items, delivery_method_id, tracking, status: 'shipped'
  • PATCH /api/v3/admin/orders/:order_id/fulfillments/:id
  • PATCH /api/v3/admin/orders/:order_id/fulfillments/:id/fulfill — mark as fulfilled
  • PATCH /api/v3/admin/orders/:order_id/fulfillments/:id/cancel
  • PATCH /api/v3/admin/orders/:order_id/fulfillments/:id/resume
  • PATCH /api/v3/admin/orders/:order_id/fulfillments/:id/split

6.0: Third-party fulfillment via FulfillmentProvider interface. pickup_point_data JSON snapshot for pickup points. See 6.0-fulfillment-and-delivery.md.

Payments (nested under Orders)

  • GET /api/v3/admin/orders/:order_id/payments
  • GET /api/v3/admin/orders/:order_id/payments/:id
  • POST /api/v3/admin/orders/:order_id/payments — supports off-session admin charges via source_id (saved Spree::CreditCard belonging to the order's customer)
  • PATCH /api/v3/admin/orders/:order_id/payments/:id/capture
  • PATCH /api/v3/admin/orders/:order_id/payments/:id/void

Off-session charges: pass { payment_method_id, source_id, amount } to charge a saved card. There is no separate "PaymentSession" endpoint on the admin side — the cart/store API has payment_sessions for SCA/3DS storefront flows; admin-initiated charges use saved sources directly.

Refunds (nested under Orders)

  • GET /api/v3/admin/orders/:order_id/refunds
  • POST /api/v3/admin/orders/:order_id/refunds — with payment_id in body
  • PATCH /api/v3/admin/orders/:order_id/refunds/:id

Adjustments (nested under Orders, read-only in 5.x; replaced by typed endpoints in 6.0)

  • GET /api/v3/admin/orders/:order_id/adjustments
  • GET /api/v3/admin/orders/:order_id/adjustments/:id

5.x → 6.0: The 5.x admin API exposes /adjustments as read-only (no create/update/destroy) so client integrations don't depend on a write surface that is going away. Manual adjustment management has been removed. In 6.0, this endpoint is replaced by the three typed endpoints below.

Tax Lines (nested under Orders, read-only; replaces Adjustments in 6.0)

  • GET /api/v3/admin/orders/:order_id/tax_lines

Discounts (nested under Orders, read-only; replaces Adjustments in 6.0)

  • GET /api/v3/admin/orders/:order_id/discounts

Fees (nested under Orders, read-only; replaces Adjustments in 6.0)

  • GET /api/v3/admin/orders/:order_id/fees

6.0: Polymorphic Spree::Adjustment is dropped. Tax, discount, and fee data is returned via typed, read-only endpoints. See 6.0-split-adjustments.md.

Order Customer Assignment

Customer assignment is handled via the standard PATCH /api/v3/admin/orders/:id endpoint with customer_id in the body. No dedicated endpoint. Pass customer_id: null to unassign.

  • PATCH /api/v3/admin/orders/:id { customer_id } — assign / reassign / unassign customer (5.x bridge: also accepts user_id)

Order Gift Cards (nested under Orders)

  • POST /api/v3/admin/orders/:order_id/gift_cards { code } — apply a gift card by code
  • DELETE /api/v3/admin/orders/:order_id/gift_cards/:id — remove the applied gift card

Order Store Credits (nested under Orders)

  • POST /api/v3/admin/orders/:order_id/store_credits { amount? } — apply customer's available store credit (auto up to balance, or a specific amount)
  • DELETE /api/v3/admin/orders/:order_id/store_credits — remove store credit application

Order Discounts (promotion application)

  • POST /api/v3/admin/orders/:order_id/discounts — apply promotion or coupon
  • DELETE /api/v3/admin/orders/:order_id/discounts/:id — remove

Returns (new in 6.0)

  • GET /api/v3/admin/returns
  • GET /api/v3/admin/orders/:order_id/returns
  • GET /api/v3/admin/returns/:id
  • POST /api/v3/admin/orders/:order_id/returns
  • PATCH /api/v3/admin/returns/:id
  • DELETE /api/v3/admin/returns/:id
  • POST /api/v3/admin/returns/:id/approve
  • POST /api/v3/admin/returns/:id/receive
  • POST /api/v3/admin/returns/:id/refund

Return Items (nested under Returns)

  • PATCH /api/v3/admin/returns/:return_id/items/:id — reception + acceptance status

Exchanges (new in 6.0)

  • GET /api/v3/admin/exchanges
  • GET /api/v3/admin/orders/:order_id/exchanges
  • GET /api/v3/admin/exchanges/:id
  • POST /api/v3/admin/orders/:order_id/exchanges
  • PATCH /api/v3/admin/exchanges/:id
  • POST /api/v3/admin/exchanges/:id/approve
  • POST /api/v3/admin/exchanges/:id/receive
  • POST /api/v3/admin/exchanges/:id/fulfill

Claims (new in 6.0)

  • GET /api/v3/admin/claims
  • GET /api/v3/admin/orders/:order_id/claims
  • GET /api/v3/admin/claims/:id
  • POST /api/v3/admin/orders/:order_id/claims
  • PATCH /api/v3/admin/claims/:id
  • POST /api/v3/admin/claims/:id/approve
  • POST /api/v3/admin/claims/:id/resolve

6.0: Returns, Exchanges, and Claims are first-class models. Replaces the ReturnAuthorization → CustomerReturn → Reimbursement chain. See 6.0-returns-exchanges-claims.md.

Customers (users) — shipped 5.5

  • GET /api/v3/admin/customers
  • GET /api/v3/admin/customers/:id
  • POST /api/v3/admin/customers
  • PATCH /api/v3/admin/customers/:id
  • DELETE /api/v3/admin/customers/:id
  • POST /api/v3/admin/customers/bulk_add_to_groups / bulk_remove_from_groups
  • POST /api/v3/admin/customers/bulk_add_tags / bulk_remove_tags
  • POST /api/v3/admin/customers/:id/anonymize — GDPR right to erasure (Phase 2)
  • GET /api/v3/admin/customers/:id/export — GDPR data export, returns JSON download URL (Phase 2)

6.0: Spree.user_classSpree.customer_class. Spree::LegacyUserSpree::Customer. Customers + nested addresses/credit_cards/store_credits + CustomerGroups shipped in 5.5 (5.5-admin-customers-api.md). Anonymize + export endpoints are Phase 2 GDPR (5.4-6.0-eu-legal-compliance.md); customer group membership is managed via the customer bulk routes above, not nested members endpoints.

Customer Addresses (nested under Customers) — shipped 5.5

  • GET /api/v3/admin/customers/:customer_id/addresses
  • GET /api/v3/admin/customers/:customer_id/addresses/:id
  • POST /api/v3/admin/customers/:customer_id/addresses
  • PATCH /api/v3/admin/customers/:customer_id/addresses/:id
  • DELETE /api/v3/admin/customers/:customer_id/addresses/:id

Default-address handling via is_default_billing / is_default_shipping flags on the address PATCH (rotates the previous default in a transaction) — no separate FK write target.

Customer Credit Cards (nested under Customers, read + delete) — shipped 5.5

  • GET /api/v3/admin/customers/:customer_id/credit_cards
  • GET /api/v3/admin/customers/:customer_id/credit_cards/:id
  • DELETE /api/v3/admin/customers/:customer_id/credit_cards/:id

No create/update — cards are tokenized through the storefront/SCA flow; admin can only view and remove them. cc_typebrand, last_digitslast4.

Store Credits (nested under Customers) — shipped 5.5

  • GET /api/v3/admin/customers/:customer_id/store_credits
  • GET /api/v3/admin/customers/:customer_id/store_credits/:id
  • POST /api/v3/admin/customers/:customer_id/store_credits
  • PATCH /api/v3/admin/customers/:customer_id/store_credits/:id
  • DELETE /api/v3/admin/customers/:customer_id/store_credits/:id

Gift Cards

  • GET /api/v3/admin/gift_cards
  • GET /api/v3/admin/gift_cards/:id
  • POST /api/v3/admin/gift_cards
  • PATCH /api/v3/admin/gift_cards/:id
  • DELETE /api/v3/admin/gift_cards/:id
  • GET /api/v3/admin/gift_card_batches
  • GET /api/v3/admin/gift_card_batches/:id
  • POST /api/v3/admin/gift_card_batches — batch create

Customer Groups — shipped 5.5

  • GET /api/v3/admin/customer_groups
  • GET /api/v3/admin/customer_groups/:id
  • POST /api/v3/admin/customer_groups
  • PATCH /api/v3/admin/customer_groups/:id
  • DELETE /api/v3/admin/customer_groups/:id

Customer Group Members

Nested members CRUD was not built — membership is managed from the customer side via POST /api/v3/admin/customers/bulk_add_to_groups / bulk_remove_from_groups (body: { ids: [...], customer_group_ids: [...] }). Group contents are read via GET /api/v3/admin/customers?q[customer_group_members_customer_group_id_eq]=....

Newsletter Subscribers

  • GET /api/v3/admin/newsletter_subscribers
  • DELETE /api/v3/admin/newsletter_subscribers/:id

Tax Exemption Certificates (nested under Companies; B2B)

  • GET /api/v3/admin/companies/:company_id/tax_exemption_certificates
  • POST /api/v3/admin/companies/:company_id/tax_exemption_certificates
  • PATCH /api/v3/admin/tax_exemption_certificates/:id
  • DELETE /api/v3/admin/tax_exemption_certificates/:id

Promotions

  • GET /api/v3/admin/promotions
  • GET /api/v3/admin/promotions/:id
  • POST /api/v3/admin/promotions
  • PATCH /api/v3/admin/promotions/:id
  • DELETE /api/v3/admin/promotions/:id
  • POST /api/v3/admin/promotions/:id/clone

Promotion Actions (nested under Promotions)

  • GET /api/v3/admin/promotions/:promotion_id/promotion_actions
  • GET /api/v3/admin/promotions/:promotion_id/promotion_actions/:id
  • POST /api/v3/admin/promotions/:promotion_id/promotion_actions
  • PATCH /api/v3/admin/promotions/:promotion_id/promotion_actions/:id
  • DELETE /api/v3/admin/promotions/:promotion_id/promotion_actions/:id
  • GET /api/v3/admin/promotion_actions/types — registered action subclasses + preference schemas (for the "Add action" picker)
  • GET /api/v3/admin/promotion_actions/calculators — registered calculator subclasses + preference schemas for CalculatedAdjustments actions

Promotion Rules (nested under Promotions)

  • GET /api/v3/admin/promotions/:promotion_id/promotion_rules
  • GET /api/v3/admin/promotions/:promotion_id/promotion_rules/:id
  • POST /api/v3/admin/promotions/:promotion_id/promotion_rules
  • PATCH /api/v3/admin/promotions/:promotion_id/promotion_rules/:id
  • DELETE /api/v3/admin/promotions/:promotion_id/promotion_rules/:id
  • GET /api/v3/admin/promotion_rules/types — registered rule subclasses + preference schemas (for the "Add rule" picker)

Coupon Codes (nested under Promotions, read-only)

  • GET /api/v3/admin/promotions/:promotion_id/coupon_codes
  • GET /api/v3/admin/promotions/:promotion_id/coupon_codes/:id

Stock Locations

  • GET /api/v3/admin/stock_locations
  • GET /api/v3/admin/stock_locations/:id
  • POST /api/v3/admin/stock_locations
  • PATCH /api/v3/admin/stock_locations/:id
  • DELETE /api/v3/admin/stock_locations/:id

Stock Items

  • GET /api/v3/admin/stock_items — filterable by stock_location, variant
  • GET /api/v3/admin/stock_items/:id
  • PATCH /api/v3/admin/stock_items/:id
  • DELETE /api/v3/admin/stock_items/:id

Stock Movements (read-only; typed in 6.0)

  • GET /api/v3/admin/stock_movements — filterable by kind, fulfillment_id, order_id, return_id, stock_transfer_id

6.0: StockMovement gains kind enum (received, allocated, shipped, released, adjusted) + concrete FKs to the originating event. See 6.0-typed-stock-movements.md.

Stock Reservations (read-only; new in 6.0)

  • GET /api/v3/admin/stock_reservations — filterable by variant, order, cart
  • GET /api/v3/admin/stock_reservations/:id
  • DELETE /api/v3/admin/stock_reservations/:id — manual release

6.0: Time-limited reservations during checkout. See 6.0-stock-reservations.md.

Stock Transfers

  • GET /api/v3/admin/stock_transfers
  • GET /api/v3/admin/stock_transfers/:id
  • POST /api/v3/admin/stock_transfers
  • DELETE /api/v3/admin/stock_transfers/:id — draft only
  • PATCH /api/v3/admin/stock_transfers/:id
  • POST /api/v3/admin/stock_transfers/:id/mark_ready
  • POST /api/v3/admin/stock_transfers/:id/mark_in_transit
  • POST /api/v3/admin/stock_transfers/:id/receive — body { items: [{ id, quantity_received, discrepancy_reason }] } (partial receive)
  • POST /api/v3/admin/stock_transfers/:id/cancel

6.0: Transfer lifecycle (draft → ready_to_ship → in_transit → received) + partial receive are pending. See 6.0-inventory-operations.md.

Suppliers (new in 6.0)

  • GET /api/v3/admin/suppliers
  • GET /api/v3/admin/suppliers/:id
  • POST /api/v3/admin/suppliers
  • PATCH /api/v3/admin/suppliers/:id
  • DELETE /api/v3/admin/suppliers/:id

6.0: Spree::Supplier is the procurement source for purchase orders (see 6.0-inventory-operations.md). Not to be confused with marketplace Vendors below — Spree::Vendor is the marketplace seller (6.0-multi-vendor-marketplace.md, decisions.md 2026-07-14).

Purchase Orders (new in 6.0)

  • GET /api/v3/admin/purchase_orders
  • GET /api/v3/admin/purchase_orders/:id
  • POST /api/v3/admin/purchase_orders
  • PATCH /api/v3/admin/purchase_orders/:id
  • DELETE /api/v3/admin/purchase_orders/:id — draft only
  • POST /api/v3/admin/purchase_orders/:id/mark_ordered
  • POST /api/v3/admin/purchase_orders/:id/receive — body { items: [{ id, quantity_received }] }
  • POST /api/v3/admin/purchase_orders/:id/cancel

6.0: Spree::PurchaseOrder + Spree::Supplier replace today's "external receive" hack. See 6.0-inventory-operations.md.

Marketplace (new in 6.0)

Per 6.0-multi-vendor-marketplace.md. Vendor prefix is ven_ (matches the legacy Enterprise gem).

  • GET /api/v3/admin/vendors
  • GET /api/v3/admin/vendors/:id
  • POST /api/v3/admin/vendors
  • PATCH /api/v3/admin/vendors/:id
  • DELETE /api/v3/admin/vendors/:id
  • POST /api/v3/admin/vendors/:id/invite — (re)send the vendor invitation (Spree::Invitation, resource: vendor)
  • POST /api/v3/admin/vendors/:id/approve / POST /api/v3/admin/vendors/:id/reject / POST /api/v3/admin/vendors/:id/suspend — lifecycle transitions (approve also un-suspends)
  • GET /api/v3/admin/commission_rates
  • GET /api/v3/admin/commission_rates/:id
  • POST /api/v3/admin/commission_rates
  • PATCH /api/v3/admin/commission_rates/:id — saves rate fields + nested rules: [] in one round-trip (PriceList-style)
  • DELETE /api/v3/admin/commission_rates/:id
  • GET /api/v3/admin/commission_lines — read-only snapshot ledger (filter by vendor/order)
  • GET /api/v3/admin/vendor_transfers — read-only earnings ledger (filter by vendor/payout/status)
  • GET /api/v3/admin/vendor_payouts / GET /:id
  • POST /api/v3/admin/vendor_payouts/:id/complete — manual settlement (system provider)
  • GET /api/v3/admin/order_groups / GET /:id — the multi-order transaction container

API key scopes: read_vendors / write_vendors, read_commissions / write_commissions, read_payouts / write_payouts.

Price Lists

  • GET /api/v3/admin/price_lists
  • GET /api/v3/admin/price_lists/:id
  • POST /api/v3/admin/price_lists
  • PATCH /api/v3/admin/price_lists/:id — saves name/schedule/match policy + product_ids: [] + nested rules: [] + prices: [] in one round-trip
  • DELETE /api/v3/admin/price_lists/:id
  • PATCH /api/v3/admin/price_lists/:id/activate — draft|inactive → active (or scheduled when starts_at is in the future)
  • PATCH /api/v3/admin/price_lists/:id/deactivate
  • GET /api/v3/admin/price_lists/:id/prices — spreadsheet data feed (filter by ?currency=)
  • GET /api/v3/admin/price_lists/price_rule_types — registered rule subclasses + preference schemas (for the "Add rule" picker)

Price Rules / Price List Products

No standalone nested endpoints. Price-list rules ride along on the price list's PATCH body (rules: [{ id, type, preferences }]); product membership rides along as product_ids: [...]. Individual price overrides flow through the standalone /prices resource (or the price list's prices: [] payload). See the Prices section above.

Price History (read-only; EU Omnibus compliance)

  • GET /api/v3/admin/products/:product_id/price_history — per-variant history for Omnibus "lowest price in 30 days"

6.0: See 5.4-6.0-eu-legal-compliance.md.

Payment Methods

  • GET /api/v3/admin/payment_methods
  • GET /api/v3/admin/payment_methods/:id
  • POST /api/v3/admin/payment_methods
  • PATCH /api/v3/admin/payment_methods/:id
  • DELETE /api/v3/admin/payment_methods/:id
  • GET /api/v3/admin/payment_methods/types — registered gateway subclasses + preference schemas (for the "Add payment method" picker)

Delivery Methods (replaces Shipping Methods)

  • GET /api/v3/admin/delivery_methods
  • GET /api/v3/admin/delivery_methods/:id
  • POST /api/v3/admin/delivery_methods
  • PATCH /api/v3/admin/delivery_methods/:id
  • DELETE /api/v3/admin/delivery_methods/:id

6.0: No more calculator_id. New fields: rate_provider (class name), fulfillment_provider (class name), pickup_point_provider (for third-party pickup), fulfillment_type (enum: shipping, pickup, pickup_point, digital, local_delivery). See 6.0-delivery-rate-provider.md, 6.0-fulfillment-and-delivery.md.

Delivery Zones (new in 6.0, replaces shipping Zones)

  • GET /api/v3/admin/delivery_zones
  • GET /api/v3/admin/delivery_zones/:id
  • POST /api/v3/admin/delivery_zones
  • PATCH /api/v3/admin/delivery_zones/:id
  • DELETE /api/v3/admin/delivery_zones/:id

Delivery Zone Members (nested)

  • GET /api/v3/admin/delivery_zones/:zone_id/members
  • POST /api/v3/admin/delivery_zones/:zone_id/members
  • DELETE /api/v3/admin/delivery_zones/:zone_id/members/:id

Tax Categories

  • GET /api/v3/admin/tax_categories
  • GET /api/v3/admin/tax_categories/:id
  • POST /api/v3/admin/tax_categories
  • PATCH /api/v3/admin/tax_categories/:id
  • DELETE /api/v3/admin/tax_categories/:id

Tax Rates

  • GET /api/v3/admin/tax_rates
  • GET /api/v3/admin/tax_rates/:id
  • POST /api/v3/admin/tax_rates
  • PATCH /api/v3/admin/tax_rates/:id
  • DELETE /api/v3/admin/tax_rates/:id

6.0: zone_id FK is dropped. TaxRate gets direct country_id + state_id FKs. Tax logic moves behind a TaxProvider interface. See 6.0-tax-provider.md.

Markets

  • GET /api/v3/admin/markets
  • GET /api/v3/admin/markets/:id
  • POST /api/v3/admin/markets
  • PATCH /api/v3/admin/markets/:id
  • DELETE /api/v3/admin/markets/:id

Countries

  • GET /api/v3/admin/countries — supports ?expand=states for state/province dropdowns
  • GET /api/v3/admin/countries/:id

Store Settings

  • GET /api/v3/admin/store
  • PATCH /api/v3/admin/store

Policies

  • GET /api/v3/admin/policies
  • GET /api/v3/admin/policies/:id
  • POST /api/v3/admin/policies
  • PATCH /api/v3/admin/policies/:id
  • DELETE /api/v3/admin/policies/:id

Admin Users

  • GET /api/v3/admin/admin_users
  • GET /api/v3/admin/admin_users/:id
  • PATCH /api/v3/admin/admin_users/:id
  • DELETE /api/v3/admin/admin_users/:id

No POST /admin_users — admin users are created by accepting an invitation (/auth/invitations/:id/accept), not by direct creation. 6.0: Spree::LegacyAdminUserSpree::AdminUser. Owns auth stack (no Devise). See 6.0-platform-auth.md.

Roles

  • GET /api/v3/admin/roles — read-only (feeds role pickers on invitations + admin users)
  • GET /api/v3/admin/roles/:id
  • POST /api/v3/admin/roles
  • PATCH /api/v3/admin/roles/:id
  • DELETE /api/v3/admin/roles/:id

Custom-role CRUD is deferred; roles are currently seeded/predefined and exposed read-only.

Invitations

  • GET /api/v3/admin/invitations
  • GET /api/v3/admin/invitations/:id
  • POST /api/v3/admin/invitations
  • DELETE /api/v3/admin/invitations/:id
  • PATCH /api/v3/admin/invitations/:id/resend

Public acceptance endpoints (GET/POST /auth/invitations/:id/...) live under Authentication above.

Allowed Origins (CORS allowlist)

  • GET /api/v3/admin/allowed_origins
  • GET /api/v3/admin/allowed_origins/:id
  • POST /api/v3/admin/allowed_origins
  • PATCH /api/v3/admin/allowed_origins/:id
  • DELETE /api/v3/admin/allowed_origins/:id

Direct Uploads (Active Storage)

  • POST /api/v3/admin/direct_uploads — pre-signed blob upload helper for media/import attachments

Custom Field Definitions (renamed from Metafield Definitions)

  • GET /api/v3/admin/custom_field_definitions
  • GET /api/v3/admin/custom_field_definitions/:id
  • POST /api/v3/admin/custom_field_definitions
  • PATCH /api/v3/admin/custom_field_definitions/:id
  • DELETE /api/v3/admin/custom_field_definitions/:id

6.0: display_onstorefront_visible (boolean). metafield_typefield_type. See 5.4-6.0-custom-fields-rename.md.

Custom Fields (nested under each custom-fieldable resource)

Mounted via the custom_fieldable route concern — custom_fields is nested under each parent that includes Spree::Metafields, not a single flat /:resource_type/:resource_id/custom_fields route. Resolves the "Polymorphic custom fields routing" open question.

  • GET|POST /api/v3/admin/products/:product_id/custom_fields + PATCH|DELETE .../custom_fields/:id
  • …/variants/:variant_id/custom_fields
  • …/categories/:category_id/custom_fields
  • …/option_types/:option_type_id/custom_fields
  • …/customers/:customer_id/custom_fields
  • …/orders/:order_id/custom_fields

New parents get custom-field endpoints for free by adding concerns: :custom_fieldable to their route + including Spree::Metafields on the model.

Translations (polymorphic, nested under translatable resources)

  • GET /api/v3/admin/:resource_type/:resource_id/translations
  • PATCH /api/v3/admin/:resource_type/:resource_id/translations

Tags

  • GET /api/v3/admin/tags — autocomplete for product/order/customer tag inputs
  • POST /api/v3/admin/tags
  • DELETE /api/v3/admin/tags/:id

Tags are created/removed implicitly through their taggable resources (e.g. products/bulk_add_tags, the tags: [] param on a product). Standalone create/destroy is not exposed.

Refund Reasons

  • GET /api/v3/admin/refund_reasons
  • GET /api/v3/admin/refund_reasons/:id
  • POST /api/v3/admin/refund_reasons
  • PATCH /api/v3/admin/refund_reasons/:id
  • DELETE /api/v3/admin/refund_reasons/:id

Return Reasons (renamed from Return Authorization Reasons)

  • GET /api/v3/admin/return_reasons
  • GET /api/v3/admin/return_reasons/:id
  • POST /api/v3/admin/return_reasons
  • PATCH /api/v3/admin/return_reasons/:id
  • DELETE /api/v3/admin/return_reasons/:id

Store Credit Categories

  • GET /api/v3/admin/store_credit_categories — read-only (feeds the store-credit category dropdown)
  • GET /api/v3/admin/store_credit_categories/:id
  • POST /api/v3/admin/store_credit_categories
  • PATCH /api/v3/admin/store_credit_categories/:id
  • DELETE /api/v3/admin/store_credit_categories/:id

Webhook Endpoints

  • GET /api/v3/admin/webhook_endpoints
  • GET /api/v3/admin/webhook_endpoints/:id
  • POST /api/v3/admin/webhook_endpoints
  • PATCH /api/v3/admin/webhook_endpoints/:id
  • DELETE /api/v3/admin/webhook_endpoints/:id
  • POST /api/v3/admin/webhook_endpoints/:id/send_test — fire a test delivery
  • PATCH /api/v3/admin/webhook_endpoints/:id/enable / disable

Webhook Deliveries (nested under Webhook Endpoints, read-only)

  • GET /api/v3/admin/webhook_endpoints/:webhook_endpoint_id/deliveries
  • GET /api/v3/admin/webhook_endpoints/:webhook_endpoint_id/deliveries/:id
  • POST /api/v3/admin/webhook_endpoints/:webhook_endpoint_id/deliveries/:id/redeliver

API Keys

  • GET /api/v3/admin/api_keys
  • GET /api/v3/admin/api_keys/:id
  • GET /api/v3/admin/api_keys/current — the key the request authenticated with
  • POST /api/v3/admin/api_keys
  • PATCH /api/v3/admin/api_keys/:id
  • DELETE /api/v3/admin/api_keys/:id
  • PATCH /api/v3/admin/api_keys/:id/revoke

Keys carry Shopify-style read_*/write_* scopes. See 5.5-admin-api-key-scopes.md.

Integrations

  • GET /api/v3/admin/integrations
  • GET /api/v3/admin/integrations/:id
  • POST /api/v3/admin/integrations
  • PATCH /api/v3/admin/integrations/:id
  • DELETE /api/v3/admin/integrations/:id

6.0: Integration records back the TaxProvider, DeliveryRateProvider, FulfillmentProvider, and PickupPointProvider interfaces (along with external search, email, etc.).

Reports

  • GET /api/v3/admin/reports
  • GET /api/v3/admin/reports/:id
  • POST /api/v3/admin/reports — generate

Exports

  • GET /api/v3/admin/exports
  • GET /api/v3/admin/exports/:id — includes done, download_url, filename, byte_size
  • POST /api/v3/admin/exports — type Spree::Exports::{Products,Orders,Customers} + optional search_params / record_selection
  • DELETE /api/v3/admin/exports/:id
  • GET /api/v3/admin/exports/:id/download — redirect to the generated file

No PATCH — exports are immutable once created. See 5.5-admin-spa-csv-export.md.

Imports

  • GET /api/v3/admin/imports
  • GET /api/v3/admin/imports/:id — status, row counts
  • POST /api/v3/admin/imports — multipart/form-data
  • PATCH /api/v3/admin/imports/:id/complete_mapping

Dashboard

  • GET /api/v3/admin/dashboard/analytics

Resolved Questions

  • Bulk operations envelope — ✅ resolved: per-action bulk routes (bulk_status_update, bulk_add_to_categories, bulk_add_to_channels, bulk_add_tags, bulk_destroy, prices/bulk_upsert, customers/bulk_add_to_groups, …), bodies { ids: [...], ... }, shared via the Spree::Api::V3::BulkOperations concern. No single /bulk envelope.
  • Order customer assignment — ✅ resolved: PATCH /orders/:id { customer_id } (5.x bridge also accepts user_id). No dedicated sub-resource.
  • Polymorphic custom fields routing — ✅ resolved: custom_fields mounted via the custom_fieldable route concern under each parent resource, not a flat /:resource_type/:resource_id/custom_fields route.

Open Questions

  • Translations endpoint — one PATCH for all locales vs. one PATCH per locale? Still open; the polymorphic translations endpoints are not yet built. See 5.4-centralized-translations-admin.md, 5.4-metafield-translations.md.
  • Per-action bulk route sprawl — the per-action decision is settled, but the route table is now large. Revisit whether a documented ?action= discriminator on one route per resource would read better once the full set is in.

References

  • docs/api-reference/store-api/errors.mdx — error response conventions (shared)
  • docs/plans/6.0-admin-spa.md — consumer of this API
  • docs/plans/6.0-platform-auth.md — auth stack and Spree.admin_user_class rename
  • docs/plans/5.4-6.0-custom-fields-rename.md — Metafields → Custom Fields
  • docs/plans/6.0-fulfillment-and-delivery.md — shipping category dropped