Signals

For instructions on how to authenticate to use this endpoint, see API overview.

Endpoints

POST
GET
POST
GET
GET
GET
POST
GET
POST
POST
GET
POST
POST
GET
POST
GET
PATCH
DELETE

Create signals scout runs emissions

Batched form of the per-run emissions endpoint: return the findings every requested SignalScoutRun emitted, flattened newest-first, in a single request. Each row carries its run_id, so the caller can regroup by run. The findings UI uses this to load the whole recent window in one round-trip instead of one request per run. Strictly team-scoped — run ids belonging to another team contribute no rows (no per-run 404; one stale id never fails the batch).

Required API key scopes

signal_scout:read

Request parameters

  • run_ids
    array

Example request

POST /api/projects/:project_id/signals/scout/runs/emissions/batch
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl
-H 'Content-Type: application/json'\
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/scout/runs/emissions/batch/\
-d run_ids="array"

Example response

Status 200 Findings emitted across all requested runs, newest first.
RESPONSE
{
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"run_id": "string",
"finding_id": "string",
"description": "string",
"severity": "P0",
"tags": [
"string"
],
"source_id": "string",
"emitted_at": "2019-08-24T14:15:22Z"
}

Retrieve signals scout runs recent

Return the team's recently emitted scout findings across every run, newest first — the cross-run counterpart to the per-run emissions action. Each row carries its run_id, so you can regroup by run without first listing runs and fanning out one emissions call each. Pass skill_name to scope to a single scout, and date_from / date_to (a half-open window on emitted_at) to bound or paginate — set date_to to the oldest emission's emitted_at to walk back past the limit. Pure Postgres, no ClickHouse round-trip. Capped at 200 rows (default 50).

Required API key scopes

signal_scout:read

Query parameters

  • date_from
    string
  • date_to
    string
  • limit
    integer
  • skill_name
    string

Example request

GET /api/projects/:project_id/signals/scout/runs/emissions/recent
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl \
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/scout/runs/emissions/recent/

Example response

Status 200 Recent emitted findings across every run on the team, newest first.
RESPONSE
{
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"run_id": "string",
"finding_id": "string",
"description": "string",
"severity": "P0",
"tags": [
"string"
],
"source_id": "string",
"emitted_at": "2019-08-24T14:15:22Z"
}

Create signals scout runs emission reports

Batched form of the per-run emission-reports endpoint. For every finding the requested runs emitted, resolve the inbox SignalReport (if any) its signal grouped into — all in a single ClickHouse round-trip rather than one query per run, which is what made the findings page slow to open. report is null when a finding hasn't grouped yet, was de-duplicated, or its signal was deleted. Strictly team-scoped — run ids belonging to another team contribute no rows.

Required API key scopes

signal_scout:readtask:read

Request parameters

  • run_ids
    array

Example request

POST /api/projects/:project_id/signals/scout/runs/emissions/reports/batch
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl
-H 'Content-Type: application/json'\
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/scout/runs/emissions/reports/batch/\
-d run_ids="array"

Example response

Status 200 Per-finding inbox report links across all requested runs, newest finding first.
RESPONSE
{
"finding_id": "string",
"source_id": "string",
"report": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"title": "string",
"status": "string"
}
}

Retrieve signals scout runs findings

Return a cheap fleet-wide tally of the output the scout troop produced in the recent window — the finding count, the distinct reports authored/edited via the report channel, the number of distinct scouts behind them, and the latest output time. Backs the 'Scout findings' callout so it renders from one query instead of the client paging through the whole runs window. Counts runs that emitted at least one finding (emitted_count > 0) or authored/edited an inbox report within the last window_hours (default 72), capped to the most recent 120 such runs so the count matches what the findings list renders. Strictly team-scoped.

Required API key scopes

signal_scout:read

Query parameters

  • window_hours
    integer

Response


Example request

GET /api/projects/:project_id/signals/scout/runs/findings/summary
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl \
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/scout/runs/findings/summary/

Example response

Status 200 Fleet-wide tally of recent scout output — findings and report activity.
RESPONSE
{
"count": 0,
"scout_count": 0,
"authored_report_count": 0,
"edited_report_count": 0,
"run_count": 0,
"latest_at": "2019-08-24T14:15:22Z"
}

Retrieve signals scout runs recent per

Return the most recent per_scout_limit runs of every configured scout on the project (default 25), newest-first across the fleet. The per-scout probe is the point: a fleet-wide time window has to serve hourly and weekly scouts from one result cap, so the busy scouts crowd out the sparse ones and a scout's visible history shrinks as the rest of the fleet gets busier. Probing each skill_name gives every scout the same depth of history whatever its schedule, and bounds the response at scouts x per_scout_limit rather than at the fleet's run rate. Only scouts that still have a config are covered, so runs left behind by a deleted or renamed scout are excluded. Runs older than the staleness guard are excluded too, so a scout that stopped running reads as stale instead of healthy — max_age_days (default 30) is that guard's floor, which each scout's own cadence extends. Use list instead when you want a literal time window or a text/emitted filter. Pass skill_name to probe one scout only. Strictly team-scoped.

Required API key scopes

signal_scout:read

Query parameters

  • max_age_days
    integer
  • per_scout_limit
    integer
  • skill_name
    string

Example request

GET /api/projects/:project_id/signals/scout/runs/recent-per-scout
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl \
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/scout/runs/recent-per-scout/

Example response

Status 200 Each scout's most recent runs, newest-first across the fleet.
RESPONSE
{
"total_spend": 0,
"run_id": "string",
"skill_name": "string",
"skill_version": 0,
"status": "not_started",
"created_at": "string",
"started_at": "string",
"completed_at": "string",
"task_id": "string",
"task_run_id": "string",
"task_url": "string",
"summary": "string",
"error": "string",
"failure_reason": "string",
"emitted_count": 0,
"emitted_finding_ids": [
"string"
],
"emitted_report_ids": [
"string"
],
"edited_report_ids": [
"string"
],
"metadata": {
"harness_prompt_version": "string",
"report_channel": "string",
"skill_origin": "string",
"github_guidance": true,
"business_knowledge_maintained": true,
"model": "string",
"runtime_adapter": "string",
"reasoning_effort": "string",
"service_tier": "string",
"network_access": "string",
"write_scopes": [
"string"
],
"triggered_by": "string",
"run_note": "string",
"derived": {
"has_emit_report": true,
"has_edit_report": true,
"has_self_improvement": true,
"has_chart": true,
"has_self_validation": true,
"has_structured_output": true,
"has_not_in_use_closeout": true
}
}
}
Status 304 The runs match the `ETag` sent in `If-None-Match`, so the body is left out.
Status 412 The runs do not match the `ETag` sent in `If-Match`.

List all signals scout report check

Every check on one report, newest first. The report_id is the only input. Read this before writing one: a report already carrying a check for the same claim needs no second one, and a report holds at most five open checks at a time.

Required API key scopes

signal_scout_report:write

Query parameters

  • report_id
    string

Example request

GET /api/projects/:project_id/signals/scout/runs/report-checks
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl \
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/scout/runs/report-checks/

Example response

Status 200 The report's checks, newest first.
RESPONSE
{
"check_id": "051490c8-9abf-480a-9348-72c146208cd5",
"report_id": "5ed7905a-4735-4cf7-b1ab-521e066fb971",
"title": "string",
"kind": "string",
"status": "string",
"next_run_at": "2019-08-24T14:15:22Z",
"last_outcome": "string",
"last_outcome_reason": "string",
"run_state": "string",
"waiting_on_run": true,
"dispatched_run_id": "0d40720c-3ac9-4b33-96ee-ba348f818515",
"dispatched_at": "2019-08-24T14:15:22Z"
}
Status 400 The report does not exist for this project.

Create signals scout runs token

Return what each requested SignalScoutRun spent on model calls, summed from the $ai_generation events its sandbox produced. One query for the whole batch, cached per run: a settled run's total is final, a run still in progress reports what it has spent so far. available is false where the internal AI observability project holding those events can't be read, so an unknown cost never reads as zero. Staff-only — fleet spend is an internal operating number, and the events sit outside the project in the path. Strictly team-scoped — run ids belonging to another project contribute no rows.

Required API key scopes

signal_scout:read

Request parameters

  • run_ids
    array

Response


Example request

POST /api/projects/:project_id/signals/scout/runs/token-costs
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl
-H 'Content-Type: application/json'\
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/scout/runs/token-costs/\
-d run_ids="array"

Example response

Status 200 Model spend for each requested run that exists on this project.
RESPONSE
{
"costs": [
{
"run_id": "string",
"token_cost_usd": 0
}
],
"available": true
}
Status 403 Caller is not PostHog staff.

Retrieve signals scout scratchpad

Return SignalScratchpad entries for this project, newest-first. ILIKE matches on content and key; pass key instead for an exact single-entry lookup, and skill_name to keep the entries one scout (or pipeline:* stage) created. date_from / date_to are a half-open window on updated_at (>= date_from, < date_to); pass date_to (the updated_at of the oldest entry seen) on subsequent calls to walk past the cap. Entries whose expires_at has passed are excluded unless include_expired=true, and are hard-deleted by a daily janitor once their expiry is more than two weeks in the past. Pass keys_only=true to scan keys without pulling entry bodies, or content_max_chars to cap each content to a preview — both keep a wide orientation scan from returning every entry's full prose. Results capped at 1000.

Required API key scopes

signal_scout:read

Query parameters

  • content_max_chars
    integer
  • date_from
    string
  • date_to
    string
  • include_expired
    boolean
    Default: false
  • key
    string
  • keys_only
    boolean
  • limit
    integer
  • skill_name
    string
  • text
    string

Example request

GET /api/projects/:project_id/signals/scout/scratchpad
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl \
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/scout/scratchpad/

Example response

Status 200 Matching memory entries newest-first.
RESPONSE
{
"key": "string",
"content": "string",
"created_at": "string",
"updated_at": "string",
"expires_at": "string",
"created_by_run_id": "string",
"created_by_skill": "string",
"created_by_run_url": "string"
}

Create signals scout scratchpad

Upsert a memory keyed on (team, key). Re-using a key updates the existing entry in place. A write carries the entry's whole state, so expires_at is set when passed and cleared when omitted.

Required API key scopes

signal_scratchpad_internal:write

Request parameters

  • key
    string
  • content
    string
  • run_id
    string | null
  • expires_at
    string | null

Response


Example request

POST /api/projects/:project_id/signals/scout/scratchpad
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl
-H 'Content-Type: application/json'\
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/scout/scratchpad/\
-d key="string",\
-d content="string"

Example response

Status 200 Memory entry written or refreshed.
RESPONSE
{
"key": "string",
"content": "string",
"created_at": "string",
"updated_at": "string",
"expires_at": "string",
"created_by_run_id": "string",
"created_by_skill": "string",
"created_by_run_url": "string"
}
Status 400 Invalid memory shape (empty key/content, key too long, or an `expires_at` on a `followup:` entry). An unparseable or past `expires_at` is dropped, not rejected.

Create signals scout scratchpad

Delete an entry by key. Returns deleted=false if no row matched.

Required API key scopes

signal_scratchpad_internal:write

Request parameters

  • key
    string

Response


Example request

POST /api/projects/:project_id/signals/scout/scratchpad/forget
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl
-H 'Content-Type: application/json'\
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/scout/scratchpad/forget/\
-d key="string"

Example response

Status 200 Whether a row was removed.
RESPONSE
{
"deleted": true
}

List all signals scout suggestions

Return the pre-computed scout suggestions for this project: up to five picks, best first, each either a PostHog-authored scout to turn on or a drafted custom scout. Dismissed and already-created suggestions are omitted. An empty items with status empty means no batch has been generated yet; the interactive scout-chat-tasks path still works.

Required API key scopes

signal_scout:read

Response


Example request

GET /api/projects/:project_id/signals/scout/suggestions
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl \
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/scout/suggestions/

Example response

Status 200 The current batch.
RESPONSE
{
"status": "fresh",
"generated_at": "2019-08-24T14:15:22Z",
"model": "string",
"fleet_snapshot": [
"string"
],
"items": [
{
"id": "string",
"kind": "canonical",
"skill_name": "string",
"title": "string",
"why_here": "string",
"description": "string",
"draft_body": "string",
"proposed_config": {
"run_cron_schedule": "string",
"run_interval_minutes": 0,
"emit": true
},
"gap": true,
"confidence": "low"
}
]
}

Create signals scout suggestions

Hide one suggestion from this project's batch. Dismissal is remembered across refreshes by skill name, so the same suggestion is not shown again.

Required API key scopes

signal_scout:write

Path parameters

  • id
    string

Response


Example request

POST /api/projects/:project_id/signals/scout/suggestions/:id/dismiss
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl
-H 'Content-Type: application/json'\
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/scout/suggestions/:id/dismiss/

Example response

Status 200 The dismissed suggestion.
RESPONSE
{
"id": "string",
"kind": "canonical",
"skill_name": "string",
"title": "string",
"why_here": "string",
"description": "string",
"draft_body": "string",
"proposed_config": {
"run_cron_schedule": "string",
"run_interval_minutes": 0,
"emit": true
},
"gap": true,
"confidence": "low"
}
Status 404 No suggestion with that id in this project's batch.

Create signals scout suggestions

Re-run the suggestion scan for this project now instead of waiting for the scheduled refresh. Runs headlessly; poll the list endpoint for the new batch (generated_at advances). Capped per project per day.

Required API key scopes

signal_scout:write

Response


Example request

POST /api/projects/:project_id/signals/scout/suggestions/refresh
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl
-H 'Content-Type: application/json'\
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/scout/suggestions/refresh/

Example response

Status 202 Refresh dispatched.
RESPONSE
{
"workflow_id": "string"
}
Status 403 Organization has not approved AI data processing, or suggestions are off for this project.
Status 409 A refresh is already running for this project.
Status 429 Daily refresh cap reached.

List all signals source configs

Required API key scopes

task:read

Query parameters

  • limit
    integer
  • offset
    integer

Response


Example request

GET /api/projects/:project_id/signals/source_configs
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl \
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/source_configs/

Example response

Status 200
RESPONSE
{
"count": 123,
"next": "http://api.example.org/accounts/?offset=400&limit=100",
"previous": "http://api.example.org/accounts/?offset=200&limit=100",
"results": [
{
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"source_product": "session_replay",
"source_type": "session_analysis_cluster",
"enabled": true,
"config": {},
"created_at": "2019-08-24T14:15:22Z",
"updated_at": "2019-08-24T14:15:22Z",
"status": "running"
}
]
}

Create signals source configs

Required API key scopes

task:write

Request parameters

  • source_product
  • source_type
  • enabled
    boolean
  • config
    object

Response


Example request

POST /api/projects/:project_id/signals/source_configs
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl
-H 'Content-Type: application/json'\
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/source_configs/\
-d source_product=undefined,\
-d source_type=undefined

Example response

Status 201
RESPONSE
{
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"source_product": "session_replay",
"source_type": "session_analysis_cluster",
"enabled": true,
"config": {},
"created_at": "2019-08-24T14:15:22Z",
"updated_at": "2019-08-24T14:15:22Z",
"status": "running"
}

Retrieve signals source configs

Required API key scopes

task:read

Path parameters

  • id
    string

Response


Example request

GET /api/projects/:project_id/signals/source_configs/:id
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl \
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/source_configs/:id/

Example response

Status 200
RESPONSE
{
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"source_product": "session_replay",
"source_type": "session_analysis_cluster",
"enabled": true,
"config": {},
"created_at": "2019-08-24T14:15:22Z",
"updated_at": "2019-08-24T14:15:22Z",
"status": "running"
}

Update signals source configs

Required API key scopes

task:write

Path parameters

  • id
    string

Request parameters

  • source_product
  • source_type
  • enabled
    boolean
  • config
    object

Response


Example request

PATCH /api/projects/:project_id/signals/source_configs/:id
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl -X PATCH \
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/source_configs/:id/\
-d source_product=undefined

Example response

Status 200
RESPONSE
{
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"source_product": "session_replay",
"source_type": "session_analysis_cluster",
"enabled": true,
"config": {},
"created_at": "2019-08-24T14:15:22Z",
"updated_at": "2019-08-24T14:15:22Z",
"status": "running"
}

Delete signals source configs

Required API key scopes

task:write

Path parameters

  • id
    string

Example request

DELETE /api/projects/:project_id/signals/source_configs/:id
export POSTHOG_PERSONAL_API_KEY=[your personal api key]
curl -X DELETE \
-H "Authorization: Bearer $POSTHOG_PERSONAL_API_KEY" \
<ph_app_host>/api/projects/:project_id/signals/source_configs/:id/

Example response

Status 204 No response body