---
title: Search Streams in depth
category: find
summary: Every way to search the Stream inventory, what each parameter does, how results are ranked and labelled, and the query patterns that answer real user questions.
order: 4
updated: 2026-10-13
next: stream-schema-and-filters
related: choose-a-stream, stream-schema-and-filters
reference: /docs/mcp, /openapi.json
---

Stream search helps an agent find Streams and published Beacon recipes. Discovery is free and keyless. Stream rows stay in `results`; matching recipes appear in `beacon_results`. A catalog Beacon is a public recipe, not an account Beacon. When you propose one for saving, keep every filter in its returned `scope`. This preserves its source, media, topic, and entity filters.

`scope` contains the filters and exclusions that the Beacon applies.

`included_coverage` lists the Streams and source channels that the recipe covers. It is descriptive; do not treat it as another filter.

A Stream-scoped Beacon stays limited by its Stream selection. The coverage list does not restrict it to specific source channels.

`definition` stores the DB-owned dynamic recipe used to resolve the scope each day.

## When to use this

Use search when your user asks what exists. Examples: "do you have Bloomberg?" or "what business podcasts can you read?"

Search returns **Streams** in `results` and matching public Beacon recipes in `beacon_results`. It never returns content.

To read content, scope a pull with Stream IDs from `results`. For a Beacon, preserve its full `scope` when proposing it through the existing configure/save flow.

Pick the discovery tool that fits the question:

| Tool | Best for | Notes |
| --- | --- | --- |
| `synorb-stream-search` (MCP) | A user's natural-language ask: a source name, a topic, a format, or a published Beacon | Ranked Stream results plus a separate `beacon_results` array, free |
| `POST /streams/search` (REST) | The same ranked search from backend code | Stream results and pagination stay unchanged; published recipes are returned in `data.beacon_results` |
| `synorb-catalog` (MCP) | Browsing by facets and pages | Returns Stream match reasons, volume indicators and facets, plus matching public recipes in `beacon_results` |
| `GET /streams` (REST) | Exact, filterable catalog listing | Exact filters only. There is **no** free-text `search=` here |

## Smallest working call

```mcp
synorb-stream-search
{"query": "do you have Bloomberg?", "page_size": 5}
```

```bash
curl -sS -X POST https://api.synorb.com/streams/search \
  -H "Authorization: Bearer $SYNORB_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "do you have Bloomberg?", "page_size": 5}'
```

### Parameters of `synorb-stream-search`

| Parameter | What it does |
| --- | --- |
| `query` | Free-text intent. Typos and synonyms are handled. |
| `home_domain`, `home_domains` | Restrict to one or more of the 12 canonical domains. `["all"]` or omitted means full scope. |
| `cross_domains`, `cross_domain_logic` | Up to three secondary domains, combined with `or` (broader) or `and` (intersection). |
| `media_format` | `audio`, `video`, `social`, `text`, `data` or `regulatory`. Synonyms such as "podcasts" map to `audio`. |
| `company_kind` | `public` or `private`. Synorb derives it from organization Tags. |
| `organization_subtype` | An exact organization subtype, for example `public_company` or `private_company`. Must not be blank. |
| `ticker`, `isin` | Exact, case-insensitive. The reliable path for tickers that free text drops, such as `ON`, `IT` or `ALL`. |
| `surface` | A canonical Surface id such as `public_companies`. Synorb returns only Streams whose curated Surfaces include it. An unknown value returns an error listing the valid ids. |
| `page`, `page_size` | Zero-based page. `page_size` defaults to 10, and is silently capped at 200. |
| `include_details` | Adds a short description of each source channel so you can start a pull at once. |
| `verbosity` | `minimal` (default), `standard` (adds workflow scaffolding) or `verbose`. |
| `inventory_scope` | Only `live_public_active_mcp` is honored today. Leave it alone. |

`POST /streams/search` also accepts `surface`, although `openapi.json` does not list it yet. `GET /streams?surface=...` lists it in the schema.

A request that clearly means "all of them", such as "all podcast Streams", raises the page size to 200. If the full set fits, one page returns all of it.

## What you get back

The REST API and the MCP tool return different rows. REST (`POST /streams/search`) wraps the result in the usual envelope under `data`:

```json response
{
  "data": {
    "results": [
      {
        "stream_id": "...",
        "name": "...",
        "display_name": "...",
        "description": "...",
        "home_domain": "economics-business-work",
        "media_formats": ["text"],
        "company_kind": "public",
        "source_url": "https://...",
        "score": 0.03,
        "match_basis": "name",
        "surface_ids": ["public_companies"]
      }
    ],
    "match_strength": "strong",
    "identity_coverage": 1.0,
    "pagination": {"page": 0, "page_size": 5, "returned_count": 1, "total_count": 1, "has_more": false},
    "coverage": {"status": "ok"},
    "candidate_projection": {},
    "diagnostics": {}
  }
}
```

- **`stream_id`** is a 17-digit identifier. Keep it a string. Pass it as `stream_ids` to the Manifest query.
- **`score`** is the fused rank score. Sort by the order returned, not by comparing scores across queries.
- **`surface_ids`** lists the curated Surfaces the Stream belongs to. It is omitted when the catalog has none for that Stream.
- **`match_strength`** is `strong`, `weak` or `none`. Read it before you use the top row. See "Check whether Synorb covers it" below.
- **`match_basis`** on each row is `name`, `identity`, `description_mention` or `shared_term`. Only `name` and `identity` mean the Stream is the thing you asked for.
- **`matched_on`** on a row lists where your words were found: `name`, `ticker`, `source_channel` or `surface`, each with the matching `value`. It is absent when the row matched only through its description or loose terms.
- **`lead`** says what the search is about. `kind` is `stream`, `beacon`, `surface` or `streams`. Lead with it. It names the `stream_ids`, `beacon_id` or `surface_ids` to match.
- **`lead.totals`** counts the whole scope from the same index: Streams, source channels, Manifests and Claims for 7 days, Beacons, and related Streams. A count the index does not hold is `null`. `too_broad` is true when the scope has too many Streams to list.
- **`coverage.status`** is `ok`, `degraded`, `no_coverage` or `impossible`. On `degraded` an empty list does not prove the Stream is missing.
- REST rows carry no `freshness_state`, no `manifests_last7d` and no `match_reasons`. To check that a Stream is publishing, call `GET /streams/{stream_id}` for `freshness_state`, or `POST /manifests/query` with `mode: "count"` (free) for that `stream_id`. `include_details` adds `source_channels`, `source_classes`, `evidence_types` and a `ranking` breakdown.

The MCP tool `synorb-stream-search` returns `results` with `id` (not `stream_id`), `name`, `description`, `home_domain`, `media_formats`, `source_url` and a `security` block (`ticker`, `isin`, `figi`) for company Streams. It adds `manifests_last7d`, `manifests_last30d`, `last_manifest_at` and `match_reasons` only when the catalog row carries them, so treat them as optional. It never returns `freshness_state`. `next_call` is a ready-made suggestion for the next call. Use `match_reasons`, when present, to avoid presenting a loose match as an exact one.

Ranking favors exact identity, then multi-token relevance, then recent volume. It demotes a single generic-token match on an inactive Stream. Ranking leans on names and tokens, so lead with the source name, the company name or the ticker. Do not lead with a description. "Apple" or `AAPL` is a safer query than "the iPhone maker".

### Check whether Synorb covers it

Search always returns the closest rows it has, even when the catalog holds nothing on your topic. Do not read a returned row as proof of coverage. Read `match_strength` first.

| `match_strength` | Meaning | What to say |
| --- | --- | --- |
| `strong` | One of the top five rows is named for your query, or the query's words appear in the row's own name or URL | Use the row. Check `match_basis` to see which rows qualify. |
| `weak` | Rows exist, but they only mention your terms in descriptions or source channel text | "Synorb may not cover X." Offer the closest rows as related, not as X. |
| `none` | No row matched | "Synorb may not cover X." Do not guess a Stream. |

`identity_coverage` is the best share of your query words found in a top-five row's own name, ticker or URL. A `match_note` in plain English accompanies every `weak` and `none` answer. It also appears when a short all-caps word such as `OCC` was read as a stock ticker. In that case search again by the full name, for example "Office of the Comptroller of the Currency".

On `weak` or `none`, tell your user Synorb may not cover the source. Do not present the top result as the answer. These fields describe the match and never change the order of results. A degraded `coverage.status` returns no `match_strength`, because an empty list then proves nothing.

### Patterns that answer real questions

| Your user asks | Call |
| --- | --- |
| "Do you have Bloomberg?" | `query: "Bloomberg"`, then check the `name` that came back |
| "What business podcasts?" | `media_format: "audio"` plus `home_domains: ["economics-business-work"]` |
| "All central banks" | `surface: "central_banks"` |
| "Streams for NVDA" | `ticker: ["NVDA"]` |
| "Public companies in AI" | `company_kind: "public"` plus `query: "AI"` |
| "Sports podcasts" | The `home_domain` that covers sports, plus `media_format: "audio"` |

For "find podcasts", compose `media_format: "audio"` with a domain. A curated `podcasts` Surface also exists. It can include whole organizations that carry only one podcast source channel. This makes the set wider and sometimes off target.

## Failure modes

| Symptom | Cause | What to do |
| --- | --- | --- |
| Junk results for a broad word | Search is lexical, and broad words match loosely | Add `home_domain`, `media_format` or a ticker. Read the returned `name` and `description`. |
| A Stream you expect is missing | It is not covered, is not live and public, or the index has not caught up yet | Say "Synorb may not cover X" when `match_strength` is `weak` or `none`. Do not guess an ID. |
| `400 unsupported_filter_param` | You passed `search=` to `GET /streams` | Use `POST /streams/search`. |
| `422` on `media_format` or `home_domain` | A value outside the list, such as `podcast` | Use the values the error names. |
| A ticker returns nothing | The company is not covered, or the ticker is mistyped | Try the company name. |
| You treated search as content | Search returns Streams and Beacon recipes, not content | Scope `synorb-manifests` with the returned Stream IDs. |

## Make it good for your user

Never answer "yes, we cover that" from memory. Never answer from a count you did not read in this session. Search, then say what you found in one line, with how fresh it is: "I found three Bloomberg Streams. The main one published 41 items in the last week." If the match is weak, say so and offer the two closest alternatives. Do not substitute one without telling your user.

## Next guide

[The Stream schema and every filter](/agent-guides/stream-schema-and-filters): what each field on a Stream means and how to use it to filter.
