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

# Products API — List, Create, Retrieve, Update, Delete

> Manage your product catalog in Invoice AI. Products group one or more prices and appear as selectable line items when creating invoices.

# Products

A **product** represents something you sell — a service, subscription tier, or physical good. Products are the top-level catalog entity; each product can have one or more [prices](/api-reference/prices/overview) attached to it.

When you add a line item to an invoice by referencing a price (`price_…`), the product's name automatically appears as the line-item description unless you override it.

Deleting a product archives it (`active: false`). Archived products no longer appear as active catalog items, but all existing invoice lines referencing them are preserved.

**Base URL:** `https://invoice.horizonpay.co/api/v1`

***

## The product object

```json theme={null}
{
  "id": "prod_01hxyz1234567890abcdefghij",
  "object": "product",
  "name": "Professional Consulting",
  "description": "Hourly advisory and implementation services.",
  "images": [
    "https://cdn.example.com/consulting-icon.png"
  ],
  "active": true,
  "created": "2024-01-10T11:00:00.000Z",
  "updated": "2024-06-01T08:30:00.000Z"
}
```

<ResponseField name="id" type="string">
  Unique identifier for the product. Prefixed with `prod_`.
</ResponseField>

<ResponseField name="object" type="string">
  String literal `"product"`.
</ResponseField>

<ResponseField name="name" type="string">
  Display name of the product. Used as the default line-item description when this product's price is added to an invoice.
</ResponseField>

<ResponseField name="description" type="string | null">
  Optional longer description. Maximum 500 characters.
</ResponseField>

<ResponseField name="images" type="string[]">
  Array of public image URLs (up to 8). URLs must be accessible over HTTPS.
</ResponseField>

<ResponseField name="active" type="boolean">
  `true` while the product is available in the catalog. Set to `false` by the delete endpoint (archive).
</ResponseField>

<ResponseField name="created" type="string">
  ISO 8601 datetime at which the product was created.
</ResponseField>

<ResponseField name="updated" type="string">
  ISO 8601 datetime of the most recent update.
</ResponseField>

***

## Endpoints

<AccordionGroup>
  <Accordion title="GET /api/v1/products — List products">
    List all products in your workspace. Results are cursor-paginated, newest first.

    **Required scope:** `products:read`

    ### Query parameters

    <ParamField query="query" type="string">
      Free-text search against product names and descriptions.
    </ParamField>

    <ParamField query="active" type="boolean">
      Filter by active status. Pass `true` to return only active products, `false` for archived ones. Omit to return both.
    </ParamField>

    <ParamField query="cursor" type="string">
      Pagination cursor from a previous response's `next_cursor`. Omit to start from the first page.
    </ParamField>

    <ParamField query="limit" type="integer">
      Number of results per page. Defaults to `20`; maximum is `100`.
    </ParamField>

    ### Request

    ```bash theme={null}
    curl "https://invoice.horizonpay.co/api/v1/products?active=true&limit=2" \
      -H "Authorization: Bearer inv_live_..."
    ```

    ### Response

    ```json theme={null}
    {
      "data": [
        {
          "id": "prod_01hxyz1234567890abcdefghij",
          "object": "product",
          "name": "Professional Consulting",
          "description": "Hourly advisory and implementation services.",
          "images": [],
          "active": true,
          "created": "2024-01-10T11:00:00.000Z",
          "updated": "2024-06-01T08:30:00.000Z"
        },
        {
          "id": "prod_01hxyz9876543210zyxwvutsrq",
          "object": "product",
          "name": "Annual Support Plan",
          "description": null,
          "images": [],
          "active": true,
          "created": "2024-02-20T16:45:00.000Z",
          "updated": "2024-02-20T16:45:00.000Z"
        }
      ],
      "next_cursor": "prod_01hxyz9876543210zyxwvutsrq"
    }
    ```
  </Accordion>

  <Accordion title="POST /api/v1/products — Create a product">
    Create a new product. `name` is the only required field.

    **Required scope:** `products:write`

    ### Body parameters

    <ParamField body="name" type="string" required>
      Product name. Minimum 1 character, maximum 200 characters.
    </ParamField>

    <ParamField body="description" type="string">
      Optional description shown on invoices and the catalog.
    </ParamField>

    <ParamField body="images" type="string[]">
      Array of HTTPS image URLs (up to 8). Each URL must be valid and publicly accessible. Defaults to `[]`.
    </ParamField>

    <ParamField body="active" type="boolean">
      Whether the product is active in the catalog. Defaults to `true`.
    </ParamField>

    ### Request

    ```bash theme={null}
    curl https://invoice.horizonpay.co/api/v1/products \
      -X POST \
      -H "Authorization: Bearer inv_live_..." \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Professional Consulting",
        "description": "Hourly advisory and implementation services.",
        "images": ["https://cdn.example.com/consulting-icon.png"]
      }'
    ```

    ### Response `201 Created`

    ```json theme={null}
    {
      "data": {
        "id": "prod_01hxyz1234567890abcdefghij",
        "object": "product",
        "name": "Professional Consulting",
        "description": "Hourly advisory and implementation services.",
        "images": [
          "https://cdn.example.com/consulting-icon.png"
        ],
        "active": true,
        "created": "2024-01-10T11:00:00.000Z",
        "updated": "2024-01-10T11:00:00.000Z"
      }
    }
    ```
  </Accordion>

  <Accordion title="GET /api/v1/products/:id — Retrieve a product">
    Retrieve a single product by ID.

    **Required scope:** `products:read`

    The `:id` path parameter accepts either the `prod_`-prefixed public ID or the underlying UUID.

    ### Request

    ```bash theme={null}
    curl https://invoice.horizonpay.co/api/v1/products/prod_01hxyz1234567890abcdefghij \
      -H "Authorization: Bearer inv_live_..."
    ```

    ### Response `200 OK`

    ```json theme={null}
    {
      "data": {
        "id": "prod_01hxyz1234567890abcdefghij",
        "object": "product",
        "name": "Professional Consulting",
        "description": "Hourly advisory and implementation services.",
        "images": [
          "https://cdn.example.com/consulting-icon.png"
        ],
        "active": true,
        "created": "2024-01-10T11:00:00.000Z",
        "updated": "2024-06-01T08:30:00.000Z"
      }
    }
    ```
  </Accordion>

  <Accordion title="PATCH /api/v1/products/:id — Update a product">
    Update one or more fields on an existing product. Omitted fields are left unchanged.

    **Required scope:** `products:write`

    <Note>
      To clear an optional text field such as `description`, send `null` explicitly: `"description": null`. Sending `""` (empty string) has the same effect.
    </Note>

    ### Body parameters

    All fields are optional. Include only those you want to change.

    <ParamField body="name" type="string">
      New product name. Minimum 1 character, maximum 200 characters.
    </ParamField>

    <ParamField body="description" type="string | null">
      New description. Pass `null` or `""` to clear.
    </ParamField>

    <ParamField body="images" type="string[]">
      Replacement image URL array. Passing `[]` removes all images. Replaces the entire existing array — not merged.
    </ParamField>

    <ParamField body="active" type="boolean">
      Set to `false` to manually deactivate a product without deleting it.
    </ParamField>

    ### Request

    ```bash theme={null}
    curl https://invoice.horizonpay.co/api/v1/products/prod_01hxyz1234567890abcdefghij \
      -X PATCH \
      -H "Authorization: Bearer inv_live_..." \
      -H "Content-Type: application/json" \
      -d '{
        "description": "Hourly advisory, implementation, and code review services.",
        "images": [
          "https://cdn.example.com/consulting-v2.png"
        ]
      }'
    ```

    ### Response `200 OK`

    ```json theme={null}
    {
      "data": {
        "id": "prod_01hxyz1234567890abcdefghij",
        "object": "product",
        "name": "Professional Consulting",
        "description": "Hourly advisory, implementation, and code review services.",
        "images": [
          "https://cdn.example.com/consulting-v2.png"
        ],
        "active": true,
        "created": "2024-01-10T11:00:00.000Z",
        "updated": "2024-06-15T12:00:00.000Z"
      }
    }
    ```
  </Accordion>

  <Accordion title="DELETE /api/v1/products/:id — Archive a product">
    Archive a product. The product is set to `active: false` and will no longer be returned when filtering by `active=true`. Existing invoice lines that reference this product are not affected.

    **Required scope:** `products:write`

    To reactivate an archived product, use [PATCH /api/v1/products/:id](#) with `"active": true`.

    ### Request

    ```bash theme={null}
    curl https://invoice.horizonpay.co/api/v1/products/prod_01hxyz1234567890abcdefghij \
      -X DELETE \
      -H "Authorization: Bearer inv_live_..."
    ```

    ### Response `200 OK`

    The archived product object is returned, with `active: false`.

    ```json theme={null}
    {
      "data": {
        "id": "prod_01hxyz1234567890abcdefghij",
        "object": "product",
        "name": "Professional Consulting",
        "description": "Hourly advisory and implementation services.",
        "images": [],
        "active": false,
        "created": "2024-01-10T11:00:00.000Z",
        "updated": "2024-06-15T12:05:00.000Z"
      }
    }
    ```
  </Accordion>
</AccordionGroup>

***

## Error responses

| Status             | Cause                                                                                       |
| ------------------ | ------------------------------------------------------------------------------------------- |
| `400 Bad Request`  | Validation failed — missing `name`, invalid image URL, or `images` array exceeds 8 entries. |
| `401 Unauthorized` | Missing or invalid `Authorization` header.                                                  |
| `403 Forbidden`    | The API key lacks the required scope (`products:read` or `products:write`).                 |
| `404 Not Found`    | No product with the given ID exists in your workspace.                                      |
