<!-- source: https://docs.redpine.ai/docs/api-reference/search/searchUnlock -->
# Pay for previewed results and receive them in full

> Charges only for results not already unlocked by an earlier call against the same `queryId` -- re-sending the same ids costs nothing. Omit `resultIds` (or pass `null`) to unlock everything from the preview.

Set `includeFigures` to receive figure images as base64 in `metadata.figures[].image_data`. Images are returned for every result unlocked under this `queryId`, including ones unlocked by an earlier call -- so re-sending ids you have already paid for is how you fetch images you skipped the first time, and it charges nothing. At most 50 images are fetched per call; the ids in `resultIds` (or, when it is omitted, the results this call unlocked) get that budget first. Figures are free: they are not priced into the token cost, so the flag changes latency and response size and never the charge. Use the preview's `figureCount` to decide whether to ask.

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

## Authentication

```
Authorization: Bearer sk_live_YOUR_API_KEY
```

## Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `imageMaxHeight` | integer | no | Maximum image height in pixels |
| `imageMaxWidth` | integer | no | Maximum image width in pixels |
| `imageQuality` | integer | no | JPEG quality for fetched images |
| `includeAudio` | boolean | no | Return a signed link to each audio clip (a podcast result's own recording) of the unlocked results, in metadata.figures[]: the figure (media_type audio/*) gains `url` (expires after 7 days), `url_expires_at` (ISO-8601 UTC) and `reason`, and keeps `cues` mapping each sentence to the result text. `url` is null when no link is issued, and `reason` says why: sandbox, not_entitled or unavailable. Free, like includeFigures, and so is playing the clip. includeFigures never returns audio. |
| `includeFigures` | boolean | no | Fetch figure images, returned as base64 in each result's metadata. Candidates are every result unlocked under this queryId, this call's and earlier calls' alike, but at most 50 images are fetched per call: the ids in resultIds — or, when it is omitted, the results this call unlocked — get that budget first, and figures past it come back as captions with no image_data. Ask again for the ids you still want; re-sending ids already unlocked charges nothing. Free — figures are not priced into the token cost, so this changes latency and response size, never the charge. Off by default: use the preview's figureCount to decide. includeImages is a deprecated alias. |
| `queryId` | string | yes | The `queryId` from a previous POST /search/preview response. |
| `resultIds` | array | no | Result ids to unlock. Omit (or pass `null`) to unlock every result from the preview. Re-sending an id that is already unlocked costs nothing — only the delta is charged. |

## Responses

| Status | Description |
|---|---|
| `200` | The full result set filtered through the unlock ledger, plus what this call charged |
| `401` | Missing or invalid API key |
| `402` | Billing suspended for this organization, or insufficient credits for the delta |
| `404` | queryId not found or belongs to a different API key |
| `410` | The preview has expired, or was written by an older payload version |
| `422` | One or more resultIds are not in this preview |
| `429` | Rate or quota limit exceeded, or the API key's spending budget is exhausted. An unlock that buys new results counts as one query against the org's quota; a preview and a repeat unlock count for nothing. Nothing is charged when this fires. |
| `503` | The quota service is unavailable, so the quota slot this unlock would consume could not be counted and the unlock was refused. The refusal happens before the charge commits: nothing is charged and no result is unlocked. |

### 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. |