Skip to main content
The spree api command group turns the CLI into a first-class Admin API client: generic HTTP verbs against any endpoint, schema introspection without leaving the terminal, and credentials that resolve automatically — from a local dev project to a production store profile. It works against any Spree 5.5+ instance and is designed to be driven by both humans and AI coding agents.
spree api get /products -q status_eq=active --sort -created_at --limit 10
spree api get /orders/ord_x8k2J9aQ --expand items,payments
spree api post /products -d '{"name":"Classic Tee","prices":[{"currency":"USD","amount":"29.99"}]}'
spree api patch /orders/ord_x8k2J9aQ/cancel
spree api endpoints --resource orders
spree api schema "POST /orders/{id}/refunds"
spree api status

Credentials

Credentials resolve through four layers — first match wins:
LayerHow
Flags--base-url / --api-key (and --profile, which selects a saved profile explicitly)
EnvironmentSPREE_API_KEY (+ SPREE_BASE_URL) — the path for CI and agents. SPREE_BASE_URL is optional: a bare key defaults to http://localhost:3000, so local dev needs only the key. Set it for a remote store.
ProjectInside a Spree project with the dev stack running, the CLI mints a read-only key on first use and stores it in .spree/credentials.json (gitignored)
Profile~/.config/spree/config.json, written by spree auth login
Host and key resolve together per source — a saved or minted key is never silently re-pointed at a host from a different layer. Only --base-url (or SPREE_BASE_URL paired with SPREE_API_KEY) re-points a key deliberately. So against local dev:
SPREE_API_KEY=sk_xxx spree api get /products    # → http://localhost:3000
Zero configuration is needed for local development — the first spree api get inside your project just works:
cd my-store
spree api get /products
# No credentials found — minting a read-only API key via the dev stack...
# Saved to .spree/credentials.json (scopes: read_all)
Auto-minted keys carry read_all only. Writes always require an explicitly created key:
spree api-key create --type secret --scopes read_orders,write_products
For remote stores, create a secret key in the admin under Settings → API Keys and save it as a profile — the key is read from a prompt, never from a flag:
spree auth login --profile prod --base-url https://store.example.com
spree api get /orders --profile prod -q "status_eq=complete"
spree auth status
spree auth logout --profile prod

Reading data

-q takes Ransack predicates as repeatable key=value pairs; sorting, pagination, and shaping flags pass through under the Admin API conventions:
spree api get /products -q name_cont=shirt -q status_eq=active
spree api get /orders --sort -created_at --page 2 --limit 50
spree api get /products/prod_86Rf07xd --expand variants,variants.prices
spree api get /customers --fields id,email --format table
--fields trims the response to the attributes you name (comma-separated). The resource id is always returned even if you don’t list it, so you only need to name the extra fields. --expand is the companion axis — --fields selects top-level attributes, --expand pulls in related resources:
spree api get /products --fields name,price          # → id + name + price
spree api get /products --fields name --expand variants

Output format

Output is JSON on stdout, and it adapts to where it goes:
  • In a terminal — indented and syntax-colored, for reading.
  • Piped or redirected — compact, single-line, uncolored, so it stays fast and feeds cleanly into jq (color codes would corrupt it).
spree api get /products --limit 5 | jq '.data[].name'
--format table renders collections as a table for humans (nested objects collapse to {…}, arrays to [N]).

Writing data

Request bodies are flat JSON (no root wrapping), inline, from a file, or from stdin:
spree api post /products -d '{"name":"Classic Tee","prices":[{"currency":"USD","amount":"29.99"}]}'
spree api post /orders/ord_x8k2J9aQ/refunds -d @refund.json
cat prices.json | spree api post /prices/bulk_upsert -d -
spree api delete /products/prod_86Rf07xd
Mutations automatically carry an Idempotency-Key, so retries are safe.

Creating products

A product is a catalog grouping; everything purchasable — SKU, price, stock, weight — lives on its variants. For a simple product with no options, ship a top-level prices array and Spree creates the single backing variant for you:
spree api post /products -d '{"name":"Classic Tee","prices":[{"currency":"USD","amount":"29.99"}]}'
For a product with options, send the variants array, each with its own options and prices (and optionally stock_items):
spree api post /products -d '{
  "name": "Classic Tee",
  "variants": [
    { "sku": "TEE-S", "options": [{"name":"size","value":"Small"}], "prices": [{"currency":"USD","amount":"29.99"}] },
    { "sku": "TEE-M", "options": [{"name":"size","value":"Medium"}], "prices": [{"currency":"USD","amount":"29.99"}] }
  ]
}'
Run spree api schema "POST /products" for the full request body.

Discovering endpoints and schemas

The CLI bundles a snapshot of the Admin API OpenAPI spec, so discovery works offline:
spree api endpoints --resource orders          # methods, paths, required scopes
spree api endpoints --search "gift card"
spree api schema "POST /orders"                # parameters + request/response schema
spree api status                               # resolved credentials + server reachability
spree api endpoints shows the read_* / write_* scope each endpoint requires (see Authentication) — useful for deciding what to grant a new key.

Shell completion

Tab-completion suggests resource paths, Ransack predicate stems (name_cont=, status_eq=, …), and scope names — all offline from the bundled spec. Add the generated script to your shell:
# zsh — add to ~/.zshrc
eval "$(spree completion zsh)"
# bash — add to ~/.bashrc
eval "$(spree completion bash)"
# fish — add to ~/.config/fish/config.fish
spree completion fish | source
Then spree api get /pro⇥ completes to /products, and spree api get /products -q name_⇥ offers the Ransack predicates.

Errors and exit codes

API errors print the error envelope to stderr and exit 1; usage and configuration problems exit 2. A scope denial includes the exact remediation:
access_denied: API key lacks scope: write_products
{
  "details": { "required_scope": "write_products" }
}
Hint: mint a key carrying this scope — spree api-key create --type secret --scopes write_products

Using it with AI agents

Agents with shell access (Claude Code, Cursor, and similar) can drive a Spree back office through spree api with no extra configuration: inside a project the credentials self-provision read-only, and for remote stores two environment variables (SPREE_BASE_URL, SPREE_API_KEY) are the whole contract. The endpoints/schema commands give agents request shapes on demand instead of loading the entire API surface up front, and error messages name the missing scope so agents can self-correct.