> For the complete documentation index, see [llms.txt](https://docs.flash.im/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.flash.im/docs/api-reference/match-api.md).

# Match API

{% hint style="info" %}
`POST /v1/match`  ·  header `X-API-Key: sk_live_...`  ·  up to 100 queries per request  ·  items carry the [News Item Fields](/docs/api-reference/news-item-fields.md) schema
{% endhint %}

## Purpose

Send what your page is about; FLASH returns the news that belongs next to it. This is the core of the FLASH API: one call attaches a live, relevant news feed to any contract, asset, or topic page you run.

<figure><img src="/files/lRcPUEE0XGta3EMN9u4D" alt="The two search fields in FLASH Terminal"><figcaption><p>The search inside FLASH Terminal is this API</p></figcaption></figure>

<a href="https://flash.im/terminal" class="button primary">Try it in Terminal</a>

## Matching is by entity, not by string

At analysis time our AI extracts each article's entities in three layers (full name, aliases, ticker), and your input is checked against all three. That is why `BTC` and `Bitcoin` resolve to the same entity, and an article that only says "Fed" still matches "Federal Reserve".

<figure><img src="/files/0fypxOGQXFT4vEL4H1Tb" alt="entities as the join key"><figcaption></figcaption></figure>

## Two query modes, one endpoint

The field name selects the mode.

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th></th></tr></thead><tbody><tr><td><h4><i class="fa-quote-left" style="color:$primary;">:quote-left:</i></h4></td><td><h4>Sentence mode</h4><p>Field <code>sentence</code></p></td><td>Send a sentence such as a contract question, unchanged. The engine pulls the entities out of it. Best for prediction market contract pages.</td></tr><tr><td><h4><i class="fa-tags" style="color:$primary;">:tags:</i></h4></td><td><h4>Keyword mode</h4><p>Field <code>keywords</code></p></td><td>Send up to 5 keywords you selected. Best for trading panels and asset info pages.</td></tr></tbody></table>

Exactly one of `sentence` or `keywords` per query (both in one item = 400 error). **Up to 100 queries per request** (more = 400 error); running thousands of contracts or assets is fine, just split them across calls. **Inputs must be in English** (`sentence`, `keywords`, `subEntities`, `tags`). Output articles can be in any of the 16 languages via `lang`.

### Copy-paste starting points

One complete call per mode; swap in your own input and `category` (`crypto`, `economy`, `politics`, `geopolitics`):

{% tabs %}
{% tab title="Sentence" %}

```bash
curl -X POST https://api.flash.im/v1/match \
  -H "X-API-Key: sk_live_..." -H "Content-Type: application/json" \
  -d '{"queries":[{"queryId":"contract-31024","sentence":"Will Bitcoin hit $150,000 by 2026?","category":"crypto"}],"limit":5}'
```

{% endtab %}

{% tab title="Keyword" %}

```bash
curl -X POST https://api.flash.im/v1/match \
  -H "X-API-Key: sk_live_..." -H "Content-Type: application/json" \
  -d '{"queries":[{"queryId":"asset-1","keywords":["Bitcoin","BTC"],"category":"crypto"}],"limit":5}'
```

{% endtab %}
{% endtabs %}

## Which mode, with which input

**Sentence mode: pass the page title as-is.** No keyword extraction on your side; the engine pulls the entities out of the sentence.

```json
{ "queryId": "contract-31024",
  "sentence": "Will Bitcoin hit $150,000 by 2026?",
  "category": "crypto" }
```

**Keyword mode: you pick the terms.** For asset pages, the standard pattern is **`["<asset name>", "<ticker>"]`**:

```json
{ "queryId": "asset-1",
  "keywords": ["Bitcoin", "BTC"],
  "category": "crypto" }
```

Picking keywords:

* **Send both the name and the ticker.** Your input is checked against all three entity layers, and spellings of the same entity merge into one result: sending both forms raises the hit rate, and the same story never comes back twice
* **Up to 5 keywords.** Use the spare slots for other spellings your listing carries (the backup pattern: whichever is registered will match)
* **Proper names and tickers only.** Generic words (`price`, `token`, `market crash`) are not entities and will not match

**A contract with everything on.** A multi-choice political contract sends the question plus the candidate names (`subEntities`), its own platform labels (`tags`), and the close time (`expiresAt`):

```json
{ "queryId": "contract-84921",
  "sentence": "Who will win the 2028 US presidential election?",
  "category": "politics",
  "expiresAt": "2028-11-07T23:59:59Z",
  "subEntities": ["Trump", "Newsom"],
  "tags": ["Politics", "Elections"] }
```

{% hint style="warning" %}
**If your sentence contains no entity, `subEntities` is effectively required.** The question above names none ("who", "win", "election" are generic words), so the candidate names in `subEntities` are what the matching runs on. Send this query without it and you get `no_match` with only `fallbackNews`.
{% endhint %}

## Request

```json
POST /v1/match
X-API-Key: sk_live_...

{
  "queries": [
    { "queryId": "contract-84921",
      "sentence": "Who will win the 2028 US presidential election?",
      "category": "politics", "expiresAt": "2028-11-07T23:59:59Z",
      "subEntities": ["Trump", "Newsom"],
      "tags": ["Politics", "Elections"] },
    { "queryId": "asset-22711", "keywords": ["Aleo", "ALEO"], "category": "crypto" }
  ],
  "lang": "en", "impact": "4,5", "limit": 5
}
```

| Field                                        | Required        | Description                                                                                                                                                                                                                                                                                                          |
| -------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `queryId`                                    | **Yes**         | Your own identifier, any format. Echoed back so you can map batch results                                                                                                                                                                                                                                            |
| `sentence` / `keywords`                      | **Exactly one** | `sentence`: free text in sentence form, we extract the core entities. `keywords`: up to 5, matched as a union (OR); spellings of the same entity merge, so the same story never comes back twice                                                                                                                     |
| `category`                                   | **Yes**         | One of `crypto`, `economy`, `politics`, `geopolitics`. Invalid = 400 with allowed list                                                                                                                                                                                                                               |
| `expiresAt`                                  | No              | The query's end time (for example, the contract's close). After this time the query freezes: `matchStatus` becomes `expired`, `news` stays fixed at the list as of expiry, no new news is attached, and `fallbackNews` stops coming. Use it for prediction market contracts; omit it for assets, which do not expire |
| `subEntities`                                | No              | `sentence` mode only. Named terms not present in the sentence (e.g. candidate names on a multi-choice contract) to widen matching. **Effectively required when the sentence itself names no entity**: without it such a query returns `no_match`                                                                     |
| `tags`                                       | No              | `sentence` mode only. Your platform's own contract-category labels, passed through **unchanged** (Polymarket tags, Kalshi category). When present, more relevant news comes first                                                                                                                                    |
| `lang`, `impact`, `limit`, `since`, `fields` | No              | Same as [News Feed API](/docs/api-reference/news-feed-api.md)                                                                                                                                                                                                                                                        |

**queryId format.** Any string works. Recommended: `{surface}-{your internal id}`, like `contract-84921` or `asset-1`, in URL-safe lowercase. Two rules matter:

* **Stable**: the same page always sends the same queryId. Cursors are per query, so an id that changes between calls (a timestamp, a per-call UUID) resets incremental polling and starts that query from scratch
* **Unique** within your key: one id per contract or asset, as a number. Tickers are not unique (different assets can share a symbol), so use the numeric id you already have: your database key, or for crypto assets the `cmcId` that `coins[]` returns

## Response

```json
{
  "results": [
    {
      "queryId": "contract-84921",
      "matchStatus": "matched",
      "matchedEventTypes": ["policy-decision", "election", "appointment"],
      "news": [
        {
          "id": "n_8f3k2p",
          "headline": "Trump formally announces 2028 presidential run",
          "summary": "Donald Trump formally announced his candidacy...",
          "body": "Speaking at a rally in Florida on Tuesday, Trump declared...",
          "whyItMatters": "An early lock on the 2028 field could add volatility to related prediction market contracts.",
          "impact": 4,
          "sentiment": null,
          "eventType": "official-statement",
          "impactScore": 71,
          "categories": ["politics"],
          "entities": [ { "name": "Donald Trump", "aliases": ["Trump"], "ticker": null } ],
          "coins": [],
          "source": { "name": "Associated Press", "logoUrl": "https://.../logos/apnews.com.png" },
          "sourceUrl": "https://apnews.com/article/...",
          "publishedAt": "2026-07-31T09:14:00Z",
          "clusterId": "ev_11862044",
          "relatedCount": 9,
          "covers": [ { "url": "https://.../covers/trump-1.jpg", "width": 1750, "height": 1000 } ],
          "sourceImageUrl": "https://apnews.com/.../trump-rally-photo.jpg",
          "lang": "en"
        }
      ],
      "hasMore": false, "nextCursor": null
    },
    {
      "queryId": "asset-22711",
      "matchStatus": "no_match",
      "news": [],
      "fallbackNews": [
        { "id": "n_5rr2k8",
          "headline": "SEC approves options trading on spot bitcoin ETFs",
          "...": "same item schema as news; filled up to limit (5 here)" }
      ],
      "hasMore": false, "nextCursor": null
    }
  ]
}
```

What this example demonstrates:

* `sentiment: null` on a politics item is **neutrality by design**, not missing data
* The item matched because the sub-entity `Trump` hit the article's entity `aliases`. The question itself names no entity, so without `subEntities` this query would have been `no_match`
* `matchedEventTypes` echoes the event types derived from your `tags`. Present only when you sent tags
* `whyItMatters` can be null even at impact 4 or 5: always handle null
* When `clusterId` is null, **`relatedCount` is omitted entirely**
* The news item schema is **identical across `/v1/news` and `/v1/match`**
* `matchStatus`: `matched` | `no_match` | `expired`. News is newest first
* **Relevant-latest ordering (automatic).** When you send `tags`, stories matching the derived event types move to the top block (newest first within it), the rest follow (newest first). No sort parameter. If nothing derives, the order is plain newest-first. Keyword-mode queries are always newest-first
* `no_match` means no news matched. Fall back to `fallbackNews` in that state

## Fallback: no empty screens

{% hint style="success" %}
Mid-tier assets often have no matched news at all. When a query returns `no_match`, `fallbackNews` carries up to `limit` of the latest news from the query's `category` (same `impact` and `lang` filters), so the section never renders blank.
{% endhint %}

Queries with at least one match never carry it. `fallbackNews` is a **separate array and is never mixed into `news`**: matched news stays matched. Not sent for `expired` queries.

## Usage patterns

**1. Prediction market contract page** (sentence mode, everything on)

```json
{ "queries": [
    { "queryId": "contract-84921",
      "sentence": "Who will win the 2028 US presidential election?",
      "category": "politics", "expiresAt": "2028-11-07T23:59:59Z",
      "subEntities": ["Trump", "Newsom"], "tags": ["Politics", "Elections"] } ],
  "lang": "en", "impact": "4,5", "limit": 5 }
```

Render as a "Related News" block on the contract page: headline, impact dots, source, time, linked to `sourceUrl`. Your `tags` bring the most relevant stories to the top. After the close time, `matchStatus` turns `expired` and the list freezes into the contract's record. On `no_match`, render `fallbackNews` under its own label so the page never goes blank.

**2. Exchange trading panel** (keyword mode, compact)

```json
{ "queries": [
    { "queryId": "panel-1", "keywords": ["Bitcoin", "BTC"], "category": "crypto" } ],
  "fields": "headline,impact,publishedAt", "limit": 5 }
```

A tight list next to the chart: headline, dots, time. This is the "why did the price move" context, three fields per row.

**3. Asset detail page** (keyword mode, full section)

```json
{ "queries": [
    { "queryId": "asset-5426", "keywords": ["Solana", "SOL"], "category": "crypto" } ],
  "lang": "en", "limit": 10 }
```

A full news section for one asset. Mid-tier assets often return `no_match`: that is exactly what `fallbackNews` is for, so the section renders category news under a "Market News" label instead of sitting empty.

**4. Batch across a listing** (up to 100 queries per request)

```json
{ "queries": [
    { "queryId": "asset-1", "keywords": ["Bitcoin", "BTC"], "category": "crypto" },
    { "queryId": "asset-1027", "keywords": ["Ethereum", "ETH"], "category": "crypto" },
    { "...": "one query per listing row, up to 100" } ],
  "limit": 3 }
```

One request covers a whole watchlist or market listing; map results back by `queryId`. Poll every 10 minutes: cursors work per query, so each row receives only what is new for it (see [Incremental Fetching](/docs/api-reference/incremental-fetching-and-rate-limits.md)).
