PleasureStock API v2
B2B integration & purchasing-intelligence data exchange
🤖 AI-optimized version — single page for LLMs & automation🚀 Quick Start Guide
https://pleasurestock.com/api/v1⚠️ This is not a web page - use this URL in your code for API requests
Available APIs
Sexual wellness catalog, sales data, inventory
B2B partner integration, order management
Real-time order status updates
Rate Limits
External API requests are limited per API key:
1,000 requests/hour for all endpoints, and
60 requests/hour for order creation (POST /api/v1/external/orders).
Every response carries standard RateLimit-* headers; exceeding a limit returns
429 with {"success":false,"error":{"code":"RATE_LIMIT_EXCEEDED"}}.
Implement exponential backoff and respect the headers.
🔐 Authentication
Getting Your API Key
- Login to PleasureStock at https://pleasurestock.com
- Navigate to Settings → API Keys
- Click "Generate New Key"
- Save your key immediately (shown only once!)
Authentication Methods
Method 1: X-API-Key Header (Standard API)
X-API-Key: pk_live_a1b2c3d4e5f6g7h8i9j0
Method 2: Bearer Token (External API)
Authorization: Bearer pk_live_a1b2c3d4e5f6g7h8i9j0
- Standard/Sales API:
X-API-Key: YOUR_KEY - External API:
Authorization: Bearer YOUR_KEY
📦 Standard API Endpoints
Retrieve product catalog with filtering and pagination.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| page | integer | No | Page number (default: 1) |
| limit | integer | No | Items per page (max: 1000, default: 100) |
| supplier_id | integer | No | Filter by supplier ID |
| search | string | No | Search by name, SKU, or description |
| withImages | boolean | No | Include product images (default: false) |
| includeCategory | boolean | No | NEW: Include category information (default: false) |
| includeTcc | boolean | No | NEW: Include TCC (Total Current Cost) calculations (default: false) |
| includeTsp | boolean | No | NEW: Include TSP (Total Sale Price) calculations (default: false) |
Response Example (Basic)
{
"success": true,
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"internal_id": "ps_vj31ucxsab",
"name": "Premium Silicone Vibrator",
"sku": "VIB-001",
"price": "49.99",
"currency": "USD",
"supplier_id": "61dcd6b4-58e8-4945-85dc-b00a35db3a72",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-03-20T14:45:00Z"
}
],
"pagination": {
"page": 1,
"limit": 100,
"total": 1250,
"pages": 13
}
}
Response Example (With Category, TCC, TSP)
{
"success": true,
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"internal_id": "ps_vj31ucxsab",
"name": "Premium Silicone Vibrator",
"sku": "VIB-001",
"price": "49.99",
"currency": "USD",
"supplier_id": "61dcd6b4-58e8-4945-85dc-b00a35db3a72",
// Category fields (includeCategory=true) — single-axis taxonomy ("what it is")
"category_id": "c4f3e2d1-a2b3-4c5d-8e7f-8a9b0c1d2e3f",
"category_name": "Vibrators",
"category_name_en": "Vibrators",
// TCC fields (includeTcc=true) — landed cost, computed in TRY
"tcc_enabled": true,
"tcc_coefficient": 2,
"tcc_total_cost": 748.98,
"tcc_currency": "TRY",
// TSP fields (includeTsp=true) — target selling price, in TRY
"tsp_enabled": true,
"tsp_value": 1976.46,
"tsp_formula": "TCC * 2.5 + 104",
"tsp_currency": "TRY",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-03-20T14:45:00Z"
}
],
"pagination": {
"page": 1,
"limit": 100,
"total": 1250,
"pages": 13
}
}
Field Explanations
| Field | Description |
|---|---|
category_id |
UUID of the product category |
category_name |
Category display name (English; equals category_name_en) |
category_name_en |
Category name in English |
tcc_enabled |
Whether TCC is enabled for your company |
tcc_coefficient |
Multiplier for calculating total cost (e.g., 1.7 = +70% for shipping, taxes, customs) |
tcc_total_cost |
Total Current Cost = price × coefficient (your actual procurement cost) |
tsp_enabled |
Whether TSP is enabled for your company |
tsp_value |
Total Sale Price - recommended retail price calculated from TCC using your formula |
tsp_formula |
Your pricing formula (e.g., "TCC * 1.5", "TCC + 50") |
TCC (Total Current Cost) - Your actual procurement cost including supplier price, shipping, taxes, and customs.
Configure in: Settings → TCC Settings
TSP (Total Sale Price) - Your recommended retail price calculated using a formula based on TCC.
Configure in: Settings → TSP Settings
Note: TCC and TSP are company-specific. Each API key sees their own company's calculations.
Usage Examples
curl -X GET 'https://pleasurestock.com/api/v1/products?page=1&limit=100&includeCategory=true' \ -H 'X-API-Key: your-api-key-here'
curl -X GET 'https://pleasurestock.com/api/v1/products?page=1&limit=100&includeTcc=true&includeTsp=true' \ -H 'X-API-Key: your-api-key-here'
curl -X GET 'https://pleasurestock.com/api/v1/products?page=1&limit=100&includeCategory=true&includeTcc=true&includeTsp=true' \ -H 'X-API-Key: your-api-key-here'
🚀 Batch Query - Get Specific Products by IDs
Instead of fetching all products, you can request specific products using the ids parameter. This dramatically reduces API response size and improves performance.
curl -X GET 'https://pleasurestock.com/api/v1/products?ids=ps_vj31ucxsab,ps_w2as53xl1z,ps_j2y58ajsit&includeCategory=true&includeTcc=true&includeTsp=true' \ -H 'X-API-Key: your-api-key-here'
• Performance: Request only products you need (26 products instead of 2490)
• Bandwidth: Reduce API response size by 99%
• Flexibility: Mix UUID and ps_* IDs in the same request
• Format: Comma-separated list of IDs:
?ids=id1,id2,id3
Parameters
| Parameter | Type | Description |
|---|---|---|
ids |
string | Optional. Comma-separated list of product IDs. Supports both UUID format (db57e260-b151-4bf1-ac63-175baef9f96d) and internal ID format (ps_vj31ucxsab). When provided, only returns specified products. |
Get a single product by its ID (UUID or ps_* internal ID).
URL Parameters
| Parameter | Type | Description |
|---|---|---|
id |
string | Required. Product ID in UUID format (db57e260-b151-4bf1-ac63-175baef9f96d) or internal ID format (ps_vj31ucxsab). |
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
withImages |
boolean | true | Include product images |
includeCategory |
boolean | false | Include category information |
includeTcc |
boolean | false | Include Total Current Cost calculations |
includeTsp |
boolean | false | Include Total Sale Price calculations |
Usage Examples
curl -X GET 'https://pleasurestock.com/api/v1/products/ps_vj31ucxsab?includeCategory=true&includeTcc=true&includeTsp=true' \ -H 'X-API-Key: your-api-key-here'
curl -X GET 'https://pleasurestock.com/api/v1/products/db57e260-b151-4bf1-ac63-175baef9f96d?includeCategory=true&includeTcc=true&includeTsp=true' \ -H 'X-API-Key: your-api-key-here'
Response Example
{
"success": true,
"data": {
"id": "db57e260-b151-4bf1-ac63-175baef9f96d",
"internal_id": "ps_vj31ucxsab",
"name": "Premium Silicone Vibrator",
"sku": "SL2505044-XCM",
"price": "8.00",
"currency": "USD",
"supplier_id": "f0229142-b869-42c6-8751-9cb3d629e192",
"created_at": "2025-11-04T08:34:35.815Z",
"updated_at": "2025-11-04T08:45:47.291Z",
// Category fields (includeCategory=true) — single-axis taxonomy ("what it is")
"category_id": "61dcd6b4-58e8-4945-85dc-b00a35db3a72",
"category_name": "Vibrators",
"category_name_en": "Vibrators",
// NOTE: v1 returns category only. Product FEATURES (Rabbit, Thrusting, Realistic…) are TAGS,
// available via External API v2: GET /api/v2/external/catalog?include=tags
// TCC fields (includeTcc=true)
"tcc_enabled": true,
"tcc_coefficient": 2,
"tcc_total_cost": 16,
"tcc_currency": "TRY",
// TSP fields (includeTsp=true)
"tsp_enabled": true,
"tsp_value": 144,
"tsp_formula": "TCC * 2.5 + 104",
"tsp_currency": "TRY"
}
}
Submit sales data for Smart Reordering AI predictions and inventory tracking.
Request Body
{
"sales": [
{
"productId": "550e8400-e29b-41d4-a716-446655440000",
"date": "2025-11-03",
"quantity": 15,
"stock_on_hand": 120
},
{
"productId": "550e8400-e29b-41d4-a716-446655440000",
"date": "2025-11-03",
"quantity": 8,
"stock_on_hand": 45
}
]
}
Request Parameters
| Field | Type | Required | Format | Example |
|---|---|---|---|---|
productId |
string | ✅ Yes | UUID | "550e8400-e29b-41d4-a716-446655440000" |
date |
string | ✅ Yes | YYYY-MM-DD | "2025-11-03" |
quantity |
integer | ✅ Yes | ≥ 0 | 15 |
stock_on_hand |
integer | ❌ No | ≥ 0 | 120 |
Validation Rules
productIdaccepts only UUID format:- UUID:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx(36 characters)
- UUID:
datemust be valid ISO date (YYYY-MM-DD format)quantitymust be non-negative integerstock_on_handis optional, non-negative integer- Array
salescan contain up to 1000 records per request
Success Response
{
"success": true,
"message": "Sales data submitted successfully",
"processed": 2
}
Error Response
{
"error": "Validation errors",
"errors": [
{
"index": 0,
"error": "Invalid productId format",
"expected": "UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"received": "invalid_id_123"
}
],
"processed": 0
}
1. Login to PleasureStock
2. Go to Products section
3. Find your product by SKU
4. Column "PleasureStock ID" shows the
productId (UUID)
What Happens After Submission?
- ✅ Sales data is stored in PleasureStock
- 🤖 AI automatically calculates Smart Reordering recommendations (once daily)
- 📊 View recommendations at: Cart → Smart Reordering tab
- 🎯 See recommended quantities, current stock, and forecast accuracy
Best Practices
- 📅 Frequency: Send data daily (minimum) or every 2-4 hours (optimal)
- 📊 Historical Data: Submit past 30-90 days for better AI predictions
- 📦 Batch Size: Up to 1000 records per request
- 🎯 Accuracy: Always include
stock_on_handfor best results
You can safely send the same data multiple times - the system automatically deduplicates by date.
Example: If you send data for 2025-11-03 twice, the system will aggregate:
quantity→ SUM (total sales for the day)stock_on_hand→ MAX (highest stock level = total across warehouses)
Multiple Warehouses Support
If you have multiple warehouses, send data from each warehouse separately. The system will aggregate automatically:
{
"sales": [
{
"productId": "550e8400-e29b-41d4-a716-446655440000",
"date": "2025-11-03",
"quantity": 10, // Warehouse A sales
"stock_on_hand": 500 // Warehouse A stock
},
{
"productId": "550e8400-e29b-41d4-a716-446655440000", // Same product, different warehouse
"date": "2025-11-03",
"quantity": 5, // Warehouse B sales
"stock_on_hand": 300 // Warehouse B stock
}
]
}
Result: System aggregates to quantity: 15 (total sales) and stock_on_hand: 500 (max = total stock across all warehouses).
Out-of-Stock Days
When a product is out of stock, send stock_on_hand: 0. The AI will:
- ✅ Exclude these days from average sales calculations
- ✅ Mark them as OOS (Out-of-Stock) periods
- ✅ Provide more accurate forecasts by ignoring zero-sales due to stockouts
{
"sales": [{
"productId": "550e8400-e29b-41d4-a716-446655440000",
"date": "2025-11-03",
"quantity": 0, // No sales possible
"stock_on_hand": 0 // ⚠️ Out of stock!
}]
}
Get AI-powered inventory recommendations based on sales history.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| date | string | No | Target date for predictions (default: today) |
Convert prices to Turkish Lira with real-time exchange rates.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| amount | number | Yes | Amount to convert |
| currency | string | Yes | Source currency (EUR, USD, GBP) |
🔗 External B2B Integration API
docs/api/CLIENT_INTEGRATION_MANUAL.md.Base URL:
https://pleasurestock.com/api/v2/externalAuth:
Authorization: Bearer YOUR_API_KEY (company-scoped; same key as Standard API)Envelope:
{ "success": true, "data": {…} } / { "success": false, "error": "…", "details": [] }Product key: everything is keyed by our catalog
productId (UUID) — map your SKUs first.Number types: monetary/fractional DECIMAL fields (
price, amount,
averageDailySales, reorderPoint, daysUntilStockout…) come back as
JSON strings — parse as decimal, not blindly as float.
category per product answering "what is this thing" (~30 top-level categories,
no sub-categories). Everything about "what it's like" lives in tags, grouped by
form (Rabbit, Bullet, Wand, Suction…), style (Realistic, Fantasy…),
zone (Clitoral, G-spot…), medium (Water-based), motion (Thrusting),
wear (Wearable, Strapless…) and segment (Pocket, Training kit, Accessories).Example. A rabbit vibrator is category
vibrators with tags
Rabbit + Thrusting — not a "Rabbit" category. Former sub-categories
(Rabbit, Realistic, Water-based, Pocket…) are now tags. When mapping, match on the
stable categorySlug, never on the display category name.
1) Product mapping
Paginated export of the catalog visible to you → map your SKUs to our productId. limit ≤ 500.
include (optional): comma-separated extras for a rich projection —
description (supplier-sourced product text, cleaned & translated to English — raw material
for writing your own selling copy), tags (our classification: a controlled vocabulary
assigned by our AI consensus pipeline — structured signal that is not derivable from the description),
attributes (material, waterproof as an IP-class string like "IPX5",
vibration/heating booleans, weight, dimensions, units per box, production lead time — any may be
null). Default stays lean; unknown values are silently ignored.
Category identity. Every item carries category (human-readable name,
not a stable identifier — names are not historically unique) and categorySlug
(the stable identifier). Match on categorySlug, not on the display name.
The taxonomy is a single axis of ~30 top-level categories; product features
(Rabbit, Realistic, Water-based, Pocket…) are exposed as tags, not as sub-categories.
SKU mapping. sku is our internal unique key (it may carry a
collision suffix like -v2 that does not exist in the supplier's price list).
supplierSku is the original supplier article code with that suffix stripped — map your
listing on supplierSku (or barcode when present), not on the raw sku.
?sku= search matches both. productId (UUID) stays the permanent key.
GET /api/v2/external/catalog?limit=100&offset=0&include=description,tags,attributes
{ "success": true, "data": { "total": 6140, "limit": 100, "offset": 0,
"items": [ { "productId": "b147b193-118e-40c6-bbc0-168aaaaf32a0", "sku": "SHD-S392-2",
"supplierSku": "SHD-S392-2",
"barcode": null, "name": "Gino-RCT", "price": "12.35",
"currency": "USD", "supplier": "S-Hande Technology Co.,Ltd",
"category": "Vibrators", "categorySlug": "vibrators",
"description": "Premium device crafted from medical-grade silicone and durable ABS…",
"tags": ["abs", "Dual-motor", "Rabbit", "Remote-control", "silicone", "vibrating"],
"material": "Medical silicone + ABS", "waterproof": "IPX5", "hasVibration": true,
"hasHeating": false, "weightKg": "0.090", "dimensionsCm": null,
"unitsPerBox": null, "productionTimeDays": null } ] } }
// price/weightKg are strings (DECIMAL); barcode = UPC/EAN/GTIN or null (strongest mapping key)
// waterproof = declared IP class as a STRING ("IPX5"), not a boolean; any attribute may be null
// tags are display names, casing is mixed — compare case-insensitively; legacy values may include
// materials (silicone/abs/tpe) — use the `material` attribute for material, ignore unknown valuesTargeted candidate lookup (name substring / SKU prefix; at least one). Supports the same
include parameter as /catalog. Can't map by SKU? Use GET /matches (similarity engine).
2) PUSH — you send us data
Raw sales (every sale or daily-grouped). We compute daily volume, weighted price, trend. Append-only, idempotent on (productId,date,marketplace,saleId).
{ "sales": [ { "productId": "uuid", "date": "2026-06-01", "units": 3,
"price": 349.90, "currency": "TRY", "marketplace": "trendyol" } ] }Point-in-time stock observations. Stock is a value that changes during the day
(inventory counts, re-grading, receipts/write-offs) — each observation is a real fact, not a
duplicate. Send an observedAt timestamp; you may send many observations per day.
We store all of them (movement history) and never overwrite — "current stock" =
the observation with the latest observedAt. (Legacy "one per day, latest wins" is retired.)
Send out_of_stock (boolean) on every item — a direct
availability flag (whether the item was buyable, independent of the number). It
overrides the quantity when we decide if a day counts toward demand:
on_hand>0 + out_of_stock:true (delisted, sitting in the warehouse) →
day is not counted; on_hand:0 + out_of_stock:false
(re-grading/swap, still selling) → day is counted. If omitted we fall back to guessing
(on_hand:0 = out of stock), which is less accurate.
{ "stock": [ { "productId": "uuid", "observedAt": "2026-06-01T14:30:00Z", "on_hand": 134, "out_of_stock": false } ] }:ref = delivery_id (PS-YYYY-NNNN) or order_number (ORD-…). Cumulative payments → real landed cost (replaces ×1.7 fallback). Idempotent on (orderNumber,paymentId).
{ "payments": [ { "paymentId": "sap-pay-771", "date": "2026-04-12", "amount": 1000.00,
"currency": "USD", "kind": "goods_invoice" } ] }
// kind ∈ goods_invoice | freight | customs | vat | insurance | other7-state machine created→part_paid→paid→on_the_way→delivered→accepted (+cancelled). Report transitions as they happen → real per-mode lead time. Served by v1 today (see External API v1 section); a v2 :ref-based variant accepting delivery_id is planned.
{ "status": "on_the_way", "comment": "left warehouse" }Submit your listing → similarity engine matches it to our catalog (powers GET /matches).
3) READ — you read our calculations
Reorder recommendations per product (quantity, sea/air method, urgency, days-to-stockout, per-mode
scenarios). data.count = number of items. shippingReason is a human-readable,
currently localized explanation — display it, don't parse it.
{ "success": true, "data": { "count": 16, "items": [ { "productId": "uuid",
"recommendedQuantity": 462, "currentStock": 19, "ordersInTransit": 0,
"averageDailySales": "5.02", "forecastAccuracy": "27.08", "calculationDate": "2026-07-10",
"reorderPoint": "480.39", "safetyStock": "24", "demandTrend": "up",
"recommendedMethod": "air", "daysUntilStockout": "3.8",
"shippingUrgency": "air_urgent_stockout_risk", "shippingReason": "",
"shippingScenarios": {
"air": { "safetyStock": 24, "leadTimeDays": 61, "reorderPoint": 480.39, "recommendedQuantity": 462 },
"sea": { "safetyStock": 28.47, "leadTimeDays": 98, "reorderPoint": 670.41, "recommendedQuantity": 652 } } } ] } } Aggregated demand over the window: units summed across marketplaces, weighted-average price, trend.
{ "success": true, "data": { "productId": "uuid", "windowDays": 30, "daysWithSales": 0,
"totalUnits": 0, "avgDailyUnits": 0, "weightedAvgPrice": null, "trend": "flat" } }Landed cost / TCC for an order. source: actual (from your payments) or estimated (coefficient fallback). Also ships the delivery ETA fields (shippingMethod, estimatedArrivalDate, estimatedArrivalBasis) alongside the cost.
{ "success": true, "data": { "orderNumber": "PS-2026-0523", "source": "actual",
"landedCost": 13388.16, "currency": "USD", "orderTotal": 7887.66,
"shippingMethod": "sea", "estimatedArrivalDate": "2026-06-29", "estimatedArrivalBasis": "shipped",
"payments": [ { "paymentId": "101", "date": "2026-05-15", "amount": "2500.00", "currency": "USD", "kind": "other" } ] } }Similarity-engine candidates mapping your listing (Flow D) → our catalog products. Each candidate carries a score.
{ "success": true, "data": { "method": "text-embedding",
"embeddings": "product-hub:gemini-embedding-001:768",
"note": "Embedding-ranked candidate matching via product-hub vector-store (logic in PS).",
"products": [ { "clientProductId": "1626839826", "clientName": "…", "candidates": [
{ "productId": "uuid", "name": "Thrusting Vibrator — 36*36*225", "sku": "SN22A",
"supplierSku": "SN22A", "price": "8.85", "currency": "USD", "supplier": "Multi Fun co. Ltd", "score": 0.7041 } ] } ] } }4) PULL feed — you expose, we poll hourly
/settings/feed). Sections:
GET /orders, /payments, /sales, /stock, /products
(each with a from/to window; /stock takes a date).
Dates power the real lead time (acceptedDate − firstPaymentDate, per mode). Send real YYYY-MM-DD or omit — never a placeholder/now() for a date you don't have.
{ "orders": [ { "orderRef": "PS-2026-0523", "status": "on_the_way", "supplier": "Yuyang",
"currency": "USD", "total": 7887.66,
"lines": [ { "productId": "uuid", "units": 30, "price": 5.29, "currency": "USD" } ],
"firstPaymentDate": "2026-05-15", // first payment to supplier (deposit) — lead-time start
"shippedDate": "2026-06-12", // dispatched / in transit
"acceptedDate": "2026-06-26" // received at warehouse — lead-time end
} ] }delivery_id (PS-YYYY-NNNN) and
order_number (ORD-…). Order-scoped endpoints match by either.
Marketplace commission is configured in your cabinet (per-marketplace), not sent in the feed.
Legacy — External API v1
/api/v1/external) is legacy — still live for existing integrations.
New clients: use v2 above.Authentication: External API uses Bearer token format with your same API key:
Authorization: Bearer YOUR_API_KEYBase URL:
https://pleasurestock.com/api/v1/externalRate Limit: 1,000 req/hour per API key (60 req/hour for order creation);
RateLimit-* headers on every response, 429 on excess
X-API-Key to Authorization: Bearer.
Create new orders in PleasureStock system. Items are automatically grouped by supplier.
Request Body
{
"items": [
{
"productId": "550e8400-e29b-41d4-a716-446655440001",
"quantity": 10,
"customPrice": 25.50 // optional, uses product price if not provided
},
{
"productId": "550e8400-e29b-41d4-a716-446655440001",
"quantity": 5
}
],
"externalOrderId": "ERP-2025-001", // your internal reference
"comment": "Urgent order for customer ABC"
}
Response Example
{
"success": true,
"data": {
"orders": [
{
"orderNumber": "ORD-2025-000123",
"deliveryId": "PS-2025-0114",
"supplierId": 12,
"status": "created",
"totalAmount": 255.00,
"currency": "USD",
"items": 1,
"createdAt": "2025-01-14T10:00:00Z"
},
{
"orderNumber": "ORD-2025-000124",
"deliveryId": "PS-2025-0115",
"supplierId": 15,
"status": "created",
"totalAmount": 125.00,
"currency": "USD",
"items": 1,
"createdAt": "2025-01-14T10:00:00Z"
}
],
"totalOrders": 2,
"totalAmount": 380.00
}
}
Submit comprehensive sales data for AI-powered reordering recommendations.
Request Body
{
"products": [
{
"productId": "550e8400-e29b-41d4-a716-446655440001",
"quantity": 15,
"current_stock": 85,
"sales_history": [5, 8, 12, 6, 9, 11, 7]
}
]
}
Validation Requirements
quantitymust be non-negative numbercurrent_stockmust be non-negative numbersales_historymust contain at least 7 days of data
Retrieve all orders created by your API key with filtering options.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| status | string | No | Filter by status (created, part_paid, paid, on_the_way, delivered, accepted, cancelled) |
| limit | integer | No | Results per page (default: 20) |
| offset | integer | No | Skip records (default: 0) |
Response Example
{
"success": true,
"data": {
"orders": [
{
"orderId": "a9e312b9-d5bb-4961-8db6-7ea1007e672b",
"orderNumber": "ORD-20251103-78XZ70",
"deliveryId": "PS-2025-1103/2",
"status": "created",
"statusDisplay": {
"label": "Created",
"color": "blue",
"cssClass": "bg-blue-100 text-blue-800"
},
"supplier": { "id": "550e8400-e29b-41d4-a716-446655440001", "name": "Example Supplier" },
"items": [
{
"isCustom": false,
"productId": "b421eb2c-e1ee-4537-9c19-056d63a337c4",
"sku": "VIB-001",
"name": "Premium Silicone Vibrator",
"imageUrl": "https://pleasurestock.com/uploads/products/vib001.jpg",
"quantity": 5,
"unitPrice": "29.99",
"totalPrice": "149.95",
"currency": "USD"
}
],
"totalAmount": "5669.57",
"currency": "USD",
"shippingMethod": "sea",
"estimatedArrivalDate": null,
"estimatedArrivalBasis": null,
"estimatedDeliveryDate": null,
"createdAt": "03/11/25"
},
{
"orderId": "c7f45a1e-2b8d-4e6f-9a3c-1d5e8f012ab3",
"orderNumber": "ORD-20251108-91QW42",
"deliveryId": "PS-2025-1108/1",
"status": "on_the_way",
"statusDisplay": {
"label": "On the way",
"color": "purple",
"cssClass": "bg-purple-100 text-purple-800"
},
"supplier": { "id": "550e8400-e29b-41d4-a716-446655440001", "name": "Example Supplier" },
"items": [
{
"isCustom": false,
"productId": "b421eb2c-e1ee-4537-9c19-056d63a337c4",
"sku": "VIB-001",
"name": "Premium Silicone Vibrator",
"imageUrl": "https://pleasurestock.com/uploads/products/vib001.jpg",
"quantity": 5,
"unitPrice": "29.99",
"totalPrice": "149.95",
"currency": "USD"
}
],
"totalAmount": "5669.57",
"currency": "USD",
"shippingMethod": "air",
"estimatedArrivalDate": "2025-12-16",
"estimatedArrivalBasis": "shipped",
"estimatedDeliveryDate": "2025-12-16",
"createdAt": "08/11/25"
}
],
"pagination": {
"total": 8,
"limit": 20,
"offset": 0
}
}
}
items[] carries the catalog sku and an absolute public imageUrl (or null if no photo) so you can build product cards directly from the feed — no extra lookup needed. Use the statusDisplay object for color-coded status badges (cssClass uses Tailwind CSS classes). ETA fields (estimatedArrivalDate/estimatedArrivalBasis) ship with every list row; null until the first payment — see the Delivery ETA note below.
Get detailed information about a specific order including all items and custom line items.
Response Example
{
"success": true,
"data": {
"orderId": "a9e312b9-d5bb-4961-8db6-7ea1007e672b",
"orderNumber": "ORD-20251103-78XZ70",
"deliveryId": "PS-2025-1103/2",
"externalOrderId": null,
"status": "created",
"statusDisplay": {
"label": "Created",
"color": "blue",
"cssClass": "bg-blue-100 text-blue-800"
},
"supplier": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"name": "Example Supplier"
},
"hasCustomItems": true,
"items": [
{
"isCustom": false,
"productId": "b421eb2c-e1ee-4537-9c19-056d63a337c4",
"sku": "VIB-001",
"name": "Premium Silicone Vibrator",
"imageUrl": "https://pleasurestock.com/uploads/products/vib001.jpg",
"quantity": 5,
"unitPrice": "29.99",
"totalPrice": "149.95",
"currency": "USD",
"product": {
"name": "Premium Silicone Vibrator",
"sku": "VIB-001",
"imageUrl": "https://pleasurestock.com/uploads/products/vib001.jpg"
}
},
{
"isCustom": true,
"productId": null,
"customSku": "CUSTOM-7788",
"customName": "OEM packaging insert (client artwork)",
"customCategory": "Packaging",
"customDescription": "Printed insert, 90x50mm, matte",
"imageUrl": null,
"quantity": 2000,
"unitPrice": "0.12",
"totalPrice": "240.00",
"currency": "USD"
}
],
"totalAmount": "5669.57",
"currency": "USD",
"shippingMethod": "sea",
"estimatedArrivalDate": "2025-11-20",
"estimatedArrivalBasis": "first_payment",
"estimatedDeliveryDate": "2025-11-20",
"actualDeliveryDate": null,
"createdAt": "03/11/25",
"confirmedAt": "03/11/25",
"paidAt": null,
"shippedAt": null,
"acceptedAt": null
}
}
estimatedArrivalDate is the estimated
arrival date, based on the median of real deliveries for this
shippingMethod (sea/air). Before shipment it is
counted from the first payment; after shipment it is refined from the shipment date —
estimatedArrivalBasis tells which (first_payment |
shipped). It is null until the first payment arrives, and
null after acceptance (use acceptedAt for the actual date).
estimatedDeliveryDate is a legacy alias of estimatedArrivalDate.
isCustom boolean.
isCustom: false— catalog product: useproductId(UUID),sku,name,imageUrl(absolute public URL).isCustom: true— custom line (not in our catalog):productIdisnull; usecustomSku,customName,customCategory,customDescription;imageUrlisnull.
isCustom — present on every line in the list, detail and order.updated webhook. Do not infer from productId === null alone.
Update order status in the workflow.
Request Body
{
"status": "paid",
"externalOrderId": "EXT-123456",
"comment": "Payment confirmed"
}
created.
All subsequent status updates must come from the client (your system) via this API endpoint.
PleasureStock does not change order statuses - you are responsible for updating statuses as the order progresses through your workflow.
Status Workflow
created → part_paid → paid → on_the_way → delivered → acceptedCancellation: Can be set from any non-final status →
cancelledFinal statuses:
accepted, cancelled
| Status | Set By | Description | Notifications |
|---|---|---|---|
created |
System | Order created, awaiting payment | None |
part_paid |
External API | Partial payment received | Email to supplier |
paid |
External API | Full payment confirmed | Email to supplier |
on_the_way |
External API | Shipment dispatched | Email to buyer |
delivered |
External API | Goods delivered to buyer (awaiting acceptance) | Email to buyer and supplier |
accepted |
External API | Goods received and accepted by buyer (FINAL) | Email to supplier |
cancelled |
System/User | Order cancelled (FINAL - cannot be changed) | Email to buyer and supplier |
📋 Best Practices for Working with Order Statuses
To get only active orders and avoid cancelled duplicates, use status filters:
GET /api/v1/external/orders?status=created GET /api/v1/external/orders?status=paid GET /api/v1/external/orders?status=on_the_way
If you see multiple orders with identical content and amounts, check the
status field:• Orders with
status: "cancelled" are NOT active• Each order has unique
orderId, orderNumber, and deliveryId• Use
deliveryId to identify the latest active order (e.g., PS-2025-1103/2 is newer than PS-2025-1103/1)
Using statusDisplay Field
The API returns a statusDisplay object with formatted status information:
{
"status": "cancelled",
"statusDisplay": {
"label": "Cancelled",
"color": "red",
"cssClass": "bg-red-100 text-red-800"
}
}
This field is useful for displaying order status in your UI with appropriate styling.
Receive webhook notifications for order events.
Webhook Events
order.confirmed- Order confirmed in systemorder.status_changed- Order status updated
🤖 Connect via MCP
The PleasureStock catalog is available as a remote MCP server (Model Context Protocol), so AI agents — Claude Code, Cursor and any MCP-capable client — can read the catalog as native tools. One config line, your existing API key, no extra setup.
https://pleasurestock.com/mcp ·
Transport: Streamable HTTP (protocol 2025-11-25), stateless ·
Auth: Authorization: Bearer YOUR_API_KEY (same key as the REST API) ·
Access: read-only, free under the shared 1,000 req/hour per-key limit.
Add to Claude Code
claude mcp add --transport http pleasurestock https://pleasurestock.com/mcp \ --header "Authorization: Bearer YOUR_API_KEY"
Add to Cursor (~/.cursor/mcp.json)
{
"mcpServers": {
"pleasurestock": {
"url": "https://pleasurestock.com/mcp",
"headers": { "Authorization": "Bearer YOUR_API_KEY" }
}
}
}Available tools (phase 1 — read-only)
Search products by name and/or SKU, or browse the catalog with cursor pagination.
response_format: concise (default) or detailed.
Full card of one product by its stable productId (UUID): name, description,
wholesale price + currency, images, supplier, category, physical attributes and lead time.
Batch-resolve up to 50 products by UUID at once; returns found cards plus a not_found list.
The flat list of top-level categories to understand the catalog taxonomy. The taxonomy has a
single level — children is always an empty array, kept for backward compatibility.
Product features (Rabbit, Realistic, Water-based…) are tags, not categories.
🔔 Webhooks
Configuration
Outbound order-event webhooks are configured on request — contact [email protected] with your receiving endpoint URL. Self-service webhook management in your account settings is coming soon.
Webhook Events
| Event | Trigger | Payload |
|---|---|---|
order.created |
New order created via API | Full order details with items |
order.status_changed |
Order status updated | Order with old/new status |
order.cancelled |
Order cancelled | Order details with cancellation reason |
Webhook Security
All webhook requests include HMAC-SHA256 signature for verification:
X-Webhook-Signature: sha256=4f8b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b X-Webhook-Timestamp: 1736850600
Webhook Payload Examples
order.updated Event
PleasureStock emits a single outbound event — order.updated — whenever an order/delivery changes (status, payment, shipping). It carries the full order snapshot, including items[] with the catalog sku and an absolute public imageUrl (or null if no photo) per line, so you can create/refresh product cards directly from the webhook.
{
"event": "order.updated",
"timestamp": "2025-01-14T10:00:00Z",
"data": {
"orderId": "550e8400-e29b-41d4-a716-446655440000",
"orderNumber": "ORD-2025-000123",
"deliveryId": "PS-2025-0114",
"externalOrderId": "ERP-2025-001",
"status": "on_the_way",
"shippingMethod": "sea",
"trackingNumber": "TRK-998877",
"supplier": { "id": "550e8400-e29b-41d4-a716-446655440001", "name": "Example Supplier" },
"items": [
{
"isCustom": false,
"productId": "b421eb2c-e1ee-4537-9c19-056d63a337c4",
"sku": "VIB-001",
"name": "Premium Silicone Vibrator",
"imageUrl": "https://pleasurestock.com/uploads/products/vib001.jpg",
"quantity": 10,
"unitPrice": 25.50,
"totalPrice": 255.00,
"currency": "USD"
}
],
"totalAmount": 255.00,
"currency": "USD",
"shippingMethod": "air",
"estimatedArrivalDate": "2025-02-17",
"estimatedArrivalBasis": "shipped",
"estimatedDeliveryDate": "2025-02-17",
"orderUrl": "https://pleasurestock.com/orders/550e8400-e29b-41d4-a716-446655440000",
"paidAt": "2025-01-16T14:30:00Z",
"onTheWayAt": "2025-01-18T09:00:00Z",
"createdAt": "2025-01-14T10:00:00Z"
}
}
order.status_changed Event
{
"event": "order.status_changed",
"timestamp": "2025-01-14T11:00:00Z",
"data": {
"orderId": "550e8400-e29b-41d4-a716-446655440000",
"orderNumber": "ORD-2025-000123",
"oldStatus": "created",
"newStatus": "paid",
"changedBy": "external_api",
"comment": "Payment confirmed via bank transfer",
"changedAt": "2025-01-14T11:00:00Z"
}
}
Webhook Retry Mechanism
• 1st retry: After 2 seconds
• 2nd retry: After 4 seconds
• 3rd retry: After 8 seconds
• Timeout: 10 seconds per request
• Failed webhooks are queued for manual retry
⚠️ Error Handling
Error Response Format
{
"success": false,
"error": "Invalid request",
"message": "The request payload is invalid",
"details": ["Missing required field: productId"]
}
HTTP Status Codes
| Code | Description | Common Causes |
|---|---|---|
| 200 | Success | Request processed successfully |
| 400 | Bad Request | Invalid parameters, validation errors |
| 401 | Unauthorized | Missing or invalid API key |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Resource doesn't exist |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server-side error |
💻 Code Examples
cURL
curl -X GET "https://pleasurestock.com/api/v1/products?limit=10" \ -H "X-API-Key: pk_live_your_api_key_here"
Python
import requests
import hashlib
import hmac
import time
class PleasureStockAPI:
def __init__(self, api_key, webhook_secret=None):
self.api_key = api_key
self.webhook_secret = webhook_secret
self.base_url = "https://pleasurestock.com/api/v1"
def create_order(self, items, external_order_id=None, comment=None):
"""Create a new order in PleasureStock"""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"items": items,
"externalOrderId": external_order_id,
"comment": comment
}
response = requests.post(
f"{self.base_url}/external/orders",
json=payload,
headers=headers
)
if response.status_code == 200:
return response.json()
else:
raise Exception(f"API Error: {response.text}")
def update_order_status(self, order_number, status, comment=None):
"""Update order status"""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {"status": status}
if comment:
payload["comment"] = comment
response = requests.put(
f"{self.base_url}/external/orders/{order_number}/status",
json=payload,
headers=headers
)
return response.json()
def verify_webhook(self, signature, timestamp, body):
"""Verify webhook signature"""
if not self.webhook_secret:
return False
message = f"{timestamp}.{body}"
expected = hmac.new(
self.webhook_secret.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
return f"sha256={expected}" == signature
# Example usage
api = PleasureStockAPI("pk_live_your_api_key_here")
# Create order
order = api.create_order(
items=[
{"productId": "550e8400-e29b-41d4-a716-446655440001", "quantity": 10, "customPrice": 25.50},
{"productId": "550e8400-e29b-41d4-a716-446655440001", "quantity": 5}
],
external_order_id="ERP-2025-001",
comment="Urgent order"
)
print(f"Created {order['data']['totalOrders']} orders")
for o in order['data']['orders']:
print(f" - {o['orderNumber']}: ${o['totalAmount']}")
# Update status to paid
api.update_order_status(
order['data']['orders'][0]['orderNumber'],
"paid",
"Payment confirmed"
)
Node.js
const axios = require('axios');
class PleasureStockAPI {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseURL = 'https://pleasurestock.com/api/v1';
}
async getOrders(status = null) {
try {
const params = status ? { status } : {};
const response = await axios.get(
`${this.baseURL}/external/orders`,
{
headers: {
'Authorization': `Bearer ${this.apiKey}`
},
params
}
);
return response.data;
} catch (error) {
console.error('API Error:', error.response?.data || error.message);
throw error;
}
}
async updateOrderStatus(orderNumber, status, comment = '') {
try {
const response = await axios.put(
`${this.baseURL}/external/orders/${orderNumber}/status`,
{ status, comment },
{
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
}
}
);
return response.data;
} catch (error) {
console.error('API Error:', error.response?.data || error.message);
throw error;
}
}
}
// Usage
const api = new PleasureStockAPI('pk_live_your_api_key_here');
// Get pending orders
api.getOrders('pending')
.then(orders => {
console.log(`Found ${orders.data.orders.length} pending orders`);
});
// Update order status
api.updateOrderStatus('ORD-2024-001234', 'paid', 'Payment confirmed')
.then(result => {
console.log('Order updated:', result);
});