> 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/incremental-fetching-and-rate-limits.md).

# Integration Patterns

Render or store: pick the right integration pattern per surface.

## Two ways to integrate

Every FLASH surface can be built one of two ways. Choose per surface, not per company: many partners use both at once (render for their pages, store for their bots).

<table data-search="false"><thead><tr><th></th><th>Render on call (default)</th><th>Store &#x26; serve</th></tr></thead><tbody><tr><td>What you build</td><td>Nothing: call the API when the surface renders</td><td>A small scheduled poller plus your own storage</td></tr><tr><td>Where the news lives</td><td>On FLASH; you display it</td><td>A synced copy in your database</td></tr><tr><td>Freshness</td><td>Always current</td><td>As fresh as your polling interval</td></tr><tr><td>Missed items</td><td>Possible between calls (harmless for display: the next render shows them)</td><td>None: an <code>after</code> poll plus <code>id</code> de-duplication delivers every item</td></tr><tr><td>History depth</td><td>The newest 100 items per query (fetch once, expand client-side)</td><td>Unlimited: your database accumulates over time</td></tr><tr><td>Availability</td><td>Through an outage on our side, the edge keeps serving the last good responses for up to 72 hours; keep your own last good response too (see below)</td><td>Fully independent: your database serves through an outage of any length, and polling resumes from your stored timestamp afterwards</td></tr><tr><td>Best for</td><td>User-facing pages: news tabs, trading panels, contract pages, asset pages</td><td>Notification bots, alerting, analytics, data products, and any surface that needs hard availability independence or a permanent record</td></tr></tbody></table>

Rule of thumb: **if a person is looking at it, render on call. If a machine consumes it, store & serve.**

## Pattern 1: Render on call (default)

Call the API whenever your surface renders a news area. That is the intended pattern: identical URLs are served from cache, so per-render calls stay cheap and there is no scheduler for you to build. Cached responses stay within a short freshness window, so what you render is always current news.

The one rule that makes this work: **the same page always calls the same URL, parameters in the same order.** A URL that varies between calls misses the cache.

Where it fits:

* An exchange trading panel or asset detail page: the user opens the page, the page calls, the latest news appears
* A prediction market contract page: the "Related News" block is fetched on every page view
* The news tab of an app: fetched when the tab opens, always current, nothing stored

**"Load more", without pagination.** One call returns up to 100 items (`limit=100`), and few pages display that many at once. Fetch the full set in one call, render the first rows, and let a "load more" button reveal the rest client-side: no second request, nothing to store. The newest 100 per query is also the API's depth; a product that needs history beyond that is a store & serve case (Pattern 2), where your database accumulates every item over time with no depth limit.

**Optional: a partner-side micro-cache.** If one surface is rendered by many users at the same time, put a short-lived cache (for example, 30 to 60 seconds) in your own backend so a burst of identical renders becomes a single outbound call. This is an optimization, not a requirement: identical URLs are already served from our cache. It reduces your outbound traffic and keeps even a traffic spike well inside your rate limit.

**Loading: design the wait.** The render pattern calls on every page view, so give the news block a loading design instead of a blank or a spinner:

* **Skeleton rows, not a spinner.** Draw gray placeholder rows in the block, then swap in the items. A skeleton previews the shape of what is coming, so the wait feels shorter.
* **Reserve the space.** Fix the block's height and the covers' aspect ratio, so a late response never shifts your page layout.
* **Paint the last response instantly, refresh silently.** Keep the last successful response (the micro-cache above already stores it); on the next render, show it immediately and swap in the fresh response when it arrives. News a few minutes old, shown for a moment, is harmless.
* **Prefetch on intent.** When the user hovers an asset row or approaches a tab, fire its URL early: identical URLs are served from cache, so prefetching is cheap and the eventual click feels instant.
* **Load below-the-fold blocks lazily.** News areas outside the viewport should call when scrolled into view, so they never compete with your page's own critical resources.

If a call still has not answered after a few seconds, stop waiting: switch to the habits in "If FLASH is unreachable" below.

## Pattern 2: Store & serve

When your product needs its own copy of the news, poll on a schedule and store:

{% stepper %}
{% step %}

### Poll on a fixed interval

Call each query URL on a fixed interval (for example, every 10 minutes).
{% endstep %}

{% step %}

### Send `after` with your last timestamp

Send `after=` with the `publishedAt` of the newest item you stored. Each poll returns only what has arrived since. The boundary is inclusive, so de-duplicate by `id`.
{% endstep %}

{% step %}

### Store and serve

Store items keyed by `id` and serve your users from your own database.
{% endstep %}

{% step %}

### Recover from downtime

After downtime, resume from the `publishedAt` you stored last: nothing is missed.
{% endstep %}
{% endstepper %}

Where it fits:

* **Notification and alert pipelines** (push notifications, Telegram or Discord bots): every item must go out exactly once. Render-style calls can miss items that arrive between calls; an `after` poll does not
* **Analytics and data products**: you join news with your own data, run your own search or filters, or serve derived datasets
* **AI agents and research pipelines** that ingest continuously rather than display
* **Deep history**: the API serves the newest 100 items per query; anything older lives only in the copy your poller has accumulated
* **Hard availability independence or a permanent record**: a surface that must not depend on a third party at render time, or that must keep its own record (for example, a contract's news as of its close), keeps its own copy. Many large platforms run both patterns: render on call for most surfaces, store & serve for the few that carry a hard SLA or a compliance record

Two habits that keep large pollers healthy:

* **Spread the calls.** Polling thousands of URLs? Distribute them across the interval instead of firing them all at once: same total volume, no burst.
* **Match the cadence to the need.** An alert bot watching a handful of URLs can poll every minute or two; a full asset-catalog sync is fine at 10 minutes.

## Paging with `before` and `after`

Two time anchors do all the paging. Both take an ISO 8601 UTC timestamp ending in `Z`, and both boundaries are **inclusive**, so an item published at exactly the boundary comes back again: **de-duplicate by `id`**.

* `after=` returns items published **at or after** that time: this is how you poll for what is new. Store the `publishedAt` of the newest item you received and send it back on the next poll.
* `before=` returns items published **at or before** that time: this is how you walk backward through older news. Store the `publishedAt` of the oldest item you received and send it back for the next, older page.
* Receiving fewer items than your `limit` means you have reached the end in that direction.

```mermaid
flowchart LR
    A["First call<br/>limit=20"] --> B["Store newest publishedAt"]
    B --> C["Poll with after="]
    C --> D{"items returned?"}
    D -->|yes| E["De-duplicate by id<br/>update stored publishedAt"]
    E --> C
    D -->|"no (empty)"| F["Wait for the next poll"]
    F --> C
```

In matching mode both anchors work the same way, per URL: each matched feed pages independently.

## Choosing the right anchor

{% hint style="warning" %}
**`before` reads backward, `after` reads forward.** Sending your last poll time as `before` returns older stories, not fresh ones. To pick up what is new, use `after`.
{% endhint %}

* Both parameters take ISO 8601 UTC, and both must end in `Z`. Offsets such as `+09:00`, a bare date, and epoch integers are rejected with 400
* An empty list means you have reached the end of the retained window in that direction
* Send one anchor per call

## If FLASH is unreachable

{% hint style="info" %}
The first line of defense is ours: responses are cached at the CDN edge, and during an outage on our side the edge keeps serving the most recent cached responses for **up to 72 hours** (stale-if-error). Most renders never notice an incident: they receive slightly older news instead of an error.
{% endhint %}

Still, design your integration so that even a full outage is invisible to your users. Three habits cover it:

* **Load the news block asynchronously, with a short timeout.** Fetch news after your page's own content, with a timeout of a few seconds. A slow or failed call then affects only the news block, never your page.
* **Keep the last good response.** If you run the micro-cache from Pattern 1, extend it: on an error or timeout, serve the last successful response for that URL instead of an empty block. News a few minutes old beats an error state.
* **Degrade to nothing, not to an error.** If there is nothing to show, hide the news block or show a quiet placeholder. Never surface a FLASH error to your users, and never let one block your page.

Store & serve integrations are unaffected by design: your database keeps serving through an outage of any length, and when we are back you resume from your stored `publishedAt` with nothing missed. A surface that needs that guarantee belongs in Pattern 2.

On a 5xx or a timeout, retry with backoff (start at a few seconds and increase); do not hammer.

## Errors

| Code          | Meaning                                                                                                                                                                                |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 200           | Success (including `no_match` in matching mode)                                                                                                                                        |
| 400           | Malformed request: missing required parameter, invalid `category`, unknown `fields` name, both `sentence` and `keyword`, more than 30 keywords, more than 10 `subCategory` values      |
| 401           | Missing or invalid API key                                                                                                                                                             |
| 5xx / timeout | Temporary trouble on our side (rare: the CDN serves cached responses for up to 72 hours through an incident). Serve your last good response or hide the news block; retry with backoff |
