agentlyleads docs
Offerings API

POST /api/v1/products

Create or update up to 1,000 products in a single request. Each row is matched and upserted by sku (primary key) then externalId (secondary key); rows with no match are created.

This endpoint manages offerings (the entity behind the products path). Set each row's type to PRODUCT (default), SERVICE, SUBSCRIPTION, or CONTRACT — see Offerings & types.

Request

POST /api/v1/products HTTP/1.1
Host: agentlyleads.com
Authorization: Bearer alk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

{
  "products": [
    {
      "name": "4G Router",
      "sku": "RTR-4G",
      "price": 199,
      "cost": 120,
      "category": "Electronics",
      "unit": "each",
      "stockQty": 50,
      "type": "PRODUCT"
    },
    {
      "name": "Pro Plan",
      "sku": "PRO-1",
      "price": 29,
      "category": "SaaS",
      "type": "SUBSCRIPTION",
      "billingPeriod": "MONTHLY"
    }
  ]
}

The products array must contain at least 1 item and at most 1,000.

Response — 200 (all rows succeeded)

When every row in the batch succeeds, the response status is 200:

{
  "created": 1,
  "updated": 1,
  "errors": 0,
  "results": [
    { "index": 0, "status": "created", "id": "cmq1abc2def3ghi4jkl5" },
    { "index": 1, "status": "updated", "id": "cmq6mno7pqr8stu9vwx0" }
  ]
}

Each entry in results corresponds to the product at that position in your request array (zero-indexed).

Response — 207 Multi-Status (partial success)

When at least one row fails validation, the response status is 207. Failed rows appear in results with "status": "error":

{
  "created": 1,
  "updated": 0,
  "errors": 1,
  "results": [
    { "index": 0, "status": "created", "id": "cmq1abc2def3ghi4jkl5" },
    {
      "index": 1,
      "status": "error",
      "error": "name: String must contain at least 1 character(s); price: Number must be greater than or equal to 0"
    }
  ]
}

A row that fails validation does not abort the batch. Valid rows are written; failed rows are skipped and reported per-index in results.

Merge on update

When a row matches an existing product, only the fields present in your request body are updated. Fields you omit retain their current values — this is a merge update, not a full replace.

For example, sending only { "sku": "RTR-4G", "price": 249 } updates the price without touching name, category, stockQty, or any other field.

Quickstart

export AL_KEY="alk_live_xxx"

# Push your catalog — run this on a schedule to keep it in sync
curl -s -X POST https://agentlyleads.com/api/v1/products \
  -H "Authorization: Bearer $AL_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "products": [
      {
        "name": "4G Router",
        "sku": "RTR-4G",
        "price": 199,
        "cost": 120,
        "category": "Electronics"
      }
    ]
  }'

# Verify
curl -s "https://agentlyleads.com/api/v1/products?sku=RTR-4G" \
  -H "Authorization: Bearer $AL_KEY"

A typical integration is a nightly job in your ERP or e-commerce backend that reads its product list and POSTs it here. The first run creates everything; every later run updates by SKU.

On this page