Findings export
Endpoint
GET /v1/projects/{projectId}/findings/export?profile=security|bugs|hygiene|pipeline|all
Needs a session or bearer token that can read the project. The UI calls this from Findings → Export JSON and downloads thespider-findings-{projectSlug}.json.
Query parameters
| Parameter | Type | Description |
|---|---|---|
profile | string | security, bugs, hygiene, pipeline, or explicit aggregate all. Omission uses the project default. |
campaignId | integer | Include live findings sighted in this campaign. An opaque id, see Identifiers. Present but malformed is 400 invalid_campaign_id; unknown or foreign is 404 campaign_not_found. |
status | string | One or more persisted finding statuses, comma-separated. Also accepts the open, actionable, and closed aliases. |
severity | string | One or more canonical severity or impact values, comma-separated. Also accepts the reserved token none. |
bucket | string | Match the display bucket code: the bucket the finding's classification decided (the moved-to bucket, else the discovery bucket), with the profile fallback described under Finding fields. |
category | string | Match findings with a report in this category. |
branch | string | Match findings sighted on this branch. |
q | string | Case-insensitive title, fingerprint, or display-bucket search. |
has_link | true | false | Only findings that have (or have no) linked merge request, pull request or issue. |
basis | string | Evidence basis, comma-separated: demonstrated, speculative (a hardening note), unclassified. demonstrated,unclassified hides hardening notes. |
bucket_review | true | false | Only findings whose bucket is (or is not) flagged for review. |
sort | string | priority (default), updated_desc, updated_asc, or title. |
Filters combine with AND. The endpoint ignores list pagination fields and returns the complete filtered set up to the export safety limit.
Status and severity filters
Same rules as GET /v1/projects/{projectId}/findings. Comma-separated lists, trimmed, de-duplicated.
GET /v1/projects/{projectId}/findings/export?status=open&severity=critical,high
| Alias | Meaning |
|---|---|
open | Everything except fixed and false_positive |
actionable | Everything except wont_fix, false_positive, accepted, and fixed (what the PR gate still blocks on) |
closed | Exactly fixed or false_positive |
accepted and wont_fix stay open but drop out of actionable. Aliases and literal statuses mix (status=actionable,accepted) as a union.
severity=none matches findings with no severity. severity=critical,high excludes them; add none to include them. Unknown tokens match literally and usually return nothing.
basis and bucket_review reject anything outside their values with 400 invalid_basis /
400 invalid_bucket_review, like has_link.
filters in the response echoes the raw query strings ("open", "critical,high"), not the expanded set, including basis and bucket_review when sent. That echo is part of schema version 1. A client can tell a server that predates a filter by its missing echo.
Response
{
"schemaVersion": 1,
"exportedAt": "2026-07-22T12:00:00.000Z",
"project": {
"id": 5361849201747,
"slug": "payments-api",
"name": "Payments API",
"default_profile": "security"
},
"profile": "security",
"filters": {
"campaignId": "3318402957264",
"severity": "high"
},
"mergedFindingsIncluded": false,
"total": 1,
"findings": [
{
"id": 2417903825611,
"canonical_title": "Authorization missing on refund",
"canonical_severity": "high",
"severity_source": "reports",
"severity_report_id": 7391846205518,
"severity_model_code": "anthropic/claude-sonnet-5",
"severity_reason": "highest of 1 report: high (report #7391846205518, anthropic/claude-sonnet-5); distribution high 1",
"status": "new",
"fingerprint": "…",
"bucket_id": null,
"canonical_bucket_id": null,
"bucket_code": "auth",
"bucket_moved": false,
"discovered_bucket_code": "auth",
"canonical_category": "authz",
"category_source": "reports",
"bucket_review": null,
"suggested_category": null,
"evidence_basis": null,
"evidence_basis_source": null,
"evidence_basis_reason": null,
"links": [
{ "id": 4120958831204, "url": "https://gitlab.com/acme/api/-/merge_requests/128", "provider": "gitlab",
"kind": "merge_request", "ref": "!128", "host": "gitlab.com", "actor_user_id": null,
"created_at": "2026-10-03T10:00:00Z" }
],
"reports": [
{
"id": 7391846205518,
"model_code": "anthropic/claude-sonnet-5",
"effort": null,
"pass_code": "http",
"severity": "High",
"normalized_severity": "high",
"categories": ["authz"],
"cwes": ["CWE-862"],
"files": [
{ "path": "src/refunds.ts", "line": 88 }
],
"evidence": "…",
"impact": "…",
"recommendation": "…",
"evidence_basis": null,
"evidence_basis_source": null,
"recommendation_issues": []
}
]
}
]
}
The export includes canonical project-global finding status and only report/sighting provenance that
matches the selected profile. bucket_code is the display bucket (rule below) and
discovered_bucket_code is provenance: the bucket of the slice that first found the finding. Model-derived
strings remain plain JSON strings.
Finding fields
Each finding is the full finding row plus campaign_name, bucket_code, bucket_name, bucket_moved,
discovered_bucket_code, links and reports. The canonical quality columns:
| Field | Meaning |
|---|---|
canonical_severity | critical, high, medium, low, info, or null: the highest severity any supporting report assigned, normalized. A merge never lowers it. |
severity_source | reports, manual (set by a person; sticky) or legacy (imported or pre-upgrade, kept as a floor). |
severity_report_id | The witness report's id, or null once that report was removed. |
severity_model_code, severity_reason | The witness's model and why the value won. Both survive the report's removal. |
bucket_id | Always null. The key is kept so schema version 1 readers keep working; identify buckets by bucket_code. |
canonical_bucket_id | Always null, like bucket_id. Use bucket_moved to tell whether the finding was moved, and bucket_code for where it is displayed. |
bucket_code, bucket_name | The display bucket: the bucket the finding's classification decided, which is the moved-to bucket when the finding was moved, else the discovery bucket. profile=all always shows it. Under a selected profile it is shown when that profile's config version owns it. Otherwise (another profile's view, or a legacy row whose bucket was never computed) it falls back to the bucket of the finding's latest report in that profile. |
bucket_moved | true when the display bucket is the moved-to bucket, else false. Missing on older servers. |
discovered_bucket_code | Provenance: the discovery bucket's code (the slice that found it). It is not the display bucket. |
canonical_category, category_source | The category most supporting reports used, and reports / manual / legacy. |
bucket_review, suggested_category | Why the bucket is flagged for review (else null) and the suggested category. |
evidence_basis, evidence_basis_source, evidence_basis_reason | demonstrated / speculative / null; model / heuristic / manual; and why. |
Each report adds normalized_severity, its own evidence_basis and evidence_basis_source, and
recommendation_issues ([{ code, message, excerpt }], today only sqlite3_positional_bind). These
are computed when the export is read and never stored; the report's raw severity and text are
exactly what the model emitted.
Each report names the participant that produced it: model_code and effort, the effort its slice
was planned at (null for the model's default participant). The effort is what the runner was bound
to, not something TheSpider verified. Servers before effort participants omit effort.
A report also carries the extra fields of the profile whose campaign produced it, the same keys finding
detail returns. Each is a string or null. Security reports have none.
| Report profile | Fields |
|---|---|
bugs | reproduction_steps, expected_behavior, actual_behavior, root_cause, test_suggestion |
hygiene | usage_evidence, removal_risk |
pipeline | cost_driver, verification_plan |
With profile=all, one finding can hold reports from several profiles, and each report has only its
own profile's keys. Servers before these fields were exported omit them.
Identifiers
Every id, *_id and actor_user_id in the export is an opaque positive integer below 2^53. Ids are unordered:
never sort or compare them, and never infer how many findings, reports or campaigns exist from them. The
findings array is already in the order selected by sort. Ids belonging to rows created before opaque ids were
introduced keep their old values. See Identifiers.
Identity and merge behavior
The export includes live keeper findings only. Soft-merged tombstones are excluded, and mergedFindingsIncluded is always false in schema version 1. Reports absorbed by a keeper appear under that keeper.
Campaign filtering uses finding sightings. It does not filter by the campaign where a finding was first created.
Size behavior
A filtered export can contain at most 20,000 findings. A larger result returns:
413 export_too_large
Narrow the request with campaignId, severity, status, bucket, category, branch, or search filters. The endpoint returns one JSON document and does not paginate or create an asynchronous export job.
Errors
| Status | Error | Meaning |
|---|---|---|
400 | invalid_project_id | The path identifier is not a positive integer. |
400 | invalid_campaign_id | campaignId is present but is not a valid campaign id. |
400 | invalid_since, invalid_has_link, invalid_basis, invalid_bucket_review | A filter value is present but not one the filter accepts. A since campaign id that is unknown or belongs to another organization is invalid_since. |
401 | authentication error | No valid session or bearer token. |
404 | project_not_found | The project does not exist in the authenticated tenant or token scope. |
404 | campaign_not_found | campaignId names no campaign in this project (an unknown id and another organization's id answer alike). |
413 | export_too_large | More than 20,000 findings match. |
429 | rate_limited | Request budget exhausted. |
Use the triage guide for finding lifecycle behavior and the concepts glossary for keeper, merge, and sighting definitions.
Runner contract
Exact process, marker, output, timeout, and result-submission behavior for model runners
Agent CLI contracts
Stable JSON envelope, exit codes, and markdown packet format for thespider-agent findings, wait, progress, refine, campaigns cancel, models disable/enable, and the status, check-models and log list diagnostics