Skip to main content

pageindex-search

Python client for Page Search API by PageIndex. Upload a PDF, ask up to 10 questions at once, and get back the pages that answer each one, ranked by relevance. Every page is read.

pip install pageindex-search

Create an API key in the console and set it:

export PAGEINDEX_SEARCH_API_KEY=pis_live_...

Quick start

from pageindex_search import PageSearch

client = PageSearch()

doc = client.documents.upload("3M_2018_10K.pdf")
result = client.search(["What was capital expenditure in 2018?", "Who is the CEO?"], document=doc)

for answer in result.results:
    print(answer.question)
    for page in answer.pages:
        print(f"  page {page.page}: {page.relevance}")

print(f"cost ${result.usage.cost_usd}, credit left ${result.usage.balance_usd}")

Upload a document once and search it as often as you like; uploading the same file again returns the same document. Documents are kept until you delete them:

for doc in client.documents.list():  # every document, newest first, a page at a time
    print(doc.id, doc.filename, doc.pages)

client.search(["What was revenue in 2018?"], document="doc_60a07bcef3094b68806070f2")  # search one again by its id
client.documents.delete("doc_60a07bcef3094b68806070f2")

With many documents, search across them: the documents likely to answer are found first, then their pages.

result = client.search("What was capital expenditure in 2018?")  # every document of yours
for doc in result.results[0].documents:
    print(doc.filename, [(p.page, p.relevance) for p in doc.pages])

To find only which documents answer, use client.documents.search.

Group documents into collections, and search within one:

reports = client.collections.create("Annual reports")
client.documents.upload("3M_2018_10K.pdf", collection=reports)  # or later: client.collections.add(reports, [doc])
result = client.search("What was capital expenditure in 2018?", collection=reports)

A document may be in many collections; deleting a collection leaves its documents.

Give documents your own metadata (strings, numbers and booleans) and filter by it:

doc = client.documents.upload("3M_2018_10K.pdf", metadata={"ticker": "MMM", "year": 2018})
client.documents.update(doc, metadata={"audited": True})          # None removes a key
recent = client.documents.list(filter={"year": {"$gte": 2020}})
result = client.search("Who is the CEO?", filter={"ticker": {"$in": ["MMM", "AAPL"]}})

Filters take $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and and $or.

Read pages: their text, or a page as a picture. Each page a search lists says how it is best read (page.read: "text", or "image" for a scan or a chart, whose text may be incomplete); include_text=True lists it with its text.

texts = client.documents.pages(doc, [38, 39])   # each with .text and .read
png = client.documents.page_image(doc, 38)       # PNG bytes
part = client.documents.page_image(doc, 38, box=(0, 0.3, 1, 0.6))  # a band of it, sharper

Agents

client.tools() gives an LLM agent seven tools over your documents: list_collections (free: your collections, to search one the question names), list_documents (free: documents whose metadata meets a filter, with your metadata's keys and values in its description, so the agent can write filters), search_documents (which documents answer a question, for when a filter cannot pick them), search_pages (with each page's text), read_pages (text, with a picture of any page that is one), view_pages (pages as pictures) and zoom_page (part of a page, sharper, for small print).

tools = client.tools(collection=reports)  # or filter=..., or neither for all your documents

response = anthropic.messages.create(model=..., tools=tools.definitions, messages=messages)
for block in response.content:
    if block.type == "tool_use":
        content = tools.run(block.name, block.input).anthropic()  # the tool_result content

For OpenAI, pass tools.openai; tools.run() gives .text and .images, each image with .data_url. With the OpenAI Agents SDK (pip install "pageindex-search[agents]"), tools.openai_agents() gives the tools ready to use:

from agents import Agent, Runner

agent = Agent(name="Reports", instructions="Answer from the documents.", tools=tools.openai_agents())
print(Runner.run_sync(agent, "What was capital expenditure in 2018?").final_output)

MCP

The same tools work in Claude Desktop, Cursor and other MCP clients. Add this to the client's MCP settings:

{
  "mcpServers": {
    "page-search": {
      "command": "uvx",
      "args": ["--from", "pageindex-search[mcp]", "pageindex-search-mcp", "--collection", "col_..."],
      "env": {"PAGEINDEX_SEARCH_API_KEY": "pis_live_..."}
    }
  }
}

Leave out --collection for all your documents, or pass --filter '{"year": 2024}' for those whose metadata meets it. In Python, tools.mcp() gives the server to run yourself.

Reference

PageSearch(api_key=None, base_url=None, timeout=120, max_retries=2) The key defaults to PAGEINDEX_SEARCH_API_KEY.
documents.upload(file, collection=None, metadata=None, wait=True) A path, bytes or an open file. Returns a Document, ready to search unless wait=False.
search(questions, document=None, documents=None, collection=None, filter=None, max_documents=None, document_top_k=None, top_k=None, include_text=False, idempotency_key=None) Up to 10 questions. For each, the pages that help, under the documents they are in: of one document, or across documents (those named, a collection's, those meeting filter, or all), whose most relevant are opened: those likely to answer, at most max_documents (default 3), or exactly document_top_k. Each document lists the pages likely to help, or exactly top_k, each with how it is best read, and with include_text, its text; without top_k, more_pages lists the next most relevant, without text. A search retried after a timeout is not charged twice.
documents.search(questions, document_ids=None, collection=None, filter=None, top_k=None, idempotency_key=None) Up to 10 questions of your documents (all of them, up to 1,000, those named, or a collection's). Each answer lists the documents likely to hold it, which may be none, or exactly top_k; without top_k, more_documents lists the next most relevant. Each document is priced at the document search price.
documents.list(limit=20, offset=0, collection=None, filter=None) Your documents, newest first, or a collection's, or those whose metadata meets filter: limit of them after the first offset. Loop over it for every document; .data, .has_more and .total (how many in all) are one page.
documents.get(id), documents.delete(document) One document; delete one.
documents.pages(document, pages), documents.page_image(document, page, box=None) The text of up to 20 pages, each with how it is best read; a page as PNG bytes, or with box (x0, y0, x1, y1 as fractions of the page from its top left) part of it, sharper.
tools(collection=None, filter=None) Tools for an LLM agent: .definitions (Anthropic), .openai, .openai_agents() (OpenAI Agents SDK), .mcp() (an MCP server), and .run(name, input).
documents.update(document, metadata=) Set metadata keys; None removes one.
collections.schema(collection), documents.schema(collection=None, filter=None) The metadata keys the documents have: each key's types, how many documents have it, its least and greatest numbers and commonest strings. Counted as they are now; nothing is enforced.
collections.create(name), .list(), .get(id), .rename(collection, name), .delete(collection) Named groups of your documents, as folders: a document is in at most one. Deleting one deletes its documents.
collections.add(collection, documents), .remove(collection, document) Move documents into a collection, from any other, or one out of it, into none. documents.upload(..., collection=) makes a new one in it.
usage(start=None, end=None) Uploads, searches and cost by day, your credit and stored pages.

In 0.4, search(document, questions) became search(questions, document=document), and each answer lists documents, each with its pages (answer.pages is the first document's). Before 0.2 these were upload(), document(), documents(), iter_documents() and delete() on the client; they still work, with a warning, for now.

Errors raise PageSearchError, with the API's status, code, message and request_id.

See the API reference for every field and the pricing.

Metadata

Release files for pageindex-search 0.10.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pageindex-search 0.10.0
File Size Uploaded Provenance Actions
pageindex_search-0.10.0.tar.gz 25.8 kB Without provenance Details for pageindex_search-0.10.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pageindex-search 0.10.0
File Interpreter ABI Platform Actions
pageindex_search-0.10.0-py3-none-any.whl Python 3 none any Details for pageindex_search-0.10.0-py3-none-any.whl

Total release size: 49.1 kB

Release files / pageindex_search-0.10.0.tar.gz

Download URL pageindex_search-0.10.0.tar.gz
Size 25.8 kB
Tags Source
SHA-256 checksum
How to use checksums
5638044b23d04ed959057288c5053244424a0e8a05b75d821bb1a63e75187618
BLAKE2b-256 checksum
How to use checksums
50b41cd905f54766117492b1a9a0b2a4fbae19d4e37896c1594f1a4c3d7b3877
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / pageindex_search-0.10.0-py3-none-any.whl

Download URL pageindex_search-0.10.0-py3-none-any.whl
Size 23.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
273df9a41f2199cf28434280a24cef84e6691a9eb6db194fe8b1d1c7ef0d3d35
BLAKE2b-256 checksum
How to use checksums
6de43a24335977af8a4ea9aeca94c45faa56b9390ca1a18a6a6447f90f07b9b4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for pageindex-search 0.10.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page