Signals
For instructions on how to authenticate to use this endpoint, see API overview.
Endpoints
POST | |
POST | |
POST | |
POST | |
POST | |
POST | |
GET | |
GET | |
POST | |
GET | |
POST | |
GET | |
GET | |
GET | |
POST | |
GET | |
POST | |
POST | |
GET | |
POST |
Create signals scout emit
The second emit channel: author a complete SignalReport directly instead of emitting a weak signal. The report passes the safety judge, then surfaces at the status the scout's actionability call implies (or is suppressed). Backing evidence is written as bound signals so the report behaves like a pipeline report. Safe to retry: resending an emission returns the report the first call authored (idempotent_replay true) rather than a second one, keyed on idempotency_key or, without one, on the report's content. Use reports to find a report from an earlier run and edit-report to update it instead of authoring a near-duplicate.
Required API key scopes
signal_scout_report:writePath parameters
- run_idstring
Request parameters
- titlestring
- summarystring
- evidencearray
- actionability_explanationstring
- actionability
- already_addressedbooleanDefault:
false - repositorystring | null
- priority
- priority_explanationstring | null
- suggested_reviewersarray
- chartsarray
- metricsarray
- suggested_promptsarray
- linksarray
- idempotency_keystring | null
Response
Example request
POST /api /projects /:project_id /signals /scout /runs /:run_id /emit-reportExample response
Status 200 Report authored (READY/PENDING_INPUT/suppressed), or skipped by a preflight gate.
Status 400 Invalid report shape (empty title/summary/evidence, bad actionability).
Status 404 Run not found for this project.
Create signals scout emit
Fire emit_signal with source_product = signals_scout. The finding_id is baked into the deterministic Signal.source_id = run:<id>:finding:<id> for traceability, but this is NOT idempotent — a second call with the same finding_id emits a second signal, so do not retry an emit that may have already succeeded.
Required API key scopes
signal_scout_internal:writePath parameters
- run_idstring
Request parameters
- descriptionstring
- evidencearray
- hypothesisstring | null
- severity
- dedupe_keysarray
- tagsarray
- time_range
- mcp_trace_idstring | null
- finding_idstring | null
Response
Example request
POST /api /projects /:project_id /signals /scout /runs /:run_id /emit-signalExample response
Status 200 Finding emitted, or skipped by a preflight gate.
Status 400 Invalid emit shape (description, evidence cap).
Status 404 Run not found for this project.
Create signals scout lighthouse
Load one page in a real browser and return what makes it slow — most usefully the element the browser chose as the Largest Contentful Paint, and where the LCP time went. Field data says a route is slow; this says which element and why, so a finding can name it instead of guessing from source. Restricted to public PostHog pages: the browser signs in to nothing, so a page behind a login would report the login screen's numbers. One throttled cold load is not a p75 over real users — corroborate a field finding with it, never replace one. Capped at 5 audits per run.
Required API key scopes
signal_scout_internal:writePath parameters
- run_idstring
Request parameters
- urlstring
- form_factorDefault:
desktop
Response
Example request
POST /api /projects /:project_id /signals /scout /runs /:run_id /lighthouse-auditExample response
Status 200 The audit ran and the reduced report is attached.
Status 400 The url is not on an allowed host, is not https, redirected off the allowed hosts (the login-wall case), the run is not in progress, or the run has spent its audit budget. Also returned when the page could not be loaded at all. Every message ends with how many audits the run has left, so a rejection is distinguishable from an exhausted budget.
Status 404 No such run in this project, or the run belongs to a different scout.
Status 429 Audit rate limit exceeded; retry later.
Status 501 Lighthouse audits are not configured on this deployment.
Status 503 The browser fleet is at capacity, so no audit ran and the run keeps the slot. Retry later.
Create signals scout record
The structured-output channel: record schema-validated records this run produced. Opt-in via the scout config's structured_output_schema (a JSON Schema describing one record) — without it the call fails closed, as it does for a dry-run scout (emit off). All-or-nothing: any invalid record fails the whole call with nothing written, so fix and resubmit the batch. Each accepted record lands in the project's event stream as a $scout_structured_output event — query them like any event (insights, SQL over events). Recording is idempotent: event ids are deterministic, so resubmitting an identical batch (e.g. retrying after a 503) cannot double-count.
Required API key scopes
signal_scout_internal:writePath parameters
- run_idstring
Request parameters
- recordsarray
Response
Example request
POST /api /projects /:project_id /signals /scout /runs /:run_id /record-outputExample response
Status 200 All records validated against the configured schema and recorded as events.
Status 400 No structured_output_schema configured for this scout, the channel is off (dry-run scout, org AI-processing consent missing, or the signals_scout source disabled), a record failed schema validation (nothing written), or a batch/size cap was exceeded.
Status 404 Run not found for this project.
Status 503 The records validated but event delivery failed; nothing was recorded. Retry the same call — delivery is idempotent, so a retry cannot double-count.
Create signals scout report check
Stop a check that is no longer worth running — the claim it re-measures has changed, or a better check replaces it. Results it already recorded stay on the report. A check that has already finished cannot be cancelled.
Required API key scopes
signal_scout_report:writePath parameters
- run_idstring
Request parameters
- check_idstring
Response
Example request
POST /api /projects /:project_id /signals /scout /runs /:run_id /report-check-cancelExample response
Status 200 Check cancelled.
Status 400 The check does not exist for this project, or already finished and cannot be cancelled.
Status 404 Run not found for this project.
Create signals scout report check
Schedule a re-measurement of a report's claim, so whether the fix held becomes a stored fact instead of something a future run has to remember to look for. A metric_threshold check runs one bounded Trends query and compares the result. An agent check runs a scout instead, for a claim no single number settles.
Required API key scopes
signal_scout_report:writePath parameters
- run_idstring
Request parameters
- titlestring
- rationalestring
- kind
- config
- next_run_atstring
- run_interval_minutesinteger | null
- runs_remaininginteger
- expires_atstring
- report_idstring
Response
Example request
POST /api /projects /:project_id /signals /scout /runs /:run_id /report-check-createExample response
Status 200 Check written on the report.
Status 400 The report does not exist for this project, the config does not match the kind, the named metric is not on the report, or the report is already at its check limit.
Status 404 Run not found for this project.
List all signals scout report checks
Every check on one report, newest first. 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:writePath parameters
- run_idstring
Query parameters
- report_idstring
Example request
GET /api /projects /:project_id /signals /scout /runs /:run_id /report-checksExample response
Status 200 The report's checks, newest first.
Status 400 The report does not exist for this project.
Status 404 Run not found for this project.
Retrieve signals scout runs
Return what every scout on this project spent on model calls over the last window_days, with how many runs it started, how many of those had spend attributed, and how many inbox reports it filed or added to. Cost per day, per run, and per report are derived from those numbers by the caller, so the endpoint stays a fact table and the definitions live in one place. Spend is summed from the $ai_generation events the runs' sandboxes produced and joined to the run rows by task run id, because a team-authored scout's generations all carry the same stage tag and so cannot name it. Cached per project for 15 minutes: the window's trailing edge moves and the newest runs may still be settling, so this is a roughly current number, not a live one. available is false where the internal AI observability project holding those events can't be read, so an unknown spend never reads as zero. Staff-only, same gate as the per-run cost read. Strictly team-scoped.
Required API key scopes
signal_scout:readQuery parameters
- window_daysinteger
Response
Example request
GET /api /projects /:project_id /signals /scout /runs /costsExample response
Status 200 Model spend and output per scout over the window.
Status 403 Caller is not PostHog staff.
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:readRequest parameters
- run_idsarray
Example request
POST /api /projects /:project_id /signals /scout /runs /emissions /batchExample response
Status 200 Findings emitted across all requested runs, newest first.
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:readQuery parameters
- date_fromstring
- date_tostring
- limitinteger
- skill_namestring
Example request
GET /api /projects /:project_id /signals /scout /runs /emissions /recentExample response
Status 200 Recent emitted findings across every run on the team, newest first.
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:readRequest parameters
- run_idsarray
Example request
POST /api /projects /:project_id /signals /scout /runs /emissions /reports /batchExample response
Status 200 Per-finding inbox report links across all requested runs, newest finding first.
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:readQuery parameters
- window_hoursinteger
Response
Example request
GET /api /projects /:project_id /signals /scout /runs /findings /summaryExample response
Status 200 Fleet-wide tally of recent scout output — findings and report activity.
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. Strictly team-scoped.
Required API key scopes
signal_scout:readQuery parameters
- max_age_daysinteger
- per_scout_limitinteger
Example request
GET /api /projects /:project_id /signals /scout /runs /recent-per-scoutExample response
Status 200 Each scout's most recent runs, newest-first across the fleet.
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:writeQuery parameters
- report_idstring
Example request
GET /api /projects /:project_id /signals /scout /runs /report-checksExample response
Status 200 The report's checks, newest first.
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:readRequest parameters
- run_idsarray
Response
Example request
POST /api /projects /:project_id /signals /scout /runs /token-costsExample response
Status 200 Model spend for each requested run that exists on this project.
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. 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:readQuery parameters
- content_max_charsinteger
- date_fromstring
- date_tostring
- include_expiredbooleanDefault:
false - keystring
- keys_onlyboolean
- limitinteger
- textstring
Example request
GET /api /projects /:project_id /signals /scout /scratchpadExample response
Status 200 Matching memory entries newest-first.
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:writeRequest parameters
- keystring
- contentstring
- run_idstring | null
- expires_atstring | null
Response
Example request
POST /api /projects /:project_id /signals /scout /scratchpadExample response
Status 200 Memory entry written or refreshed.
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:writeRequest parameters
- keystring
Response
Example request
POST /api /projects /:project_id /signals /scout /scratchpad /forgetExample response
Status 200 Whether a row was removed.
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:readResponse
Example request
GET /api /projects /:project_id /signals /scout /suggestionsExample response
Status 200 The current batch.
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:writePath parameters
- idstring
Response
Example request
POST /api /projects /:project_id /signals /scout /suggestions /:id /dismiss