<!-- source: https://docs.redpine.ai/docs/api-reference/search/searchPreview -->
# Preview search results without charging

> Run the same pipeline as POST /api/v1/search/query -- same entitlement checks, same retraction filtering, same relevance ranking -- but never charge for it. Every result of a new preview comes back locked, with a teaser snippet in `text` and the cost to unlock it. Works even at a zero balance. Consumes no credits, no trial query and no quota.

Call POST /api/v1/search/unlock with the returned `queryId` to pay for and receive some or all of the results in full.

Send an `Idempotency-Key` header to make retries safe: a repeat with the same key and body returns the original `queryId` instead of buying the same results again under a new one.

`POST` `https://api.redpine.ai/api/v1/search/preview`

## Authentication

```
Authorization: Bearer sk_live_YOUR_API_KEY
```

## Header parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `Idempotency-Key` | string | no | Optional, 1-255 characters, scoped to your organization and kept for 24 hours. A repeat with the same key and the same body runs no new search: it returns the original `queryId`, rendered through the unlock ledger, so rows already unlocked come back in full with `locked: false`. Every row keeps its `cost` and `tokens`. The same key with a different body is a 422 `IDEMPOTENCY_KEY_REUSED`. A repeat while the first request is still running is a 409 `IDEMPOTENCY_KEY_IN_PROGRESS`, so retry it. If the original preview has expired, the request runs a new preview and the key moves to it. The response says which happened in `Idempotency-Status`. |

## Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `collection` | string | no | Name of the collection to search. Mutually exclusive with `collections`. |
| `collections` | array | no | Collections to search together (max 5, unique). Each collection is searched with its own access entitlements and the results are merged into one relevance-ranked list; each result carries its origin `collection`. Mutually exclusive with `collection`. |
| `filters` | object | no | Optional metadata filter. Two accepted forms. Flat (top-level keys are ANDed): `{"journal": "Nature", "publication_date": {"gte": "2020-01-01"}}`. Structured DSL (for OR / nesting): `{"and": [{"field": "journal", "eq": "Nature"}]}`. Operators: `eq`, `ne`, `in`, `not_in`, `gt`, `gte`, `lt`, `lte`, `between`. Combinators: `and`, `or`, `not`. One operator per condition, except range bounds (`gt`, `gte`, `lt`, `lte`) together; put other combinations in separate conditions under `and`. Mixing the flat and structured forms in one filter is rejected. Exclusion uses `ne` / `not_in` / `not` — there is no separate syntax: `{"and": [{"field": "issn", "not_in": ["1234-5679"]}]}`. Indexed on every collection (a field that is not a known result metadata field is rejected; a known field without an index, such as `pmid`, is matched by scanning and returns a `filterWarnings` entry, and is rejected on a collection that has no index for it): `article_type`, `chapter_authors`, `chapter_number`, `chapter_title`, `doi`, `isbn`, `issn`, `journal`, `keywords`, `open_access`, `publication_date`, `publisher`, `section`. Indexed on the editorial collections only: `has_results`, `last_updated_date`, `medical_board_approved`, `nct_id`, `overall_status`, `phases`, `topic`, `url`. `open_access` is also answered for a collection that holds only open-access content and carries no such field: `true` matches everything there, `false` (or the field under `or` / `not`) excludes that collection with a `filterWarnings` entry. `license` is returned in result metadata but is not filterable. `issn` accepts hyphenated or bare, upper- or lower-case X (`"1234-561X"`, `"1234561x"`). `doi` is matched case-insensitively and an optional `https://doi.org/` or `doi:` prefix is accepted. `journal_metric.2yr_mean_citedness`, `journal_metric.h_index`, `journal_metric.i10_index` and `journal_metric.impact_factor` accept range operators only and are resolved server-side into the matching ISSNs; see `journalMetricExpansions` in the response. `impact_factor` is an impact factor computed by Redpine from OpenAlex citation data with the JIF formula. It is not Clarivate's Journal Impact Factor. Journals that publish many news items, letters and editorials (for example BMJ, JAMA, The Lancet) score far below their JCR figure, because OpenAlex classifies those items as articles. A metric condition may match at most 35,000 journal ISSNs and a request may send at most 70,000 filter values. A threshold with one end must stay within its documented bound: journal_metric.2yr_mean_citedness: gte 1.1 or higher, or lte 0.0 or lower; journal_metric.h_index: gte 35 or higher, or lte 3 or lower; journal_metric.i10_index: gte 160 or higher (no upper bound alone); journal_metric.impact_factor: gte 0.9 or higher, or lte 0.2 or lower. On h_index and i10_index, gt N counts as gte N+1 and lt N as lte N-1. A range with both ends (gte with lte, or between) may sit anywhere if it matches no more than 35,000 ISSNs. For 'below X' use not with gte X, X at or above the metric's minimum; not also keeps documents with no journal metric, and on impact_factor journals with no impact_factor. Two conditions near their bounds use most of the 70,000 values, so a third, or a long in/not_in list beside them, can be refused. |
| `limit` | integer | no | Maximum results to return (default 10, max 30) |
| `query` | string | yes | Natural language or keyword search query. |

## Responses

| Status | Description |
|---|---|
| `200` | The results, each locked unless an Idempotency-Key replay finds it already unlocked, plus the total cost to unlock the rest |
| `401` | Missing or invalid API key |
| `403` | API key does not have access to the requested collection |
| `404` | Collection not found |
| `409` | IDEMPOTENCY_KEY_IN_PROGRESS: a request with this Idempotency-Key is still running |
| `422` | Validation error (e.g. query too long), or IDEMPOTENCY_KEY_REUSED: the Idempotency-Key was already used with a different body |
| `429` | This org's preview rate limit was exceeded (shared with the MCP preview tool) -- not the org's query quota, which a preview never consumes. |
| `503` | Search temporarily unavailable (Qdrant is down), or an Idempotency-Key was sent and the idempotency store (Redis) is unavailable. A keyed request fails closed rather than run without its guarantee; retry it. |

### 200 response fields

| Field | Type | Required | Description |
|---|---|---|---|
| `costCharged` | string | no | What THIS call charged — not a running total. Null on a preview (always free) and on an unlock whose entire delta was already unlocked. |
| `costToUnlockRemaining` | string | yes | Cost to unlock every result not yet unlocked. An estimate, not a binding quote: it is summed from each result's own individually-rounded cost, while the amount actually charged on the next unlock is computed per collection at that call's combined token total — the two can differ by a rounding fraction. |
| `discovery` | object | no | A catalogue answer: what data sources, publishers or journals your key can reach. Present only when the query asked about the catalogue rather than for literature; then `results` is empty and nothing was charged (`X-Query-Cost: 0.000000`). |
| `filterWarnings` | array | no | Advisory warnings about the supplied filter — for example filtering on a known field with no payload index, which is matched by scanning, or a collection excluded because it holds only open-access content and the filter asked for open_access=false. The search still runs. Omitted when there are none. |
| `filtersApplied` | object | no | The filters you sent, after validation. Omitted when no filters were sent. |
| `journalMetricExpansions` | array | no | How each journal-metric condition resolved to ISSNs; omitted when no metric filter was used. |
| `queryId` | string | yes | Pass this to POST /search/unlock. |
| `results` | object[] | yes | Every result from the query, filtered through the unlock ledger. |