Preview and unlock

See what a search would return, and what it would cost, before you pay for it.

Why preview

POST /api/v1/search/query charges for a search whether or not it turned out to be worth paying for. Preview removes that risk: it runs the same search pipeline, but returns teaser rows and an estimated price instead of billing you. You decide whether to pay only after seeing what's there.

  • POST /api/v1/search/preview: free. Runs the search, returns teaser rows plus a queryId and an estimated cost to unlock.
  • POST /api/v1/search/unlock: pays for and returns some or all of those results in full.

POST /api/v1/search/query is unchanged. Preview is additive: use it when you want to inspect relevance before spending, and keep calling /api/v1/search/query directly wherever that fits your flow better.

One identifier runs through both calls: the same queryId a search already returns. There is no second id to learn, and it works the same way GET /api/v1/search/results/{queryId} already does: a preview is available for 7 days.

Preview works even at a zero credit balance, since it costs nothing. It's the right call to make when deciding whether to top up. Unlock is the paywall: it's the only one of the two that spends a quota slot, and it counts as the query.

Preview

POST /api/v1/search/preview

Takes the same request body as POST /api/v1/search/query (collection/collections, query, limit, filters, include_metadata). Every result comes back with locked: true, text holding a short teaser rather than the full chunk, and tokens/cost showing what unlocking that one row would cost. Locators (doi, pmid, pmcid, url) and full metadata are included in the free response, so you can judge relevance before paying for anything.

Preview consumes no quota and no trial query: it is unmetered on the billing side. It is rate limited per organization per hour; see Rate limits for the current default. Extra API keys on the same organization share one limit, they don't multiply it.

Example request

curl -X POST "https://api.redpine.ai/api/v1/search/preview" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "collection": "my-collection",
    "query": "What are the symptoms of diabetes?",
    "limit": 10
  }'

preview takes the same filters as search, including the F builder from Filtering.

Example response

{
  "queryId": "qry_a1b2c3d4e5f6",
  "results": [
    {
      "id": "abc123",
      "text": "Type 2 diabetes symptoms include increased...",
      "metadata": {
        "title": "Diabetes Overview",
        "doi": "10.1234/example"
      },
      "collection": "my-collection",
      "locked": true,
      "tokens": 214,
      "cost": "0.214000",
      "relevance": 0.812
    }
  ],
  "costToUnlockRemaining": "0.214000",
  "costCharged": null
}

Retrying a preview safely

Send an Idempotency-Key header (1–255 characters, any value you choose) when your client might retry a preview, for example after a timeout. A repeat with the same key and the same body runs no new search: it returns the original queryId, and any rows you already unlocked come back in full with locked: false. Without a key, a retry creates a new queryId, and unlocking it would buy the same results again.

curl -X POST "https://api.redpine.ai/api/v1/search/preview" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Idempotency-Key: preview-7f3a9c" \
  -H "Content-Type: application/json" \
  -d '{"collection": "my-collection", "query": "What are the symptoms of diabetes?"}'
  • Keys are scoped to your organization and kept for 24 hours.
  • The Idempotency-Status response header says created when a preview ran and replayed when the stored queryId was returned.
  • The same key with a different body returns 422 IDEMPOTENCY_KEY_REUSED. Use a new key for a new request.
  • A repeat while the first request is still running returns 409 IDEMPOTENCY_KEY_IN_PROGRESS. Retry it shortly.
  • If the original preview has expired, the request runs a new preview and the key moves to it.

Unlock

POST /api/v1/search/unlock

Pays for previewed results and returns them in full.

Request body

ParameterTypeRequiredDescription
queryIdstringYesqueryId from a previous POST /api/v1/search/preview
resultIdsstring[] | nullNoResults to unlock, by id. Omit (or pass null) to unlock every result from the preview

Response fields

FieldTypeDescription
queryIdstringPass this to a later POST /api/v1/search/unlock call for the same preview
resultsarrayThe full result set, filtered through the unlock ledger: rows this call (or an earlier one) paid for come back in full, the rest still teased. Every row, locked or not, carries relevance: how well the passage answers the query, 0 to 1. Unlocked rows also carry chunk_index in metadata
costToUnlockRemainingstringCost to unlock every result not yet unlocked
costChargedstring | nullWhat this call charged, not a running total. Null on a preview (always free) and on an unlock whose entire delta was already unlocked
filterWarningsarray | nullAdvisory warnings for filter fields with no payload index; omitted when there are none. Same diagnostics POST /api/v1/search/query returns. See Search
journalMetricExpansionsarray | nullHow each journal-metric filter condition resolved to ISSNs; omitted when no metric filter was used
filtersAppliedobject | nullThe filters the preview was sent, after validation. Returned by preview only; omitted when no filters were sent
discoveryobject | nullA catalogue answer, set only when the query asked about the catalogue rather than for literature; nothing is charged. Returned by preview only. See Catalogue questions

An unlock that buys something is the query: it spends one quota slot. A retried unlock (one whose resultIds were already paid for) charges nothing and burns no quota slot, so it's safe to retry.

Example request

curl -X POST "https://api.redpine.ai/api/v1/search/unlock" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "queryId": "qry_a1b2c3d4e5f6"
  }'

Two errors are specific to unlock and have their own SDK classes: 402 when the organisation cannot pay for the delta (InsufficientCredits, Go *InsufficientCreditsError) and 410 when the preview is past its 7-day window (Expired, Go *ExpiredError). Neither is retried.

Example response

{
  "queryId": "qry_a1b2c3d4e5f6",
  "results": [
    {
      "id": "abc123",
      "text": "Type 2 diabetes symptoms include increased thirst, frequent urination, unexplained weight loss...",
      "metadata": {
        "title": "Diabetes Overview",
        "doi": "10.1234/example"
      },
      "collection": "my-collection",
      "locked": false,
      "tokens": 214,
      "cost": "0.214000"
    }
  ],
  "costToUnlockRemaining": "0.000000",
  "costCharged": "0.214000"
}

Partial unlock

Pass resultIds to buy some rows from a preview and leave the rest locked:

curl -X POST "https://api.redpine.ai/api/v1/search/unlock" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "queryId": "qry_a1b2c3d4e5f6",
    "resultIds": ["abc123", "def456"]
  }'

Only the ids you pass are charged. Re-sending an id you already unlocked (in this call or an earlier one) costs nothing, since unlock charges the delta only: call it again later with more ids to unlock the rest of the same preview, or the same ids again with no risk of a duplicate charge.

costToUnlockRemaining is an estimate, summed per row. The amount actually charged by an unlock is computed per billing group, so the two can differ by a rounding fraction.

Two-call example

# 1. Preview for free -- see teasers and the price
curl -X POST "https://api.redpine.ai/api/v1/search/preview" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"collection": "my-collection", "query": "What are the symptoms of diabetes?", "limit": 10}'

# 2. Unlock the results worth paying for
curl -X POST "https://api.redpine.ai/api/v1/search/unlock" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"queryId": "qry_a1b2c3d4e5f6", "resultIds": ["abc123"]}'
Was this page helpful?

On this page