---
title: "Products API reference"
canonical_url: https://zentrik.ai/docs/api/products
markdown_url: https://zentrik.ai/docs/api/products.md
last_reviewed: 2026-09-03
---

# Products API reference

Create and manage workspace products, their stable external sync identifiers, feature maps and images, personas, clarification questions, and portfolio metrics.

- Human reference: https://zentrik.ai/docs/api/products
- Complete Markdown index: https://zentrik.ai/docs/api/index.md
- Base URL: `https://zentrik.ai/api`
- Authentication: `Authorization: Bearer YOUR_API_KEY`
- Shared pagination and rate limits: https://zentrik.ai/docs/api/index.md#shared-conventions

## GET /external/v1/products — List all products

Operation ID: `list-products`

List products in the current workspace. Use `externalId` for deterministic syncs from external systems.

Required API key scopes: `products:read`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | number | No | Maximum number of products to return. |
| `offset` | number | No | Number of products to skip. |
| `q` | string | No | Case-insensitive search across name, description, type, and externalId. |
| `name` | string | No | Exact product name match. |
| `externalId` | string | No | Exact client-side sync identifier. |
| `include` | string | No | Comma-separated: features, personas, questions, metrics. |

### Example request

```curl
curl -X GET 'https://zentrik.ai/api/external/v1/products?externalId=prd-discovery&include=metrics' \
  -H 'Authorization: Bearer YOUR_API_KEY'
```

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | Products were successfully retrieved. | Array<Product> |

### Example response

```json
[
  {
    "id": "product-uuid",
    "name": "Zentrik Discovery",
    "description": "AI-assisted discovery workspace for product teams.",
    "descriptionAnalysis": null,
    "type": "SaaS",
    "stack": "NestJS, React, PostgreSQL",
    "mission": "Preserve customer intent from signal to roadmap.",
    "externalId": "prd-discovery",
    "sourceAliases": [
      {
        "value": "Zentrik DS",
        "match": "exact"
      }
    ],
    "workspaceId": "workspace-uuid",
    "kpis": [
      "Reduce insight review time",
      "Increase evidence coverage"
    ],
    "techStack": [
      {
        "name": "PostgreSQL",
        "roles": [
          "Backend Engineer"
        ]
      }
    ],
    "analysis": {},
    "createdAt": "2026-05-14T10:00:00Z",
    "updatedAt": "2026-05-14T10:00:00Z",
    "metrics": {
      "insights": 12,
      "ideas": 5,
      "opportunities": 3,
      "signals": 21
    }
  }
]
```

---

## GET /external/v1/products/:id — Get one product

Operation ID: `get-product`

Retrieve one product by Zentrik UUID. Add `include` to hydrate context collections.

Required API key scopes: `products:read`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID. |
| `include` | string | No | Comma-separated: features, personas, questions, metrics. |

### Example request

```curl
curl -X GET 'https://zentrik.ai/api/external/v1/products/product-uuid?include=features,personas' \
  -H 'Authorization: Bearer YOUR_API_KEY'
```

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | The product was successfully retrieved. | Product |
| 404 | Not Found | No product found with the provided id in the current workspace. | — |

### Example response

```json
{
  "id": "product-uuid",
  "name": "Zentrik Discovery",
  "description": "AI-assisted discovery workspace for product teams.",
  "descriptionAnalysis": null,
  "type": "SaaS",
  "stack": "NestJS, React, PostgreSQL",
  "mission": "Preserve customer intent from signal to roadmap.",
  "externalId": "prd-discovery",
  "sourceAliases": [
    {
      "value": "Zentrik DS",
      "match": "exact"
    }
  ],
  "workspaceId": "workspace-uuid",
  "kpis": [
    "Reduce insight review time",
    "Increase evidence coverage"
  ],
  "techStack": [
    {
      "name": "PostgreSQL",
      "roles": [
        "Backend Engineer"
      ]
    }
  ],
  "analysis": {},
  "createdAt": "2026-05-14T10:00:00Z",
  "updatedAt": "2026-05-14T10:00:00Z",
  "features": [
    {
      "id": "feature-uuid",
      "productId": "product-uuid",
      "name": "Signal intake",
      "description": "Bring customer evidence into Discovery.",
      "parentId": null
    }
  ],
  "personas": [
    {
      "id": "persona-uuid",
      "productId": "product-uuid",
      "name": "Product Manager",
      "description": "Owns discovery synthesis.",
      "goals": [],
      "painPoints": [],
      "useCases": []
    }
  ]
}
```

---

## POST /external/v1/products — Create a product

Operation ID: `create-product`

Create a product. `externalId` is optional but recommended for idempotent external syncs. Initial features and personas may be provided on create only.

Required API key scopes: `products:write`

### JSON request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | Yes | Product display name. |
| `description` | string | Yes | Product description. |
| `externalId` | string | No | Stable client-side identifier, unique per workspace when present. |
| `sourceAliases` | array | No | Source labels that should resolve to this product during imports. |
| `type` | string | No | Product category. |
| `stack` | string | No | Short implementation stack summary. |
| `mission` | string | No | Outcome or mission statement. |
| `kpis` | string[] | No | Outcome measures tracked for this product. |
| `techStack` | array | No | Structured technologies and roles. |
| `features` | array | No | Initial feature records. |
| `personas` | array | No | Initial persona records. |

### Example request

```json
{
  "name": "Zentrik Discovery",
  "description": "AI-assisted discovery workspace for product teams.",
  "externalId": "prd-discovery",
  "sourceAliases": [
    {
      "value": "Zentrik DS",
      "match": "exact"
    }
  ],
  "mission": "Preserve customer intent from signal to roadmap.",
  "features": [
    {
      "name": "Signal intake",
      "description": "Bring customer evidence into Discovery."
    }
  ],
  "personas": [
    {
      "name": "Product Manager",
      "description": "Owns discovery synthesis."
    }
  ]
}
```

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 201 | Created | The product was successfully created. | Product |
| 409 | Conflict | The externalId already exists in this workspace. | — |

### Example response

```json
{
  "id": "product-uuid",
  "name": "Zentrik Discovery",
  "description": "AI-assisted discovery workspace for product teams.",
  "descriptionAnalysis": null,
  "type": "SaaS",
  "stack": "NestJS, React, PostgreSQL",
  "mission": "Preserve customer intent from signal to roadmap.",
  "externalId": "prd-discovery",
  "sourceAliases": [
    {
      "value": "Zentrik DS",
      "match": "exact"
    }
  ],
  "workspaceId": "workspace-uuid",
  "kpis": [
    "Reduce insight review time",
    "Increase evidence coverage"
  ],
  "techStack": [
    {
      "name": "PostgreSQL",
      "roles": [
        "Backend Engineer"
      ]
    }
  ],
  "analysis": {},
  "createdAt": "2026-05-14T10:00:00Z",
  "updatedAt": "2026-05-14T10:00:00Z"
}
```

---

## PATCH /external/v1/products/:id — Update a product

Operation ID: `update-product`

Update core product fields. Nested features, personas, and questions are rejected here; use the dedicated subresource endpoints.

Required API key scopes: `products:write`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID. |

### JSON request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | No | Updated product name. |
| `description` | string | No | Updated product description. |
| `externalId` | string \| null | No | Update or clear the external sync identifier. |
| `sourceAliases` | array \| null | No | Replace source labels used for product import matching. |
| `mission` | string \| null | No | Updated mission statement. |
| `kpis` | string[] \| null | No | Replace product KPIs. |

### Example request

```curl
curl -X PATCH https://zentrik.ai/api/external/v1/products/product-uuid \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"sourceAliases":[{"value":"Legacy Discovery","match":"exact"}]}'
```

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Updated | The product was successfully updated. | Product |

---

## DELETE /external/v1/products/:id — Delete a product

Operation ID: `delete-product`

Delete a product from the current workspace.

Required API key scopes: `products:delete`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID. |

### Example request

```curl
curl -X DELETE https://zentrik.ai/api/external/v1/products/product-uuid \
  -H 'Authorization: Bearer YOUR_API_KEY'
```

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Deleted | The product was successfully deleted. | — |

---

## GET /external/v1/products/portfolio-summary — Get portfolio summary

Operation ID: `get-products-portfolio-summary`

Return products plus aggregate counts used by portfolio views.

Required API key scopes: `products:read`

### Example request

```curl
curl -X GET https://zentrik.ai/api/external/v1/products/portfolio-summary \
  -H 'Authorization: Bearer YOUR_API_KEY'
```

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | Portfolio summary was successfully retrieved. | — |

### Example response

```json
{
  "products": [
    {
      "id": "product-uuid",
      "name": "Zentrik Discovery",
      "description": "AI-assisted discovery workspace for product teams.",
      "descriptionAnalysis": null,
      "type": "SaaS",
      "stack": "NestJS, React, PostgreSQL",
      "mission": "Preserve customer intent from signal to roadmap.",
      "externalId": "prd-discovery",
      "sourceAliases": [
        {
          "value": "Zentrik DS",
          "match": "exact"
        }
      ],
      "workspaceId": "workspace-uuid",
      "kpis": [
        "Reduce insight review time",
        "Increase evidence coverage"
      ],
      "techStack": [
        {
          "name": "PostgreSQL",
          "roles": [
            "Backend Engineer"
          ]
        }
      ],
      "analysis": {},
      "createdAt": "2026-05-14T10:00:00Z",
      "updatedAt": "2026-05-14T10:00:00Z"
    }
  ],
  "metrics": {
    "product-uuid": {
      "insights": 12,
      "ideas": 5,
      "opportunities": 3,
      "signals": 21
    }
  },
  "meta": {
    "signalsLookbackDays": 7
  }
}
```

---

## GET /external/v1/products/:id/metrics — Get product metrics

Operation ID: `get-product-metrics`

Return aggregate metrics for a single product.

Required API key scopes: `products:read`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID. |

### Example request

```curl
curl -X GET https://zentrik.ai/api/external/v1/products/product-uuid/metrics \
  -H 'Authorization: Bearer YOUR_API_KEY'
```

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | Product metrics were successfully retrieved. | — |

### Example response

```json
{
  "contextPersonas": 2,
  "contextFeatureMapNodes": 8,
  "initiativesTotal": 3,
  "insights": 12,
  "signals": 21,
  "opportunities": 3,
  "ideas": 5
}
```

---

## GET /external/v1/products/:id/features — List product features

Operation ID: `list-product-features`

List feature map nodes for a product.

Required API key scopes: `products:read`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID. |

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | Features were successfully retrieved. | Array<ProductFeature> |

### Example response

```json
[
  {
    "id": "feature-uuid",
    "productId": "product-uuid",
    "name": "Signal intake",
    "description": "Bring customer evidence into Discovery.",
    "parentId": null
  }
]
```

---

## POST /external/v1/products/:id/features — Create a product feature

Operation ID: `create-product-feature`

Create one product feature. `parentId` may reference an existing feature in the same product.

Required API key scopes: `products:write`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID. |

### JSON request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | Yes | Feature name. |
| `description` | string | Yes | Feature description. |
| `parentId` | uuid \| null | No | Optional parent feature. |

### Example request

```json
{
  "name": "Signal intake",
  "description": "Bring customer evidence into Discovery."
}
```

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 201 | Created | The feature was successfully created. | ProductFeature |

---

## PATCH /external/v1/products/:id/features/:featureId — Update a product feature

Operation ID: `update-product-feature`

Update one product feature. The feature must belong to the requested product.

Required API key scopes: `products:write`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID. |
| `featureId` | uuid | Yes | The feature UUID. |

### JSON request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | No | Updated feature name. |
| `description` | string | No | Updated feature description. |
| `parentId` | uuid \| null | No | Updated parent feature. |

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Updated | The feature was successfully updated. | ProductFeature |

---

## DELETE /external/v1/products/:id/features/:featureId — Delete a product feature

Operation ID: `delete-product-feature`

Delete one product feature.

Required API key scopes: `products:write`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID. |
| `featureId` | uuid | Yes | The feature UUID. |

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Deleted | The feature was successfully deleted. | — |

---

## POST /external/v1/products/:id/features/:featureId/images — Attach an image to a product feature

Operation ID: `attach-product-feature-image`

Attach an image to an existing feature as multipart/form-data. The file field name must be `file`; stored object keys are never exposed.

Required API key scopes: `products:write`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID. |
| `featureId` | uuid | Yes | The feature UUID. |

### JSON request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `file` | file | Yes | PNG, JPEG, WebP, or GIF image up to 10 MB. |

### Example request

```curl
curl -X POST https://zentrik.ai/api/external/v1/products/product-uuid/features/feature-uuid/images \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -F 'file=@feature-overview.png'
```

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 201 | Created | The image was attached to the feature. | ProductFeatureImage |
| 400 | Bad Request | The file is empty, too large, or not a supported image type. | — |
| 404 | Not Found | The product or feature was not found in the API key workspace. | — |

### Example response

```json
{
  "id": "image-uuid",
  "productId": "product-uuid",
  "featureId": "feature-uuid",
  "fileName": "feature-overview.png",
  "mimeType": "image/png",
  "size": 184320,
  "createdAt": "2026-07-24T10:00:00Z",
  "updatedAt": "2026-07-24T10:00:00Z"
}
```

---

## GET /external/v1/products/:id/personas — List product personas

Operation ID: `list-product-personas`

List personas for a product.

Required API key scopes: `products:read`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID. |

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | Personas were successfully retrieved. | Array<ProductPersona> |

### Example response

```json
[
  {
    "id": "persona-uuid",
    "productId": "product-uuid",
    "name": "Product Manager",
    "description": "Owns discovery synthesis.",
    "goals": [],
    "painPoints": [],
    "useCases": []
  }
]
```

---

## POST /external/v1/products/:id/personas — Create a product persona

Operation ID: `create-product-persona`

Create one product persona.

Required API key scopes: `products:write`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID. |

### JSON request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | Yes | Persona name. |
| `description` | string | Yes | Persona description. |
| `goals` | string[] | No | Persona goals. |
| `painPoints` | string[] | No | Persona pain points. |
| `useCases` | string[] | No | Persona use cases. |

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 201 | Created | The persona was successfully created. | ProductPersona |

---

## PATCH /external/v1/products/:id/personas/:personaId — Update a product persona

Operation ID: `update-product-persona`

Update one product persona.

Required API key scopes: `products:write`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID. |
| `personaId` | uuid | Yes | The persona UUID. |

### JSON request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | No | Updated persona name. |
| `description` | string | No | Updated persona description. |
| `goals` | string[] | No | Replacement goals. |
| `painPoints` | string[] | No | Replacement pain points. |
| `useCases` | string[] | No | Replacement use cases. |

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Updated | The persona was successfully updated. | ProductPersona |

---

## DELETE /external/v1/products/:id/personas/:personaId — Delete a product persona

Operation ID: `delete-product-persona`

Delete one product persona.

Required API key scopes: `products:write`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID. |
| `personaId` | uuid | Yes | The persona UUID. |

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Deleted | The persona was successfully deleted. | — |

---

## GET /external/v1/products/:id/questions — List product questions

Operation ID: `list-product-questions`

List product clarification questions. This API is read-only for questions.

Required API key scopes: `products:read`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID. |

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | Questions were successfully retrieved. | Array<ProductQuestion> |

---

## GET /external/v1/products/:id/questions/:questionId — Get a product question

Operation ID: `get-product-question`

Retrieve one product clarification question.

Required API key scopes: `products:read`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID. |
| `questionId` | uuid | Yes | The question UUID. |

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | The question was successfully retrieved. | ProductQuestion |
