Brand Product API

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.

Quick Start

1

Get your API key

Your agency partner will provide an API key from their Brand settings page.

2

Set the auth header

Include X-Api-Key: key_id:secret on every request.

3

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

Authentication

Every request must include the X-Api-Key header.

Header X-Api-Key
Format key_id:secret
Content-Type application/json
Keep your API secret secure. If compromised, ask your agency to revoke and re-issue.

Base URL

https://dotcph.com/api/v1

All endpoints below are relative to this base URL.

Endpoints

POST /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.

Request Body

{
  "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 Reference

Field Type Required Description
skustringrequiredUnique product identifier. Used as the match key for upserts.
namestringrequiredProduct display name.
purchase_pricenumberrequiredWholesale price to the agency (in brand's default currency).
selling_pricenumberrequiredRecommended retail price.
descriptionstringoptionalProduct description / copy.
lead_time_daysintegeroptionalProduction or delivery lead time in days.
variantsarrayoptionalProduct variants (colors, sizes, materials). See variant fields below.
imagesarrayoptionalProduct images. Each can be base64-encoded or a URL reference.
metadataobjectoptionalArbitrary nested data — dimensions, materials, certifications, etc.

Variant Fields

Field Type Description
namestringVariant name (required). E.g. "Black / Oak".
sku_suffixstringSuffix appended to parent SKU. E.g. "-BLK-OAK". Used as match key for variant upserts.
sizestringSize description.
colorstringColor name.
fabric_materialstringFabric or material type.
compositionstringMaterial composition. E.g. "80% Cotton, 20% Polyester".
purchase_price_overridenumber|nullOverride product purchase price for this variant. null = inherit.
selling_price_overridenumber|nullOverride product selling price for this variant. null = inherit.

Image Fields

Field Type Description
datastringBase64-encoded image bytes. Supported: JPG, PNG, GIF, WebP. Mutually exclusive with url.
urlstringPublic URL to an existing image. Use this if your images are already hosted.
filenamestringOriginal filename — used to determine file extension when uploading via data.
alt_textstringAccessibility / descriptive text for the image.

Response — 200 (all succeeded) / 207 (partial errors)

{
  "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 Example

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"
      }
    }]
  }'
GET /products List all products

Returns all products belonging to your brand. Useful for verifying what's currently synced.

Response — 200

{
  "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

curl -H "X-Api-Key: bk_abc123:your_secret" https://dotcph.com/api/v1/products
GET /products/{sku} Get single product

Retrieve a single product by its SKU. Returns 404 if the SKU doesn't exist for your brand.

cURL

curl -H "X-Api-Key: bk_abc123:your_secret" https://dotcph.com/api/v1/products/FIBER-CHAIR-001
DELETE /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.

Response — 200

{ "status": "deactivated", "sku": "FIBER-CHAIR-001" }

cURL

curl -X DELETE -H "X-Api-Key: bk_abc123:your_secret" https://dotcph.com/api/v1/products/FIBER-CHAIR-001
GET /health Health check

Validates that your API key is working and returns your brand name and whether image storage is configured.

Response — 200

{
  "status": "ok",
  "brand": "Muuto",
  "storage_configured": true
}

Error Handling

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" }

Best Practices

  • Send your full catalog each time. Products are upserted by SKU — unchanged products are simply updated with the same data. This is idempotent.
  • Use consistent SKUs. The SKU is how we match products across syncs. Changing a SKU creates a new product.
  • Batch your products. Send up to 100 products in a single request for best performance.
  • Images: base64 or URL. If your images are already hosted, just pass the url field. Use data (base64) when you want us to store them on our CDN.
  • Metadata can be anything. Nest objects, use arrays, mix types — the agency's backend renders it all dynamically. Common useful keys: dimensions, materials, certifications, weight_kg, designer, care_instructions.
  • Use DELETE to discontinue. Products are soft-deleted. Send them again via POST to reactivate.

Full Python Example

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))