SteveSignalsDocs

API

Pull API and keys

Create an API key, read events with GET /api/public/v1/events, page with a cursor, read a run webhook's pages with pageToken and map signal.v1 to the webhook event.

The pull API returns your organization's events on request. Use it to read the pages of a run webhook, to load history, to poll when you cannot receive webhooks, or to read LinkedIn engagement, which webhooks never send.

Different shape from webhooks

Without pageToken, the pull API returns signal.v1 objects. With a pageToken from a webhook, it returns the event shape (version: 1). The two have different keys. Use the mapping table to convert.

Create an API key

Open Developers

In the sidebar, choose Developers. The API keys section is at the top.

Create the key

Click Create key. Enter a Name, so your team knows where the key is used. Click Create key again.

Copy it now

The panel says "Shown once. Store it now." Click Copy key, store it in your secret manager, then click Done.
API keys on the Developers page
Your keys are listed with their creation date and a Revoke button.

To remove a key, click Revoke next to it and confirm. Anything using the key stops working right away. This cannot be undone.

Keys belong to the organization. Treat them like passwords, and never put one in a browser or a public repo.

Request

bash
1curl "$SIGNALS_HOST/api/public/v1/events?limit=1" \
2 -H "x-api-key: $SIGNALS_API_KEY"

SIGNALS_HOST is the address of the app you sign in to. The Try it box on the Developers page shows the exact command.

Send the key in the x-api-key header. There is no daily request limit. Requests are throttled per IP address at 30 per minute; above that you get 429 with Retry-After: 60.

Query parameters
pageTokenoptional
string
A firstPageToken or nextPageToken from a run webhook. When present, every other parameter is ignored and the response is a run page.
cursoroptional
string
Opaque. Pass the nextCursor of the previous page.
limitoptional
integer
1 to 100. Default 30. A number outside the range is clamped into it. A non-integer is a 400.
sinceoptional
string
ISO 8601 date or timestamp, for example 2026-10-14 or 2026-10-14T09:00:00Z. Only events stored at or after this time. Any other format is a 400.
projectIdoptional
string
Only events of one project. A project that does not exist (or was deleted) is a 404.
trackerIdoptional
string
Only events from one tracker.
sourceoptional
string
One tracker source: reviews, news, linkedin, linkedin_mentions, reddit_mentions, linkedin_engagement, x, instagram, ads, website, job_postings or competitor_discovery. Any other value is a 400.

source accepts reddit_mentions

The reference text inside the app lists fewer values. The API accepts every source above, including reddit_mentions and linkedin_engagement.

Events of deleted projects are never returned.

Response

json
1{
2 "items": [ /* signal.v1 objects, oldest first */ ],
3 "nextCursor": "eyJ…",
4 "hasMore": true
5}
Response
items
signal.v1[]
Events, oldest first by the time Signals stored them.
nextCursor
string|null
Pass it as cursor to get the next page. It is null on the last page.
hasMore
boolean
true when another page follows.
StatusMeaning
200OK.
400A query parameter is invalid. The body is { "error": "..." } and names the parameter.
401Missing, invalid or revoked key.
403The key does not belong to a Signals organization.
404projectId is not a live project of your organization, or Signals is switched off.
429More than 30 requests per minute from one IP. Retry after 60 s.

Run webhook pages

A run webhook carries a firstPageToken per event name. Pass it as pageToken:

bash
1curl "$SIGNALS_HOST/api/public/v1/events?pageToken=$TOKEN" \
2 -H "x-api-key: $SIGNALS_API_KEY"

The response is { run, eventName, total, page, totalPages, items, nextPageToken }. items holds up to 100 events in the event shape, not signal.v1. Follow nextPageToken until it is null.

StatusMeaning
400The token was changed, cut short or is not from Signals.
404The run is not in your key's organization, or the webhook was deleted.
410The token is more than 30 days old. Read the run with GET /api/public/v1/events?trackerId=…&since=… and keep the items whose runId is the run's id.

The fields, an example and a loop you can copy are on Read a run's events.

Poll for new events

Pages come oldest first, so you can walk forward and never skip an event. nextCursor is null on the last page, so you cannot reuse it as a bookmark. Keep the detectedAt of the newest event you saw, use it as since on the next poll, and dedupe on id.

Request a page

GET /api/public/v1/events?limit=100&since=…. Set since to the detectedAt you saved last time, minus about 10 minutes of overlap.

Handle each event

Walk items. Skip any id you already handled.

Follow the cursor

While hasMore is true, request again with cursor set to nextCursor.

Save your place

Store the newest detectedAt you saw. Use it on the next poll, a few minutes later.

The first run without since reads the whole history. Pass a since to start later.

signal.v1 fields

signal.v1
schema
'signal.v1'
The object version.
type
string
The event name, for example reviews.new, ads.new, website.changed, jobs.new, engagement.new.
id
string
The event id. Same value as eventId in a webhook page.
orgId
string
Your organization.
trackerId
string
The tracker that found it.
projectIdoptional
string
The tracker's project. Missing only when the tracker no longer exists and the event was stored before projects existed.
runId
string
The tracker run that found it.
source
string
The tracker source, for example reviews, ads, job_postings, linkedin_engagement.
subject
object
The competitor: { key: string, display: string }. display is its name.
change
'added'|'updated'|'removed'
What happened to the item.
changeSeq
number
Position in the Signals change log. Grows across all events of your organization, not per item.
itemKey
string
The item's identity within its source.
observedAt
string|null
When the item was published or observed, ISO 8601. null for removed.
detectedAt
string
When Signals detected the change, ISO 8601.
item
object
Common item fields: title, text, url, author, metrics, media. Same meaning as in the event shape.
detail
object|null
Source-specific fields, such as rating for a review. null for removed.
relevance
object
The relevance verdict: { status: 'unjudged' } or { status: 'relevant', … }. See the event shape.
signal.v1Real event from a Signals run
json
1{
2 "schema": "signal.v1",
3 "type": "ads.new",
4 "id": "617692b7-2c85-499c-bc3b-6810f3673190",
5 "orgId": "org_3KikADUjsyXrUJ4RJhWpwcp98Yf",
6 "trackerId": "3c8918c2-7011-4518-9185-31344c83861f",
7 "runId": "b0b5b63b-fc3d-4eef-84ab-879e2335c06d",
8 "source": "ads",
9 "subject": {
10 "key": "google:AR10072600183532683265@-|meta:https://www.facebook.com/hubspot|linkedin:hubspot",
11 "display": "HubSpot"
12 },
13 "change": "added",
14 "changeSeq": 254,
15 "itemKey": "ads|google|CR00389017023612452865",
16 "observedAt": "2025-07-30T14:52:54.000Z",
17 "detectedAt": "2026-10-27T20:28:40.624Z",
18 "item": {
19 "title": null,
20 "text": null,
21 "url": null,
22 "author": {
23 "name": "Hubspot, Inc."
24 },
25 "metrics": null,
26 "media": [
27 {
28 "type": "image",
29 "url": "https://tpc.googlesyndication.com/archive/simgad/13161062113949559550"
30 }
31 ]
32 },
33 "detail": {
34 "source": "ads",
35 "kind": "ad",
36 "adSource": "google",
37 "externalId": "CR00389017023612452865",
38 "format": "text",
39 "isActive": true,
40 "startedAt": "2025-07-30T14:52:54.000Z",
41 "endedAt": null,
42 "previewUrl": "https://adstransparency.google.com/advertiser/AR10072600183532683265/creative/CR00389017023612452865?region=anywhere",
43 "cta": null,
44 "landingUrl": null,
45 "paidBy": null,
46 "postedBy": null,
47 "logoUrl": null,
48 "daysShown": 455
49 },
50 "relevance": {
51 "status": "unjudged"
52 }
53}
signal.v1 · linkedin_engagement (pull API only)Real event from a Signals run
json
1{
2 "schema": "signal.v1",
3 "type": "engagement.new",
4 "id": "1742a36b-08e8-477d-8f35-10b3d6207930",
5 "orgId": "org_3KikADUjsyXrUJ4RJhWpwcp98Yf",
6 "trackerId": "271e9142-1ab6-41ad-b905-3d58e065e71d",
7 "runId": "57a0369d-23f8-4aa5-86db-6c64b3cea6e4",
8 "source": "linkedin_engagement",
9 "subject": {
10 "key": "engagement:hubspot|posts:5",
11 "display": "HubSpot"
12 },
13 "change": "added",
14 "changeSeq": 27,
15 "itemKey": "linkedin_engagement|7520919497038557184|acoaac8dmlgbxyqanprimg25nxbncr5bqy0hpe8",
16 "observedAt": "2026-10-27T20:28:38.954Z",
17 "detectedAt": "2026-10-27T20:28:38.954Z",
18 "item": {
19 "title": "Diksha Sabharwal",
20 "text": "B2B Marketing Strategy | Lifecycle Operations | MS in Marketing Analytics",
21 "url": "https://www.linkedin.com/in/ACoAAC8DMLgBxyqaNpRiMg25NxBNcR5bQY0HPE8",
22 "author": {
23 "name": "Diksha Sabharwal",
24 "handle": "acoaac8dmlgbxyqanprimg25nxbncr5bqy0hpe8",
25 "url": "https://www.linkedin.com/in/ACoAAC8DMLgBxyqaNpRiMg25NxBNcR5bQY0HPE8"
26 },
27 "metrics": {},
28 "media": []
29 },
30 "detail": {
31 "source": "linkedin_engagement",
32 "kind": "person",
33 "personHandle": "acoaac8dmlgbxyqanprimg25nxbncr5bqy0hpe8",
34 "name": "Diksha Sabharwal",
35 "headline": "B2B Marketing Strategy | Lifecycle Operations | MS in Marketing Analytics",
36 "profileUrl": "https://www.linkedin.com/in/ACoAAC8DMLgBxyqaNpRiMg25NxBNcR5bQY0HPE8",
37 "how": "reacted",
38 "reaction": "ENTERTAINMENT",
39 "commentText": null,
40 "postUrn": "7520919497038557184",
41 "postUrl": "https://www.linkedin.com/feed/update/urn:li:activity:7520919497038557184/",
42 "postTitle": "Which of these is “frighteningly” on brand? 👻"
43 },
44 "relevance": {
45 "status": "unjudged"
46 }
47}

signal.v1 to webhook event

signal.v1 (pull API)Webhook event (page item)Note
schema: "signal.v1"version: 1Different versioning.
typesignalTypeSame names.
ideventIdSame value.
changeactionadded → new, updated → updated, removed → removed.
changeSeqsequenceSame number.
observedAtoccurredAtBoth null for removed items.
subject { key, display }competitor { name, domain, subjectKey }display → name, key → subjectKey. The webhook adds domain.
item + detail<family>Response and metadataThe webhook merges the item fields and the source fields into one object. It is sent twice, under the family key (for example reviewResponse) and under metadata.
sourcesource, plus eventType and subEventTypeThe webhook also splits the source into family and platform.
orgId, trackerId, projectId, runId, itemKey, detectedAt, relevanceSame namesIn the webhook, itemKey has `

LinkedIn engagement has no webhook event. Read it here with source=linkedin_engagement.

Reference

The machine-readable OpenAPI spec and an interactive viewer are at /api/public/docs on your Signals host. Sign in first.