Authentication
Create an API key in the portal (app.signalraven.ai → Settings → API keys), then use the OAuth client_credentialsgrant to get a bearer token (≈30 min lifetime), and call the API with it.
# 1. Get a token
curl -s -X POST https://auth.signalraven.ai/oauth2/token \
-d grant_type=client_credentials \
-d client_id=YOUR_CLIENT_ID \
-d client_secret=YOUR_CLIENT_SECRET \
-d 'scope=read:signals read:sources read:icp'
# 2. Call the API
curl -s https://api.signalraven.ai/api/v1/signals?limit=5 \
-H "Authorization: Bearer eyJ…"Scopes
| Scope | Grants |
|---|---|
| read:signals | List + fetch signals |
| read:sources | List monitored sources + metrics |
| read:watchlist | List watchlist posts |
| read:icp | Read the workspace ICP profile |
| read:intelligence | Read Account + Person Intelligence reports |
| read:usage | Read workspace activity counts |
| read:destinations | Read where signals are delivered |
| write:intelligence | Start new Intelligence runs (spends credits) |
| write:destinations | Create + change delivery destinations |
A token only works on endpoints matching its scopes; anything else returns 403 insufficient_scope. Every response carries a _meta.mode of "live" or "sample" (see approved vs. sample data).
GET /signals
List signals, newest first.
| Param | Type | Default | Notes |
|---|---|---|---|
| limit | int | 25 | 1–100 |
| offset | int | 0 | pagination |
| type | string | — | filter by signal type |
| minStrength | int | — | strength ≥ this (0–10) |
{
"data": [
{
"id": "8af4c6c7-…",
"type": "KEYWORD_SEARCH_REACTION",
"strength": 9,
"person": { "company": "Globex", "linkedinUrl": "https://…", "location": "New York, US" },
"whyItMatters": "Publicly weighing a multi-CDN migration …",
"suggestedOpener": "Saw your note on tail-latency …",
"talkingPoints": ["Multi-CDN failover", "Live-event QoE"],
"createdAt": "2026-01-01T00:00:00.000Z"
}
],
"total": 866,
"limit": 25,
"offset": 0,
"_meta": { "mode": "live" }
}GET /signals/{id}
Fetch one signal by id. Returns { data, _meta }, or 404 if not found.
GET /sources
List monitored sources with activity metrics. Query period = 7, 30 (default), or all (days). Each source includes type, linkedinUrl, displayName, isActive, strengthScore, signalCount, totalPosts, qualifiedPosts.
GET /watchlist
List watchlist posts (limit 1–100, offset). Each post: sourceId, url, author, preview, relevanceScore, lifecycleStatus, createdAt.
GET /icp
The workspace’s default ICP profile: minEmployees, maxEmployees, targetIndustries, targetTitles, targetPersonas, targetSeniority, additionalContext. 404 if none configured.
GET /intelligence
The Account and Person Intelligence reports run in your workspace, newest first (limit 1–100, offset, type of all | account | person, q free-text on the name). Every row carries id, type, slug, name, status and createdAt; account rows add disposition, industry, employeeCount and committeeSize, person rows add currentTitle, currentCompany and signalsCount. Fields belonging to the other family are null, so branch on type.
GET /intelligence/accounts/{id}
One Account Intelligence report: firmographics, disposition and dispositionReason, and the buyingCommittee array. 404if the report isn’t yours or doesn’t exist.
GET /intelligence/people/{id}
One Person Intelligence snapshot: profile, aiRead (classification + reasoning), notable (hiring, openToWork, recentJobChange), the signals it surfaced, and rawActivityCount. Raw activity is returned as a count only — the same thing the product shows a human.
POST /intelligence/accounts
Start an Account Intelligence run. Body: companyUrl. Returns 202 with { id, slug, status, cached:false } when a run was queued, or 200 with cached:true when a report from the last 30 days already covered it — in which case nothing is spent. Poll GET /intelligence/accounts/{id} until status is ready.
POST /intelligence/people
Same shape for a person. Body: personUrl, optionally sourceCompanyReportId to keep the lineage from an account report.
GET /usage
Workspace activity counts: signalsThisMonth, signalsLastMonth, sourcesActive, postsMonitored, postsThisMonth, discoveryRunsToDate, plus systemStatus. Counts only — no billing figures.
GET /destinations
Where your signals are delivered, with type, name, isActive, health, consecutiveFailures and routing. Connection settings are never returned — a read scope should not hand back webhook URLs or tokens.
POST / PATCH / DELETE /destinations
Create (destinationType, name, config, optional routing), update (name, isActive, routing), or remove one. DELETE deactivates rather than hard-deleting, so delivery history survives. Creating returns 409when the plan’s destination limit is reached.
Errors
| Status | error | When |
|---|---|---|
| 401 | unauthorized | missing / invalid / expired token |
| 403 | insufficient_scope | token lacks the required scope |
| 403 | forbidden | token isn’t workspace-scoped |
| 403 | client_denied | client denied by SignalRaven |
| 404 | not_found | resource doesn’t exist |
| 403 | workspace_inactive | writes need an active workspace |
| 409 | plan_destination_cap_reached | at the plan’s destination limit |