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
Create the key
Copy it now
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
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.
pageTokenoptionalfirstPageToken or nextPageToken from a run webhook. When present, every other parameter is ignored and the response is a run page.cursoroptionalnextCursor of the previous page.limitoptional30. A number outside the range is clamped into it. A non-integer is a 400.sinceoptional2026-10-14 or 2026-10-14T09:00:00Z. Only events stored at or after this time. Any other format is a 400.projectIdoptional404.trackerIdoptionalsourceoptionalreviews, 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
1{2 "items": [ /* signal.v1 objects, oldest first */ ],3 "nextCursor": "eyJ…",4 "hasMore": true5}
itemsnextCursorcursor to get the next page. It is null on the last page.hasMoretrue when another page follows.| Status | Meaning |
|---|---|
200 | OK. |
400 | A query parameter is invalid. The body is { "error": "..." } and names the parameter. |
401 | Missing, invalid or revoked key. |
403 | The key does not belong to a Signals organization. |
404 | projectId is not a live project of your organization, or Signals is switched off. |
429 | More 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:
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.
| Status | Meaning |
|---|---|
400 | The token was changed, cut short or is not from Signals. |
404 | The run is not in your key's organization, or the webhook was deleted. |
410 | The 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
items. Skip any id you already handled.Follow the cursor
hasMore is true, request again with cursor set to nextCursor.Save your place
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
schematypereviews.new, ads.new, website.changed, jobs.new, engagement.new.ideventId in a webhook page.orgIdtrackerIdprojectIdoptionalrunIdsourcereviews, ads, job_postings, linkedin_engagement.subject{ key: string, display: string }. display is its name.changechangeSeqitemKeyobservedAtnull for removed.detectedAtitemdetailrating for a review. null for removed.relevance1{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": 45549 },50 "relevance": {51 "status": "unjudged"52 }53}
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: 1 | Different versioning. |
type | signalType | Same names. |
id | eventId | Same value. |
change | action | added → new, updated → updated, removed → removed. |
changeSeq | sequence | Same number. |
observedAt | occurredAt | Both null for removed items. |
subject { key, display } | competitor { name, domain, subjectKey } | display → name, key → subjectKey. The webhook adds domain. |
item + detail | <family>Response and metadata | The 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. |
source | source, plus eventType and subEventType | The webhook also splits the source into family and platform. |
orgId, trackerId, projectId, runId, itemKey, detectedAt, relevance | Same names | In 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.