Searching documents

Find the most relevant passages in your documents using a natural-language query.

Retrieve the most relevant passages from your documents, ranked and ready for your agent.

Search is how your application answers questions from documents. Send a query in plain language, get back ranked passages from across your corpus. No SQL, no keyword matching, no index tuning.

Under the hood Context212 runs a hybrid pipeline: vector search for meaning, lexical search for exact terms, then a reranker that scores every candidate against the full query and returns the best results.

This tutorial walks through POST /api/v1/search. For the full schema and every parameter, see the API reference.

If you've already uploaded documents, this is all you need:

const response = await fetch("https://api.context212.com/api/v1/search", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.C212_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ query: "What is the JWT token expiry policy" }),
});

const data = await response.json();
for (const result of data.results) {
  console.log(result.content);
  console.log(`  → ${result.source.filename}, p.${result.source.page_start}${result.source.page_end}`);
  console.log(`  → score ${result.score.toFixed(2)}`);
}

By default this searches every document your API key can reach and returns the top 10 results. That's usually a good starting point.

Scoping to a subset of documents

When you want to limit search to a specific team's workspace, a handful of files, or a tagged collection, use one of the three scoping parameters. file_id is mutually exclusive with workspace_id and tag_id, while workspace_id and tag_id can be combined.

Best for multi-tenant products where each customer or team has their own workspace.

{
    query: "deployment runbook",
    workspace_id: [12, 15],
}

Reading the response

Each result contains:

  • content: the matched passage text. null for vision-mode chunks.
  • score: the relevance score you rank on. With scoring on (the default), it equals scores.relevance (0–1); with scoring off, it's the combined retrieval score (unbounded).
  • scores: the per-signal breakdown behind that score. See Understanding the scores below.
  • source: where the chunk came from: file_id, filename, title, mime_type, size_bytes, page_start/page_end, total_pages, tags, and external_metadata for connector-imported files.
  • workspace: the workspace the document belongs to.
{
  "results": [
    {
      "chunk_id": "550e8400-e29b-41d4-a716-446655440000",
      "content": "JWT tokens are signed using RS256 and expire after 1 hour.",
      "score": 0.95,
      "scores": {
        "text": 0.91,
        "vision": null,
        "keyword": 0.43,
        "multivector": 12.4,
        "relevance": 0.95
      },
      "source": {
        "file_id": 512,
        "filename": "auth-system.pdf",
        "title": "Authentication System Design",
        "mime_type": "pdf",
        "size_bytes": 482113,
        "page_start": 3,
        "page_end": 4,
        "total_pages": 12,
        "tags": [{"id": 7, "name": "security"}],
        "external_metadata": null
      },
      "workspace": {"id": 42, "name": "Engineering Docs"}
    }
  ]
}

Understanding the scores

score is the single number you should rank and threshold on, and results come back ordered by it, descending. When relevance scoring runs (the default), score is the cross-encoder relevance score (scores.relevance, 0–1). When you turn scoring off with relevance_scoring: none, score falls back to the combined retrieval score (unbounded, higher is better). Use it directly unless you have a reason to inspect the parts.

scores exposes those individual signals so you can debug why a chunk ranked where it did, or build your own re-ranking on top. Each signal is null when it didn't apply to that chunk.

SignalWhat it measuresRangeWhen it's null
textDense text-embedding similarity between the query and the chunk (1 − cosine distance). The core semantic-match signal.~0–1, higher is closerIn vision mode (no text embedding is scored).
visionVisual page similarity from the vision embedding — matches layout, diagrams, and scanned content rather than extracted text.~0–1, higher is closerWhen the document has no vision index.
keywordBM25 lexical score — rewards exact term and phrase overlap, the way classic keyword search does. Catches identifiers, codes, and rare terms that embeddings can blur.≥0, unbounded, higher is strongerIn vision mode (no text is scored).
multivectorColBERT multi-vector (MaxSim) score — a fine-grained token-level match that reranks candidates more precisely than a single embedding.≥0, unbounded, higher is strongerWhen multi-vector reranking is disabled.
relevanceCross-encoder reranker confidence — the model reads the query and chunk together and scores how well the passage actually answers the query. The strongest single signal, and equal to the top-level score when present.0–1, higher is more relevantWhen relevance_scoring is none or the reranker is unavailable.

Only text, keyword, and multivector share a comparable footing within a single response; relevance is a calibrated probability and vision lives on its own scale. Don't compare raw signal values against each other or across queries — for ranking, always use the top-level score.

Tuning result count and latency

max_results (default 10, range 1–50) controls how many ranked chunks come back.

For lower latency, set relevance_scoring: "none". You lose the reranker's quality boost but the pipeline becomes a straight hybrid lookup: results come back in retrieval order, score is the combined retrieval score, and scores.relevance will be null.

{
    query: "incident response playbook",
    max_results: 5,
    relevance_scoring: "none",
}

relevance_scoring also accepts scoring_only (score every candidate but return them all, without filtering below the quality threshold). Omit it for the default (scoring_and_filtering).

Searching images and diagrams

Switch to vision mode to search documents by their visual content, useful for scanned pages, slide decks, architecture diagrams, or any document where the meaning is in the layout rather than the words.

{
    query: "network topology diagram showing DMZ",
    mode: "vision",
    include_image: true,
    max_results: 3,
}

Vision mode requires documents to have been indexed with vision embeddings (status_vision: "embedded"). With include_image: true, each result includes an image.b64_content field with the page rendered as a base64 image. In text mode, the image is fetched from the vision chunk covering the chunk's start page, or an empty string if no vision index exists for that page.

Narrowing search with relation filters

Use ontology relation filters to keep only documents connected to matching concepts. Start with Filtering documents by relations, or the Ontology overview.

{
  query: "renewal terms",
  workspace_id: [12],
  relation: ["references.Organization(region:EU)"],
  include_relations: true,
}

Common errors

StatusCause
400Request body is not parsable JSON
403None of the provided filters resolve to authorized resources
422Validation error, e.g. file_id combined with workspace_id/tag_id, or max_results out of range
429Rate limit exceeded
500Unexpected server error

On this page