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

# Signals API reference

Signals are the imported transcripts, tickets, reviews, and reports that feed Discovery. Use these endpoints when you want to bring evidence into Zentrik from another source.

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

Operation ID: `list-signals`

List a bounded page of signals in your workspace. Signals are imported pieces of customer or market evidence such as transcripts, support tickets, reviews, and discovery reports.

Required API key scopes: `signals:read`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | number | No | Maximum number of items to return (default: 20, maximum: 100) |
| `offset` | number | No | Number of items to skip (default: 0) |
| `include` | string | No | Comma-separated extras. 'raw' adds rawData: the whole stored data column, including every attributed transcript turn and the provider's import response. Opt-in because it can exceed several hundred KB per signal. |

### Example request

```curl
curl -X GET https://zentrik.ai/api/external/v1/signals?limit=10 \
  -H 'Authorization: Bearer YOUR_API_KEY'
```

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | Signals were successfully retrieved. Pagination metadata is returned in X-Total-Count, X-Limit, X-Offset, and X-Has-More headers. | Array<Signal> |

### Example response

```json
[
  {
    "id": "uuid",
    "name": "Support Ticket - 3/20/24, 10:00 AM",
    "publicId": "SIGNAL-42",
    "workspaceId": "workspace-uuid",
    "status": "processed",
    "context": "support_request",
    "signalType": null,
    "processorType": "zendesk",
    "providerType": "zendesk",
    "evidenceKind": "support_ticket",
    "analysisProfile": "support_ticket",
    "provenance": {
      "providerType": "zendesk",
      "evidenceKind": "support_ticket",
      "analysisProfile": "support_ticket"
    },
    "sourceFacet": "zendesk:support_ticket",
    "sourceLabel": "Zendesk support ticket",
    "sourceLinks": [
      {
        "name": "Open Zendesk ticket",
        "url": "https://example.zendesk.com/agent/tickets/12345",
        "type": "support_ticket",
        "primary": true
      }
    ],
    "source": {
      "type": "zendesk",
      "external_data": {
        "ticketId": "12345"
      }
    },
    "data": {
      "subject": "Users cannot reset password",
      "severity": "high"
    },
    "transcript": null,
    "import": null,
    "insightIds": [],
    "accountIds": [],
    "accountId": null,
    "accountExternalId": null,
    "externalId": "external-123",
    "createdAt": "2024-03-20T10:00:00Z",
    "updatedAt": "2024-03-20T10:00:00Z"
  }
]
```

---

## GET /external/v1/signals/:publicId — Get one signal

Operation ID: `get-signal`

Retrieve a single signal by its public ID.

Required API key scopes: `signals:read`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `publicId` | string | Yes | Workspace-scoped public ID, such as SIGNAL-42 |
| `include` | string | No | Comma-separated extras. 'raw' adds rawData: the whole stored data column. Opt-in because a processed transcript's column can exceed several hundred KB. |

### Example request

```curl
curl -X GET https://zentrik.ai/api/external/v1/signals/SIGNAL-42 \
  -H 'Authorization: Bearer YOUR_API_KEY'
```

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | The signal was successfully retrieved. | Signal |
| 404 | Not Found | No signal found with the provided ID. | — |

### Example response

```json
{
  "id": "uuid",
  "name": "Support Ticket - 3/20/24, 10:00 AM",
  "publicId": "SIGNAL-42",
  "workspaceId": "workspace-uuid",
  "status": "processed",
  "context": "support_request",
  "signalType": null,
  "processorType": "zendesk",
  "providerType": "zendesk",
  "evidenceKind": "support_ticket",
  "analysisProfile": "support_ticket",
  "provenance": {
    "providerType": "zendesk",
    "evidenceKind": "support_ticket",
    "analysisProfile": "support_ticket"
  },
  "sourceFacet": "zendesk:support_ticket",
  "sourceLabel": "Zendesk support ticket",
  "sourceLinks": [
    {
      "name": "Open Zendesk ticket",
      "url": "https://example.zendesk.com/agent/tickets/12345",
      "type": "support_ticket",
      "primary": true
    }
  ],
  "source": {
    "type": "zendesk",
    "external_data": {
      "ticketId": "12345"
    }
  },
  "data": {
    "sessionId": "session-123"
  },
  "transcript": null,
  "import": null,
  "insightIds": [
    "insight-uuid"
  ],
  "accountIds": [],
  "accountId": null,
  "accountExternalId": null,
  "externalId": "external-123",
  "createdAt": "2024-03-20T10:00:00Z",
  "updatedAt": "2024-03-20T10:00:00Z"
}
```

---

## POST /external/v1/signals — Create a signal

Operation ID: `create-signal`

Create a new signal by sending the text you want Zentrik to analyze. This is the recommended public API flow for transcripts, support tickets, reviews, structured feedback records, and discovery reports. Advanced raw signal bodies are also accepted on this same endpoint and are auto-queued.

Required API key scopes: `signals:write`

### JSON request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | No | Optional display name for the signal. When omitted for typed text signals, Zentrik generates one from the signal type and timestamp. |
| `text` | string | Yes | The raw transcript, support ticket, review, structured feedback record, or discovery report text that Zentrik should process into insights. Transcript input may be copied or exported with speaker labels, timestamps, captions, markdown headings, and common meeting metadata; Zentrik preserves the source and normalizes its conversation structure automatically. |
| `signalType` | 'transcript' \| 'support_ticket' \| 'review' \| 'feedback_record' \| 'discovery_report' | Yes | What kind of text you are sending. This determines how Zentrik processes the signal. New text signals are automatically queued for processing after creation. |
| `providerType` | string | No | Optional source system or channel identifier, such as app_store, reddit, g2, gartner, manual_csv, or external_api. |
| `evidenceKind` | string | No | Optional evidence shape override. For typed text this defaults from signalType and usually does not need to be sent. |
| `analysisProfile` | 'default_transcript' \| 'support_ticket' \| 'survey_response' \| 'discovery_report' \| 'community_discussion' | No | Optional processing profile override. For typed text this defaults from signalType. |
| `additionalContext` | string | No | Optional notes to help Zentrik interpret the text correctly. Use this for things like where the text came from, what batch it belongs to, or any lightweight analyst note. This is treated as supplemental context, not an instruction override. |
| `occurredAt` | iso-date | No | Optional timestamp representing when the original signal happened. When provided, it is used as the signal creation timestamp for trend accuracy. |
| `productIds` | uuid[] | No | Optional list of products to narrow the product context used during processing. |
| `accountId` | uuid | No | Optional account to associate with this signal and the insights created from typed text processing. |
| `accountIds` | uuid[] | No | Optional list of account IDs to associate directly with this signal. Use accountId for the common single-account case. |
| `accountExternalId` | string | No | Optional stable client-side account identifier, such as crm-example-account. Prefer this for CRM automations that should not persist backend UUIDs. |
| `participants` | { email?: string; contactId?: uuid; accountId?: uuid; name?: string; role?: string; affiliation?: "external" \| "internal" \| "unknown" }[] | No | People observed in this record. Add a valid email or an existing Contact ID. Mark colleagues as internal. Zentrik changes People only when participantPolicy requests it. |
| `participantPolicy` | 'none' \| 'match_existing' \| 'create_missing' | No | Controls participant handling. Omit it or use none for no People changes. Use match_existing to link only saved People, or create_missing to create reliably identified People. |
| `createMissingAccounts` | boolean | No | When participantPolicy is create_missing, also allow one missing Account to be created from each unambiguous professional email domain. Defaults to false; public email domains never create Accounts. |
| `sourceKey` | string | No | Stable namespace for externalId, such as granola.note or crm.call. Set it when several clients send records into the same workspace. |
| `externalId` | string | No | Stable record identifier within sourceKey. Later deliveries reuse the Signal and can add newly available participant links. Use Idempotency-Key only to replay one exact request. |
| `sourceLinks` | { name?: string; url: string; type?: string; primary?: boolean }[] | No | Links users should land on from Zentrik, such as the original app store review, G2 review, Reddit thread or comment, Gartner report, support ticket, source row, or meeting link. HTTP and HTTPS URLs are normalized and returned on the Signal object. |
| `sourceExternalData` | object | No | Provider-specific metadata to keep with the signal, such as reviewId, rating, locale, reportId, threadId, productSlug, or dataset. For Granola, send noteId for a public note ID or granolaDocumentId for a legacy desktop ID; send both when known. Zentrik stores this under source.external_data. |

### Example request

```json
{
  "name": "G2 Reviews - March Batch",
  "text": "Users love the product overall, but several reviewers mention that exports are hard to find and slow to complete for large datasets.",
  "signalType": "review",
  "providerType": "g2",
  "externalId": "g2-review:review-row-1",
  "additionalContext": "Public review batch collected from G2 and Capterra.",
  "occurredAt": "2025-03-11T14:30:00Z",
  "productIds": [
    "product-uuid-1"
  ],
  "participants": [
    {
      "email": "buyer@example.com",
      "name": "Primary product contact",
      "role": "VP Product",
      "affiliation": "external"
    }
  ],
  "participantPolicy": "create_missing",
  "createMissingAccounts": false,
  "sourceExternalData": {
    "reviewId": "review-row-1",
    "rating": 3,
    "productSlug": "example-product"
  },
  "sourceLinks": [
    {
      "name": "Open G2 review",
      "url": "https://www.g2.com/products/example-product/reviews/review-row-1",
      "type": "review",
      "primary": true
    }
  ]
}
```

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 201 | Created | The signal was successfully created and queued for asynchronous processing. Create responses include a queued job id. | Signal |

### Example response

```json
{
  "id": "uuid",
  "name": "Review - 3/11/25, 2:30 PM",
  "publicId": "SIGNAL-43",
  "workspaceId": "workspace-uuid",
  "status": "processing",
  "context": null,
  "signalType": "review",
  "processorType": "manual-text",
  "providerType": "g2",
  "evidenceKind": "review",
  "analysisProfile": "survey_response",
  "provenance": {
    "providerType": "g2",
    "evidenceKind": "review",
    "analysisProfile": "survey_response"
  },
  "sourceFacet": "g2:review",
  "sourceLabel": "G2 Review",
  "sourceLinks": [
    {
      "name": "Open G2 review",
      "url": "https://www.g2.com/products/example-product/reviews/review-row-1",
      "type": "review",
      "primary": true
    }
  ],
  "source": {
    "type": "manual-text",
    "external_data": {
      "reviewId": "review-row-1",
      "rating": 3,
      "productSlug": "example-product",
      "links": [
        {
          "name": "Open G2 review",
          "url": "https://www.g2.com/products/example-product/reviews/review-row-1",
          "type": "review",
          "primary": true
        }
      ]
    }
  },
  "data": {
    "s3Key": "signals/manual-text/workspace-uuid/uuid.txt",
    "productIds": [
      "product-uuid-1"
    ],
    "accountId": "account-uuid-1",
    "accountExternalId": "crm-example-account",
    "additionalContext": "Public review batch collected from G2 and Capterra.",
    "uploadedAt": "2025-03-21T10:00:00Z"
  },
  "insightIds": [],
  "accountId": "account-uuid-1",
  "accountIds": [
    "account-uuid-1"
  ],
  "contactIds": [
    "contact-uuid-1"
  ],
  "externalId": "g2-review:review-row-1",
  "createdAt": "2025-03-11T14:30:00Z",
  "updatedAt": "2025-03-21T10:00:00Z",
  "jobId": "job-123",
  "processing": {
    "lastJobId": "job-123",
    "startedAt": "2025-03-21T10:00:00Z",
    "processedAt": null,
    "failedAt": null,
    "lastError": null
  }
}
```

---

## PATCH /external/v1/signals/:publicId — Update a signal

Operation ID: `update-signal`

Update fields of an existing signal, such as metadata or linked insights.

Required API key scopes: `signals:write`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `publicId` | string | Yes | Workspace-scoped public ID, such as SIGNAL-43 |

### JSON request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `source` | object | No | Updated source descriptor for the signal. |
| `data` | object | No | Updated arbitrary context payload. |
| `insightIds` | string[] | No | Replace the list of linked insight IDs. |
| `externalId` | string | No | Updated external record identifier. |
| `status` | 'pending' \| 'processed' | No | Processing status for the signal. |
| `context` | string | No | Persisted signal context, such as user_interview or support_request. |

### Example request

```curl
curl -X PATCH https://zentrik.ai/api/external/v1/signals/SIGNAL-43 \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"insightIds": ["insight-uuid"]}'
```

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Updated | The signal was successfully updated. | Signal |

---

## DELETE /external/v1/signals/:publicId — Delete a signal

Operation ID: `delete-signal`

Permanently delete a signal from the workspace.

Required API key scopes: `signals:delete`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `publicId` | string | Yes | Workspace-scoped public ID, such as SIGNAL-43 |

### Example request

```curl
curl -X DELETE https://zentrik.ai/api/external/v1/signals/SIGNAL-43 \
  -H 'Authorization: Bearer YOUR_API_KEY'
```

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 200 | Success | The signal was successfully deleted. | { message: string } |

### Example response

```json
{
  "message": "Deleted"
}
```

---

## POST /external/v1/signals/:publicId/process — Process a signal

Operation ID: `process-signal`

Queue signal processing for an existing signal. Use this for explicit re-runs or manual retries.

Required API key scopes: `signals:write`

### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `publicId` | string | Yes | Workspace-scoped public ID, such as SIGNAL-43. |

### Example request

```curl
curl -X POST https://zentrik.ai/api/external/v1/signals/SIGNAL-43/process \
  -H 'Authorization: Bearer YOUR_API_KEY'
```

### Responses

| Status | Meaning | Description | Schema |
| --- | --- | --- | --- |
| 201 | Queued | The signal was queued for asynchronous processing. | { jobId: string } |

### Example response

```json
{
  "jobId": "job-456"
}
```
