Skip to content

Saved Searches API

A saved search is a named report query owned by your organization. In the app it drives the Reports page’s condition builder and the channel wizard’s Pre-Filter step; over the API it is a resource you create, list, rename, and delete, and a query a channel can reference as its pre-filter. A channel that references a search reads its query live, so editing the search changes the routing of every channel that uses it. This page documents the saved search resource and the query it stores. Everything here is also in the Platform API reference.

Requests are JSON:API over HTTPS against https://api.disclosebot.io/v1 with a service account bearer token, as described in the API quickstart.

Terminal window
curl https://api.disclosebot.io/v1/saved_searches \
-H "Authorization: Bearer disclosebot_svc_xxxxxxxx"
AttributeTypeMeaning
namestringUnique within the organization, up to 80 characters.
queryobjectThe query in two parts (below).
conditionsarrayThe query as conditions, one per chip the app shows (below).
created_by_iduuid, nullableThe user who saved it from the app; null when created over the API.
created_at, updated_atdate-timeTimestamps.

Relationships: organization and channels, the channels whose pre-filter references the search.

A query has two parts, both optional but at least one condition between them:

{
"q": { "title_cont": "sql", "submitted_through_id_in": ["9b1e…"], "created_at_gteq": "2026-09-01" },
"results": { "severity": { "eq": "critical" }, "cvss": { "gteq": "9" } }
}

Ransack predicates, keyed <attribute>_<predicate>:

AttributePredicatesExample
title_or_description, title, descriptioncont, not_cont, eq, not_eq{ "title_cont": "sql" }
submitted_through_id (the brief, by id)in, not_in (an array of brief ids){ "submitted_through_id_in": ["9b1e…"] }
created_at (received at)gteq, lteq{ "created_at_gteq": "2026-09-01" }
encryptedeq{ "encrypted_eq": "false" }

A map of result key to predicate to value. A result is a value written against a report by a triage step, an integration, or the API; each report holds at most one value per key.

KeyValuesWritten by
severitycritical, high, medium, lowThe severity triage step.
validityvalid, invalidThe validity triage step.
scopein_scope, out_of_scopeThe scope triage step.
duplicatetrue, falseThe duplicate triage step.
summarytextThe summary triage step.
any other keytextAn integration, for example a local app posting cvss or jira_status.

Predicates are eq, not_eq, cont, not_cont, gteq, and lteq; values are compared as text. Any key that exists in your organization’s results is accepted; the app’s condition builder lists the triage keys and every key your results hold. not_eq and not_cont match reports that have no result with that value, including reports without the key.

Conditions across both parts are combined with AND. Unknown q keys, unknown predicates, and malformed result keys are dropped; a query with nothing left is refused with 422.

conditions is the same query described one condition at a time, in the order the app shows them:

{ "part": "q", "attribute": "submitted_through_id", "label": "brief", "predicate": "in", "predicate_label": "is", "value": ["9b1e…"], "value_label": "Mobile app" }

value_label is the value as the app shows it: the brief names for a brief condition, the value itself otherwise.

Every condition applies when the query filters the Reports page or GET /v1/reports. A channel’s pre-filter runs at dispatch, before any triage step, so it ignores results conditions there (the app shows them struck through in the channel wizard). A brief condition decides which briefs’ reports the channel takes.

GET /v1/reports accepts the same two parts as request parameters: q[...] for the report’s fields and results[<key>][<predicate>] for results.

Terminal window
curl "https://api.disclosebot.io/v1/reports?q[title_cont]=sql&q[submitted_through_id_in][]=9b1e…&results[severity][eq]=critical" \
-H "Authorization: Bearer disclosebot_svc_xxxxxxxx"

GET /v1/saved_searches

Ordered by name. Filters: q[name_cont], q[created_at_gteq], and q[s] to sort (for example created_at desc). Pagination: page[number] and page[size] (at most 30).

Terminal window
curl "https://api.disclosebot.io/v1/saved_searches?q[name_cont]=critical&page[size]=20" \
-H "Authorization: Bearer disclosebot_svc_xxxxxxxx"
200
{
"data": [
{
"id": "7f2c…",
"type": "saved_search",
"attributes": {
"name": "Critical SQL",
"query": { "q": { "title_cont": "sql" }, "results": { "severity": { "eq": "critical" } } },
"conditions": [
{ "part": "q", "attribute": "title", "label": "title", "predicate": "cont", "predicate_label": "contains", "value": "sql", "value_label": "sql" },
{ "part": "results", "attribute": "severity", "label": "severity", "predicate": "eq", "predicate_label": "is", "value": "critical", "value_label": "critical" }
],
"created_by_id": null,
"created_at": "2026-09-09T20:08:19.000Z",
"updated_at": "2026-09-09T20:08:19.000Z"
},
"relationships": {
"organization": { "data": { "id": "6d4d…", "type": "organization" } }
}
}
],
"links": { "self": "…", "first": "…", "last": "…" }
}

POST /v1/saved_searches

Terminal window
curl -X POST https://api.disclosebot.io/v1/saved_searches \
-H "Authorization: Bearer disclosebot_svc_xxxxxxxx" \
-H "Content-Type: application/vnd.api+json" \
-d '{
"data": {
"type": "saved_search",
"attributes": {
"name": "Critical SQL",
"query": { "q": { "title_cont": "sql" }, "results": { "severity": { "eq": "critical" } } }
}
}
}'

201 with the saved search. A blank or taken name, or a query with no valid condition, answers 422:

422
{
"errors": [
{ "status": "422", "title": "Unprocessable Entity", "detail": "Name is already used by another saved search", "source": { "pointer": "/data/attributes/name" } },
{ "status": "422", "title": "Unprocessable Entity", "detail": "Query needs at least one condition", "source": { "pointer": "/data/attributes/query" } }
]
}

GET /v1/saved_searches/{id}

200 with the saved search; 404 for an unknown id or another organization’s search.

PATCH /v1/saved_searches/{id}

Send name, query, or both. Every channel that references the search reads the new query from the next report on.

Terminal window
curl -X PATCH https://api.disclosebot.io/v1/saved_searches/7f2c… \
-H "Authorization: Bearer disclosebot_svc_xxxxxxxx" \
-H "Content-Type: application/vnd.api+json" \
-d '{ "data": { "type": "saved_search", "attributes": { "name": "Critical SQL injections" } } }'

DELETE /v1/saved_searches/{id}

204. While a channel references the search the request answers 422 with a detail naming the channels, for example Used by channels: Critical SQL, Paging; a channel draft that references it loses the reference.

A channel references a search through prefilter_saved_search_id and reads its query live; it stores no copy. Create the channel with the id and no prefilter_query, as documented on the Channels API page:

Terminal window
curl -X POST https://api.disclosebot.io/v1/channels \
-H "Authorization: Bearer disclosebot_svc_xxxxxxxx" \
-H "Content-Type: application/vnd.api+json" \
-d '{
"data": {
"type": "channel",
"attributes": {
"name": "Critical SQL",
"prefilter_saved_search_id": "7f2c…",
"definition": { "steps": { "severity": { "kind": "triage", "task": "severity", "engine": "ai", "scale": "standard" } } }
}
}
}'

The channel evaluates the search’s q conditions at dispatch and ignores its severity result condition there. Change the search and the channel follows. Pass include=prefilter_saved_search on channel requests to embed the search.

Errors are a JSON:API errors array of objects with status, title, detail, and source.pointer.

StatusWhen
401Missing or invalid bearer token.
404Unknown id, or a saved search of another organization.
422A blank or taken name, a query with no valid condition, or deleting a search a channel references.
  • page[number] and page[size] (default and maximum 30); responses carry meta.pagination (current, records = the total, and first, prev, next, last when they exist) and links for the same pages. See Pagination.
  • q[...] filters use ransack predicates on name and created_at; q[s] sorts.