Search
Search finds pages, not answers. It is the retrieval half of the product exposed directly — useful for locating a document, and not a substitute for a table, which is what turns pages into something you can read down a column of.
What it searches
The page images of every ingested document in the workspace, matched semantically rather than by keyword. A match is a page — the same file items Files describes — with a relevance score and a short-lived share link to the image.
There is no text in a match, because there is no page text to hand over: ingest embeds page images, and no text column exists. If you want extracted text, that is what a table is for.
Search costs credits
It is the third thing that spends, alongside ingest and extraction, and it is the one that surprises people because a search feels like a read.
Charging is in blocks of 100 searches for 40 credits — $0.10, or $0.001 a search. The first search of a block buys the whole block; the next 99 cost nothing extra. So a client that searches once a day pays 40 credits and has 99 searches banked, and one that searches in a loop pays the same per search as one that does not.
The organisation's monthly spend cap applies. A search that would pass it — or a balance that cannot cover the next block — is a 402. Unlike a run's 402, this one is plain text: it comes from middleware, so there is no cost/balance body to read. See Errors.
A 503 here means the meter itself could not be written. Searching without counting is how a counter stalls into free searches, so the request is refused rather than served. Retry — the block claim is idempotent, so a retry is never charged twice.
Text, image, or both
query takes a bare string, or an object with text and image_url. Both together is a single query combining the two, not two searches.
An image can be a public https URL, or an inline data URI — JPEG or PNG only, as data:image/jpeg;base64,… or data:image/png;base64,…. A remote URL is fetched by the embedding provider rather than by us, and is still checked against the same guard uploads use: loopback, link-local and internal addresses are refused with 400. A query that resolves to nothing usable — neither text nor a readable image — is also a 400.
The response says datasetId
The request takes fileIds. The response's matches carry datasetId, because this handler is shared with v1. Both hold the same ds_… value.
Sending datasetIds instead of fileIds is silently ignored — unknown keys are stripped, so the filter vanishes and the whole workspace is searched while the response reports success. Nothing tells you. This is the one place on these pages where a plausible-looking mistake produces a wrong answer instead of an error.
Search a workspace
POST/v2/workspaces/:workspaceId/search
Requires Read on the workspace
Spends credits — 40 per block of 100 searches. Read access is enough: searching does not change anything.
col is the page number. score is relevance, higher is better; the scale is not calibrated across queries, so compare within a result set rather than against a threshold you picked once.
No matches is a 200 with an empty data array, and it still counts against the block.
Parameters
query | string | object · requiredA string, or { "text": "…", "image_url": "…" }. Give at least one of the two. |
|---|---|
fileIds | string[]Up to 100 ds_… ids to search within. Omit for the whole workspace. Ids not starting ds_ are dropped. Not datasetIds — see above. |
limit | numberMatches to return. Defaults to 5, clamped to 100. |
expiresInSeconds | numberLifetime of the share URLs on the matches. 60 to 86,400, default 600. |
curl -X POST https://api.ragextract.com/v2/workspaces/wks_7Kq2mB4nR8vXpL3d/search \
-H "x-api-key: $RAGEXTRACT_API_KEY" \
-H "content-type: application/json" \
-d '{
"query": "indemnity cap and carve-outs",
"fileIds": ["ds_M3xJ8pQ1vK5nB7wT"],
"limit": 10
}'{
"success": true,
"total": 2,
"data": [
{
"id": "dsx_R4hN9kP2sD6yF1cV",
"datasetId": "ds_M3xJ8pQ1vK5nB7wT",
"row": "embedding_image",
"col": 31,
"score": 0.8123,
"createdAt": 1756108900000,
"share": {
"url": "https://api.ragextract.com/v2/share/dsx_R4hN9kP2sD6yF1cV?token=v1.…",
"token": "v1.1756110000.9Xk…",
"expiresAt": 1756110000000
}
},
{ "id": "dsx_T7bM1wG5jH8qE3zY", "datasetId": "ds_M3xJ8pQ1vK5nB7wT", "col": 32, "score": 0.7715, ... }
]
}