Search

Run a web search via Brave and optionally fetch each result page as markdown in the same call. The discovery primitive in the Gyrence pipeline — Search finds, Fetch reads one, Gyre walks many.

Use this when

  • You need discovery → cleaned markdown in a single call (RAG, agent tool, research pipeline).
  • You're running site-scoped queries (site:sec.gov 8-K, site:fda.gov recall) and want the bodies, not just the SERP.
  • You want recency control beyond Brave's defaults (tbs: "y2", "y3", "y5").
  • You need per-URL credit attribution — results[].fetch.via tells you which result paid HTTP vs browser tier.
MethodPOST
Path/api/v1/search
AuthBearer
Credits2 base + (1 per http sub-fetch, 3 per browser sub-fetch) when fetch: true

Request

ParameterTypeDescription
query
required
stringSearch query. Min length 1.
limitnumber
default: 10
Max results. Range 1..20.
fetchboolean
default: false
Fetch each result URL inline and attach a fetch field to each result.
fetchFormats("markdown" | "links" | "html")[]
default: ["markdown"]
Which fields to populate on each inline fetch. html is accepted but not currently emitted.
tbs"d" | "w" | "m" | "y" | "y2" | "y3" | "y5"Time window: past day / week / month / 1–5 years. y2/y3/y5 post-filter Brave's 1-year cap.

Example body

{
  "query": "site:sec.gov 8-K corporate action",
  "limit": 10,
  "fetch": true,
  "fetchFormats": ["markdown"],
  "tbs": "m"
}

Response

FieldTypeDescription
querystringEcho of the input query.
totalResultsnumberCount of returned results.
via"http" | "browser" | "n/a"Dominant sub-fetch tier across results, or the literal "n/a" when fetch: false.
results[]SearchResult[]Ordered results, rank 1..N.
results[].ranknumber1-based rank after filtering.
results[].titlestringPage title from Brave.
results[].urlstringResult URL.
results[].snippetstringBrave-provided snippet.
results[].descriptionstringAlias of snippet.
results[].agestring?Relative age (e.g. "3 months ago") when Brave provides it.
results[].page_agestring?ISO timestamp when Brave provides it.
results[].fetchobject?Present only when fetch: true. See below.

results[].fetch on success

FieldTypeDescription
oktrue
markdownstringEmpty string when "markdown" not in fetchFormats.
linksstring[]Empty array when "links" not in fetchFormats.
statusCodenumberOrigin HTTP status.
via"http" | "browser"Tier that produced this sub-fetch. Drives credit cost (1 vs 3).

results[].fetch on failure

FieldTypeDescription
okfalse
errorstringFailure reason (timeout, SSRF block, upstream error).
via"http" | "browser"Always "http" on failure.

Example response

{
  "ok": true,
  "data": {
    "query": "site:sec.gov 8-K corporate action",
    "totalResults": 2,
    "via": "http",
    "results": [
      {
        "rank": 1,
        "title": "Form 8-K — Example Corp",
        "url": "https://www.sec.gov/...",
        "snippet": "Item 8.01 Other Events...",
        "description": "Item 8.01 Other Events...",
        "page_age": "2026-05-12T14:00:00Z",
        "fetch": {
          "ok": true,
          "markdown": "# Form 8-K\n\n...",
          "links": [],
          "statusCode": 200,
          "via": "http"
        }
      }
    ]
  }
}

Example

curl -X POST https://www.gyrence.com/api/v1/search \
  -H "Authorization: Bearer $GYRENCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"site:sec.gov 8-K","limit":10,"fetch":true,"tbs":"m"}'

Errors

CodeHTTPMeaning
bad_request400query missing/empty, or any field fails validation.
unauthorized401Missing, malformed, or revoked Authorization header.
credits_exhausted402Workspace balance below request cost.
timeout408Request exceeded the 25-second hard deadline.
rate_limited429Per-workspace rate limit, or Brave returned 429.
upstream_error502Brave returned 5xx or unparseable response.
{ "ok": false, "error": "brave returned 503", "code": "upstream_error" }
Credits

Total = 2 base + Σ sub-fetches, where each sub-fetch is 1 (HTTP tier) or 3 (browser tier). With fetch: false, it's a flat 2. Read results[].fetch.via to attribute cost per URL. Failed sub-fetches are not charged.

Coverage & known limits
  • Max 20 results per call. For deeper coverage, paginate by refining query or tbs.
  • Sub-fetches are capped at 10s each (independent of the 25s request deadline). Slow origins surface as { ok: false, error: "timeout" } on that result; the envelope still succeeds.
  • SSRF sub-fetches to private / loopback / link-local hosts return { ok: false } per result — they don't fail the whole call.
  • tbs ≥ y2 uses Brave's freshness=py and post-filters by parsed date. Results without a parseable page_age / age are kept (not dropped).
  • html in fetchFormats is accepted today but not emitted. Use /fetch directly when you need the cleaned HTML field.

Notes

  • Inline-fetch concurrency. Up to 5 sub-fetches run in parallel; each capped at 10s independent of the 25s request deadline.
  • Result ordering. Results are returned in Brave's relevance order, then filtered (e.g. by tbs), then re-numbered with rank: 1..N.
  • Per-URL fetcher. Each sub-fetch uses the same two-tier pipeline as /fetch (HTTP → browser escalation, SEC.gov fast path, block-page detection).
Try it

Run Search from the console at /app/search — toggle fetch, set a tbs window, and inspect per-result tier.