Signals

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

Endpoints

POST
DELETE
GET
GET
POST
GET
GET
POST
POST
GET
GET
POST
POST
POST
POST
POST
POST
GET
GET

Create signals scout notes

Leave a steering note the scout fleet reads on its next runs. Address it to one scout via skill_name (a configured scout), to one stage of the report pipeline via a reserved audience (pipeline:report-research), or omit it for a general note every scout sees. Each call creates a new note (no upsert); delete retires one. Attributed to the authenticated user.

Required API key scopes

signal_scout:writellm_skill:write

Request parameters

  • content
    string
  • skill_name
    string
  • expires_at
    string | null

Response


Example request

POST /api/projects/:project_id/signals/scout/notes
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/notes/\
-d content="string"

Example response

Status 201 The note as created.
RESPONSE
{
"id": "string",
"skill_name": "string",
"content": "string",
"created_at": "string",
"expires_at": "string",
"created_by_name": "string",
"origin": "string"
}
Status 400 Invalid note (empty content, oversized, bad skill_name target).

Delete signals scout notes

Delete one note by its id, retiring it from every scout's view. Use this when a note has been acted on or no longer applies; time-boxed notes can instead carry an expires_at and retire themselves.

Required API key scopes

signal_scout:writellm_skill:write

Path parameters

  • id
    string

Example request

DELETE /api/projects/:project_id/signals/scout/notes/: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/scout/notes/:id/

Example response

Status 204 Note deleted.
Status 404 Note not found for this project.

Retrieve signals scout project profile

Return the team's deterministic project profile. The response opens with a compact summary envelope carrying the emit gate and the inbox report counts, then the full payload. The inventory runs to tens of kilobytes, so a client that truncates a long tool result still keeps the gate. Pass summary_only=true to omit payload entirely. For the internal scout token the response reflects the newest non-expired cached row or a freshly-built one (lazy compute on cache miss); force_refresh=true skips the cache and rebuilds from authoritative sources. Public read callers (session auth or a signal_scout:read PAK) get the newest stored profile whatever its age, with stale=true when it is past expires_at. They never build inline: when the team has no profile, the call starts a background build and returns 404, so retry in a minute. Read this at the start of a run to orient on the team's product mix, integrations, warehouse sources, signal coverage, and existing inbox surface.

Required API key scopes

signal_scout:read

Query parameters

  • force_refresh
    boolean
    Default: false
  • run_id
    string | null
  • summary_only
    boolean
    Default: false

Response


Example request

GET /api/projects/:project_id/signals/scout/project_profile/current
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/project_profile/current/

Example response

Status 200 The team's current project profile: freshly built for the internal scout token, or the newest stored profile for other callers, with `stale=true` when it is past `expires_at`.
RESPONSE
{
"summary": {
"emit_eligibility": {
"ai_processing_approved": true,
"source_enabled": true,
"scout_emit_enabled": true,
"can_emit": true,
"blocking_reason": "string",
"remediation": "string"
},
"existing_inbox_reports": {
"total": 0,
"by_status": [
{
"status": "string",
"count": 0
}
]
}
},
"profile_id": "string",
"computed_at": "string",
"expires_at": "string",
"stale": true,
"source_version": "string",
"payload": {
"inventory": {
"project_context": {
"product_description": "string",
"app_urls": [
"string"
]
},
"products_in_use": [
"string"
],
"product_intents": [
{
"product_type": "string",
"activated_at": "string",
"created_at": "string"
}
],
"integrations": [
{
"kind": "string",
"created_at": "string"
}
],
"external_data_sources": [
{
"source_type": "string",
"status": "string",
"prefix": "string",
"created_at": "string",
"last_run_at": "string",
"latest_error": "string"
}
],
"signal_source_configs": {
"enabled": [
{
"source_product": "string",
"source_type": "string"
}
],
"disabled": [
{
"source_product": "string",
"source_type": "string"
}
]
},
"emit_eligibility": {
"ai_processing_approved": true,
"source_enabled": true,
"scout_emit_enabled": true,
"can_emit": true,
"blocking_reason": "string",
"remediation": "string"
},
"scout_fleet": {
"enabled": [
{
"skill_name": "string",
"run_interval_minutes": 0,
"run_cron_schedule": "string",
"emit": true,
"last_run_at": "string",
"last_emitted_at": "string",
"not_running_reason": "string",
"pause_reason": "string"
}
],
"disabled": [
{
"skill_name": "string",
"run_interval_minutes": 0,
"run_cron_schedule": "string",
"emit": true,
"last_run_at": "string",
"last_emitted_at": "string",
"not_running_reason": "string",
"pause_reason": "string"
}
],
"emitted_lookback_days": 0
},
"existing_inbox_reports": {
"total": 0,
"by_status": [
{
"status": "string",
"count": 0
}
]
},
"recent_activity": {
"window_days": 0,
"by_scope": [
{
"scope": "string",
"edits": 0,
"users": 0,
"last_edit": "string"
}
]
},
"recent_reviewer_corrections": {
"window_days": 0,
"corrections": [
{
"report_id": "string",
"report_title": "string",
"before": [
"string"
],
"after": [
"string"
],
"at": "string"
}
]
},
"recent_dashboards": [
{
"id": 0,
"name": "string",
"last_accessed_at": "string",
"last_refresh": "string",
"created_at": "string"
}
],
"recent_surveys": {
"total_count": 0,
"active_count": 0,
"recent": [
{
"id": "string",
"name": "string",
"type": "string",
"status": "string",
"updated_at": "string"
}
]
},
"recent_feature_flags": {
"total_count": 0,
"active_count": 0,
"recent": [
{
"id": 0,
"key": "string",
"name": "string",
"active": true,
"updated_at": "string"
}
]
},
"recent_experiments": {
"total_count": 0,
"running_count": 0,
"recent": [
{
"id": 0,
"name": "string",
"status": "string",
"feature_flag_key": "string",
"updated_at": "string"
}
]
},
"recent_alerts": {
"total_count": 0,
"enabled_count": 0,
"recent": [
{
"id": "string",
"name": "string",
"enabled": true,
"state": "string",
"calculation_interval": "string",
"insight_id": 0,
"created_at": "string"
}
]
},
"recent_hog_functions": {
"total_count": 0,
"enabled_count": 0,
"recent": [
{
"id": "string",
"name": "string",
"type": "string",
"kind": "string",
"enabled": true,
"updated_at": "string"
}
]
},
"recent_hog_flows": {
"total_count": 0,
"active_count": 0,
"recent": [
{
"id": "string",
"name": "string",
"status": "string",
"updated_at": "string"
}
]
},
"recent_notebooks": {
"total_count": 0,
"recent": [
{
"short_id": "string",
"title": "string",
"last_modified_at": "string"
}
]
},
"recent_cohorts": {
"total_count": 0,
"recent": [
{
"id": 0,
"name": "string",
"is_static": true,
"count": 0,
"created_at": "string"
}
]
},
"recent_actions": {
"total_count": 0,
"recent": [
{
"id": 0,
"name": "string",
"updated_at": "string"
}
]
},
"top_events": [
{
"window_days": 0,
"event": "string",
"count": 0,
"distinct_users": 0,
"recent_24h_count": 0,
"recent_24h_users": 0,
"first_seen_in_window": "string",
"last_seen_in_window": "string"
}
]
}
}
}
Status 404 The team has no project profile yet, and the caller is not the internal scout token (which builds on cache miss). A background build starts, so retry in a minute.

Retrieve signals scout rubrics

Required API key scopes

signal_scout:read

Path parameters

  • id
    string

Response


Example request

GET /api/projects/:project_id/signals/scout/rubrics/: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/scout/rubrics/:id/

Example response

Status 200
RESPONSE
{
"config_id": "d1d31429-d888-4f1c-b9c1-4e842f9bce5b",
"skill_name": "string",
"revision": 0,
"criteria": [
{
"id": "string",
"title": "string",
"description": "string",
"pass_condition": "string",
"applicability": "string",
"enabled": true,
"source": "default"
}
],
"generation": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "queued",
"requested_at": "2019-08-24T14:15:22Z",
"context": "string",
"completed_at": "2019-08-24T14:15:22Z",
"task_id": "736fde4d-9029-4915-8189-01353d6982cb",
"task_run_id": "7f0ef9d7-e7b5-4d52-8247-ce8351652f8b",
"error": "string",
"suggestions": [
{
"id": "string",
"title": "string",
"description": "string",
"pass_condition": "string",
"applicability": "string",
"enabled": true,
"source": "default"
}
],
"summary": "string",
"reference_context": {
"schema_version": 0,
"skill_id": "string",
"skill_name": "string",
"skill_version": 0,
"description": "string",
"instructions": "string",
"instructions_truncated": true,
"report_channel": "none",
"report_disposition_instructions": "string",
"reference_files": [
"string"
],
"reference_files_truncated": true,
"reference_texts": [
{
"path": "string",
"content_type": "string",
"content": "string"
}
],
"reference_limits": {
"omitted_files": 0,
"truncated_files": [
"string"
]
}
}
},
"reference_context": {
"schema_version": 0,
"skill_id": "string",
"skill_name": "string",
"skill_version": 0,
"description": "string",
"instructions": "string",
"instructions_truncated": true,
"report_channel": "none",
"report_disposition_instructions": "string",
"reference_files": [
"string"
],
"reference_files_truncated": true,
"reference_texts": [
{
"path": "string",
"content_type": "string",
"content": "string"
}
],
"reference_limits": {
"omitted_files": 0,
"truncated_files": [
"string"
]
}
},
"reference_generation_id": "11e5f8e6-0022-4fc6-aae5-6cfd0717dc91"
}

Create signals scout rubrics

Required API key scopes

signal_scout:write

Path parameters

  • id
    string

Request parameters

  • context
    string
    Default:

Response


Example request

POST /api/projects/:project_id/signals/scout/rubrics/:id/generate
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/rubrics/:id/generate/\
-d context="string"

Example response

Status 202
RESPONSE
{
"config_id": "d1d31429-d888-4f1c-b9c1-4e842f9bce5b",
"skill_name": "string",
"revision": 0,
"criteria": [
{
"id": "string",
"title": "string",
"description": "string",
"pass_condition": "string",
"applicability": "string",
"enabled": true,
"source": "default"
}
],
"generation": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "queued",
"requested_at": "2019-08-24T14:15:22Z",
"context": "string",
"completed_at": "2019-08-24T14:15:22Z",
"task_id": "736fde4d-9029-4915-8189-01353d6982cb",
"task_run_id": "7f0ef9d7-e7b5-4d52-8247-ce8351652f8b",
"error": "string",
"suggestions": [
{
"id": "string",
"title": "string",
"description": "string",
"pass_condition": "string",
"applicability": "string",
"enabled": true,
"source": "default"
}
],
"summary": "string",
"reference_context": {
"schema_version": 0,
"skill_id": "string",
"skill_name": "string",
"skill_version": 0,
"description": "string",
"instructions": "string",
"instructions_truncated": true,
"report_channel": "none",
"report_disposition_instructions": "string",
"reference_files": [
"string"
],
"reference_files_truncated": true,
"reference_texts": [
{
"path": "string",
"content_type": "string",
"content": "string"
}
],
"reference_limits": {
"omitted_files": 0,
"truncated_files": [
"string"
]
}
}
},
"reference_context": {
"schema_version": 0,
"skill_id": "string",
"skill_name": "string",
"skill_version": 0,
"description": "string",
"instructions": "string",
"instructions_truncated": true,
"report_channel": "none",
"report_disposition_instructions": "string",
"reference_files": [
"string"
],
"reference_files_truncated": true,
"reference_texts": [
{
"path": "string",
"content_type": "string",
"content": "string"
}
],
"reference_limits": {
"omitted_files": 0,
"truncated_files": [
"string"
]
}
},
"reference_generation_id": "11e5f8e6-0022-4fc6-aae5-6cfd0717dc91"
}

List all signals scout runs

Return the most recent SignalScoutRun summaries for this project, newest first. Used by the headless scout to dedupe against work other runs already covered. ILIKE matches on summary. date_from / date_to are a half-open window on created_at (>= date_from, < date_to); pass date_to on subsequent calls to walk past the 100-row cap. Pass emitted=true to see only runs that surfaced at least one finding. Pass skill_name (optionally with skill_version) to scope to a single scout. Results capped at 100.

Required API key scopes

signal_scout:read

Query parameters

  • date_from
    string
  • date_to
    string
  • emitted
    boolean | null
  • limit
    integer
  • skill_name
    string
  • skill_version
    integer
  • text
    string

Example request

GET /api/projects/:project_id/signals/scout/runs
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/

Example response

Status 200 Recent run summaries newest-first.
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
}
}
}

Retrieve signals scout runs

Return the full SignalScoutRun row. Status, timing, and error flow from the linked tasks.TaskRun. Strictly team-scoped — a UUID belonging to another team returns 404.

Required API key scopes

signal_scout:read

Path parameters

  • run_id
    string

Response


Example request

GET /api/projects/:project_id/signals/scout/runs/:run_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/scout/runs/:run_id/

Example response

Status 200 Full run detail.
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 404 Run not found or not visible to this project.

Create signals scout record check

Close the follow-up check this run was dispatched to answer. The run note carries the check id and what to establish; this call is the only thing that records the answer, so a run that investigates and says nothing leaves the check unanswered. The verdict lands on the report as a check_result entry people read in the inbox. failed retires the check, passed re-arms a recurring one, and errored retries it. inconclusive with the awaiting_data reason looks again later, and any other reason ends the check. Send the outcome you actually reached rather than the one that closes the loop. A run may close the check it was dispatched for, or a check on its own scout that is due or waiting on a run.

Required API key scopes

signal_scout_internal:write

Path parameters

  • run_id
    string

Request parameters

  • check_id
    string
  • outcome
  • reason
  • explanation
    string
  • observed_value
    number | null

Response


Example request

POST /api/projects/:project_id/signals/scout/runs/:run_id/check-result
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/:run_id/check-result/\
-d check_id="string",\
-d outcome=undefined,\
-d explanation="string"

Example response

Status 200 Verdict recorded on the report, and the check advanced or retired.
RESPONSE
{
"check_id": "051490c8-9abf-480a-9348-72c146208cd5",
"outcome": "string",
"check_status": "string",
"runs_remaining": 0
}
Status 400 The check does not exist for this project, already finished, waits for its report to resolve, is measured by the coordinator rather than a run, runs on another scout, or is neither waiting on a run nor due.
Status 404 Run not found for this project.

Create signals scout edit

Rewrite a report's title/summary, append a note or fresh evidence, set its suggested reviewers, and/or point it at another repository. Can target ANY of the project's inbox reports, not just scout-authored ones — so the edit is attributed to this scout. Reviewers and repository are how you rescue a report that surfaced routed to no one or against the wrong codebase: each replaces what the report holds and re-runs autostart, so a report that was missing a qualifying reviewer or a repository can open a draft PR. The response carries the repository the report holds after the edit, and the call fails when a repository it named did not land. Set actionability and/or priority (each with its explanation) when new evidence changed your judgment: each replaces the report's decision and re-runs autostart, without changing the report's inbox status. Title/summary edits are best-effort: the pipeline may later re-research them. Set supersedes_implementation alongside a rewrite when the fix changed. Verified automated predecessor PRs close only after the replacement completes with verified open PRs.

Required API key scopes

signal_scout_report:write

Path parameters

  • run_id
    string

Request parameters

  • expected_reviewers
  • reviewer_outcome
    Default: selected
  • reviewer_retry_reason
    string
  • report_id
    string
  • title
    string | null
  • summary
    string | null
  • append_note
    string | null
  • corroboration_only
    boolean
  • append_evidence
    Click to open
    array | null
  • suggested_reviewers
    Click to open
    array
  • repository
    string | null
  • charts
    Click to open
    array | null
  • metrics
    Click to open
    array | null
  • suggested_prompts
    array | null
  • links
    Click to open
    array
  • supersedes_implementation
    boolean
  • actionability
  • actionability_explanation
    string | null
  • already_addressed
    boolean | null
  • priority
  • priority_explanation
    string | null

Response


Example request

POST /api/projects/:project_id/signals/scout/runs/:run_id/edit-report
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/:run_id/edit-report/\
-d report_id="string"

Example response

Status 200 Report edited.
RESPONSE
{
"report_id": "string",
"updated_fields": [
"string"
],
"note_appended": true,
"evidence_appended": 0,
"links_appended": 0,
"reviewers_set": true,
"repository_set": true,
"repository": "string",
"charts_set": 0,
"metrics_set": 0,
"suggested_prompts_set": 0,
"is_content_revision": true,
"content_revision_count": 0,
"supersedes_implementation": true,
"decision_fields_set": [
"string"
],
"warnings": [
{
"field": "string",
"message": "string"
}
],
"corroboration_collapsed": true
}
Status 400 Nothing to edit, empty note, or report not found for this project.
Status 404 Run not found for this project.

Retrieve signals scout runs

Return the findings a SignalScoutRun emitted to the inbox, newest first — one row per emit with its description (the finding text as surfaced), severity, and the deterministic source_id that joins back to the underlying signal. Lets a team and its agents see what a run surfaced without parsing emitted_finding_ids or scanning the signal store. Strictly team-scoped — a run UUID belonging to another team returns 404.

Required API key scopes

signal_scout:read

Path parameters

  • run_id
    string

Example request

GET /api/projects/:project_id/signals/scout/runs/:run_id/emissions
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/:run_id/emissions/

Example response

Status 200 Findings this run emitted to the inbox, 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"
}
Status 404 Run not found or not visible to this project.

Retrieve signals scout runs emission

Best-effort reverse of the report -> signals link. For each finding the run emitted, resolve the inbox SignalReport (if any) its underlying signal grouped into by walking the deterministic source_id back through the signal store. report is null when the finding hasn't grouped into a report yet, was de-duplicated away, or its signal was deleted. Lets the scout UI surface which inbox report a finding contributed to — the reverse of the report's evidence list. Strictly team-scoped — a run UUID belonging to another team returns 404.

Required API key scopes

signal_scout:readtask:read

Path parameters

  • run_id
    string

Example request

GET /api/projects/:project_id/signals/scout/runs/:run_id/emissions/reports
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/:run_id/emissions/reports/

Example response

Status 200 Per-finding inbox report links for this run, newest finding first.
RESPONSE
{
"finding_id": "string",
"source_id": "string",
"report": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"title": "string",
"status": "string"
}
}
Status 404 Run not found or not visible to this project.

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:write

Path parameters

  • run_id
    string

Request parameters

  • title
    string
  • summary
    string
  • evidence
    Click to open
    array
  • actionability_explanation
    string
  • actionability
  • already_addressed
    boolean
    Default: false
  • repository
    string | null
  • priority
  • priority_explanation
    string | null
  • suggested_reviewers
    Click to open
    array
  • charts
    Click to open
    array
  • metrics
    Click to open
    array
  • suggested_prompts
    array
  • links
    Click to open
    array
  • idempotency_key
    string | null

Response


Example request

POST /api/projects/:project_id/signals/scout/runs/:run_id/emit-report
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/:run_id/emit-report/\
-d title="string",\
-d summary="string",\
-d evidence="array",\
-d actionability_explanation="string",\
-d actionability=undefined

Example response

Status 200 Report authored (READY/PENDING_INPUT/suppressed), or skipped by a preflight gate.
RESPONSE
{
"report_id": "string",
"report_status": "string",
"emitted": true,
"skipped_reason": "string",
"safety_explanation": "string",
"remediation": "string",
"idempotent_replay": true
}
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:write

Path parameters

  • run_id
    string

Request parameters

  • description
    string
  • evidence
    Click to open
    array
  • hypothesis
    string | null
  • severity
  • dedupe_keys
    array
  • tags
    array
  • time_range
  • mcp_trace_id
    string | null
  • finding_id
    string | null

Response


Example request

POST /api/projects/:project_id/signals/scout/runs/:run_id/emit-signal
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/:run_id/emit-signal/\
-d description="string",\
-d evidence="array"

Example response

Status 200 Finding emitted, or skipped by a preflight gate.
RESPONSE
{
"finding_id": "string",
"emitted": true,
"skipped_reason": "string",
"remediation": "string"
}
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:write

Path parameters

  • run_id
    string

Request parameters

  • url
    string
  • form_factor
    Default: desktop

Response


Example request

POST /api/projects/:project_id/signals/scout/runs/:run_id/lighthouse-audit
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/:run_id/lighthouse-audit/\
-d url="string"

Example response

Status 200 The audit ran and the reduced report is attached.
RESPONSE
{
"requested_url": "string",
"final_url": "string",
"form_factor": "string",
"lighthouse_version": "string",
"performance_score": 0,
"metrics": {
"property1": 0.1,
"property2": 0.1
},
"lcp_element": {
"selector": "string",
"snippet": "string",
"node_label": "string"
},
"lcp_phases": [
{
"phase": "string",
"timing_ms": 0,
"percent": "string"
}
],
"lcp_checks_failed": [
{
"audit_id": "string",
"title": "string",
"savings_ms": 0
}
],
"opportunities": [
{
"audit_id": "string",
"title": "string",
"savings_ms": 0
}
],
"audits_remaining": 0
}
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:write

Path parameters

  • run_id
    string

Request parameters

  • records
    Click to open
    array

Response


Example request

POST /api/projects/:project_id/signals/scout/runs/:run_id/record-output
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/:run_id/record-output/\
-d records="array"

Example response

Status 200 All records validated against the configured schema and recorded as events.
RESPONSE
{
"recorded_count": 0,
"record_ids": [
"497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
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:write

Path parameters

  • run_id
    string

Request parameters

  • check_id
    string

Response


Example request

POST /api/projects/:project_id/signals/scout/runs/:run_id/report-check-cancel
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/:run_id/report-check-cancel/\
-d check_id="string"

Example response

Status 200 Check cancelled.
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 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:write

Path parameters

  • run_id
    string

Request parameters

  • title
    string
  • rationale
    string
  • kind
  • config
  • next_run_at
    string
  • run_interval_minutes
    integer | null
  • runs_remaining
    integer
  • expires_at
    string
  • report_id
    string

Response


Example request

POST /api/projects/:project_id/signals/scout/runs/:run_id/report-check-create
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/:run_id/report-check-create/\
-d title="string",\
-d kind=undefined,\
-d config=undefined,\
-d report_id="string"

Example response

Status 200 Check written on the report.
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, 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:write

Path parameters

  • run_id
    string

Query parameters

  • report_id
    string

Example request

GET /api/projects/:project_id/signals/scout/runs/:run_id/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/:run_id/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.
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:read

Query parameters

  • window_days
    integer

Response


Example request

GET /api/projects/:project_id/signals/scout/runs/costs
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/costs/

Example response

Status 200 Model spend and output per scout over the window.
RESPONSE
{
"window_days": 0,
"scouts": [
{
"skill_name": "string",
"spend_usd": 0.1,
"run_count": 0,
"priced_run_count": 0,
"reports_touched": 0
}
],
"available": true
}
Status 403 Caller is not PostHog staff.