@spree/docs 0.1.32 → 0.1.34
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.
- package/dist/api-reference/admin-api/authentication.md +161 -0
- package/dist/api-reference/admin-api/errors.md +277 -0
- package/dist/api-reference/admin-api/introduction.md +68 -0
- package/dist/api-reference/admin-api/querying.md +296 -0
- package/dist/api-reference/store.yaml +610 -528
- package/package.json +2 -2
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Authentication"
|
|
3
|
+
sidebarTitle: "Authentication"
|
|
4
|
+
description: "How to authenticate requests to the Admin API"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The Admin API supports two authentication methods: **secret API keys** for server-to-server integrations, and **JWT bearer tokens** for admin SPA / interactive sessions. Every request must include credentials — there is no public surface.
|
|
8
|
+
|
|
9
|
+
> **WARNING:** Secret API keys grant full administrative access to your store. **Never embed them in client-side code, mobile apps, or public repositories.** Use them only from secure server environments.
|
|
10
|
+
|
|
11
|
+
## Secret API key
|
|
12
|
+
|
|
13
|
+
Pass the key via the `X-Spree-Api-Key` header:
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
```typescript SDK
|
|
17
|
+
import { createAdminClient } from '@spree/admin-sdk'
|
|
18
|
+
|
|
19
|
+
const client = createAdminClient({
|
|
20
|
+
baseUrl: 'https://store.example.com',
|
|
21
|
+
secretKey: 'sk_xxx',
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// The SDK automatically sends the secret key with every request
|
|
25
|
+
const { data: products } = await client.products.list()
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```bash cURL
|
|
29
|
+
curl -X GET 'https://store.example.com/api/v3/admin/orders' \
|
|
30
|
+
-H 'X-Spree-Api-Key: sk_xxx'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
Secret API keys are prefixed with `sk_`. Create them in the Spree admin under **Settings → API Keys** or via the Spree CLI:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
spree api-key create --type secret # Create a new secret key
|
|
38
|
+
spree api-key list # List existing keys
|
|
39
|
+
spree api-key revoke <key_id> # Revoke a key
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
> **WARNING:** If you omit the API key, the API returns `401 Unauthorized`:
|
|
43
|
+
>
|
|
44
|
+
> ```json
|
|
45
|
+
{
|
|
46
|
+
"error": {
|
|
47
|
+
"code": "authentication_required",
|
|
48
|
+
"message": "Authentication required"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## JWT bearer token (admin user)
|
|
54
|
+
|
|
55
|
+
For interactive admin sessions (the Spree admin SPA, custom dashboards, etc.) authenticate as an admin user and use the returned JWT token for subsequent requests.
|
|
56
|
+
|
|
57
|
+
### Login
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
```typescript SDK
|
|
61
|
+
const { token, user } = await client.auth.login({
|
|
62
|
+
email: 'admin@example.com',
|
|
63
|
+
password: 'secret123',
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// Use the token for authenticated requests
|
|
67
|
+
const orders = await client.orders.list({}, { bearerToken: token })
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```bash cURL
|
|
71
|
+
curl -X POST 'https://store.example.com/api/v3/admin/auth/login' \
|
|
72
|
+
-H 'X-Spree-Api-Key: sk_xxx' \
|
|
73
|
+
-H 'Content-Type: application/json' \
|
|
74
|
+
-d '{"email": "admin@example.com", "password": "secret123"}'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
### Token refresh
|
|
79
|
+
|
|
80
|
+
JWT tokens expire after 1 hour by default. Refresh them with the current token:
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
```typescript SDK
|
|
84
|
+
const { token } = await client.auth.refresh({ token: currentToken })
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```bash cURL
|
|
88
|
+
curl -X POST 'https://store.example.com/api/v3/admin/auth/refresh' \
|
|
89
|
+
-H 'X-Spree-Api-Key: sk_xxx' \
|
|
90
|
+
-H 'Authorization: Bearer <current_jwt_token>'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
## Permissions
|
|
95
|
+
|
|
96
|
+
Authorization works differently depending on which credentials you use.
|
|
97
|
+
|
|
98
|
+
### Secret API keys: scopes
|
|
99
|
+
|
|
100
|
+
Each secret API key carries a list of **scopes** that grant access to specific resources. Scopes follow a `read_<resource>` / `write_<resource>` convention; `write_<resource>` implies `read_<resource>`.
|
|
101
|
+
|
|
102
|
+
| Scope | Endpoints |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `read_orders` / `write_orders` | `/orders/*`, `/orders/:id/items` |
|
|
105
|
+
| `read_products` / `write_products` | `/products/*`, `/variants/*`, `/option_types/*`, `/media/*` |
|
|
106
|
+
| `read_customers` / `write_customers` | `/customers/*`, `/customers/:id/addresses`, `/customers/:id/credit_cards` |
|
|
107
|
+
| `read_payments` / `write_payments` | `/orders/:id/payments` |
|
|
108
|
+
| `read_fulfillments` / `write_fulfillments` | `/orders/:id/fulfillments` |
|
|
109
|
+
| `read_refunds` / `write_refunds` | `/orders/:id/refunds` |
|
|
110
|
+
| `read_gift_cards` / `write_gift_cards` | `/orders/:id/gift_cards` |
|
|
111
|
+
| `read_store_credits` / `write_store_credits` | `/customers/:id/store_credits`, `/orders/:id/store_credits` |
|
|
112
|
+
| `read_categories` / `write_categories` | `/categories/*` |
|
|
113
|
+
| `read_settings` / `write_settings` | `/payment_methods`, `/markets`, `/countries`, `/tax_categories`, `/store` |
|
|
114
|
+
| `read_dashboard` | `/dashboard/*` (analytics) |
|
|
115
|
+
|
|
116
|
+
Two convenience aliases:
|
|
117
|
+
|
|
118
|
+
- **`read_all`** — every `read_*` scope
|
|
119
|
+
- **`write_all`** — every `read_*` and `write_*` scope (full admin)
|
|
120
|
+
|
|
121
|
+
If the key lacks the required scope, the API returns `403 Forbidden`:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"error": {
|
|
126
|
+
"code": "access_denied",
|
|
127
|
+
"message": "API key lacks scope: write_orders",
|
|
128
|
+
"details": {
|
|
129
|
+
"required_scope": "write_orders"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The `details.required_scope` field tells you exactly which scope to add.
|
|
136
|
+
|
|
137
|
+
Pick scopes when creating the key in **Settings → API Keys**. Choose the narrowest set that covers your integration's needs.
|
|
138
|
+
|
|
139
|
+
### JWT bearer tokens: CanCanCan abilities
|
|
140
|
+
|
|
141
|
+
JWT-authenticated admin users are authorized via [CanCanCan](https://github.com/CanCanCommunity/cancancan) abilities derived from their `Spree::Role`s. The SPA uses this fine-grained model to render UI conditionally; partial-permission staff users see only the resources their role grants.
|
|
142
|
+
|
|
143
|
+
If the caller lacks permission for a specific action, the API returns `403 Forbidden`:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"error": {
|
|
148
|
+
"code": "access_denied",
|
|
149
|
+
"message": "You are not authorized to perform this action"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Authentication summary
|
|
155
|
+
|
|
156
|
+
| Method | Header | Use case | Authorization |
|
|
157
|
+
|---|---|---|---|
|
|
158
|
+
| Secret API key | `X-Spree-Api-Key: sk_xxx` | Server-to-server integrations | Scopes |
|
|
159
|
+
| JWT token | `Authorization: Bearer <token>` | Interactive admin sessions; SPA | CanCanCan abilities |
|
|
160
|
+
|
|
161
|
+
> **NOTE:** If both headers are present, the JWT token wins: CanCanCan applies and scopes are ignored. This lets you use `sk_xxx` to bootstrap a session and then issue per-user JWTs for individual admin actions.
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Errors
|
|
3
|
+
description: Error response format and admin-specific error codes
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
The Admin API uses the same Stripe-style error format as the rest of the Spree v3 API. Every error response carries a machine-readable `code` and a human-readable `message`.
|
|
7
|
+
|
|
8
|
+
## Error response format
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"error": {
|
|
13
|
+
"code": "record_not_found",
|
|
14
|
+
"message": "Order not found"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Validation errors include a `details` field with per-field error messages:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"error": {
|
|
24
|
+
"code": "validation_error",
|
|
25
|
+
"message": "Email is invalid and Phone can't be blank",
|
|
26
|
+
"details": {
|
|
27
|
+
"email": ["is invalid"],
|
|
28
|
+
"phone": ["can't be blank"]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Some specialized errors carry structured `details` (for example, scope errors include the `required_scope`):
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"error": {
|
|
39
|
+
"code": "access_denied",
|
|
40
|
+
"message": "API key lacks scope: write_orders",
|
|
41
|
+
"details": {
|
|
42
|
+
"required_scope": "write_orders"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Schema
|
|
49
|
+
|
|
50
|
+
| Field | Type | Description |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `error.code` | `string` | Machine-readable error code (see tables below) |
|
|
53
|
+
| `error.message` | `string` | Human-readable description of the error |
|
|
54
|
+
| `error.details` | `object` | Optional. Field-specific validation errors or structured context (varies by `code`). |
|
|
55
|
+
|
|
56
|
+
## HTTP status codes
|
|
57
|
+
|
|
58
|
+
| Status | Meaning | When |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| `400` | Bad Request | Missing required parameters, malformed JSON |
|
|
61
|
+
| `401` | Unauthorized | Missing or invalid API key, expired or invalid JWT token |
|
|
62
|
+
| `403` | Forbidden | Authenticated but lacks permission (CanCanCan ability) or scope |
|
|
63
|
+
| `404` | Not Found | Resource doesn't exist, isn't accessible to the calling key, or belongs to a different store |
|
|
64
|
+
| `409` | Conflict | Resource was modified by a concurrent request (optimistic locking) |
|
|
65
|
+
| `422` | Unprocessable Content | Validation failed, invalid state transition, business rule violation |
|
|
66
|
+
| `429` | Too Many Requests | Login/refresh rate limit exceeded |
|
|
67
|
+
|
|
68
|
+
## Authentication & authorization
|
|
69
|
+
|
|
70
|
+
| Code | Status | Description |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `authentication_required` | 401 | Request reached a protected endpoint with no credentials |
|
|
73
|
+
| `authentication_failed` | 401 | Login email/password was wrong |
|
|
74
|
+
| `invalid_token` | 401 | API key or JWT token is invalid or expired |
|
|
75
|
+
| `invalid_refresh_token` | 401 | Refresh token is invalid or expired |
|
|
76
|
+
| `access_denied` | 403 | Caller lacks permission. For API-key callers, `details.required_scope` indicates the missing scope (see [Authentication](authentication.md#permissions)). For JWT callers, the user's role lacks the CanCanCan ability for this action. |
|
|
77
|
+
| `current_password_invalid` | 422 | Current password is wrong (when changing password) |
|
|
78
|
+
|
|
79
|
+
## Resources
|
|
80
|
+
|
|
81
|
+
| Code | Status | Description |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| `record_not_found` | 404 | Resource doesn't exist or isn't accessible in the calling key's store |
|
|
84
|
+
| `resource_invalid` | 422 | Resource couldn't be saved |
|
|
85
|
+
|
|
86
|
+
## Validation
|
|
87
|
+
|
|
88
|
+
| Code | Status | Description |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `validation_error` | 422 | Model validation failed. Inspect `details` for field-specific messages. |
|
|
91
|
+
| `parameter_missing` | 400 | A required parameter is missing |
|
|
92
|
+
| `parameter_invalid` | 400 | A parameter has an invalid value |
|
|
93
|
+
|
|
94
|
+
## Orders
|
|
95
|
+
|
|
96
|
+
| Code | Status | Description |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `cart_cannot_transition` | 422 | Order state machine refused the transition (e.g., completing an order without an address or payment) |
|
|
99
|
+
| `cart_already_updated` | 409 | Order was modified by a concurrent request. Refetch and retry. See [optimistic locking](#optimistic-locking) below. |
|
|
100
|
+
| `cart_invalid_state` | 422 | Order is in a state that doesn't allow this operation |
|
|
101
|
+
| `cart_empty` | 422 | Cannot complete an order with no line items |
|
|
102
|
+
|
|
103
|
+
## Customers
|
|
104
|
+
|
|
105
|
+
| Code | Status | Description |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| `customer_has_orders` | 422 | Cannot delete a customer with completed orders. Anonymize instead. |
|
|
108
|
+
|
|
109
|
+
## Store credits
|
|
110
|
+
|
|
111
|
+
| Code | Status | Description |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `store_credit_in_use` | 422 | Cannot edit `amount` on or delete a store credit that has been partially or fully used. |
|
|
114
|
+
|
|
115
|
+
## Tags
|
|
116
|
+
|
|
117
|
+
| Code | Status | Description |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `invalid_taggable_type` | 422 | The `taggable_type` query param isn't one of the allowed values (`Spree::Product`, `Spree::Order`, `Spree::User`). |
|
|
120
|
+
|
|
121
|
+
## Payments
|
|
122
|
+
|
|
123
|
+
| Code | Status | Description |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| `payment_failed` | 422 | Payment was declined by the gateway |
|
|
126
|
+
| `payment_processing_error` | 422 | Spree couldn't process the payment due to an internal error |
|
|
127
|
+
| `gateway_error` | 422 | Payment gateway returned an error |
|
|
128
|
+
|
|
129
|
+
## Examples
|
|
130
|
+
|
|
131
|
+
### Insufficient scope (API key)
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
curl -X POST 'https://store.example.com/api/v3/admin/orders' \
|
|
135
|
+
-H 'X-Spree-Api-Key: sk_xxx' \
|
|
136
|
+
-H 'Content-Type: application/json' \
|
|
137
|
+
-d '{"email": "test@example.com"}'
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```json 403
|
|
141
|
+
{
|
|
142
|
+
"error": {
|
|
143
|
+
"code": "access_denied",
|
|
144
|
+
"message": "API key lacks scope: write_orders",
|
|
145
|
+
"details": {
|
|
146
|
+
"required_scope": "write_orders"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Validation error (customer create)
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
curl -X POST 'https://store.example.com/api/v3/admin/customers' \
|
|
156
|
+
-H 'X-Spree-Api-Key: sk_xxx' \
|
|
157
|
+
-H 'Content-Type: application/json' \
|
|
158
|
+
-d '{}'
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```json 422
|
|
162
|
+
{
|
|
163
|
+
"error": {
|
|
164
|
+
"code": "validation_error",
|
|
165
|
+
"message": "Email can't be blank",
|
|
166
|
+
"details": {
|
|
167
|
+
"email": ["can't be blank"]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Customer with completed orders
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
curl -X DELETE 'https://store.example.com/api/v3/admin/customers/cus_xxx' \
|
|
177
|
+
-H 'X-Spree-Api-Key: sk_xxx'
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
```json 422
|
|
181
|
+
{
|
|
182
|
+
"error": {
|
|
183
|
+
"code": "customer_has_orders",
|
|
184
|
+
"message": "Cannot delete customer with completed orders"
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Concurrent order update
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
curl -X PATCH 'https://store.example.com/api/v3/admin/orders/or_xxx' \
|
|
193
|
+
-H 'X-Spree-Api-Key: sk_xxx' \
|
|
194
|
+
-H 'Content-Type: application/json' \
|
|
195
|
+
-d '{"email": "new@example.com"}'
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
```json 409
|
|
199
|
+
{
|
|
200
|
+
"error": {
|
|
201
|
+
"code": "cart_already_updated",
|
|
202
|
+
"message": "Order was modified by another request. Refetch and retry."
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Handling errors with the SDK
|
|
208
|
+
|
|
209
|
+
`@spree/admin-sdk` throws a `SpreeError` for every non-2xx response:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { SpreeError } from '@spree/admin-sdk'
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
await client.orders.update(orderId, { email: 'new@example.com' })
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (error instanceof SpreeError) {
|
|
218
|
+
console.log(error.code) // 'cart_already_updated'
|
|
219
|
+
console.log(error.message) // 'Order was modified by another request...'
|
|
220
|
+
console.log(error.status) // 409
|
|
221
|
+
console.log(error.details) // undefined or { ... }
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Common patterns
|
|
227
|
+
|
|
228
|
+
**Branch on error code**:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
try {
|
|
232
|
+
await client.customers.delete(customerId)
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (error instanceof SpreeError && error.code === 'customer_has_orders') {
|
|
235
|
+
// Anonymize instead of deleting
|
|
236
|
+
return anonymizeCustomer(customerId)
|
|
237
|
+
}
|
|
238
|
+
throw error
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Retry on optimistic-lock conflicts**:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
async function updateOrderWithRetry(id, params, attempts = 3) {
|
|
246
|
+
for (let i = 0; i < attempts; i++) {
|
|
247
|
+
try {
|
|
248
|
+
return await client.orders.update(id, params)
|
|
249
|
+
} catch (error) {
|
|
250
|
+
if (error instanceof SpreeError && error.code === 'cart_already_updated' && i < attempts - 1) {
|
|
251
|
+
continue
|
|
252
|
+
}
|
|
253
|
+
throw error
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Show field-level validation errors**:
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
try {
|
|
263
|
+
await client.customers.create(customerData)
|
|
264
|
+
} catch (error) {
|
|
265
|
+
if (error instanceof SpreeError && error.details) {
|
|
266
|
+
for (const [field, messages] of Object.entries(error.details)) {
|
|
267
|
+
setFieldError(field, (messages as string[]).join(', '))
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Optimistic locking
|
|
274
|
+
|
|
275
|
+
Orders use a `state_lock_version` column to detect concurrent modifications. Every state-changing operation increments it; if two callers update the same order simultaneously, the second write fails with `cart_already_updated` (409) — refetch and retry.
|
|
276
|
+
|
|
277
|
+
This protects against race conditions when multiple clients (or the same client, retried) try to mutate the same order. Combined with idempotency at the integration level (e.g., dedupe webhook deliveries by event ID), it makes admin order management safe under concurrency.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Admin API"
|
|
3
|
+
sidebarTitle: "Introduction"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> **WARNING:** **Developer Preview.** The Admin API is in active development and may change between major versions.
|
|
7
|
+
|
|
8
|
+
The Admin API is a REST API for managing a Spree stores programmatically — products, orders, customers, fulfillments, payments, and more. It is intended for backend integrations, custom admin tooling, and automation.
|
|
9
|
+
|
|
10
|
+
All routes are prefixed with `/api/v3/admin`. During development the API is available under `http://localhost:3000/api/v3/admin`. For production, replace `http://localhost:3000` with your Spree application URL.
|
|
11
|
+
|
|
12
|
+
## Admin API vs Store API
|
|
13
|
+
|
|
14
|
+
| | Admin API | Store API |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| **Purpose** | Manage store data | Power storefronts |
|
|
17
|
+
| **Audience** | Staff users, backend integrations | Customers, storefronts |
|
|
18
|
+
| **Authentication** | Secret API key (`sk_…`) or admin JWT | Publishable API key (`pk_…`), customer JWT, order token |
|
|
19
|
+
| **Permissions** | API key scopes (API key authentication) or Admin Staff permission sets | Customer can only read/modify their own data |
|
|
20
|
+
| **Write operations** | Full CRUD on most resources | Limited to the current customer's cart, addresses, profile |
|
|
21
|
+
|
|
22
|
+
If you're building a storefront, use the [Store API](../store-api/introduction.md). The Admin API exposes administrative operations that should never be invoked from a browser.
|
|
23
|
+
|
|
24
|
+
## Using the SDK
|
|
25
|
+
|
|
26
|
+
We recommend using `@spree/admin-sdk` to interact with the Admin API. It provides typed clients, automatic retries, and idempotency support.
|
|
27
|
+
|
|
28
|
+
### Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install @spree/admin-sdk
|
|
32
|
+
# or
|
|
33
|
+
yarn add @spree/admin-sdk
|
|
34
|
+
# or
|
|
35
|
+
pnpm add @spree/admin-sdk
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Quick start
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { createAdminClient } from '@spree/admin-sdk'
|
|
42
|
+
|
|
43
|
+
const client = createAdminClient({
|
|
44
|
+
baseUrl: 'http://localhost:3000',
|
|
45
|
+
secretKey: 'sk_xxx',
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const { data: orders } = await client.orders.list({
|
|
49
|
+
status_eq: 'complete',
|
|
50
|
+
limit: 25,
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## What's covered
|
|
55
|
+
|
|
56
|
+
The Admin API today (in Spree 5.5) covers:
|
|
57
|
+
|
|
58
|
+
- **Catalog** — products, variants, prices
|
|
59
|
+
- **Orders** — list, create, update, items, complete, cancel, approve, resume; nested fulfillments, payments, refunds, gift cards, store credits
|
|
60
|
+
- **Customers** — full CRUD, addresses, store credits, credit cards
|
|
61
|
+
|
|
62
|
+
See the **Endpoints** section in the sidebar for the complete reference, generated from the OpenAPI spec.
|
|
63
|
+
|
|
64
|
+
Before integrating, read:
|
|
65
|
+
|
|
66
|
+
- [Authentication](authentication.md) — secret API keys, scopes, and JWT tokens
|
|
67
|
+
- [Errors](errors.md) — error format and admin-specific codes
|
|
68
|
+
- [Querying](querying.md) — filtering, sorting, pagination, and `expand`
|