---
title: "Signal evidence API reference"
canonical_url: https://zentrik.ai/docs/api/signal-evidence
markdown_url: https://zentrik.ai/docs/api/signal-evidence.md
last_reviewed: 2026-10-05
---

# Signal evidence API reference

Read exact evidence and record what delivered work addressed. Keys with signals:read can read source-linked evidence; signal-evidence:read is needed for its connections, signal-evidence:curate creates or corrects claims and connections, and signal-evidence:write resolves or reopens them.

- Human reference: https://zentrik.ai/docs/api/signal-evidence
- 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/signal-evidence — List evidence records

Operation ID: `list-signal-evidence`

Filter evidence by type, category and source Signal account. Keys granted signals:read can list evidence too. Filters combine with AND before pagination and counting. Each record includes its UUID as id, a stable workspace-scoped publicId such as EVIDENCE-123, evidenceType and compact accounts linked to its source Signal. Active visibility excludes completed and not-relevant records; visibility=all includes both. Use the Ideas evidence endpoint to read an Idea’s direct evidence links. List responses are arrays with X-Total-Count, X-Limit, X-Offset and X-Has-More headers. Continue until X-Has-More is false; concurrent edits can shift offset pages.

Required API key scopes: `signal-evidence:read`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | integer | No | Page size, 1–100. Default 20. |
| `offset` | integer | No | Matching rows to skip. Default 0. |
| `signalId` | uuid | No | Filter by source Signal. |
| `evidenceType` | string | No | Exact, case-sensitive stored type, such as need, friction or product_feedback; up to 48 characters. |
| `category` | string | No | Exact, case-sensitive category label, such as Product bug or Product request; up to 120 characters. |
| `accountId` | uuid | No | Account linked to the source Signal. Resolve the UUID with GET /external/v1/accounts. Evidence without a source account does not match; unknown or other-workspace accounts return an empty list. |
| `visibility` | enum | No | active (default) or all. |
| `q` | string | No | Search public ID, title, description, excerpt and category; up to 200 characters. |

### Example request

```http
GET /external/v1/signal-evidence?evidenceType=product_feedback&category=Product%20bug&accountId=44444444-4444-4444-8444-444444444444&limit=20
```

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | The requested data is returned. | — |
| 400 | Invalid request | Invalid ID, unsupported field, empty patch, or invalid value. | — |
| 403 | Missing scope | The key does not grant the required resource scope. | — |
| 404 | Not found | The addressed record or assignment is outside this workspace or team. | — |

### Example response

```json
[
  {
    "id": "33333333-3333-4333-8333-333333333333",
    "publicId": "EVIDENCE-123",
    "title": "Export duplicates rows",
    "description": "Retrying the export adds a second copy.",
    "extracts": null,
    "category": "Product bug",
    "evidenceType": "product_feedback",
    "signalId": "55555555-5555-4555-8555-555555555555",
    "accounts": [
      {
        "id": "44444444-4444-4444-8444-444444444444",
        "name": "Acme"
      }
    ],
    "resolvedAt": null,
    "resolution": null,
    "resolvedByUserId": null,
    "dismissedAt": null
  }
]
```

---

## GET /external/v1/signal-evidence/:id — Get an evidence record

Operation ID: `get-signal-evidence`

Read one exact evidence record, including completed or not-relevant evidence. Use either the UUID or EVIDENCE-n ID from the Evidence sidebar. publicId is stable within the workspace; signalId identifies its source Signal. Keys granted signals:read can read evidence too.

Required API key scopes: `signal-evidence:read`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Evidence UUID or workspace-scoped public ID, such as EVIDENCE-123. |

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | The requested data is returned. | — |
| 400 | Invalid request | Invalid ID, unsupported field, empty patch, or invalid value. | — |
| 403 | Missing scope | The key does not grant the required resource scope. | — |
| 404 | Not found | The addressed record or assignment is outside this workspace or team. | — |

### Example response

```json
{
  "id": "33333333-3333-4333-8333-333333333333",
  "publicId": "EVIDENCE-123",
  "title": "Export duplicates rows",
  "description": "Retrying the export adds a second copy.",
  "extracts": null,
  "category": "Product bug",
  "signalId": null,
  "resolvedAt": null,
  "resolution": null,
  "resolvedByUserId": null,
  "dismissedAt": null
}
```

---

## GET /external/v1/signal-evidence/:id/links — List evidence connections

Operation ID: `list-signal-evidence-links`

List every record one evidence claim is connected to, with the relationship role (supports, qualifies, contradicts or context) and origin (manual for a curated connection, automatic_relevance for one the router proposed). Read this before changing a connection so an existing role is not overwritten blindly. Insight connections that were previously removed are excluded. A connection alone does not mean the work addressed the claim. Connections name the linked records, so this route needs signal-evidence:read even for keys granted signals:read.

Required API key scopes: `signal-evidence:read`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Evidence UUID or workspace-scoped public ID, such as EVIDENCE-123. |

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | The claim's current connections are returned. | — |
| 400 | Invalid request | Invalid ID, unsupported field, empty patch, or invalid value. | — |
| 403 | Missing scope | The key does not grant the required resource scope. | — |
| 404 | Not found | The record is not available in this workspace. | — |

### Example response

```json
{
  "evidenceId": "7c1f...",
  "links": [
    {
      "targetType": "insight",
      "targetId": "4d2a...",
      "publicId": "INS-18",
      "name": "Export retries duplicate rows",
      "role": "supports",
      "origin": "manual",
      "confidence": null
    }
  ]
}
```

---

## PATCH /external/v1/signal-evidence/:id — Correct an evidence claim

Operation ID: `update-signal-evidence`

Correct one evidence claim in a single save: title, description, exact source quote, category, productId or featureIds. Omitted fields stay unchanged; productId accepts null to clear the assignment. extracts must match one exact, continuous passage from the source Signal; pass null to clear it. This changes the reviewer-owned claim only. The source Signal, links and linked Insight wording are not changed. Not-relevant evidence cannot be corrected. The response confirms the database save; the search embedding refreshes in the background. To protect a field you read from a concurrent edit, pass its original value in expectedValues. The API returns 409 only when one of those fields changed and your patch would replace it. Repeating the same patch is safe.

Required API key scopes: `signal-evidence:curate`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `expectedValues` | object | No | Original values for fields you read. A changed field returns 409 with the current record. |
| `id` | string | Yes | Evidence UUID or workspace-scoped public ID, such as EVIDENCE-123. |
| `title` | string | No | 3–500 characters. |
| `description` | string | No | Up to 10,000 characters. Send an empty string to remove inaccurate wording. |
| `extracts` | string \| null | No | One exact, continuous quote from the source Signal, preserving punctuation and spacing. Send null to clear it. |
| `category` | enum | No | Exact stored category, for example Product bug. |
| `productId` | uuid \| null | No | Assigned product, or null to clear it. |
| `featureIds` | uuid[] | No | At most 50 product features. |

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 409 | Conflict | A field in expectedValues changed since it was read. The response includes the current record and changed field names. | — |
| 200 | Success | The corrected record is returned. | — |
| 400 | Invalid request | Invalid ID, unsupported field, empty patch, or invalid value. | — |
| 403 | Missing scope | The key does not grant the required resource scope. | — |
| 404 | Not found | The record is not available in this workspace. | — |

### Example response

```json
{
  "id": "7c1f...",
  "title": "Export retries duplicate CRM rows",
  "category": "Product bug",
  "productId": null,
  "resolvedAt": null,
  "dismissedAt": null
}
```

---

## PUT /external/v1/signal-evidence/:id/links/:targetType/:targetId — Connect evidence to a record

Operation ID: `link-signal-evidence`

Connect one evidence claim to an insight, opportunity, idea or initiative with an explicit role, or change the role of an existing connection. The connection is recorded as manual, so automatic routing will not overwrite or remove it. Connecting a claim to an insight also adds that insight to the insightIds of the claim's source Signal. Repeating the same call is safe. Connect only claims the target record genuinely concerns; a connection is a research statement, not a delivery decision.

Required API key scopes: `signal-evidence:curate`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Evidence UUID or workspace-scoped public ID, such as EVIDENCE-123. |
| `targetType` | enum | Yes | insight, opportunity, idea or initiative. |
| `targetId` | uuid | Yes | Target record UUID in this workspace. |
| `role` | enum | No | supports (default), qualifies, contradicts or context. |

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | The saved connection is returned. | — |
| 400 | Invalid request | Invalid ID, unsupported field, empty patch, or invalid value. | — |
| 403 | Missing scope | The key does not grant the required resource scope. | — |
| 404 | Not found | The record is not available in this workspace. | — |

### Example response

```json
{
  "evidenceId": "7c1f...",
  "targetId": "4d2a...",
  "targetType": "insight",
  "role": "supports"
}
```

---

## DELETE /external/v1/signal-evidence/:id/links/:targetType/:targetId — Remove an evidence connection

Operation ID: `unlink-signal-evidence`

Remove one evidence connection. Removing an insight connection records a durable exclusion that automatic routing will not recreate, and rebuilds the insight from its remaining evidence. The insight stays in the source Signal's insightIds while another claim from that Signal still supports it, and leaves when the last one is removed. Opportunity, idea and initiative connections are removed outright, and automatic routing may propose them again later; this matches the behaviour of the same action in the app. Removing a connection does not dismiss, delete or complete the claim.

Required API key scopes: `signal-evidence:curate`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Evidence UUID or workspace-scoped public ID, such as EVIDENCE-123. |
| `targetType` | enum | Yes | insight, opportunity, idea or initiative. |
| `targetId` | uuid | Yes | Target record UUID in this workspace. |

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | The removal is confirmed. | — |
| 400 | Invalid request | Invalid ID, unsupported field, empty patch, or invalid value. | — |
| 403 | Missing scope | The key does not grant the required resource scope. | — |
| 404 | Not found | The record is not available in this workspace. | — |

### Example response

```json
{
  "evidenceId": "7c1f...",
  "targetId": "4d2a...",
  "targetType": "insight",
  "linked": false
}
```

---

## PATCH /external/v1/signal-evidence/:id/resolution — Resolve or reopen evidence

Operation ID: `set-signal-evidence-resolution`

Mark one selected evidence record acted on, or reopen it. Bugs become fixed, requests implemented, and other categories addressed. Completion is independent of relevance; not-relevant evidence cannot be completed. Repeated writes preserve the original completion timestamp. The decision survives source reprocessing. API-key writes have resolvedByUserId:null and are attributed to the key in the API audit; they never impersonate its creator. New reports and other linked records are unaffected.

Required API key scopes: `signal-evidence:write`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Evidence UUID or workspace-scoped public ID, such as EVIDENCE-123. |

### JSON request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `resolved` | boolean | Yes | true completes the record; false reopens it. Strings are rejected. |

### Example request

```json
{
  "resolved": true
}
```

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | The saved record is returned. | — |
| 400 | Invalid request | Invalid ID, unsupported field, empty patch, or invalid value. | — |
| 403 | Missing scope | The key does not grant the required resource scope. | — |
| 404 | Not found | The addressed record or assignment is outside this workspace or team. | — |
| 409 | Conflict | The observed status changed, the sprint is completed, or a source-managed field must be edited in Linear. | — |

### Example response

```json
{
  "id": "33333333-3333-4333-8333-333333333333",
  "publicId": "EVIDENCE-123",
  "resolvedAt": "2026-09-10T10:00:00Z",
  "resolution": "fixed",
  "resolvedByUserId": null,
  "dismissedAt": null
}
```
