> ## Documentation Index
> Fetch the complete documentation index at: https://docs.makelocalads.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Products

> Create and maintain the products that every creative workflow works from.

Products contain the customer-facing information and images LocalAds uses in
creative workflows. Every product belongs to a brand, and every creative job
starts from one.

**Endpoints:** `POST /products`, `GET /products`, `GET /products/{product_id}`,
`PATCH /products/{product_id}`, `DELETE /products/{product_id}`
under `https://makelocalads.com/api/v1`
**Permissions:** read with `products:read`, create with `products:create`,
update with `products:update`, delete with `products:delete`

<Card title="Open in LocalAds" icon="arrow-up-right" href="https://makelocalads.com/app">
  Your workspace's products on the app's home screen.
</Card>

## Create a product

A product can come from a product page URL, or from a name plus images. LocalAds
reads a `source_url` for the name, description and images; with images you
already have, send `image_urls` instead. Do not combine the two.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl --request POST \
    --url https://makelocalads.com/api/v1/products \
    --header "Authorization: Bearer $LOCALADS_API_KEY" \
    --header "Content-Type: application/json" \
    --data '{
      "brand_id": "1423f915-beca-4c53-a5e4-7c8c99537be9",
      "name": "Hydrating Face Serum",
      "description": "A lightweight daily serum that hydrates dry skin.",
      "kind": "skincare",
      "image_urls": ["https://assets.makelocalads.com/public-api/example/images/3a4c79b4-999b-48c2-a77c-1db08b884908.jpg"]
    }'
  ```

  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const response = await fetch("https://makelocalads.com/api/v1/products", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.LOCALADS_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      brand_id: "1423f915-beca-4c53-a5e4-7c8c99537be9",
      name: "Hydrating Face Serum",
      description: "A lightweight daily serum that hydrates dry skin.",
      kind: "skincare",
      image_urls: [
        "https://assets.makelocalads.com/public-api/example/images/3a4c79b4-999b-48c2-a77c-1db08b884908.jpg",
      ],
    }),
  });

  const product = await response.json();
  console.log(product.id);
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import os
  import requests

  response = requests.post(
      "https://makelocalads.com/api/v1/products",
      headers={
          "Authorization": f"Bearer {os.environ['LOCALADS_API_KEY']}",
          "Content-Type": "application/json",
      },
      json={
          "brand_id": "1423f915-beca-4c53-a5e4-7c8c99537be9",
          "name": "Hydrating Face Serum",
          "description": "A lightweight daily serum that hydrates dry skin.",
          "kind": "skincare",
          "image_urls": [
              "https://assets.makelocalads.com/public-api/example/images/3a4c79b4-999b-48c2-a77c-1db08b884908.jpg"
          ],
      },
  )

  product = response.json()
  print(product["id"])
  ```
</CodeGroup>

### Input fields

<ParamField body="brand_id" type="string" required>
  The brand this product belongs to, from [Brands](/setup/brands).
</ParamField>

<ParamField body="name" type="string">
  The product's name, 1 to 200 characters. Optional when `source_url` is sent;
  LocalAds reads it from the page.
</ParamField>

<ParamField body="description" type="string">
  Customer-facing product context used by creative workflows, 1 to 10,000
  characters. Send `null` on update to clear it.
</ParamField>

<ParamField body="kind" type="string">
  The product's category, 1 to 100 characters. Defaults to `product`. Recipes
  offered for a campaign can depend on it.
</ParamField>

<ParamField body="source_url" type="string">
  A product page URL. LocalAds reads the page for the name, description and
  images. Do not combine with `image_urls`.
</ParamField>

<ParamField body="image_urls" type="array">
  Up to 6 public HTTPS image URLs, or URLs returned by
  [Image uploads](/setup/image-uploads). Do not combine with `source_url`.
</ParamField>

<Accordion title="Complete JSON schema">
  ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "type": "object",
    "additionalProperties": false,
    "required": ["brand_id"],
    "properties": {
      "brand_id": { "type": "string", "format": "uuid" },
      "name": { "type": "string", "minLength": 1, "maxLength": 200 },
      "description": { "type": ["string", "null"], "minLength": 1, "maxLength": 10000 },
      "kind": { "type": "string", "minLength": 1, "maxLength": 100, "default": "product" },
      "source_url": { "type": ["string", "null"], "format": "uri" },
      "image_urls": { "type": "array", "maxItems": 6, "items": { "type": "string", "format": "uri" } }
    }
  }
  ```
</Accordion>

### Wait for `ready`

A request with `source_url` or `image_urls` returns `202 Accepted` with
`status: "processing"`. Retrieve the product until its `status` is `ready`
before starting creative work:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl https://makelocalads.com/api/v1/products/a1516a75-9be5-461a-93af-c1896a0a3127 \
  --header "Authorization: Bearer $LOCALADS_API_KEY"
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "a1516a75-9be5-461a-93af-c1896a0a3127",
  "brand_id": "1423f915-beca-4c53-a5e4-7c8c99537be9",
  "name": "Hydrating Face Serum",
  "description": "A lightweight daily serum that hydrates dry skin.",
  "kind": "skincare",
  "source_url": null,
  "image_urls": ["https://cdn.example.com/serum-front.jpg"],
  "status": "ready",
  "audiences": { "count": 3, "generation_status": "ready" },
  "created_at": "2026-07-24T14:30:00.000Z",
  "updated_at": "2026-07-24T14:32:00.000Z"
}
```

The product's audiences are generated at the same time. See
[Audiences](/setup/audiences). `Idempotency-Key` is optional on creation and
safely replays a repeated request.

## List products

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl "https://makelocalads.com/api/v1/products?brand_id=1423f915-beca-4c53-a5e4-7c8c99537be9&limit=20" \
  --header "Authorization: Bearer $LOCALADS_API_KEY"
```

The response uses cursor pagination. When `next_cursor` is not `null`, pass it
unchanged in the next request:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl "https://makelocalads.com/api/v1/products?brand_id=1423f915-beca-4c53-a5e4-7c8c99537be9&limit=20&cursor=CURSOR" \
  --header "Authorization: Bearer $LOCALADS_API_KEY"
```

`limit` is 1 to 100 and defaults to 20.

## Retrieve a product

`GET /products/{product_id}` returns the product, including its
`audiences` summary. Resources outside the API key's organization return
`404`, just like missing resources.

## Update a product

Send only the fields that should change:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request PATCH \
  --url https://makelocalads.com/api/v1/products/a1516a75-9be5-461a-93af-c1896a0a3127 \
  --header "Authorization: Bearer $LOCALADS_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "Hydrating Face Serum, 30 ml",
    "description": "A lightweight daily serum for lasting hydration.",
    "image_urls": [
      "https://cdn.example.com/serum-front.jpg",
      "https://cdn.example.com/serum-side.jpg"
    ]
  }'
```

Supplied fields replace their current values. Send `description: null` to
clear the description. `image_urls` replaces the complete image list; send an
empty array to clear the images. Supplying new image URLs returns
`202 Accepted`; retrieve the product until its `status` is `ready`.

## Delete a product

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request DELETE \
  --url https://makelocalads.com/api/v1/products/a1516a75-9be5-461a-93af-c1896a0a3127 \
  --header "Authorization: Bearer $LOCALADS_API_KEY"
```

A successful deletion returns `204 No Content`. The product and its related
resources are no longer available through the API, active generation stops,
and subsequent requests for the product return `404 Not Found`. Deletion
cannot be reversed through the public API.
