Skip to content

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.

bash
pip install index4ai

The package requires Python 3.10+.

Authentication ​

index4.ai is project-scoped, not account-scoped. The client enforces this locally:

python
from index4ai import Index4AI

# Reads INDEX4AI_API_KEY from the environment.
# Validates the prefix and raises InvalidApiKeyError before any HTTP call.
client = Index4AI()
Env varPurposeDefault
INDEX4AI_API_KEYProject API key, prefix poma_proj_gr_…. Account keys (poma_acc_…) are rejected at construction.required
INDEX4AI_API_BASE_URLAPI 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.

python
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:

python
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:

python
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 with client.collect(err.job_id).
  • SubmitUncertainError — the connection failed mid-submit, so a job may or may not exist. Check client.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.

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.

python
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):

python
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:

python
# 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:

python
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):

python
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 pomapip install index4ai
from poma import Grill, AsyncGrillfrom index4ai import Index4AI, AsyncIndex4AI
POMA_GRILL_API_KEYINDEX4AI_API_KEY (same project key)
GrillIngestResult (find the doc via list_docs())IngestResult with doc_id
GrillContextSearchContext
InvalidGrillApiKeyErrorInvalidApiKeyError
labels / meta_int_*, labels_any… / meta_int_*_gte…attributes / attribute_schema, attribute_filters (see Attributes)
return_page_imagesremoved

Next ​