Push your product catalog directly to your sales agency. Products are upserted by SKU — send us your full catalog and we'll keep everything in sync. Images can be base64-encoded in the payload and are stored on a CDN.
Get your API key
Your agency partner will provide an API key from their Brand settings page.
Set the auth header
Include X-Api-Key: key_id:secret on every request.
Push your catalog
POST your products as JSON. They're upserted by SKU — send the same data again to update.
Test your credentials:
curl -H "X-Api-Key: YOUR_KEY_ID:YOUR_SECRET" \
https://dotcph.com/api/v1/health
Every request must include the X-Api-Key header.
| Header | X-Api-Key |
| Format | key_id:secret |
| Content-Type | application/json |
https://dotcph.com/api/v1
All endpoints below are relative to this base URL.
/products
Upsert products
Create or update one or more products. Products are matched by SKU — if a product with the same SKU already exists, it will be updated in place. Send your full catalog on each sync.
{
"products": [
{
// ── Required fields ──────────────────────────────
"sku": "FIBER-CHAIR-001", // unique product identifier
"name": "Fiber Armchair", // product display name
"purchase_price": 1200.00, // wholesale price to agency
"selling_price": 2400.00, // recommended retail price
// ── Optional core fields ─────────────────────────
"description": "Innovative shell chair…", // product description
"lead_time_days": 28, // production/delivery lead time
// ── Variants (optional) ──────────────────────────
"variants": [
{
"name": "Black / Oak", // required per variant
"sku_suffix": "-BLK-OAK", // appended to product SKU
"size": "Standard",
"color": "Black",
"fabric_material": "Wood fiber composite",
"composition": "70% wood fiber, 30% PP",
"purchase_price_override": null, // null = inherit product price
"selling_price_override": null
}
],
// ── Images (optional) ────────────────────────────
"images": [
{
"data": "iVBORw0KGgoAAAA…", // base64-encoded image bytes
"filename": "fiber-chair-front.jpg", // used for file extension
"alt_text": "Front view" // accessibility text
},
{
"url": "https://cdn.example.com/img.jpg",// OR reference an existing URL
"alt_text": "Side view"
}
],
// ── Free-form metadata (optional) ────────────────
// Nest as deeply as you like — the agency sees it all.
"metadata": {
"materials": {
"shell": "Wood fiber composite",
"base": "Solid oak",
"finish": "Lacquered"
},
"dimensions": {
"width_cm": 51,
"height_cm": 77,
"depth_cm": 53,
"seat_height_cm": 44
},
"certifications": ["FSC", "EU Ecolabel"],
"weight_kg": 5.2,
"designer": "Iskos-Berlin",
"year_introduced": 2014
}
}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
| sku | string | required | Unique product identifier. Used as the match key for upserts. |
| name | string | required | Product display name. |
| purchase_price | number | required | Wholesale price to the agency (in brand's default currency). |
| selling_price | number | required | Recommended retail price. |
| description | string | optional | Product description / copy. |
| lead_time_days | integer | optional | Production or delivery lead time in days. |
| variants | array | optional | Product variants (colors, sizes, materials). See variant fields below. |
| images | array | optional | Product images. Each can be base64-encoded or a URL reference. |
| metadata | object | optional | Arbitrary nested data — dimensions, materials, certifications, etc. |
| Field | Type | Description |
|---|---|---|
| name | string | Variant name (required). E.g. "Black / Oak". |
| sku_suffix | string | Suffix appended to parent SKU. E.g. "-BLK-OAK". Used as match key for variant upserts. |
| size | string | Size description. |
| color | string | Color name. |
| fabric_material | string | Fabric or material type. |
| composition | string | Material composition. E.g. "80% Cotton, 20% Polyester". |
| purchase_price_override | number|null | Override product purchase price for this variant. null = inherit. |
| selling_price_override | number|null | Override product selling price for this variant. null = inherit. |
| Field | Type | Description |
|---|---|---|
| data | string | Base64-encoded image bytes. Supported: JPG, PNG, GIF, WebP. Mutually exclusive with url. |
| url | string | Public URL to an existing image. Use this if your images are already hosted. |
| filename | string | Original filename — used to determine file extension when uploading via data. |
| alt_text | string | Accessibility / descriptive text for the image. |
{
"processed": 1,
"errors": 0,
"results": [
{
"sku": "FIBER-CHAIR-001",
"action": "created", // or "updated"
"product_id": 42,
"images_uploaded": 2
}
],
"error_details": null // or array of { index, sku, error }
}
curl -X POST https://dotcph.com/api/v1/products \
-H "X-Api-Key: bk_abc123:your_secret_here" \
-H "Content-Type: application/json" \
-d '{
"products": [{
"sku": "FIBER-CHAIR-001",
"name": "Fiber Armchair",
"purchase_price": 1200,
"selling_price": 2400,
"metadata": {
"weight_kg": 5.2,
"designer": "Iskos-Berlin"
}
}]
}'
/products
List all products
Returns all products belonging to your brand. Useful for verifying what's currently synced.
{
"count": 2,
"products": [
{
"id": 1,
"sku": "FIBER-CHAIR-001",
"name": "Fiber Armchair",
"description": "Innovative shell chair…",
"purchase_price": 1200.00,
"selling_price": 2400.00,
"lead_time_days": 28,
"is_active": true,
"synced_via_api": true,
"metadata": { ... },
"images": [
{ "url": "https://…", "alt_text": "Front", "sort_order": 0 }
],
"variants": [
{
"name": "Black / Oak",
"sku_suffix": "-BLK-OAK",
"size": null,
"color": "Black",
"fabric_material": "Wood fiber composite",
"composition": null,
"purchase_price_override": null,
"selling_price_override": null,
"is_active": true
}
]
}
]
}
curl -H "X-Api-Key: bk_abc123:your_secret" https://dotcph.com/api/v1/products
/products/{sku}
Get single product
Retrieve a single product by its SKU. Returns 404 if the SKU doesn't exist for your brand.
curl -H "X-Api-Key: bk_abc123:your_secret" https://dotcph.com/api/v1/products/FIBER-CHAIR-001
/products/{sku}
Deactivate product
Soft-deletes a product by marking it inactive. The product is not permanently removed — it can be re-activated by sending it again via POST.
{ "status": "deactivated", "sku": "FIBER-CHAIR-001" }
curl -X DELETE -H "X-Api-Key: bk_abc123:your_secret" https://dotcph.com/api/v1/products/FIBER-CHAIR-001
/health
Health check
Validates that your API key is working and returns your brand name and whether image storage is configured.
{
"status": "ok",
"brand": "Muuto",
"storage_configured": true
}
| Status | Meaning | When |
|---|---|---|
200 |
Success | All products processed successfully. |
207 |
Partial Success | Some products succeeded, some failed. Check error_details. |
400 |
Bad Request | Missing or malformed request body. |
401 |
Unauthorized | Missing, invalid, or revoked API key. |
404 |
Not Found | Product with the given SKU does not exist. |
All errors return a JSON body:
{ "error": "Description of what went wrong" }
url field. Use data (base64) when you want us to store them on our CDN.
dimensions, materials, certifications, weight_kg, designer, care_instructions.
import requests
import base64
import json
from pathlib import Path
API_URL = "https://dotcph.com/api/v1/products"
API_KEY = "bk_your_key_id:your_secret"
headers = {
"X-Api-Key": API_KEY,
"Content-Type": "application/json",
}
# Encode a local image file to base64
def encode_image(path):
data = Path(path).read_bytes()
return base64.b64encode(data).decode("ascii")
payload = {
"products": [
{
"sku": "OUTLINE-3S",
"name": "Outline Sofa 3-Seater",
"description": "Elegant sofa with slim silhouette",
"purchase_price": 8500,
"selling_price": 18900,
"lead_time_days": 42,
"variants": [
{
"name": "Cognac Leather",
"sku_suffix": "-COG",
"color": "Cognac",
"fabric_material": "Refine Leather",
}
],
"images": [
{
"data": encode_image("photos/outline-front.jpg"),
"filename": "outline-front.jpg",
"alt_text": "Front view",
},
{
"url": "https://cdn.muuto.com/outline-detail.jpg",
"alt_text": "Detail shot",
}
],
"metadata": {
"dimensions": {"width": 220, "depth": 84, "height": 71},
"weight_kg": 58,
"designer": "Anderssen & Voll",
"certifications": ["EU Ecolabel"],
},
}
]
}
resp = requests.post(API_URL, headers=headers, json=payload)
print(f"Status: {resp.status_code}")
print(json.dumps(resp.json(), indent=2))