index4.ai: Python Client
Index4AI is the Python client for index4.ai, the managed RAG / hybrid-search service. It indexes your document server-side into a project namespace and serves prompt-ready retrieval context directly: no vector store to run, no embeddings to compute, no context assembly to wire up.
pip install index4aiThe package requires Python 3.10+.
Authentication
index4.ai is project-scoped, not account-scoped. The client enforces this locally:
from index4ai import Index4AI
# Reads INDEX4AI_API_KEY from the environment.
# Validates the prefix and raises InvalidApiKeyError before any HTTP call.
client = Index4AI()| Env var | Purpose | Default |
|---|---|---|
INDEX4AI_API_KEY | Project API key, prefix poma_proj_gr_…. Account keys (poma_acc_…) are rejected at construction. | required |
INDEX4AI_API_BASE_URL | API host override. | https://api.index4.ai |
The client targets API version 1 (https://api.index4.ai/v1). Full provisioning flow: Authentication and Create a project.
Ingest
client.ingest(...) submits a file, waits for the job, and returns a typed IngestResult. No archive is downloaded — the document is indexed server-side into your project namespace and is immediately searchable. result.doc_id is the id to search and manage the document by.
from index4ai import Index4AI
client = Index4AI()
result = client.ingest("annual-report.pdf")
print(result.doc_id, result.status, result.usage)Instead of uploading a file, you can let the server fetch it from a public URL:
result = client.ingest(remote_url="https://example.com/annual-report.pdf")If you need to submit now and wait later (e.g. for batch ingest, or to free the calling process), use the split form:
job_id = client.submit("annual-report.pdf")
# … minutes or hours later, even from another process …
result = client.collect(job_id)Waiting can always be resumed with the same job_id; submitting the file again creates a second job. Two exceptions tell you which case you are in:
JobWaitError— the job exists and keeps running; only the wait failed. Resume withclient.collect(err.job_id).SubmitUncertainError— the connection failed mid-submit, so a job may or may not exist. Checkclient.list_docs()before submitting again.
If the same content was already indexed, the server keeps the existing document: result.deduplicated is True and result.doc_id is the pre-existing document's id.
Search
client.search(query) runs hybrid search across every document in the project namespace and returns a SearchContext — a single string field of XML + Markdown, structured and token-budgeted, that drops straight into an LLM prompt.
ctx = client.search("How did operating margin change year over year?")
print(ctx.context)To scope a search to one document (e.g. an in-document Q&A widget), use client.search_in_doc(query, doc_filter):
ctx = client.search_in_doc("summarize section 3", "doc_abc123")min_relevance, target_tokens, max_tokens, return_assets, exclude_doc_ids, and attribute_filters are accepted on both — see the Index4AI client reference for the full signature.
Manage documents
The namespace is queryable and mutable:
# List every document in the project namespace
for doc in client.list_docs():
print(doc.doc_id, doc.filename, doc.pages, doc.source_job_id)
# Inspect one
info = client.get_doc("doc_abc123")
# Remove one — frees both vectors and stored content
deleted = client.delete_doc("doc_abc123")
print(deleted.vectors_deleted, deleted.storage_deleted)DocInfo.source_job_id lets you correlate the document back to the job_id returned by submit(...).
Attributes
Documents can carry typed attributes that you filter on at query time. Attribute names are permanent per project and capped, so call client.list_attributes() before declaring any and reuse an existing name:
known = {a.name: a.type for a in client.list_attributes().attributes}
client.ingest(
"annual-report.pdf",
attributes={"source": "treasury"},
)
ctx = client.search(
"operating margin",
attribute_filters=[{"attr": "source", "op": "eq", "value": "treasury"}],
)Async
AsyncIndex4AI mirrors the sync surface for use inside asyncio code (web servers, agents, pipelines):
import asyncio
from index4ai import AsyncIndex4AI
async def main() -> None:
async with AsyncIndex4AI() as client:
result = await client.ingest("annual-report.pdf")
ctx = await client.search("operating margin year over year")
print(ctx.context)
asyncio.run(main())Migrating from poma.Grill
index4.ai was called Grill, and its Python client shipped in the poma package up to version 0.7. Your project, documents and project key stay the same; only the package and the names change:
Before (poma ≤ 0.7) | Now (index4ai) |
|---|---|
pip install poma | pip install index4ai |
from poma import Grill, AsyncGrill | from index4ai import Index4AI, AsyncIndex4AI |
POMA_GRILL_API_KEY | INDEX4AI_API_KEY (same project key) |
GrillIngestResult (find the doc via list_docs()) | IngestResult with doc_id |
GrillContext | SearchContext |
InvalidGrillApiKeyError | InvalidApiKeyError |
labels / meta_int_*, labels_any… / meta_int_*_gte… | attributes / attribute_schema, attribute_filters (see Attributes) |
return_page_images | removed |
Next
- Index4AI client reference — every method signature and return type.
- AsyncIndex4AI client reference.
- index4.ai for developers — endpoint-level reference and end-to-end concepts.