E-Commerce API
Manage your product catalog, track orders, monitor cart activity, and analyze customer RFM segments.
All endpoints on this page are relative to the base URL: https://api-eu1.joryio.com - see API Overview.
ID Naming Convention
Joryio uses a clear naming convention for identifiers:
Your IDs (use these when creating/tracking data)
| Field | Description | Example |
|---|---|---|
productId | Your product identifier (SKU, product ID from your e-commerce platform) | "SKU-12345" |
orderId | Your order identifier (order number from your platform) | "ORD-2024-001" |
userId | Your user identifier | "user-123" |
Joryio IDs (returned in API responses)
| Field | Description | When to use |
|---|---|---|
id | Joryio's internal ID for products/orders | Returned in API responses, use for updates/deletes |
joryioUserId | Joryio's internal user ID (the 24-hex id returned in user API responses) | Alternative to userId when you want to reference by Joryio's ID |
Flexible User Identification
When creating orders or tracking events, you can identify users using either:
userId- Your user identifier (most common)joryioUserId- Joryio's internal user ID
// Using your user ID (recommended)
{ "orderId": "ORD-001", "userId": "user-123", ... }
// Using Joryio's internal user ID
{ "orderId": "ORD-001", "joryioUserId": "66a1f2c3d4e5f6a7b8c9d0e1", ... }
Authentication
All requests require JWT authentication:
Authorization: Bearer your_jwt_token
Content-Type: application/json
X-Workspace-Id: your_workspace_id
Product Catalog
Create Product
Create a new product in your catalog.
POST /catalog/products
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
productId | string | Yes | Your unique product identifier (SKU, product ID) |
name | string | Yes | Product name |
price | number | Yes | Product price |
description | string | No | Product description |
compareAtPrice | number | No | Original price (for discounts) |
currency | string | No | Currency code (default: USD) |
categories | string[] | No | Product categories |
tags | string[] | No | Product tags |
brand | string | No | Brand name |
imageUrl | string | No | Main product image URL |
url | string | No | Product page URL |
inStock | boolean | No | Stock availability (default: true) |
sku | string | No | Stock keeping unit |
variants | object[] | No | Product variants |
customFields | object | No | Custom attributes |
Example Request:
curl -X POST https://api-eu1.joryio.com/catalog/products \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"productId": "SKU-12345",
"name": "Classic Blue T-Shirt",
"price": 29.99,
"compareAtPrice": 39.99,
"currency": "USD",
"categories": ["Clothing", "T-Shirts"],
"tags": ["sale", "bestseller"],
"brand": "Acme Apparel",
"imageUrl": "https://example.com/images/blue-tshirt.jpg",
"url": "https://example.com/products/blue-tshirt",
"inStock": true,
"sku": "BTS-001-BL",
"variants": [
{
"id": "var-s",
"name": "Small",
"sku": "BTS-001-BL-S",
"price": 29.99,
"inStock": true,
"options": { "size": "S", "color": "Blue" }
},
{
"id": "var-m",
"name": "Medium",
"sku": "BTS-001-BL-M",
"price": 29.99,
"inStock": true,
"options": { "size": "M", "color": "Blue" }
}
]
}'
Response:
{
"id": "prod_abc123",
"productId": "SKU-12345",
"name": "Classic Blue T-Shirt",
"price": 29.99,
"createdAt": "2024-01-20T10:00:00.000Z"
}
Bulk Upsert Products (array body)
There is no separate bulk endpoint: POST /catalog/products accepts either a single product object or a bare JSON array of product objects (no wrapper object). The array form creates or updates up to 500 products in one request, upserted by productId.
POST /catalog/products
Request Body:
A JSON array (max 500 elements; an empty array or more than 500 returns 400). Each element follows the same format as the single-object form, including the optional per-product source field.
Every element receives full validation. An invalid element is reported in failed by its array index - it is never silently accepted - and the remaining valid elements are still upserted.
[
{ "productId": "SKU-001", "name": "Product 1", "price": 19.99 },
{ "productId": "SKU-002", "name": "Product 2", "price": 29.99, "source": "shopify" }
]
Response:
Unlike the object form (which returns the created product), the array form returns an aggregate summary:
{
"processed": 2,
"upserted": 2,
"failed": []
}
| Field | Description |
|---|---|
processed | Number of elements received in the request array |
upserted | Products actually created or updated |
failed | Per-element failures: index (position in the request array), productId / sku (when present on the element), reason |
List Products
Query products with filtering and pagination.
GET /catalog/products
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
search | string | Search by name, description, or SKU |
categories | string[] | Filter by categories |
tags | string[] | Filter by tags |
brand | string | Filter by brand |
inStock | boolean | Filter by stock status |
minPrice | number | Minimum price |
maxPrice | number | Maximum price |
limit | number | Results per page (default: 50) |
offset | number | Pagination offset |
sortBy | string | Sort field: name, price, createdAt, updatedAt |
sortOrder | string | asc or desc |
Example:
curl "https://api-eu1.joryio.com/catalog/products?categories=T-Shirts&inStock=true&limit=20" \
-H "Authorization: Bearer $TOKEN"
Get Categories
List all product categories.
GET /catalog/categories
Get Brands
List all product brands.
GET /catalog/brands
Catalogue Stats
Totals and catalogue health, counted over every product - never over a page.
GET /catalog/stats
Response:
{
"productCount": 8214,
"categoryCount": 37,
"brandCount": 12,
"outOfStockCount": 341,
"lowStockCount": 96,
"unclassifiedCount": 18,
"lowStockThreshold": 10
}
| Field | Description |
|---|---|
outOfStockCount | Products with quantity 0, or inStock: false when no quantity is tracked |
lowStockCount | Products with quantity between 1 and lowStockThreshold |
unclassifiedCount | Products missing a category or a brand |
lowStockThreshold | Units-remaining boundary lowStockCount was computed at |
Orders
Create Order
Track a new order.
POST /orders
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
orderId | string | Yes | Your order ID (order number from your platform) |
userId | string | Either/Or | Your customer user ID |
joryioUserId | string | Either/Or | Joryio's internal user ID (alternative to userId) |
total | number | Yes | Order total |
items | object[] | Yes | Line items |
status | string | No | Order status (default: pending) |
currency | string | No | Currency code |
subtotal | number | No | Subtotal before discounts |
discount | number | No | Discount amount |
shipping | number | No | Shipping cost |
tax | number | No | Tax amount |
totalRefunded | number | No | Amount refunded so far, in the order currency (default 0). For a partial refund send the partial amount with status: partiallyRefunded; for a full refund set it to total with status: refunded. Refunds are netted out of attributed revenue. |
couponCode | string | No | Applied coupon |
shippingAddress | object | No | Shipping address |
campaignId | string | No | Attribution campaign |
canvasId | string | No | Attribution canvas |
source | string | No | Order source (email, sms, direct) |
utmSource | string | No | UTM source |
utmMedium | string | No | UTM medium |
utmCampaign | string | No | UTM campaign |
Either userId or joryioUserId must be provided. Use userId with your own user identifiers (most common). Use joryioUserId when you have Joryio's internal user ID (the 24-hex id returned in user API responses).
Line Item Structure:
{
"productId": "prod_abc123",
"name": "Blue T-Shirt",
"sku": "BTS-001",
"quantity": 2,
"price": 29.99,
"total": 59.98,
"imageUrl": "https://example.com/image.jpg"
}
Example Request:
curl -X POST https://api-eu1.joryio.com/orders \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"orderId": "ORD-2024-001",
"userId": "user-123",
"total": 89.97,
"subtotal": 99.97,
"discount": 10.00,
"shipping": 5.99,
"tax": 4.99,
"currency": "USD",
"couponCode": "SAVE10",
"items": [
{
"productId": "prod_abc123",
"name": "Blue T-Shirt",
"quantity": 2,
"price": 29.99,
"total": 59.98
},
{
"productId": "prod_def456",
"name": "Black Jeans",
"quantity": 1,
"price": 49.99,
"total": 49.99
}
],
"source": "email",
"campaignId": "camp_xyz789"
}'
Fulfill Order
Mark an order as shipped.
POST /orders/:id/fulfill
Request Body:
{
"trackingNumber": "1Z999AA10123456784",
"carrier": "UPS"
}
Cancel Order
Cancel an order.
POST /orders/:id/cancel
Request Body:
{
"reason": "Customer requested cancellation"
}
Refund Order
Process a refund.
POST /orders/:id/refund
Request Body:
{
"refundAmount": 29.99,
"reason": "Product defective",
"partial": true
}
Order Statistics
Get order statistics for a date range.
GET /orders/stats?startDate=2024-01-01&endDate=2024-01-31
Response:
{
"totalOrders": 156,
"totalRevenue": 12450.50,
"averageOrderValue": 79.81,
"ordersByStatus": {
"pending": 5,
"processing": 12,
"shipped": 45,
"delivered": 90,
"cancelled": 4
}
}
Cart Tracking
Update Cart
Track or update a user's cart.
PUT /carts
Request Body:
{
"userId": "user_123",
"items": [
{
"productId": "prod_abc123",
"name": "Blue T-Shirt",
"price": 29.99,
"quantity": 2,
"total": 59.98,
"imageUrl": "https://example.com/image.jpg"
}
],
"value": 59.98,
"currency": "USD",
"checkoutUrl": "https://store.example.com/checkout?cart=abc123"
}
Add to Cart
Add an item to a user's cart.
POST /carts/add
Request Body:
{
"userId": "user_123",
"item": {
"productId": "prod_abc123",
"name": "Blue T-Shirt",
"price": 29.99,
"quantity": 1,
"total": 29.99
}
}
Get Abandoned Carts
List abandoned carts for recovery campaigns.
GET /carts/abandoned
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
minValue | number | Minimum cart value |
abandonedMinutesAgo | number | Max minutes since abandonment |
limit | number | Results per page |
offset | number | Pagination offset |
Response:
{
"data": [
{
"id": "cart_123",
"userId": "user_456",
"items": [...],
"value": 149.99,
"abandonedAt": "2024-01-20T15:30:00.000Z",
"checkoutUrl": "https://store.example.com/checkout?cart=abc"
}
],
"total": 42,
"hasMore": true
}
Cart Statistics
Get cart and abandonment statistics.
GET /carts/stats
Response:
{
"totalCarts": 1250,
"abandonedCarts": 312,
"recoveredCarts": 87,
"totalAbandonedValue": 45670.50,
"recoveryRate": 27.88
}
RFM Analysis
RFM (Recency, Frequency, Monetary) analysis segments customers based on purchase behavior.
Get User RFM Data
Get RFM scores and segment for a specific user.
GET /rfm/user/:userId
Response:
{
"totalOrders": 12,
"totalSpent": 849.50,
"averageOrderValue": 70.79,
"firstOrderDate": "2023-06-15T10:00:00.000Z",
"lastOrderDate": "2024-01-18T14:30:00.000Z",
"daysSinceLastOrder": 2,
"hasActiveCart": false,
"rfmRecency": 5,
"rfmFrequency": 4,
"rfmMonetary": 4,
"rfmScore": "544",
"rfmSegment": "champions"
}
Get RFM Distribution
Get customer distribution across RFM segments.
GET /rfm/distribution
Response:
{
"champions": { "count": 150, "totalValue": 125000 },
"loyal": { "count": 320, "totalValue": 89000 },
"potential_loyalists": { "count": 180, "totalValue": 32000 },
"new_customers": { "count": 450, "totalValue": 28000 },
"at_risk": { "count": 95, "totalValue": 42000 },
"lost": { "count": 280, "totalValue": 15000 }
}
RFM Segments
| Segment | Description | Typical RFM Scores |
|---|---|---|
| Champions | Best customers, buy often, spend most | 555, 554, 545 |
| Loyal | Consistent customers | 444, 443, 434 |
| Potential Loyalists | Recent with average frequency | 433, 343, 333 |
| New Customers | Just made first purchase | 511, 512, 411 |
| Promising | Recent but low frequency | 422, 322, 312 |
| Needs Attention | Average, declining engagement | 332, 322, 233 |
| About to Sleep | Below average, at risk | 211, 212, 221 |
| At Risk | Were loyal, haven't bought recently | 144, 143, 244 |
| Can't Lose | Were best customers, now inactive | 155, 154, 255 |
| Hibernating | Low engagement, long inactive | 122, 121, 112 |
| Lost | Lowest scores, likely churned | 111 |
Webhooks
E-commerce events emit webhooks that can trigger canvas flows:
| Event | Description |
|---|---|
ecommerce.order.created | New order placed |
ecommerce.order.fulfilled | Order shipped |
ecommerce.order.cancelled | Order cancelled |
ecommerce.order.refunded | Order refunded |
ecommerce.order.status_changed | Order status changed |
ecommerce.cart.abandoned | Cart marked as abandoned |
ecommerce.cart.recovered | Abandoned cart recovered |
ecommerce.cart.updated | Cart contents changed |
Lookup by Your IDs
Get Product by Your Product ID
Retrieve a product using your product identifier.
GET /catalog/products/by-product-id/:productId
Example:
curl "https://api-eu1.joryio.com/catalog/products/by-product-id/SKU-12345" \
-H "Authorization: Bearer $TOKEN"
Get Order by Your Order ID
Retrieve an order using your order identifier.
GET /orders/by-order-id/:orderId
Example:
curl "https://api-eu1.joryio.com/orders/by-order-id/ORD-2024-001" \
-H "Authorization: Bearer $TOKEN"
Error Responses
{
"statusCode": 404,
"message": "Product SKU-12345 not found",
"error": "Not Found"
}
| Status Code | Description |
|---|---|
| 400 | Invalid request body |
| 401 | Authentication required |
| 403 | Insufficient permissions |
| 404 | Resource not found |
| 409 | Conflict (duplicate external ID) |
| 500 | Internal server error |