
REST API
MiniShop3 provides a REST API for integration with the frontend and external systems.
Entry points
| Purpose | URL | Authorization |
|---|---|---|
| Web API (frontend) | /assets/components/minishop3/api.php | MS3TOKEN token |
| Manager API (manager) | /assets/components/minishop3/connector.php | MODX session |
This documentation describes the Web API for the frontend.
Base URL
/assets/components/minishop3/api.php?route=/api/v1/{endpoint}All requests pass the route via the route parameter.
Authorization
Getting a token
Before working with the cart and orders you need to get a client token:
GET /api/v1/customer/token/getResponse:
{
"success": true,
"data": {
"token": "abc123def456..."
},
"message": ""
}Token storage (httpOnly cookie)
As of v1.6, the token is stored in an httpOnly cookie ms3_token. The server sets the cookie when the token is obtained or refreshed.
Security
The httpOnly cookie is not accessible from JavaScript, which protects the token from XSS. The browser sends the cookie with every request.
Token resolution order on the server (TokenMiddleware):
Authorization: Bearer {token}header (for mobile apps)HTTP_MS3TOKENheader (legacy)- httpOnly cookie
ms3_token(primary for web)
The cookie is configured using MODX session parameters: session_cookie_domain, session_cookie_path, session_cookie_secure, session_cookie_samesite.
Response format
All responses use a single format:
Success:
{
"success": true,
"data": { ... },
"message": "Success message"
}Error:
{
"success": false,
"message": "Error description",
"code": 400
}Cart
Add product
POST /api/v1/cart/addParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id | int | Yes | Product ID |
count | int | No | Quantity (default 1) |
options | object | No | Product options (color, size, etc.) |
render | array | No | Snippet tokens for SSR |
Request example:
fetch('/assets/components/minishop3/api.php?route=/api/v1/cart/add&ms3_token=' + token, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: 123,
count: 2,
options: {
color: 'Red',
size: 'XL'
}
})
})Response:
{
"success": true,
"data": {
"last_key": "123_a1b2c3d4",
"cart": [
{
"key": "123_a1b2c3d4",
"id": 123,
"count": 2,
"price": 1500,
"cost": 3000,
"weight": 0.5,
"options": {"color": "Red", "size": "XL"},
"name": "Product",
"thumb": "/assets/images/product.jpg"
}
],
"status": {
"total_count": 2,
"total_cost": 3000,
"total_weight": 1.0,
"total_positions": 1
}
},
"message": "Product added to cart"
}Change quantity
POST /api/v1/cart/changeParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
product_key | string | Yes | Unique product key in cart |
count | int | Yes | New quantity |
Example:
{
"product_key": "123_a1b2c3d4",
"count": 5
}Remove product
POST /api/v1/cart/removeParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
product_key | string | Yes | Unique product key |
Get cart
GET /api/v1/cart/getResponse:
{
"success": true,
"data": {
"cart": [...],
"status": {
"total_count": 5,
"total_cost": 7500,
"total_weight": 2.5,
"total_positions": 3
}
}
}Clear cart
POST /api/v1/cart/cleanOrder
Get order draft
GET /api/v1/order/getResponse:
{
"success": true,
"data": {
"order": {
"email": "user@example.com",
"phone": "+7 999 123-45-67",
"first_name": "John",
"delivery": 1,
"payment": 1,
"address_city": "Moscow",
"address_street": "Main St",
"comment": ""
},
"deliveries": [...],
"payments": [...]
}
}Add/update field
POST /api/v1/order/addParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Field name |
value | mixed | Yes | Value |
Available fields:
| Field | Description |
|---|---|
email | Customer email |
phone | Phone |
first_name | First name |
last_name | Last name |
delivery | Delivery method ID |
payment | Payment method ID |
comment | Order comment |
city | City |
street | Street |
building | Building |
room | Apartment/office |
index | Postal code |
address_hash | Saved address hash |
Example:
{
"key": "email",
"value": "user@example.com"
}Set multiple fields
POST /api/v1/order/setParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
fields | object | Yes | Object with fields |
Example:
{
"fields": {
"email": "user@example.com",
"phone": "+79991234567",
"first_name": "John",
"delivery": 1
}
}Remove field
POST /api/v1/order/removeParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Field name |
Submit order
POST /api/v1/order/submitResponse (success):
{
"success": true,
"data": {
"order_id": 15,
"order_num": "24/12-15",
"redirect_url": "/thank-you?msorder=15"
},
"message": "Order submitted successfully"
}Response (validation error):
{
"success": false,
"message": "Fill required fields",
"data": {
"errors": {
"email": "Enter email",
"phone": "Enter phone"
}
},
"code": 400
}Clear order
POST /api/v1/order/cleanCost
Total cost
GET /api/v1/order/costResponse:
{
"success": true,
"data": {
"cart_cost": 5000,
"delivery_cost": 300,
"payment_cost": 0,
"total_cost": 5300,
"discount": 0
}
}Cart cost
GET /api/v1/order/cost/cartDelivery cost
GET /api/v1/order/cost/deliveryPayment fee
GET /api/v1/order/cost/paymentDelivery addresses
Set saved address
POST /api/v1/order/address/setParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
address_hash | string | Yes | Address MD5 hash |
Clear address
POST /api/v1/order/address/cleanDelivery validation
Validation rules
GET /api/v1/order/delivery/validation-rulesResponse:
{
"success": true,
"data": {
"city": {"required": true, "min": 2},
"street": {"required": true},
"building": {"required": true},
"phone": {"required": true, "pattern": "^\\+?[0-9]+$"}
}
}Required fields
GET /api/v1/order/delivery/required-fieldsResponse:
{
"success": true,
"data": ["city", "street", "building", "phone"]
}Customer
Registration
POST /api/v1/customer/registerParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | Yes | |
password | string | Yes | Password |
password_confirm | string | Yes | Password confirmation |
first_name | string | No | First name |
last_name | string | No | Last name |
phone | string | No | Phone |
privacy_accepted | bool | Depends on settings | Data processing consent |
Response:
{
"success": true,
"object": {
"customer": {
"id": 5,
"email": "user@example.com",
"first_name": "John",
"last_name": "Doe",
"phone": "+79991234567",
"email_verified": false
},
"token": "abc123def456...",
"expires_at": "2026-03-16 12:34:56",
"email_verification_required": false,
"redirect_url": ""
},
"message": "Registration successful"
}Breaking change (v1.6)
Registration response format changed:
- Before (v1.5):
token— object{token: "...", expires_at: "..."} - After (v1.6):
token— string,expires_atat top level
Custom themes that use result.object.token.token should switch to result.object.token.
Login
POST /api/v1/customer/loginParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | Yes | |
password | string | Yes | Password |
Response:
{
"success": true,
"data": {
"customer_id": 5,
"token": "session_token_xyz789",
"customer": {
"id": 5,
"email": "user@example.com",
"first_name": "John"
}
}
}Update profile
PUT /api/v1/customer/profileRequires authorization (authenticated customer token)
Parameters:
| Parameter | Type | Description |
|---|---|---|
first_name | string | First name |
last_name | string | Last name |
phone | string | Phone |
Email verification
GET /api/v1/customer/email/verify?token=verification_tokenResend verification
POST /api/v1/customer/email/resend-verificationRequires authorization
Customer addresses
All endpoints require authorization.
List addresses
GET /api/v1/customer/addressesResponse:
{
"success": true,
"data": [
{
"id": 1,
"hash": "abc123...",
"city": "Moscow",
"street": "Main St",
"building": "10",
"room": "5",
"is_default": true
}
]
}Get address
GET /api/v1/customer/addresses/{id}Create address
POST /api/v1/customer/addressesParameters:
| Parameter | Type | Description |
|---|---|---|
city | string | City |
street | string | Street |
building | string | Building |
room | string | Apartment/office |
index | string | Postal code |
country | string | Country |
region | string | Region |
is_default | bool | Default address |
Update address
PUT /api/v1/customer/addresses/{id}Delete address
DELETE /api/v1/customer/addresses/{id}Set default address
PUT /api/v1/customer/addresses/{id}/set-defaultCustomer orders
Cancel order
POST /api/v1/customer/orders/{id}/cancelRequires authorization (authenticated customer token)
Cancels the customer's order if the current status is in the allowed list (ms3_customer_cancel_allowed_statuses).
Response (success):
{
"success": true,
"message": "Order canceled",
"data": {
"order_id": 15,
"status_id": 5
}
}Response (error — status not allowed):
{
"success": false,
"message": "Order cannot be canceled in its current status",
"code": 400
}Response (error — not found):
{
"success": false,
"message": "Order not found",
"code": 404
}Related settings:
| Setting | Description |
|---|---|
ms3_customer_cancel_allowed_statuses | Status IDs for which cancellation is allowed (default 2,3) |
ms3_status_canceled | Target status ID for canceled orders |
Health Check
GET /api/v1/healthResponse:
{
"success": true,
"data": {
"status": "ok",
"version": "1.0.0",
"timestamp": 1703952000,
"api": "web"
}
}Middleware
CORS
Configured via system setting ms3_cors_allowed_origins:
*— allow all domainshttps://example.com,https://shop.example.com— list of domains
Rate Limiting
DDoS protection via system settings:
ms3_rate_limit_max_attempts— max requests (default 60)ms3_rate_limit_decay_seconds— period in seconds (default 60)
When limit is exceeded:
{
"success": false,
"message": "Too many requests",
"code": 429
}SSR (Server-Side Rendering)
The API supports server-side HTML rendering for updating parts of the page.
Usage
Pass an array of snippet tokens in the render parameter:
fetch('/api/v1/cart/add?ms3_token=' + token, {
method: 'POST',
body: JSON.stringify({
id: 123,
render: ['ms3_abc123...', 'ms3_def456...']
})
})Response includes HTML:
{
"success": true,
"data": {
"cart": [...],
"status": {...},
"render": {
"ms3_abc123...": "<div class=\"cart\">...</div>",
"ms3_def456...": "<span class=\"count\">5</span>"
}
}
}Registering snippets
Tokens are generated automatically when calling snippets with the selector parameter:
{'msCart' | snippet: [
'tpl' => 'tpl.msCart',
'selector' => '#cart-container'
]}Custom routes
To add your own endpoints create a file:
core/config/ms3_routes_web.custom.phpExample:
<?php
use MiniShop3\Router\Response;
$router->group('/api/v1', function($router) use ($modx) {
$router->get('/custom/endpoint', function($params) use ($modx) {
return Response::success(['custom' => 'data']);
});
});Custom routes are loaded after system routes and can override them.
JavaScript client
MiniShop3 provides a JavaScript library for working with the API:
// Add to cart
ms3.cart.add(123, 2, {color: 'red'});
// Submit order
ms3.order.submit();
// Handle responses via hooks
ms3.hooks.add('afterAddToCart', ({response}) => {
console.log('Product added', response.data);
});See Frontend JavaScript for details.
Error codes
| Code | Description |
|---|---|
| 400 | Bad request (missing parameters, validation error) |
| 401 | Authorization token required |
| 403 | Access denied |
| 404 | Resource not found |
| 429 | Too many requests |
| 500 | Internal server error |
Debugging
Enable debug mode via the ms3_api_debug setting:
{
"success": false,
"message": "Internal server error",
"code": 500,
"debug": {
"exception": "Exception",
"message": "Detailed error message",
"file": "/path/to/file.php",
"line": 123
}
}Security
Do not enable debug mode in production — it exposes the application's internal structure.
