skillZs
★ LIVE SKILL TAGS ★
>>> LIVE SKILLS INDEX <<<
* OPEN SOURCE *
NO LOGIN, NO TRACKING
※ REAL INSTALL DATA ※
← back to all skills
spree/agent-skills125 installs

spree-api-v3

Use when the user is calling or debugging Spree 6's REST API v3 — the Store API (storefronts, mobile apps, customers), the Admin API (integrations, back-office tools) or the Seller API (marketplace sellers). Covers auth (publishable/secret keys, JWT audiences), scopes and permissions, prefixed IDs, the cart → order split, checkout `requirements[]`, order statuses, the `{data, meta}` envelope, expand/fields, Ransack filters, errors, idempotency, localization headers and rate limits. Common phrasings include "Spree API", "Store API", "Admin API", "publishable key", "secret key", "X-Spree-Api-Key", "X-Spree-Token", "API scopes", "403 required_scope", "cart_ vs or_", "complete cart", "cart 404 after checkout", "prefixed IDs", "expand", "Idempotency-Key", "429 rate limit", "v3 endpoint". For ADDING a new endpoint use spree-resource; for roles/permissions internals use spree-auth-permissions.

How do I install this agent skill?

npx skills add https://github.com/spree/agent-skills --skill spree-api-v3
view source ↗

Is this agent skill safe to install?

  • Gen Agent Trust Hubpass

    The skill provides comprehensive documentation and code examples for interacting with the Spree API v3. It covers authentication, data structures, and filtering without introducing any security risks. The examples use dummy credentials consistent with vendor documentation.

  • Socketpass

    No alerts

  • Snykwarn

    Risk: MEDIUM · 1 issue

What does this agent skill do?

Spree API v3

Spree 6 has three REST surfaces under /api/v3/, and they share one set of conventions:

SurfacePathCallerAuthAuthorization
Store API/api/v3/store/*Storefronts, mobile apps, shoppersX-Spree-Api-Key: pk_… + optional Authorization: Bearer <jwt> (aud: store_api) + guest X-Spree-TokenOwnership (see spree-auth-permissions)
Admin API/api/v3/admin/*Integrations, back-office apps, the dashboardX-Spree-Api-Key: sk_… or Authorization: Bearer <jwt> (aud: admin_api)Key scopes / role permission keys
Seller API/api/v3/seller/*Marketplace seller panelSeller JWT (aud: seller_api) + X-Spree-Seller-IdSeller role keys + current_seller scoping (see spree-marketplace)

A token issued for one audience is rejected on the other surfaces. The v2 Storefront/Platform APIs don't exist in Spree 6. If you're migrating an old integration, see node_modules/@spree/docs/dist/api-reference/store-api/migrating-from-storefront-api-v2.md.

Store API

  • Anonymous browsing needs only X-Spree-Api-Key: pk_…. Publishable keys are safe in client code.
  • Guest cart/checkout/order: also send X-Spree-Token: <cart.token>.
  • Signed-in customer: also send Authorization: Bearer <jwt> from POST /api/v3/store/auth/login. The response body contains token, refresh_token and user. Rotate with POST /auth/refresh and { "refresh_token": "…" }.
  • Context headers:
    • X-Spree-Country resolves the market, which sets the default locale and currency.
    • X-Spree-Locale and X-Spree-Currency override those defaults.
    • X-Spree-Channel: <code> selects the channel; the store default is used if it's absent. A publishable key bound to a channel decides the channel itself, and a conflicting header returns channel_mismatch.
  • Responses are customer-safe: no cost prices, no internal notes. On channels that hide prices from guests, money fields come back null, so type them string | null.
  • The Store API is read-only by default. Writes are limited to carts, checkout, the customer's own account (/customers/me/*), wishlists, returns/claims on the customer's own orders, and newsletter/company self-service.
curl -H "X-Spree-Api-Key: pk_xxx" -H "X-Spree-Country: DE" \
     "https://shop.example.com/api/v3/store/products?q[name_cont]=shirt&expand=default_variant"

Carts and orders are different resources

A cart (cart_…, /api/v3/store/carts) is mutable and has no status. Completing it creates an immutable order (or_…).

POST   /carts                         → cart (token in response)
POST   /carts/{id}/items              { variant_id, quantity }
PATCH  /carts/{id}                    { email, shipping_address, billing_address, use_shipping, … }
PATCH  /carts/{id}/fulfillments/{id}  { selected_delivery_rate_id }
POST   /carts/{id}/payment_sessions   … gateway flow …
POST   /carts/{id}/complete           → the Order (or an OrderGroup for split marketplace checkouts)
GET    /orders/{id}                   → order by or_ id, OR by the original cart_ id
  • After completion, GET /carts/{id} returns 404 (cart_not_found), because completed carts drop out of cart lookups. Read the result with GET /orders/{cart_id}. The order endpoint accepts the cart's prefixed ID and resolves it to the order created from it (order.cart_id). Guests keep using the same X-Spree-Token.
  • POST /carts/{id}/complete is safe to retry: if a payment webhook already completed the cart, the retry returns the existing order.
  • requirements[] on the cart lists what still blocks completion, as { step, field, code, message } (e.g. { "step": "delivery", "field": "delivery_method", "code": "delivery_method_required" }). Steps are only advisory; completion is the one hard gate. Drive the checkout UI from requirements, not from a state name. See spree-checkout.
  • Coupons: POST /carts/{id}/discount_codes { code }. Both carts and orders expose coupon_code.
  • A signed-in customer's history is at GET /customers/me/orders.

Order fields you'll use

  • status (Admin API): draft | placed | canceled. Filter with q[status_eq]=placed.
  • payment_status: none, authorized, partially_paid, paid, partially_refunded, refunded, overcharged, voided.
  • fulfillment_status: unfulfilled, partial, fulfilled, delivered, backorder, canceled (legacy pending / ready / shipped can appear on migrated orders).
  • Totals: item_total, delivery_total, discount_total, tax_total (included_/additional_), fee_total, gift_card_total, store_credit_total, total, amount_due, each paired with display_*.
  • withdrawal_period_ends_at / within_withdrawal_period (EU right of withdrawal).

There is no state, payment_state or shipment_state, and no ship_total/promo_total/adjustment rows. Money is itemized as tax lines, discounts and fees (see spree-order-totals).

Admin API

Secret key (integrations): X-Spree-Api-Key: sk_…. The key selects its store. JWT (people): POST /api/v3/admin/auth/login { email, password } (or { provider, … } for SSO). The response body is { token, user }. The refresh token is set as an HttpOnly cookie scoped to /api/v3/admin/auth, and POST /auth/refresh takes no body. Admin JWTs expire after 5 minutes by default. For multi-store hosts, send X-Spree-Store-Id.

If a request carries both credentials, the JWT wins.

Permissions

Every Admin controller maps its actions to one key: index/show → read_<resource>, everything else (including member actions like /orders/{id}/cancel) → write_<resource>. write_* implies read_*.

  • A secret key is checked against its scopes. Failure is 403 access_denied with details.required_scope.
  • JWT staff are checked against their roles' permission keys on this store. Failure is 403 with details.required_permission. A 403 without that detail means a record-level rule refused — those rules come from Spree.ability_class (swappable via Spree::Dependencies.ability_class; secret keys use Spree::ApiKeyAbility instead). See spree-auth-permissions.
  • Keys never decide which rows you see: every endpoint scopes through the current store.
  • Secret-key scopes are fixed at creation. To change them, mint a new key and revoke the old one.

The full scope list with groups and covered resources is in references/scopes.md. At runtime, GET /api/v3/admin/permissions returns the same list, and Spree::ApiKey.known_scopes gives it in Ruby. Common picks:

Order sync out ............ read_orders (+ read_payments, read_fulfillments)
Warehouse / 3PL ........... read_orders, write_fulfillments, write_stock
ERP product feed .......... write_products (+ write_categories / write_collections)
Refund automation ......... read_orders, write_refunds
Staff provisioning ........ write_staff
Everything (avoid) ........ write_all

Staff roles are data (/api/v3/admin/roles). See spree-auth-permissions.

Admin-only response traits

  • Timestamps, internal notes, cost prices, status, and custom fields that aren't storefront-visible.
  • Actor pairs. Records that remember who acted (canceler, approver, created_by, refunder, …) expose *_id plus *_type. The ID is adm_… for staff or key_… for a secret key, and *_type is admin_user or api_key. Treat *_type as an open list. expand=canceler returns { id, type, label }.

Shared conventions

Prefixed IDs

IDs are always Stripe-style prefixed strings, on both reads and writes ("variant_id": "variant_k5nR8xLq"). Never send or expect integer IDs. Common prefixes:

PrefixResourcePrefixResource
prod_Productvariant_Variant
cart_Cartor_Order
ogrp_OrderGroup (split checkout)li_LineItem
py_Paymentre_Refund
ful_Fulfillmentret_Return
tl_TaxLinedisc_Discount
fee_Feecust_Customer
adm_AdminUseraddr_Address
ctg_Categorycoll_Collection
sl_StockLevelsloc_StockLocation
mkt_Marketch_Channel
sel_Sellermedia_Media
cf_CustomFieldpromo_Promotion
gc_GiftCardrole_Role
key_ApiKey record (not the pk_/sk_ token)store_Store

The prefix identifies the resource type, so there is no type field on the envelope. Verify any others with grep -rn has_prefix_id spree/core/app/models. There is no adj_, because Adjustment doesn't exist in Spree 6.

Envelope, pagination, sorting

Lists return { data: [...], meta: { page, limit, count, pages, from, to, in, previous, next } }. A single record is returned as a bare object. Pagination: ?page=2&limit=50 (default 25, max 100). Sort: ?sort=-completed_at,number.

Expand and fields

  • ?expand=default_variant,media,variants.prices sideloads associations; dot notation reaches nested ones. Allowed keys vary per resource (see the OpenAPI spec).
  • Admin API expansions are permission-checked per segment: order(s) needs read_orders, payment(s)/payment_splits read_payments, customer(s) read_customers, gift_card(s) read_gift_cards, store_credit(s) read_store_credits (so orders.payments needs both). A path the caller can't cover is silently dropped, not a 403. Gift card code is masked (all but the last four characters) unless the caller holds read_gift_cards.
  • ?fields=name,slug returns a sparse response (id is always included).

Filtering (Ransack)

q[<attr>_<predicate>]=value, e.g. q[name_cont]=shirt, q[status_eq]=placed, q[completed_at_gteq]=2026-09-01. Predicates include _eq, _not_eq, _in, _cont, _start, _gt, _gteq, _lt, _lteq, _present, _blank. Attributes outside the model's ransack allowlist are silently ignored: you get 200 with that filter dropped. Allowlist them with Spree.ransack.add_attribute(Spree::Product, :brand_id).

The allowlists are narrowed per audience (Ransack auth_object: nil for Admin, :store, :seller):

  • Store API follows only a model's storefront_ransackable_associations (products: tags, categories, collections; empty for every other model) and drops the model's private_ransackable_attributes[:store] (e.g. order email, considered_risky, coupon_code and the search/multi_search scopes; variant cost_price; store credit memo).
  • Seller API follows no associations and drops private_ransackable_attributes[:seller] (order email, considered_risky, coupon_code; plus the search/multi_search scopes).
  • Admin API gets the full allowlist. So Spree.ransack.add_association(Spree::Product, :brand) makes q[brand_name_eq] work for staff only — to let shoppers filter through it too, add Spree::Product.storefront_ransackable_associations |= %w[brand] (only for associations whose data the storefront may see). Attributes added with add_attribute reach every audience unless listed as private.

Addresses

Use first_name, last_name, address1, city, postal_code, country_code (ISO2), state_code. On carts, use_shipping: true copies shipping to billing. country_iso/state_abbr still appear in responses but are deprecated.

Rich text

Rich-text attributes come back twice: description as plain text and description_html as sanitized HTML. Render *_html and write to the plain name (the value you send is HTML). Spree sanitizes it on save.

Money

Amounts are decimal strings ("29.99"), each paired with a display_* formatted string. When prices are hidden they're null. On writes, prices are arrays: "prices": [{ "currency": "USD", "amount": "29.99" }].

Idempotency

Send Idempotency-Key: <uuid> (max 255 chars) on POST/PUT/PATCH/DELETE. For 24 hours a retry with the same key and body replays the stored response (header Idempotent-Replayed: true). Reusing a key with a different body returns 422 idempotency_key_reused. 5xx responses aren't cached. The SDKs and the CLI add the key automatically.

Errors

{ "error": { "code": "validation_error", "message": "Email can't be blank",
             "details": { "email": ["can't be blank"] } } }
StatusTypical code
401authentication_required, invalid_token, authentication_failed, invalid_refresh_token
403access_denied (details.required_scope / required_permission), channel_mismatch
404record_not_found, cart_not_found, order_not_found, line_item_not_found, variant_not_found
422validation_error (per-attribute details, with base for form-level errors), cart_cannot_complete, insufficient_stock, payment_failed, idempotency_key_reused
413request_too_large (body > max_request_body_size, 100 KB)
429rate_limit_exceeded

Rate limits

Built on Rails rate_limit, with counters in Rails.cache. That cache must be shared (Solid Cache or Redis) across processes. Configure through Spree::Api::Config or the env vars:

Preference (env)DefaultBucket
rate_limit_per_key (SPREE_RATE_LIMIT_PER_KEY)300 / windowPublishable key + client IP (per visitor); client IP when no key is sent
rate_limit_per_secret_key (SPREE_RATE_LIMIT_PER_SECRET_KEY)600 / windowPer secret key, across the whole API
rate_limit_window (SPREE_RATE_LIMIT_WINDOW)60 s
rate_limit_login5Per IP: store/admin/seller login, invitation acceptance
rate_limit_register3Per IP: customer registration, newsletter subscribe
rate_limit_refresh10Per IP: refresh, logout, admin auth providers
rate_limit_password_reset3Per IP

Responses include X-RateLimit-Limit, X-RateLimit-Remaining and, when limited, Retry-After. Inbound payment/fulfillment webhook endpoints have their own fixed limit of 120/min.

Debugging recipes

  • 401 everywhere: a pk_ key sent to the Admin API or an sk_ key to the Store API; a revoked key; an admin JWT older than 5 minutes (refresh via the cookie); a customer token sent to the Admin API (wrong aud).
  • 403 on Admin: read details.required_scope / required_permission. For a key, mint a new one with that scope. For staff, add the key to their role on this store.
  • Cart 404 right after checkout: expected. Fetch GET /orders/{cart_id}.
  • Empty list but records exist: wrong channel (X-Spree-Channel or a channel-bound key), product not published on that channel, no price in the requested currency or market, or a silently dropped Ransack filter.
  • Probe from a terminal: spree api get /orders/or_xxx --expand payments,fulfillments (see spree-cli).

Where to read further

  • OpenAPI specs (authoritative): node_modules/@spree/docs/dist/api-reference/store.yaml, admin.yaml, seller.yaml
  • Store API guides: node_modules/@spree/docs/dist/api-reference/store-api/ (authentication, localization, idempotency, rate limiting, querying, relations)
  • Admin API guides: node_modules/@spree/docs/dist/api-reference/admin-api/ (authentication, errors, querying)
  • Webhook events: node_modules/@spree/docs/dist/api-reference/webhooks-events.md
  • Related skills: spree-auth-permissions, spree-typescript-sdk, spree-cli, spree-resource (add endpoints), spree-checkout, spree-marketplace (Seller API), spree-events-webhooks.

Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.

<a href="https://skillzs.dev/skills/spree/agent-skills/spree-api-v3">View spree-api-v3 on skillZs</a>