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

# 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/features/:featureId/images — List product feature images

Operation ID: `list-product-feature-images`

Read the complete attachment inventory for this exact workspace-owned product and feature. Returns image ID, product ID, feature ID, filename, MIME type, byte size and timestamps, newest first, without storage keys. The response is private and uncached. Download and inspect the selected image before changing it; a filename alone does not establish that an image shows the claimed feature.

Required API key scopes: `products:read`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID in this workspace. |
| `featureId` | uuid | Yes | The feature UUID belonging to this product. |

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | All image metadata for this feature; an empty array means no attachments. | ProductFeatureImage[] |
| 400 | Invalid ID | A product or feature ID is not a full UUID. | — |
| 404 | Not found | The product or feature is missing or belongs outside the requested scope. | — |

---

## GET /external/v1/products/:id/features/:featureId/images/:imageId/content — Read product feature image content

Operation ID: `get-product-feature-image-content`

Read the original stored bytes for one image from its inventory ID. The API checks the workspace, product, feature and image before storage access. Send the Bearer credential on this request; it is not an anonymous public image URL. The response uses the stored image MIME type, byte length, a safely encoded filename, nosniff and private, no-store caching. Use these bytes to inspect the actual image and calculate a fresh file hash before a precise removal.

Required API key scopes: `products:read`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID in this workspace. |
| `featureId` | uuid | Yes | The feature UUID belonging to this product. |
| `imageId` | uuid | Yes | The exact image UUID from this feature inventory. |

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Image content | The stored PNG, JPEG, WebP or GIF bytes, not JSON. | binary |
| 400 | Invalid ID | A path ID is not a full UUID. | — |
| 404 | Not found | The product, feature or image is missing or belongs outside the requested scope. | — |

---

## DELETE /external/v1/products/:id/features/:featureId/images/:imageId — Remove a product feature image

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

Remove only the exact attachment identified by this workspace, product, feature and image. Keep any needed recovery copy of the downloaded bytes before deleting. Re-list the feature afterward and confirm that other attachments remain. Removing an attachment does not delete the feature or rewrite its description. The existing image service attempts object-storage cleanup; a successful response confirms removal from the attachment inventory, not a guarantee of physical storage erasure. A second removal returns not found.

Required API key scopes: `products:write`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | Yes | The product UUID in this workspace. |
| `featureId` | uuid | Yes | The feature UUID belonging to this product. |
| `imageId` | uuid | Yes | The exact image UUID freshly inspected in this feature inventory. |

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Deleted | The selected attachment was removed from the inventory. | { message: string } |
| 400 | Invalid ID | A path ID is not a full UUID. | — |
| 404 | Not found | The product, feature or image is missing or belongs outside the requested scope. | — |

---

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