# Documents
Source: https://docs.polyvia.ai/api-reference/endpoint/documents
List, retrieve, update, and delete documents in your workspace
The Documents endpoints let you list, fetch, update, and delete documents in your workspace.
## List Documents
List documents in your workspace with optional filters.
### Endpoint
`GET /api/v1/documents`
### Query Parameters
Filter by status: `uploading`, `parsing`, `completed`, `failed`
Filter to documents in a specific group.
Comma-separated list of group IDs. Returns documents belonging to any of the specified groups.
Cannot be combined with `group_id`.
### Response
Array of document objects
Document identifier
Document display name
Processing status: `uploading`, `parsing`, `completed`, `failed`
MIME type of the uploaded file
AI-generated summary of the document
Unix timestamp (milliseconds) of when the document was uploaded
Group identifier if the document belongs to a group, otherwise `null`
### Examples
```bash All completed documents theme={null}
curl "https://app.polyvia.ai/api/v1/documents?status=completed" \
-H "Authorization: Bearer poly_"
```
```bash Filter by group theme={null}
curl "https://app.polyvia.ai/api/v1/documents?group_id=g_..." \
-H "Authorization: Bearer poly_"
```
```bash Filter by multiple groups theme={null}
curl "https://app.polyvia.ai/api/v1/documents?group_ids=g_abc,g_def" \
-H "Authorization: Bearer poly_"
```
```python Python SDK theme={null}
from polyvia import Polyvia
client = Polyvia(api_key="poly_...")
# Filter by status and group
docs = client.documents.list(status="completed", group_id="g_...")
# Filter across multiple groups
docs = client.documents.list(group_ids=["g_abc", "g_def"])
```
```json Success theme={null}
{
"documents": [
{
"id": "k57abc123...",
"title": "Q4 2024 Report",
"status": "completed",
"file_type": "application/pdf",
"summary": "This document covers Q4 financial results...",
"created_at": 1712345678000,
"group_id": "g_finance"
}
]
}
```
***
## Get Document
Fetch metadata for a single document.
### Endpoint
`GET /api/v1/documents/{document_id}`
### Path Parameters
The document identifier
### Response
Document identifier
Document display name
Processing status: `uploading`, `parsing`, `completed`, `failed`
MIME type of the uploaded file
Signed URL to download the original file
AI-generated summary of the document
Unix timestamp (milliseconds) of upload
Group identifier if the document belongs to a group
### Example
```bash cURL theme={null}
curl "https://app.polyvia.ai/api/v1/documents/k57abc123..." \
-H "Authorization: Bearer poly_"
```
```json Success theme={null}
{
"id": "k57abc123...",
"title": "Q4 2024 Report",
"status": "completed",
"file_type": "application/pdf",
"file_url": "https://...",
"summary": "This document covers Q4 financial results...",
"created_at": 1712345678000,
"group_id": "g_finance"
}
```
***
## Update Document
Update a document's group assignment. Pass `null` to remove it from its current group.
### Endpoint
`PATCH /api/v1/documents/{document_id}`
### Path Parameters
The document identifier
### Request Body
`application/json`
The group to assign the document to, or `null` to remove it from any group.
### Example
```bash Move to a group theme={null}
curl -X PATCH "https://app.polyvia.ai/api/v1/documents/k57abc123..." \
-H "Authorization: Bearer poly_" \
-H "Content-Type: application/json" \
-d '{"group_id": "g_finance"}'
```
```bash Remove from group theme={null}
curl -X PATCH "https://app.polyvia.ai/api/v1/documents/k57abc123..." \
-H "Authorization: Bearer poly_" \
-H "Content-Type: application/json" \
-d '{"group_id": null}'
```
```python Python SDK theme={null}
from polyvia import Polyvia
client = Polyvia(api_key="poly_...")
client.documents.update("k57abc123...", group_id="g_finance") # move
client.documents.update("k57abc123...", group_id=None) # remove from group
```
```json Success theme={null}
{ "ok": true }
```
***
## Delete Document
Permanently delete a document and all its indexed content.
### Endpoint
`DELETE /api/v1/documents/{document_id}`
### Path Parameters
The document identifier
### Example
```bash cURL theme={null}
curl -X DELETE "https://app.polyvia.ai/api/v1/documents/k57abc123..." \
-H "Authorization: Bearer poly_"
```
```python Python SDK theme={null}
client.documents.delete("k57abc123...")
```
```json Success theme={null}
{ "ok": true }
```
Deletion is permanent and cannot be undone. All indexed content for the document is removed immediately.
# Groups
Source: https://docs.polyvia.ai/api-reference/endpoint/groups
Organise documents into named collections
Groups let you organise documents into named collections that can be queried or managed together. Assign documents to a group on ingest, or move them later via the [Documents](/api-reference/endpoint/documents) endpoint.
## List Groups
Return all groups in your workspace.
### Endpoint
`GET /api/v1/groups`
### Response
Group identifier
Display name
Hex colour used in the Polyvia Platform UI
Unix timestamp (ms)
### Example
```bash cURL theme={null}
curl "https://app.polyvia.ai/api/v1/groups" \
-H "Authorization: Bearer poly_"
```
```python Python SDK theme={null}
from polyvia import Polyvia
client = Polyvia(api_key="poly_...")
for g in client.groups.list():
print(g.id, g.name)
```
```json Success theme={null}
{
"groups": [
{
"id": "g_finance",
"name": "Finance",
"color": "#3b82f6",
"created_at": 1712345678000
}
]
}
```
***
## Create Group
Create a new group.
### Endpoint
`POST /api/v1/groups`
### Request Body
`application/json`
Display name for the group
### Response
The newly created group identifier
### Example
```bash cURL theme={null}
curl -X POST "https://app.polyvia.ai/api/v1/groups" \
-H "Authorization: Bearer poly_" \
-H "Content-Type: application/json" \
-d '{"name": "Finance"}'
```
```python Python SDK theme={null}
group = client.groups.create("Finance")
group_id = group["group_id"]
```
```json Success theme={null}
{
"group_id": "g_finance"
}
```
***
## Delete All Documents in a Group
Remove all documents assigned to a group. The group itself is kept.
### Endpoint
`DELETE /api/v1/groups/{group_id}/documents`
### Path Parameters
The group identifier
### Example
```bash cURL theme={null}
curl -X DELETE "https://app.polyvia.ai/api/v1/groups/g_finance/documents" \
-H "Authorization: Bearer poly_"
```
```python Python SDK theme={null}
client.groups.delete_documents("g_finance")
```
```json Success theme={null}
{ "ok": true }
```
***
## Delete Group
Delete an empty group. The group must have no documents assigned to it — call [Delete All Documents in a Group](#delete-all-documents-in-a-group) first, or pass `delete_documents=true` in the Python SDK.
### Endpoint
`DELETE /api/v1/groups/{group_id}`
### Path Parameters
The group identifier
### Example
```bash cURL theme={null}
# First remove all documents from the group, then delete it
curl -X DELETE "https://app.polyvia.ai/api/v1/groups/g_finance/documents" \
-H "Authorization: Bearer poly_"
curl -X DELETE "https://app.polyvia.ai/api/v1/groups/g_finance" \
-H "Authorization: Bearer poly_"
```
```python Python SDK theme={null}
# Convenience flag — deletes documents first, then the group
client.groups.delete("g_finance", delete_documents=True)
# Or in two steps
client.groups.delete_documents("g_finance")
client.groups.delete("g_finance")
```
```json Success theme={null}
{ "ok": true }
```
A group with documents still assigned cannot be deleted — you will receive a `400` error. Remove or reassign all documents first.
# Ingest
Source: https://docs.polyvia.ai/api-reference/endpoint/ingest
Upload documents and track ingestion status
The Ingest endpoints handle document uploads and status polling. Parsing
runs asynchronously — upload returns a `task_id` immediately, which you
then poll until ingestion completes.
**Use an official SDK if you can** — the [Python](/products/python-sdk)
and [JavaScript](/products/js-sdk) SDKs wrap the direct-upload flow into
a single `client.ingest.file(...)` / `client.ingest.batch(...)` call
that works for any file size. The endpoint-level details below are only
needed if you're calling the REST API directly.
## Upload a Document
The default upload flow streams your file bytes straight from your client
to Polyvia's storage backend, then asks our API to register and parse it.
This is a **three-step** flow that works for any file size — there is no
practical upper limit.
### Step 1 — Get an upload URL
`POST /api/v1/ingest/upload-url`
No request body.
Short-lived signed URL for the storage backend. Expires in \~1 hour and
is single-use.
```json Response theme={null}
{ "upload_url": "https://.convex.cloud/api/storage/upload?token=..." }
```
### Step 2 — PUT the file to that URL
Stream the raw file bytes to the returned `upload_url`. Set `Content-Type`
to the file's MIME type.
Do **not** include your Polyvia `Authorization: Bearer poly_`
header on this request. The URL is already signed, and forwarding the
key to a different origin would leak it unnecessarily.
Storage responds with the new object's identifier:
```json theme={null}
{ "storageId": "kg2abc...", "size": 12345678 }
```
### Step 3 — Finalize the upload
`POST /api/v1/ingest/finalize`
`application/json`
The `storageId` returned by storage in Step 2.
MIME type of the uploaded file.
Display name. Defaults to "Untitled" if omitted.
Assign the document to a group on creation.
### Response
Unique identifier for the uploaded document
Ingestion task identifier — use this to poll for status
Initial status: always `pending`
### Example
```bash cURL + jq theme={null}
# 1. Get a signed upload URL
UPLOAD_URL=$(curl -s -X POST https://app.polyvia.ai/api/v1/ingest/upload-url \
-H "Authorization: Bearer poly_" | jq -r '.upload_url')
# 2. PUT the file bytes directly to storage (no auth header!)
STORAGE_ID=$(curl -s -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/pdf" \
--data-binary @report.pdf | jq -r '.storageId')
# 3. Finalize — register the document and queue parsing
curl -X POST https://app.polyvia.ai/api/v1/ingest/finalize \
-H "Authorization: Bearer poly_" \
-H "Content-Type: application/json" \
-d "{\"storage_id\": \"$STORAGE_ID\", \"file_type\": \"application/pdf\", \"name\": \"Q4 Report\", \"group_id\": \"g_...\"}"
```
```python httpx theme={null}
import httpx
API_KEY = "poly_"
BASE = "https://app.polyvia.ai"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# 1. Get an upload URL
upload_url = httpx.post(
f"{BASE}/api/v1/ingest/upload-url", headers=HEADERS
).json()["upload_url"]
# 2. PUT the file directly to storage (no auth header here!)
with open("report.pdf", "rb") as f:
storage_id = httpx.put(
upload_url,
content=f.read(),
headers={"Content-Type": "application/pdf"},
timeout=300,
).json()["storageId"]
# 3. Finalize
resp = httpx.post(
f"{BASE}/api/v1/ingest/finalize",
headers=HEADERS,
json={
"storage_id": storage_id,
"file_type": "application/pdf",
"name": "Q4 Report",
"group_id": "g_...",
},
).json()
# {"document_id": "...", "task_id": "...", "status": "pending"}
```
```python Python SDK theme={null}
from polyvia import Polyvia
client = Polyvia(api_key="poly_...")
# The SDK runs the three-step direct-upload flow internally.
result = client.ingest.file("report.pdf", name="Q4 Report", group_id="g_...")
client.ingest.wait(result.task_id)
```
```json Finalize response theme={null}
{
"document_id": "k57abc123...",
"task_id": "3f2e1d0c-...",
"status": "pending"
}
```
***
## Upload Multiple Documents
For batches, run the direct-upload flow once per file. The official SDKs
do this in `client.ingest.batch(...)` — each file is uploaded and
finalized independently, so a failure on one file is isolated to that
entry instead of failing the whole batch.
```python Python SDK theme={null}
from polyvia import Polyvia
client = Polyvia(api_key="poly_...")
batch = client.ingest.batch(
["q3.pdf", "q4.pdf"],
names=["Q3 Report", "Q4 Report"],
group_id="g_...",
)
for item in batch.results:
if item.error:
print(f"failed: {item.file}: {item.error}")
else:
client.ingest.wait(item.task_id)
```
```ts JavaScript SDK theme={null}
import { Polyvia } from "polyvia";
const client = new Polyvia({ apiKey: "poly_..." });
const items = await client.ingest.batch(["q3.pdf", "q4.pdf"], {
names: ["Q3 Report", "Q4 Report"],
groupId: "g_...",
});
for (const item of items) {
if (item.error) {
console.error(`failed: ${item.error}`);
} else if (item.task_id) {
await client.ingest.wait(item.task_id);
}
}
```
***
## Quick Multipart Upload (small files)
These multipart endpoints proxy file bytes through the API server,
which has a **4.5 MB total request-body limit**. They exist as a
one-call convenience for small uploads. For any file size, prefer the
direct-upload flow above (or use an SDK, which does it for you).
### `POST /api/v1/ingest`
`multipart/form-data`
The document to upload. See [Supported File Formats](#supported-file-formats) below.
Display name in your workspace. Defaults to the filename.
Assign the document to a group on upload.
Returns the same `{document_id, task_id, status}` shape as `/finalize`.
```bash theme={null}
curl -X POST https://app.polyvia.ai/api/v1/ingest \
-H "Authorization: Bearer poly_" \
-F "file=@/path/to/report.pdf" \
-F "name=Q4 2024 Report" \
-F "group_id=g_..."
```
### `POST /api/v1/ingest/batch`
`multipart/form-data`. Same fields as `/ingest` but `files` is repeated
per file and `names` is a comma-separated string aligned to `files`.
Returns `{results: [...], errors: [...] | null}`.
```bash theme={null}
curl -X POST https://app.polyvia.ai/api/v1/ingest/batch \
-H "Authorization: Bearer poly_" \
-F "files=@q3.pdf" \
-F "files=@q4.pdf" \
-F "names=Q3 Report,Q4 Report" \
-F "group_id=g_..."
```
***
## Check Ingestion Status
Poll a parse task started by either upload flow.
### Endpoint
`GET /api/v1/ingest/{task_id}`
### Path Parameters
The task identifier returned by `/ingest/finalize` (or the legacy multipart endpoints)
### Response
Task identifier
Document identifier
Processing status (see table below)
Error message if status is `failed`, otherwise `null`
| `status` value | Meaning |
| -------------- | --------------------------------- |
| `pending` | Queued, not yet started |
| `parsing` | Being parsed and indexed |
| `completed` | Ready to query |
| `failed` | Parsing failed; see `error` field |
### Example
```bash cURL theme={null}
curl https://app.polyvia.ai/api/v1/ingest/3f2e1d0c-... \
-H "Authorization: Bearer poly_"
```
```bash Poll until completed theme={null}
while true; do
STATUS=$(curl -s \
-H "Authorization: Bearer poly_" \
"https://app.polyvia.ai/api/v1/ingest/3f2e1d0c-..." \
| jq -r '.status')
echo "Status: $STATUS"
[ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ] && break
sleep 5
done
```
```python Python SDK theme={null}
from polyvia import Polyvia
client = Polyvia(api_key="poly_...")
# Blocks until done — raises IngestionError on failure
done = client.ingest.wait(task_id, poll_interval=5, timeout=300)
```
```json Success theme={null}
{
"task_id": "3f2e1d0c-...",
"document_id": "k57abc123...",
"status": "completed",
"error": null
}
```
***
## Supported File Formats
| Category | Extensions |
| --------- | ----------------------------------------- |
| Documents | `.pdf`, `.docx`, `.pptx` |
| Text | `.txt`, `.md` (Markdown) |
| Images | `.png`, `.jpg` / `.jpeg`, `.webp`, `.gif` |
| Audio | `.wav`, `.mp3`, `.m4a` |
See [Supported Formats](/products/supported-formats) for parser-by-parser details on what gets extracted from each file type.
Documents are typically processed within 1–2 minutes. Audio files take longer — roughly real-time playback for transcription. Poll the status endpoint every few seconds to check progress.
# Query
Source: https://docs.polyvia.ai/api-reference/endpoint/query
Ask natural-language questions about your documents
The Query endpoint lets you ask questions about your documents using natural language. Scope the query to a single document, a group (or multiple groups), or leave it unscoped to search your entire workspace.
## Query Documents
### Endpoint
`POST /api/v1/query`
### Scoping behaviour
| Parameters provided | Scope |
| ------------------- | ------------------------------------------- |
| *(none)* | All completed documents in your workspace |
| `document_id` | That document only (fastest) |
| `group_id` | All completed documents in that group |
| `group_ids` | All completed documents across those groups |
### Request Body
`application/json`
Your natural-language question
Restrict the query to a single document.
Restrict the query to documents in a specific group. Cannot be combined with `document_id` or `group_ids`.
Restrict the query to documents across multiple groups. Cannot be combined with `document_id` or `group_id`.
### Response
The answer to your question
The document used to answer (present only for single-document queries)
### Examples
```bash All documents theme={null}
curl -X POST https://app.polyvia.ai/api/v1/query \
-H "Authorization: Bearer poly_" \
-H "Content-Type: application/json" \
-d '{"query": "What risks are mentioned across all reports?"}'
```
```bash Single document theme={null}
curl -X POST https://app.polyvia.ai/api/v1/query \
-H "Authorization: Bearer poly_" \
-H "Content-Type: application/json" \
-d '{
"query": "What is the executive summary?",
"document_id": "k57abc123..."
}'
```
```bash Group query theme={null}
curl -X POST https://app.polyvia.ai/api/v1/query \
-H "Authorization: Bearer poly_" \
-H "Content-Type: application/json" \
-d '{
"query": "What are the key findings?",
"group_id": "g_finance"
}'
```
```bash Multi-group query theme={null}
curl -X POST https://app.polyvia.ai/api/v1/query \
-H "Authorization: Bearer poly_" \
-H "Content-Type: application/json" \
-d '{
"query": "Compare the Q3 and Q4 results.",
"group_ids": ["g_q3", "g_q4"]
}'
```
```python Python SDK theme={null}
from polyvia import Polyvia
client = Polyvia(api_key="poly_...")
# All documents
answer = client.query("What risks are mentioned across all reports?")
# Single document (fastest)
answer = client.query("Summarise section 3.", document_id="doc_...")
# Group
answer = client.query("Key findings?", group_id="g_finance")
# Multiple groups
answer = client.query("Compare Q3 vs Q4.", group_ids=["g_q3", "g_q4"])
print(answer.answer)
```
```json Success theme={null}
{
"answer": "The gross margin improved by 4 pp year-over-year, driven by...",
"document_id": "k57abc123..."
}
```
Documents must have `status=completed` before they can be queried. Use the [Check Ingestion Status](/api-reference/endpoint/ingest) endpoint to confirm a document is ready.
# Usage & Rate Limits
Source: https://docs.polyvia.ai/api-reference/endpoint/usage
Monitor your API consumption and current rate-limit headroom
These endpoints let you track how much of your plan you've used and how much capacity you have right now.
## Get Usage
Return request and document counts for the current API key, both for the current calendar month and all-time.
### Endpoint
`GET /api/v1/usage`
Counters in this response have two scopes. **Per-key:** `requests`, `ingests`, `queries` reflect just what *this* API key has done. **Per-workspace:** `pages`, `audio_seconds`, and `documents_stored` reflect the workspace the key belongs to (across all keys and in-app activity).
### Response
Requests made this calendar month
All-time request count
Documents ingested this calendar month
All-time ingest count
Queries made this calendar month
All-time query count
Pages processed this calendar month (workspace-wide)
All-time pages processed (workspace-wide)
Seconds of audio processed this calendar month (workspace-wide)
All-time audio seconds processed (workspace-wide)
Number of documents currently in your workspace
### Example
```bash cURL theme={null}
curl "https://app.polyvia.ai/api/v1/usage" \
-H "Authorization: Bearer poly_"
```
```python Python SDK theme={null}
from polyvia import Polyvia
client = Polyvia(api_key="poly_...")
usage = client.usage()
print(usage.usage.requests.period) # requests this month (per key)
print(usage.usage.pages.period) # pages this month (workspace)
print(usage.usage.audio_seconds.period / 60) # minutes of audio this month
print(usage.usage.documents_stored) # live document count
```
```json Success theme={null}
{
"usage": {
"requests": { "period": 142, "total": 3801 },
"ingests": { "period": 12, "total": 204 },
"queries": { "period": 130, "total": 3597 },
"pages": { "period": 318, "total": 5421 },
"audio_seconds": { "period": 1840, "total": 34920 },
"documents_stored": 47
}
}
```
***
## Get Rate Limits
Return the rate-limit thresholds for your plan and how much capacity remains right now.
### Endpoint
`GET /api/v1/rate-limits`
### Response
Hard limits for your plan (requests per minute, per month, etc.)
Real-time headroom — how much capacity remains in each window.
ISO 8601 timestamps of when each counter resets.
When the per-minute window resets
When the monthly counters reset (first day of next month)
### Example
```bash cURL theme={null}
curl "https://app.polyvia.ai/api/v1/rate-limits" \
-H "Authorization: Bearer poly_"
```
```python Python SDK theme={null}
limits = client.rate_limits()
print(limits.limits["requests_per_minute"])
print(limits.current["remaining_this_minute"])
print(limits.resets_at.month) # ISO timestamp of next monthly reset
```
```json Success theme={null}
{
"limits": {
"requests_per_minute": 60,
"requests_per_month": 10000,
"documents_per_month": 500,
"queries_per_month": 2000
},
"current": {
"remaining_this_minute": 58,
"remaining_requests_this_month": 9858,
"remaining_documents_this_month": 488,
"remaining_queries_this_month": 1870
},
"resets_at": {
"minute": "2025-04-23T14:32:00Z",
"month": "2025-05-01T00:00:00Z"
}
}
```
Rate limits are informational — the API does not block requests when limits are reached, but you should monitor these values and implement back-off logic in production integrations.
# Overview
Source: https://docs.polyvia.ai/api-reference/introduction
Authentication, base URL, and scoping for the Polyvia REST API
The Polyvia REST API lets you upload documents, organise them into groups, ask natural-language questions across your workspace, and monitor usage. This page covers authentication, the base URL, and workspace scoping; the pages that follow document each endpoint.
The [Python SDK](/products/python-sdk) (`pip install polyvia`) wraps every endpoint with a typed client and adds first-class support for the MCP server and agent frameworks.
## Authentication
All API requests require authentication using an API key. Include your key in the `Authorization` header:
```bash theme={null}
Authorization: Bearer poly_
```
Create, use, and manage your key — and how workspace binding works.
## Workspace Scoping
Each API key is bound to **one workspace** at the moment you create it — either your personal workspace or a specific organization. Every request made with that key reads and writes only that workspace's data; the key cannot reach across workspaces.
* Mint a key while you're in your **personal workspace** → it sees personal documents, groups, and chats only.
* Mint a key while you're in an **organization** → it sees that org's shared documents and groups. Any teammate's API key minted in the same org reads the same data.
To work across multiple workspaces from the same script, switch workspace and create a separate key for each (on the **API** page). Use the appropriate key per request.
Switching your *active* workspace in the UI later does not change what an existing key can access. The binding is permanent — revoke and re-mint to change scope.
## Base URL
```
https://app.polyvia.ai
```
All endpoints are prefixed with `/api/v1`.
## Quick Start
Here's a complete example: ingest a document and query for insights.
```python Python theme={null}
import httpx
import time
API_KEY = "poly_"
BASE = "https://app.polyvia.ai"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# 1. Upload
with open("report.pdf", "rb") as f:
resp = httpx.post(
f"{BASE}/api/v1/ingest",
headers=HEADERS,
files={"file": ("report.pdf", f, "application/pdf")},
data={"name": "Q4 Report"},
)
resp.raise_for_status()
task_id = resp.json()["task_id"]
document_id = resp.json()["document_id"]
# 2. Poll until ingestion completes
while True:
status = httpx.get(f"{BASE}/api/v1/ingest/{task_id}", headers=HEADERS).json()["status"]
print(f"Status: {status}")
if status in ("completed", "failed"):
break
time.sleep(5)
# 3. Query
answer = httpx.post(
f"{BASE}/api/v1/query",
headers=HEADERS,
json={"query": "What are the key findings?", "document_id": document_id},
).json()["answer"]
print(answer)
```
```bash cURL theme={null}
# 1. Upload
curl -X POST https://app.polyvia.ai/api/v1/ingest \
-H "Authorization: Bearer poly_" \
-F "file=@/path/to/report.pdf" \
-F "name=Q4 Report"
# 2. Check status (replace TASK_ID)
curl https://app.polyvia.ai/api/v1/ingest/TASK_ID \
-H "Authorization: Bearer poly_"
# 3. Query
curl -X POST https://app.polyvia.ai/api/v1/query \
-H "Authorization: Bearer poly_" \
-H "Content-Type: application/json" \
-d '{"query": "What are the key findings?", "document_id": "DOCUMENT_ID"}'
```
## Support
Need help? Reach out to our team:
* **Email**: [mgierlach5@gmail.com](mailto:mgierlach5@gmail.com)
* **Platform**: [Polyvia Platform](/products/platform)
# Core concepts
Source: https://docs.polyvia.ai/concepts
Documents, groups, ingestion, querying and citations
A quick mental model of how Polyvia works. Everything in the API and SDKs maps
to these five ideas.
## Documents
A **document** is anything you ingest — a PDF, slide deck, spreadsheet, scan,
image or audio file. Polyvia extracts the real data points (not 300-token
captions) from each page and indexes them into the knowledge graph. Each
document has an opaque `document_id`.
PDFs, Office & Google docs (DOCX / PPTX / XLSX), Markdown, text, images, audio.
## Groups
A **group** is a named collection of documents — a project, a deal, a quarter.
Groups have a human **name** and an opaque backend **id**; you usually just pass
the name and the SDK resolves it. Scope a query to a group to ask a question
across exactly that set of documents.
```python theme={null}
client.ingest.file("q4.pdf", group="FY24 Earnings")
client.query("Revenue trend?", group="FY24 Earnings")
```
## Ingestion is asynchronous
Uploading a document kicks off a background **task**. The upload call returns
immediately with a `task_id` and a `document_id`; the document moves through
`pending → processing → completed` (or `failed`). Poll
`ingest.status(task_id)` or block with `ingest.wait(task_id)` before querying.
SDKs stream file bytes straight to storage — there's no practical file-size
cap and a failure on one file in a batch never affects the others.
## Querying
A **query** is a natural-language question. Scope it three ways:
| Scope | How | When |
| --------------- | --------------------------------- | ------------------------- |
| One document | `document_id=...` | Fastest; precise context |
| A group | `group="..."` / `group_ids=[...]` | Ask across a project |
| Whole workspace | *(no scope)* | Search everything indexed |
Every answer is **grounded**: it comes back with citations pointing to the exact
document, page and visual region the facts were drawn from.
## Workspaces & keys
Each API key is bound to **one workspace** (your personal space or a specific
org) at creation time, and only ever reads and writes that workspace's data. To
work across workspaces, mint a separate key per workspace.
How keys, the Bearer header and workspace binding work.
# How to get API key
Source: https://docs.polyvia.ai/get-api-key
Create, use, and manage your Polyvia API key
Every request to the Polyvia API is authenticated with an API key. Here's how to
create one, use it, and keep it scoped correctly.
## Create a key
Open the **Polyvia Platform** and sign up or log in.
Click **API** in the sidebar, then **Create API Key**, give it a name, and optionally set
an expiry date.
The key is shown **only once** — copy it now and store it somewhere safe. All
keys start with `poly_`.
Keep your API key secret. Never commit it to a repo or expose it in client-side
code. If a key leaks, revoke it on the **API** page and mint a new one.
## Use the key
Pass the key when you create a client:
```python Python theme={null}
from polyvia import Polyvia
client = Polyvia(api_key="poly_")
```
```ts TypeScript theme={null}
import { Polyvia } from "polyvia";
const client = new Polyvia({ apiKey: "poly_" });
```
```bash cURL theme={null}
curl https://app.polyvia.ai/api/v1/usage \
-H "Authorization: Bearer poly_"
```
Prefer not to hard-code it? Both SDKs also read the key from the
`POLYVIA_API_KEY` environment variable — `export POLYVIA_API_KEY=poly_` and
you can drop the `api_key` / `apiKey` argument.
## Workspace scoping
Each key is bound to **one workspace** at the moment you create it — your personal
workspace or a specific organization — and only ever reads and writes that
workspace's data.
* Mint a key in your **personal workspace** → it sees your personal documents,
groups, and chats only.
* Mint a key in an **organization** → it sees that org's shared documents and
groups; any teammate's key minted in the same org reads the same data.
To work across workspaces, switch workspace and create a
separate key for each.
Switching your *active* workspace in the UI later does not change what an
existing key can access — the binding is permanent. Revoke and re-mint to
change scope.
## Manage & revoke
Return to the **API** page any time to see your keys, their names and expiry,
and to delete (revoke) a key. Revocation takes effect immediately.
Have your key? Ingest your first batch and query it in a couple of minutes.
# Polyvia
Source: https://docs.polyvia.ai/index
Multimodal Document Retrieval API
**Polyvia: Multimodal Document Retrieval API.**
We're releasing **Polyvia 1**, as two products:
* **Polyvia API**: Multimodal Document Retrieval API (for developers of AI agents).
* **Polyvia Platform**: Search & Exploration over multimodal docs (for knowledge workers in enterprises).
Agentic, file-by-file search (e.g. Claude Code, Claude Cowork, Codex) works only
up to \~100 multimodal files — past that it's too slow. So at scale, when you're
connecting an enterprise's large internal datasets, you still need retrieval. And
the multimodal infra tools today stop at visual extractors / PDF parsers (e.g.
Reducto, LlamaIndex). We built Polyvia Engine — an end-to-end pipeline for multimodal document
retrieval: **VLM Visual Extractor → Multimodal Knowledge Ontology →
Self-Improving Retrieval Agent**.
We index your unstructured & visual & multimodal docs (PDFs, charts, slides, complex
tables, infographics, scans, handwriting, invoices, and more) into multimodal knowledge
ontology, and provide you with a retrieval endpoint.
## Start in 30 seconds
**Get your API key** in the **Polyvia Platform** — open **API** in the sidebar and
click **Create API Key**. It's shown only once, and all keys start with `poly_`.
```bash pip theme={null}
pip install polyvia
```
```bash npm theme={null}
npm install polyvia
```
Ingest a batch into a group, then ask one question across the whole corpus.
```python Python theme={null}
from polyvia import Polyvia
client = Polyvia(api_key="poly_")
# Ingest a batch into a group, then ask one question across all of it.
items = client.ingest.batch(
["q1.pdf", "q2.pdf", "q3.pdf", "q4.pdf"],
group="FY24 Earnings",
)
for item in items:
client.ingest.wait(item.task_id)
# Answers cite the exact page in each document.
print(client.query("How did revenue trend across the four quarters?",
group="FY24 Earnings").answer)
```
```ts TypeScript theme={null}
import { Polyvia } from "polyvia";
const client = new Polyvia({ apiKey: "poly_" });
// Ingest a batch into a group, then ask one question across all of it.
const items = await client.ingest.batch(
["q1.pdf", "q2.pdf", "q3.pdf", "q4.pdf"],
{ group: "FY24 Earnings" },
);
await Promise.all(items.map((i) => client.ingest.wait(i.task_id)));
// Answers cite the exact page in each document.
const answer = await client.query(
"How did revenue trend across the four quarters?",
{ group: "FY24 Earnings" },
);
console.log(answer.answer);
```
**See also**
Get a key, ingest a batch, scope queries to a group — in a couple of minutes.
Typed client with sync + async, MCP and agent-tools support.
## Polyvia 1
**Polyvia 1** ships as two products — the **Polyvia API** and the **Polyvia Platform**.
Multimodal Document Retrieval API, for developers of AI agents.
Search & Exploration over multimodal docs, for knowledge workers in enterprises.
**See also**
Faster ingestion, Office/Google formats, knowledge-graph view, and more — see what's shipped.
## Polyvia Engine
One pipeline turns scattered visual & multimodal files into a queryable knowledge
layer — then answers in sub-200ms, grounded in a visual citation.
Reads the hardest visual documents — charts, infographics, complex multi-page
tables, slides, scans, handwriting, pictures — into structured facts.
Disambiguates and connects every extracted fact into one semantic ontology
over your whole corpus — a single, queryable source of truth.
Agentic, multi-hop retrieval that self-improves over time. Every answer
grounded in a visual citation tied to the exact source page.
**See also**
Documents, groups, ingestion, querying and citations.
Every modality and file type Polyvia ingests.
Import from Drive, Dropbox, OneDrive, Notion, S3 and Slack.
## Build with Polyvia
Typed client with sync + async, MCP and agent-tools support.
Multimodal Document Retrieval API — for developers of AI agents. Ingest,
groups, query and usage.
Fully-typed ESM/CJS client for Node and modern JS.
Connect Polyvia to Claude, Cursor and other MCP tools.
Drop-in skills for Claude Code, Cursor and agent environments.
## Stay connected
# Polyvia API
Source: https://docs.polyvia.ai/products/api
Multimodal Document Retrieval API — for developers of AI agents
**Polyvia API** is the **Multimodal Document Retrieval API — for developers of AI agents.** It gives you programmatic access to document ingestion, group management, natural-language querying, and workspace analytics — multimodal retrieval as a tool, so your agents can reason over 100K+ visual & multimodal docs where file-by-file agentic search stalls. Build applications that upload documents, organise them into groups, and ask questions across your workspace, with every answer grounded in a cited source page.
## Overview
Upload single or multiple documents and poll ingestion status
List, update, and delete documents in your workspace
Create and manage document groups
Ask natural-language questions — workspace-wide, by group, or per document
Monitor request and document counts for the current period
Check your plan limits and remaining capacity
## Quick Example
Here's a complete workflow: ingest a batch into a group, wait for processing, then query across all of it. The quickest path is an official SDK.
```python Python theme={null}
from polyvia import Polyvia
client = Polyvia(api_key="poly_")
# Ingest a batch into a group, then ask one question across all of it.
items = client.ingest.batch(
["q1.pdf", "q2.pdf", "q3.pdf", "q4.pdf"],
group="FY24 Earnings",
)
for item in items:
client.ingest.wait(item.task_id)
# Answers cite the exact page in each document.
print(client.query("How did revenue trend across the four quarters?",
group="FY24 Earnings").answer)
```
```ts TypeScript theme={null}
import { Polyvia } from "polyvia";
const client = new Polyvia({ apiKey: "poly_" });
// Ingest a batch into a group, then ask one question across all of it.
const items = await client.ingest.batch(
["q1.pdf", "q2.pdf", "q3.pdf", "q4.pdf"],
{ group: "FY24 Earnings" },
);
await Promise.all(items.map((i) => client.ingest.wait(i.task_id)));
// Answers cite the exact page in each document.
const answer = await client.query(
"How did revenue trend across the four quarters?",
{ group: "FY24 Earnings" },
);
console.log(answer.answer);
```
## Or use the REST API directly
The same workflow over raw HTTP — no SDK required.
### 1. Create a Group
```python theme={null}
import httpx
API_KEY = "poly_"
BASE = "https://app.polyvia.ai"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
group_id = httpx.post(
f"{BASE}/api/v1/groups",
headers=HEADERS,
json={"name": "Finance"},
).json()["group_id"]
```
### 2. Ingest Documents
```python theme={null}
import time
with open("q3.pdf", "rb") as f:
r1 = httpx.post(f"{BASE}/api/v1/ingest", headers=HEADERS,
files={"file": ("q3.pdf", f)},
data={"group_id": group_id}).json()
with open("q4.pdf", "rb") as f:
r2 = httpx.post(f"{BASE}/api/v1/ingest", headers=HEADERS,
files={"file": ("q4.pdf", f)},
data={"group_id": group_id}).json()
# Wait for both
for task_id in [r1["task_id"], r2["task_id"]]:
while True:
status = httpx.get(f"{BASE}/api/v1/ingest/{task_id}", headers=HEADERS).json()["status"]
if status in ("completed", "failed"):
break
time.sleep(5)
```
### 3. Query the Group
```python theme={null}
answer = httpx.post(
f"{BASE}/api/v1/query",
headers=HEADERS,
json={"query": "Compare the key findings.", "group_id": group_id},
).json()["answer"]
print(answer)
```
## Next Steps
Full endpoint documentation with all parameters
Typed Python SDK with MCP and agent-tools support
Typed TypeScript SDK for Node.js and modern JS frameworks
Connect Polyvia to Claude Desktop and other AI tools
Upload and manage documents in the web UI
# Integrations
Source: https://docs.polyvia.ai/products/integrations
Import documents directly from Google Drive, Dropbox, OneDrive, Notion, Amazon S3, and Slack
Polyvia Platform can import documents from the platforms your team already uses. Imported files go through the same parsing and indexing pipeline as drag-and-drop uploads — once an import finishes, the document is searchable, citable, and visible on the Documents page.
Pick a platform below for the full setup walkthrough.
Including native Docs, Sheets, and Slides
Multi-select via the Dropbox Chooser
Personal OneDrive and SharePoint sites
Pick which workspace pages to share
Read-only IAM credentials for one bucket
Import files visible to your account
## Supported file types
All integrations import the same formats Polyvia parses elsewhere:
* **PDF** (`.pdf`)
* **Word** (`.docx`, `.doc`)
* **PowerPoint** (`.pptx`, `.ppt`)
* **Excel** (`.xlsx`, `.xls`)
* **Images** (`.png`, `.jpg`, `.jpeg`, `.webp`)
* **Markdown / text** (`.md`, `.txt`)
Files outside this list import successfully but won't be indexed for search.
# Amazon S3
Source: https://docs.polyvia.ai/products/integrations/amazon-s3
Connect an S3 bucket with read-only IAM credentials
S3 needs a one-time **Connect** step where you provide AWS credentials with read access to a single bucket.
In the AWS console, go to **IAM → Users → Create user** and name it (e.g. `polyvia-s3-reader`). Skip console access. Attach a policy with the permissions below — replace `YOUR_BUCKET` with your bucket name:
```json theme={null}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::YOUR_BUCKET"]
},
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::YOUR_BUCKET/*"]
}
]
}
```
Open the IAM user → **Security credentials** → **Create access key**. Choose **Application running outside AWS** and copy the access key ID and secret (the secret is only shown once).
On the Documents page, open the Import dropdown and click **Amazon S3**. Paste the access key ID, secret access key, region (e.g. `us-east-1`), and bucket name. Save.
The picker opens automatically after Save. Browse the bucket using the standard `/` delimiter — click a folder to descend into, or click a file to import.
Use a dedicated read-only IAM user. Never paste a root access key. The region must match the bucket — using the wrong region returns a redirect error.
Credentials are stored in your browser's `localStorage` (key: `s3_integration_config`). They aren't synced to other browsers and only leave your environment over TLS to Polyvia when you list or import objects.
## Logging out
Open **Settings → Integrations** and click **Log out** on the Amazon S3 row. This clears the locally-stored credentials. Previously imported objects remain in Polyvia.
# Dropbox
Source: https://docs.polyvia.ai/products/integrations/dropbox
Import files using the Dropbox Chooser popup
No setup required. Each user authenticates with their own Dropbox account in the Chooser popup.
On the Documents page, click the **Import from Dropbox** button.
The Dropbox Chooser popup opens and walks you through sign-in if needed.
Select files in the Chooser and confirm. Multiple files can be selected at once.
## Changing account
Polyvia doesn't store a Dropbox credential — your sign-in lives in your dropbox.com browser session. Open **Settings → Integrations** and click **Change account** on the Dropbox row to open [dropbox.com/logout](https://www.dropbox.com/logout) in a new tab. After logging out there, the next click on Import will prompt you to sign in again.
# Google Drive
Source: https://docs.polyvia.ai/products/integrations/google-drive
Import files from Google Drive — including Docs, Sheets, and Slides
No setup is required in Polyvia. Each user signs in to their own Google account on first import.
On the Documents page, click the **Import from Google Drive** button.
A Google sign-in popup opens. Pick the account whose files you want to import.
The Google Picker opens. Choose any supported file — including native Google Docs, Sheets, and Slides (Polyvia auto-exports them to Word, Excel, and PowerPoint format).
## Logging out
Open **Settings → Integrations** and click **Log out** on the Google Drive row. The next click on Import will prompt you to sign in again.
# Notion
Source: https://docs.polyvia.ai/products/integrations/notion
Connect a Notion workspace and import pages as documents
Notion needs a one-time **Connect** step because Polyvia has to know which pages you want it to read.
On the Documents page, open the Import dropdown and click **Notion**. If you haven't connected yet, you'll be redirected to Notion to authorize Polyvia.
On the Notion authorization screen, **Select pages** and pick the workspace pages (and their child pages) you want Polyvia to be able to import. Submit.
You'll land back on the Documents page with the Notion picker open. The dialog lists the pages you granted access to — click one to import. The page is converted to markdown (preserving headings, lists, code, callouts, images, tables, and inline formatting) and stored as a document.
To grant access to additional pages later, open **Settings → Integrations**, disconnect, and reconnect — or share pages with the Polyvia integration directly inside Notion (**Share → Connections → Polyvia**).
Each Notion page becomes one document in Polyvia.
## Logging out
Open **Settings → Integrations** and click **Log out** on the Notion row. Previously imported pages remain in Polyvia.
# OneDrive & SharePoint
Source: https://docs.polyvia.ai/products/integrations/onedrive
Import files from personal OneDrive or SharePoint document libraries
The same picker covers personal OneDrive and SharePoint sites your account has access to. No setup required in Polyvia.
On the Documents page, click the **Import from OneDrive** button.
The Microsoft picker opens. Sign in with the account that has access to the files (personal OneDrive or a SharePoint site).
Browse OneDrive folders or SharePoint document libraries and select files.
## Changing account
Polyvia doesn't store a Microsoft credential — your sign-in lives in your Microsoft browser session. Open **Settings → Integrations** and click **Change account** on the OneDrive row to sign out at [Microsoft](https://login.microsoftonline.com/common/oauth2/v2.0/logout) in a new tab. After logging out there, the next click on Import will prompt you to sign in again.
# Slack
Source: https://docs.polyvia.ai/products/integrations/slack
Connect Slack and import files visible to your account
Slack needs a one-time **Connect** step that authorizes Polyvia to see the files visible to your Slack account.
On the Documents page, open the Import dropdown and click **Slack**. If you haven't connected yet, you'll be redirected to Slack to authorize Polyvia. Pick the workspace and approve the requested scopes (`files:read` plus channel-read scopes so Slack returns channel context).
You'll land back on the Documents page with the Slack picker open. The dialog lists the most recent files visible to your Slack account, with a name filter. Click a file to import.
Polyvia uses a **user token**, so you'll see your own files and channels — not a separate bot's files. Revoking the Polyvia app from your Slack workspace immediately stops new imports; previously-imported files stay in Polyvia.
## Logging out
Open **Settings → Integrations** and click **Log out** on the Slack row. Previously imported files remain in Polyvia.
# JavaScript / TypeScript SDK
Source: https://docs.polyvia.ai/products/js-sdk
Official JS/TS SDK for the Polyvia API
The `polyvia` npm package wraps the entire REST API in a fully-typed, ESM/CJS-compatible client.
```bash theme={null}
npm install polyvia
```
Requires Node.js 18+. Works in TypeScript and plain JavaScript.
***
## Quick Start
```ts theme={null}
import { Polyvia } from "polyvia";
const client = new Polyvia({ apiKey: "poly_" });
// Ingest → wait → query
const result = await client.ingest.file("report.pdf");
await client.ingest.wait(result.task_id);
const answer = await client.query("What are the key findings?");
console.log(answer.answer);
```
Prefer not to hard-code the key? The SDK also reads it from the
`POLYVIA_API_KEY` environment variable — set `export POLYVIA_API_KEY=poly_`
and you can drop the `apiKey` option.
***
## Usage
### Ingest
The SDK uploads file bytes directly to Polyvia's storage backend (the API
server is not in the upload path), so there is no practical file-size cap
from the SDK side and large batches don't fail on a request-body limit.
Each file in a batch is uploaded and finalized independently — a failure
on one file is captured in `BatchIngestItem.error` and does not affect
the others.
```ts theme={null}
// Single file — accepts a file path, Buffer, or Blob.
// `group` takes the group name; it's created if it doesn't exist yet.
const result = await client.ingest.file("report.pdf", {
name: "Q4 Report",
group: "Finance",
});
// Multiple files (the group is resolved once for the batch)
const items = await client.ingest.batch(["q3.pdf", "q4.pdf"], {
names: ["Q3 Report", "Q4 Report"],
group: "Finance",
});
// Check status
const status = await client.ingest.status(result.task_id);
// Block until done — throws IngestionError on failure, IngestionTimeout on timeout
await client.ingest.wait(result.task_id, { pollInterval: 5, timeout: 300 });
```
### Query
```ts theme={null}
// All completed documents
const answer = await client.query("What risks are mentioned across all reports?");
// Single document (fastest)
const answer = await client.query("Summarise section 3.", { documentId: "doc_" });
// Scoped to a group — by name (must already exist)
const answer = await client.query("Key findings?", { group: "Finance" });
// Multiple groups — by id
const answer = await client.query("Compare results.", { groupIds: ["g_", "g_"] });
console.log(answer.answer);
```
### Groups
Groups have a human **name** and an opaque backend **id**. Pass the name to
`ingest` / `query` and the SDK resolves it — you rarely touch the id. Use
`getOrCreate` when you want the group object itself.
```ts theme={null}
// Idempotent: returns the existing "Finance" group or creates it (matched by
// exact name, so never a duplicate).
const group = await client.groups.getOrCreate("Finance");
group.id; // backend id, if you ever need it
group.name; // "Finance"
// Look one up without creating it (undefined if there isn't one)
const existing = await client.groups.find("Finance");
// List
const groups = await client.groups.list();
// Delete all documents in a group, then the group itself
await client.groups.delete(group.id, { deleteDocuments: true });
```
### Documents
```ts theme={null}
// List — filter by status and/or group
const docs = await client.documents.list({ status: "completed", groupId: "g_" });
const docs = await client.documents.list({ groupIds: ["g_", "g_"] });
// Get one
const doc = await client.documents.get("doc_");
// Move to a different group / remove from group
await client.documents.update("doc_", { groupId: "g_other" });
await client.documents.update("doc_", { groupId: null });
// Delete
await client.documents.delete("doc_");
```
### Usage & Rate Limits
```ts theme={null}
const usage = await client.usage();
console.log(usage.usage.requests.period); // requests this calendar month
console.log(usage.usage.documents_stored); // live document count
const limits = await client.rateLimits();
console.log(limits.limits["requests_per_minute"]);
console.log(limits.current["remaining_this_minute"]);
```
***
## Error Handling
```ts theme={null}
import {
AuthenticationError, // 401 — bad or missing API key
ForbiddenError, // 403 — document belongs to another user
NotFoundError, // 404 — document, group, or task not found
RateLimitError, // 429 — too many requests
IngestionError, // task finished with status "failed"
IngestionTimeout, // ingest.wait() exceeded its timeout
} from "polyvia";
try {
await client.ingest.wait(taskId, { timeout: 60 });
} catch (e) {
if (e instanceof IngestionError) console.error("Parsing failed:", e.error);
else if (e instanceof IngestionTimeout) console.error("Timed out");
else if (e instanceof RateLimitError) console.error("Rate limited");
else if (e instanceof NotFoundError) console.error("Not found");
else if (e instanceof AuthenticationError) console.error("Invalid API key");
else throw e;
}
```
***
## MCP & Agent Tools
Connect Polyvia to Claude, OpenAI, and other AI clients via MCP — including SDK helpers, programmatic agent tools, and Claude Desktop config.
***
## Links
npm install polyvia
Source code and examples
# MCP Integration of Polyvia
Source: https://docs.polyvia.ai/products/mcp
Use Polyvia with Model Context Protocol
Polyvia implements the [Model Context Protocol (MCP)](https://modelcontextprotocol.io), enabling seamless integration with AI assistants like Claude Desktop, IDEs, and other MCP-compatible tools.
**Using Claude Code or Cursor?** The [Polyvia plugin](/products/skills) installs the MCP server and agent skills in one command — no config editing required.
```
/plugin add polyvia-ai/skills
```
## What is MCP?
Model Context Protocol is an open standard that allows AI applications to securely access external data sources and tools. Polyvia's MCP server exposes your document workspace as a set of tools that AI assistants can use to ingest, browse, and query your documents — all without leaving the conversation.
Polyvia's MCP server is hosted at `https://app.polyvia.ai/mcp` and uses the streamable HTTP transport. No installation required — just point your client at the URL and pass your API key as a bearer token.
## Connecting from Claude Code
Add the server with a single command:
```bash theme={null}
claude mcp add --transport http polyvia https://app.polyvia.ai/mcp \
--header "Authorization: Bearer poly_"
```
This registers `polyvia` for the current project. Add `--scope user` to make it available in every project, or `--scope project` to share it with your team via a checked-in `.mcp.json`. Run `claude mcp list` to confirm it's connected, then ask Claude about your documents.
Generate a dedicated API key for each MCP client so you can revoke it independently.
## Connecting from Claude Desktop & other clients
Claude Desktop and most other MCP clients read a JSON config file rather than a CLI. Open (or create) `~/.claude/claude_desktop_config.json` and add the `polyvia` entry:
```json theme={null}
{
"mcpServers": {
"polyvia": {
"type": "http",
"url": "https://app.polyvia.ai/mcp",
"headers": {
"Authorization": "Bearer poly_"
}
}
}
}
```
Restart Claude Desktop. You should see **Polyvia** appear in the MCP server list.
## Connecting via SDK
Both the Python and TypeScript SDKs expose a `client.mcp` helper that generates the correct config for each major AI client.
| Python method | TypeScript method | Use with |
| ---------------------------- | ------------------------- | ----------------------------------- |
| `claude_code_command()` | `claudeCodeCommand()` | The `claude mcp add …` command line |
| `to_anthropic_mcp_server()` | `toAnthropicMcpServer()` | Anthropic beta messages API |
| `to_openai_responses_tool()` | `toOpenAIResponsesTool()` | OpenAI Responses API |
| `to_openai_mcp_server()` | `toOpenAIMcpServer()` | OpenAI Agents SDK |
| `to_claude_desktop_config()` | `toClaudeDesktopConfig()` | Claude Desktop config file |
### Anthropic SDK
```python Python theme={null}
from anthropic import Anthropic
from polyvia import Polyvia
polyvia = Polyvia(api_key="poly_")
ant = Anthropic()
response = ant.beta.messages.create(
model="claude-opus-4-5",
max_tokens=1000,
messages=[{"role": "user", "content": "What are my Q4 findings?"}],
mcp_servers=[polyvia.mcp.to_anthropic_mcp_server()],
betas=["mcp-client-2025-04-04"],
)
print(response.content[0].text)
```
```ts TypeScript theme={null}
import Anthropic from "@anthropic-ai/sdk";
import { Polyvia } from "polyvia";
const polyvia = new Polyvia({ apiKey: "poly_" });
const ant = new Anthropic();
const response = await ant.beta.messages.create({
model: "claude-opus-4-5",
max_tokens: 1000,
messages: [{ role: "user", content: "What are my Q4 findings?" }],
mcp_servers: [polyvia.mcp.toAnthropicMcpServer()],
betas: ["mcp-client-2025-04-04"],
});
```
### OpenAI Responses API
```python Python theme={null}
from openai import OpenAI
from polyvia import Polyvia
polyvia = Polyvia(api_key="poly_")
oai = OpenAI()
response = oai.responses.create(
model="gpt-4o",
tools=[polyvia.mcp.to_openai_responses_tool()],
input="What are my Q4 findings?",
)
print(response.output_text)
```
```ts TypeScript theme={null}
import OpenAI from "openai";
import { Polyvia } from "polyvia";
const polyvia = new Polyvia({ apiKey: "poly_" });
const oai = new OpenAI();
const response = await oai.responses.create({
model: "gpt-4o",
tools: [polyvia.mcp.toOpenAIResponsesTool()],
input: "What are my Q4 findings?",
});
console.log(response.output_text);
```
### OpenAI Agents SDK
```python Python theme={null}
from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHTTP
from polyvia import Polyvia
polyvia = Polyvia(api_key="poly_")
cfg = polyvia.mcp.to_openai_mcp_server()
server = MCPServerStreamableHTTP(url=cfg["url"], headers=cfg["headers"])
agent = Agent(name="Research", mcp_servers=[server])
result = Runner.run_sync(agent, "What do my Q4 reports say about revenue?")
print(result.final_output)
```
```ts TypeScript theme={null}
import { Agent, Runner } from "@openai/agents";
import { MCPServerStreamableHTTP } from "@openai/agents/mcp";
import { Polyvia } from "polyvia";
const cfg = new Polyvia({ apiKey: "poly_" }).mcp.toOpenAIMcpServer();
const server = new MCPServerStreamableHTTP({ url: cfg.url, headers: cfg.headers });
const agent = new Agent({ name: "Research", mcpServers: [server] });
const result = await Runner.runSync(agent, "What do my Q4 reports say about revenue?");
console.log(result.finalOutput);
```
### Claude Desktop
```python Python theme={null}
# Print a snippet to copy-paste into ~/.claude/claude_desktop_config.json
client.mcp.print_claude_desktop_snippet()
# Or wire it up programmatically
import json, pathlib
cfg_path = pathlib.Path.home() / ".claude" / "claude_desktop_config.json"
config = json.loads(cfg_path.read_text()) if cfg_path.exists() else {}
config.setdefault("mcpServers", {})["polyvia"] = client.mcp.to_claude_desktop_config()
cfg_path.write_text(json.dumps(config, indent=2))
```
```ts TypeScript theme={null}
import { Polyvia } from "polyvia";
// Print a snippet to copy-paste into ~/.claude/claude_desktop_config.json
new Polyvia({ apiKey: "poly_" }).mcp.printClaudeDesktopSnippet();
```
***
## Agent Tools (programmatic)
For frameworks that don't support remote MCP, use `client.tools` to get JSON-schema tool definitions and an executor that calls the REST API directly. All 10 Polyvia tools are available: ingest, status, list/get/update/delete documents, list/create/delete groups, and query.
### Anthropic Messages API
```python Python theme={null}
import anthropic
from polyvia import Polyvia
client = Polyvia(api_key="poly_")
ant = anthropic.Anthropic()
tools, call = client.tools.anthropic()
response = ant.messages.create(
model="claude-opus-4-5",
max_tokens=2048,
messages=[{"role": "user", "content": "Summarise my Finance documents."}],
tools=tools,
)
for block in response.content:
if block.type == "tool_use":
print(call(block.name, block.input))
```
```ts TypeScript theme={null}
import Anthropic from "@anthropic-ai/sdk";
import { Polyvia } from "polyvia";
const client = new Polyvia({ apiKey: "poly_" });
const ant = new Anthropic();
const [tools, callTool] = client.tools.anthropic();
const response = await ant.messages.create({
model: "claude-opus-4-5",
max_tokens: 2048,
messages: [{ role: "user", content: "Summarise my Finance documents." }],
tools,
});
for (const block of response.content) {
if (block.type === "tool_use") {
console.log(await callTool(block.name, block.input as Record));
}
}
```
### OpenAI ChatCompletion
```python Python theme={null}
import json
from openai import OpenAI
from polyvia import Polyvia
client = Polyvia(api_key="poly_")
oai = OpenAI()
tools, call = client.tools.openai()
response = oai.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "What are my Q4 findings?"}],
tools=tools,
)
for tc in response.choices[0].message.tool_calls or []:
print(call(tc.function.name, json.loads(tc.function.arguments)))
```
```ts TypeScript theme={null}
import OpenAI from "openai";
import { Polyvia } from "polyvia";
const client = new Polyvia({ apiKey: "poly_" });
const oai = new OpenAI();
const [tools, callTool] = client.tools.openai();
const response = await oai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "What are my Q4 findings?" }],
tools,
});
for (const tc of response.choices[0]?.message.tool_calls ?? []) {
console.log(await callTool(tc.function.name, JSON.parse(tc.function.arguments)));
}
```
### LangChain (Python)
Requires `pip install "polyvia[langchain]"`.
```python theme={null}
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
from polyvia import Polyvia
client = Polyvia(api_key="poly_")
tools = client.tools.langchain()
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant with access to a document workspace."),
("user", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent = create_tool_calling_agent(ChatOpenAI(model="gpt-4o"), tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
executor.invoke({"input": "What risks are mentioned in my reports?"})
```
***
## Available Tools
### `polyvia_ingest_document`
Upload a document from a public URL or raw base64 content.
**Parameters:**
* `source_url` (string, either/or): Publicly accessible URL of the document
* `file_content_base64` (string, either/or): Base64-encoded file bytes
* `file_name` (string, required with base64): Filename with extension (e.g. `report.pdf`)
* `file_mime_type` (string, optional): MIME type; inferred from `file_name` if omitted
* `name` (string, optional): Display name in Polyvia
Returns `{ "document_id", "task_id", "status": "pending" }`.
### `polyvia_check_ingestion_status`
Poll a parse task started by `polyvia_ingest_document`.
**Parameters:**
* `task_id` (string, required): From `polyvia_ingest_document`
Returns `{ "task_id", "document_id", "status", "error" }`.
### `polyvia_list_documents`
List documents in your workspace.
**Parameters:**
* `status` (string, optional): Filter by `uploading`, `parsing`, `completed`, or `failed`
* `response_format` (string, optional): `markdown` (default) or `json`
### `polyvia_get_document`
Get metadata and summary for one document.
**Parameters:**
* `document_id` (string, required): From `polyvia_list_documents`
* `response_format` (string, optional): `markdown` (default) or `json`
### `polyvia_query`
Ask a natural-language question about your documents.
**Parameters:**
* `query` (string, required): Your question (max 2000 chars)
* `document_id` (string, optional): Scope to one document
* `group_id` (string, optional): Scope to documents in a specific group
* `group_ids` (string\[], optional): Scope to documents across multiple groups
* Omit all three to search your entire workspace
Documents must have `status=completed` before they can be queried.
***
### `polyvia_list_groups`
List all groups in your workspace.
No parameters required. Returns an array of group objects with `id`, `name`, `color`, and `created_at`.
***
### `polyvia_create_group`
Create a new group.
**Parameters:**
* `name` (string, required): Display name for the group
Returns `{ "group_id": "g_" }`.
***
### `polyvia_update_document`
Move a document to a different group, or remove it from its current group.
**Parameters:**
* `document_id` (string, required): The document to update
* `group_id` (string | null, required): Target group ID, or `null` to remove from any group
***
### `polyvia_delete_document`
Permanently delete a document and all its indexed content.
**Parameters:**
* `document_id` (string, required): The document to delete
***
### `polyvia_delete_group`
Delete a group. The group must be empty, or you must pass `delete_documents: true` to wipe its documents first.
**Parameters:**
* `group_id` (string, required): The group to delete
* `delete_documents` (boolean, optional): If `true`, delete all documents in the group before deleting the group itself. Defaults to `false`.
## Example Workflows
### Ingest a public PDF and ask a question
```
User: Ingest this paper and tell me the main contributions:
https://arxiv.org/pdf/2501.12345
Claude:
1. polyvia_ingest_document(source_url="https://arxiv.org/pdf/2501.12345")
→ { document_id: "k57abc...", task_id: "3f2e..." }
2. polyvia_check_ingestion_status(task_id="3f2e...")
→ status: "parsing" … (waits) … status: "completed"
3. polyvia_query(query="What are the main contributions?",
document_id="k57abc...")
→ "The paper introduces three key contributions: ..."
```
### Find information across your whole workspace
```
User: What do all my uploaded reports say about supply chain risks?
Claude:
1. polyvia_list_documents(status="completed")
→ lists completed documents
2. polyvia_query(query="What supply chain risks are mentioned across all reports?")
→ "Across your documents, three recurring supply chain risks appear: ..."
```
### Check what documents you have before querying
```
User: Do I have anything about GDPR compliance?
Claude:
1. polyvia_list_documents(status="completed", response_format="json")
→ scans titles and summaries
2. polyvia_get_document(document_id="")
→ confirms the document is about GDPR
3. polyvia_query(query="What does this document say about data retention?",
document_id="")
→ focused answer from that document
```
## Troubleshooting
* Verify the configuration file path is correct (`~/.claude/claude_desktop_config.json`)
* Check that the API key is valid and starts with `poly_`
* Restart Claude Desktop completely
* Check Claude's logs for error messages
* Ensure your API key is correctly set in the `Authorization` header
* Verify the API key has not been revoked or expired
* Check that you have an active Polyvia account
* Large workspaces may take longer to search
* Use `document_id` in `polyvia_query` to narrow to a specific document
## Next Steps
Full Python SDK reference
Full TypeScript / JavaScript SDK reference
Build custom integrations with the REST API
Upload documents and manage your workspace
# Polyvia Platform
Source: https://docs.polyvia.ai/products/platform
Search & Exploration over multimodal docs — for knowledge workers in enterprises
**Polyvia Platform** is **Search & Exploration over multimodal docs - for knowledge workers in enterprises.** It maps your
internal data and processes into a **Multimodal Knowledge Ontology**: all your
documents organized into one queryable knowledge graph for search and exploration.
Upload your files, ask questions in natural language, and explore the knowledge
graph, with every answer cited back to the exact page. No code required.
## What you can do
Ask questions across your whole corpus with smooth streaming and reliable
citation links to the source page.
See the entities, claims and evidence extracted from your docs. Filter by
group and click any claim to jump to its source page in a side panel.
Drag & drop PDFs, Office/Google docs (DOCX/PPTX/XLSX) and images — up to
50 MB — with live parsing progress on each card.
Track pages, queries and API calls (this month + lifetime), and manage your
API keys.
## Integrations
Import documents from the tools your team already uses — Google Drive, Dropbox,
OneDrive / SharePoint, Amazon S3, Notion and Slack — all through a unified
**Import** dropdown. Imported files go through the same parsing and indexing
pipeline as uploads.
Connect your sources.
## On-prem for enterprise
Polyvia Platform can be deployed **inside your own cloud or VPC** — your
documents never leave your systems. This unlocks regulated buyers (banks, funds,
pharma, healthcare, government) who can't use cloud-only tools. Talk to us at
[mgierlach5@gmail.com](mailto:mgierlach5@gmail.com).
## For developers
Building this into your own app or agent? Use the
[Polyvia API](/products/api), the [Python](/products/python-sdk) /
[TypeScript](/products/js-sdk) SDKs, or the [MCP server](/products/mcp).
# Polyvia Python SDK
Source: https://docs.polyvia.ai/products/python-sdk
Official Python SDK for the Polyvia API
The `polyvia` Python package wraps the entire REST API in a typed, IDE-friendly client.
```bash theme={null}
pip install polyvia
```
Requires Python 3.9+. LangChain agent support:
```bash theme={null}
pip install "polyvia[langchain]"
```
***
## Quick Start
```python theme={null}
from polyvia import Polyvia
client = Polyvia(api_key="poly_")
# Ingest a batch into a group, then ask one question across all of it.
items = client.ingest.batch(
["q1.pdf", "q2.pdf", "q3.pdf", "q4.pdf"],
group="FY24 Earnings",
)
for item in items:
client.ingest.wait(item.task_id)
# Answers cite the exact page in each document.
print(client.query("How did revenue trend across the four quarters?",
group="FY24 Earnings").answer)
```
Prefer not to hard-code the key? The SDK also reads it from the
`POLYVIA_API_KEY` environment variable — set `export POLYVIA_API_KEY=poly_`
and you can drop the `api_key` argument.
***
## Usage
### Ingest
The SDK uploads file bytes directly to Polyvia's storage backend (the API
server is not in the upload path), so there is no practical file-size cap
from the SDK side and large batches don't fail on a request-body limit.
Each file in a batch is uploaded and finalized independently — a failure
on one file is captured in `BatchIngestItem.error` and does not affect
the others.
```python theme={null}
# Single file — accepts a file path, bytes, or file-like object.
# `group` takes the group name; it's created if it doesn't exist yet.
result = client.ingest.file("report.pdf", group="Finance")
# → IngestResult(document_id="", task_id="", status="pending")
# Multiple files (the group is resolved once for the batch)
items = client.ingest.batch(
["q3.pdf", "q4.pdf"],
names=["Q3 Report", "Q4 Report"],
group="Finance",
)
# Check status
status = client.ingest.status(result.task_id)
# → IngestionStatus(task_id="", document_id="", status="completed")
# Block until done — raises IngestionError on failure, IngestionTimeout on timeout
client.ingest.wait(result.task_id, poll_interval=5, timeout=300)
```
### Query
```python theme={null}
# All completed documents
answer = client.query("What risks are mentioned across all reports?")
# Single document (fastest)
answer = client.query("Summarise section 3.", document_id="doc_")
# Scoped to a group — by name (must already exist)
answer = client.query("Key findings?", group="Finance")
# Multiple groups — by id
answer = client.query("Compare results.", group_ids=["g_", "g_"])
print(answer.answer)
```
### Groups
Groups have a human **name** and an opaque backend **id**. Pass the name to
`ingest` / `query` and the SDK resolves it — you rarely touch the id. Use
`get_or_create` when you want the group object itself.
```python theme={null}
# Idempotent: returns the existing "Finance" group or creates it (matched by
# exact name, so never a duplicate). Returns a Group.
group = client.groups.get_or_create("Finance")
group.id # backend id, if you ever need it
group.name # "Finance"
# Look one up without creating it (None if there isn't one)
existing = client.groups.find("Finance")
# List
groups = client.groups.list()
# Delete all documents in a group, then the group itself
client.groups.delete(group.id, delete_documents=True)
# Or separately
client.groups.delete_documents(group.id)
client.groups.delete(group.id)
```
### Documents
```python theme={null}
# List — filter by status and/or group
docs = client.documents.list(status="completed", group_id="g_")
docs = client.documents.list(group_ids=["g_", "g_"])
# Get one
doc = client.documents.get("doc_")
# Move to a different group / remove from group
client.documents.update("doc_", group_id="g_other")
client.documents.update("doc_", group_id=None)
# Delete
client.documents.delete("doc_")
```
### Usage & Rate Limits
```python theme={null}
usage = client.usage()
print(usage.usage.requests.period) # requests this calendar month
print(usage.usage.documents_stored) # live document count
limits = client.rate_limits()
print(limits.limits["requests_per_minute"])
print(limits.current["remaining_this_minute"])
print(limits.resets_at.month) # ISO timestamp of next monthly reset
```
***
## Async Client
Every method on `AsyncPolyvia` is a coroutine — same API surface as the sync client.
```python theme={null}
import asyncio
from polyvia import AsyncPolyvia
async def main():
async with AsyncPolyvia(api_key="poly_") as client:
result = await client.ingest.file("report.pdf")
await client.ingest.wait(result.task_id)
answer = await client.query("Key findings?")
print(answer.answer)
asyncio.run(main())
```
***
## Error Handling
```python theme={null}
from polyvia import (
AuthenticationError, # 401 — bad or missing API key
ForbiddenError, # 403 — document belongs to another user
NotFoundError, # 404 — document, group, or task not found
RateLimitError, # 429 — too many requests
IngestionError, # task finished with status='failed'
IngestionTimeout, # ingest.wait() exceeded its timeout
)
try:
client.ingest.wait(task_id, timeout=60)
except IngestionError as e:
print(f"Parsing failed: {e.error}")
except IngestionTimeout:
print("Timed out — document may still be processing")
except RateLimitError:
print("Rate limit hit — back off and retry")
except NotFoundError:
print("Document or task not found")
except AuthenticationError:
print("Invalid API key")
```
***
## MCP & Agent Tools
Connect Polyvia to Claude, OpenAI, and other AI clients via MCP — including SDK helpers, programmatic agent tools, and LangChain integration.
***
## Links
pip install polyvia
Source code and examples
# Agent Skills
Source: https://docs.polyvia.ai/products/skills
Install Polyvia skills into any AI coding agent
[Agent Skills](https://skills.sh) is an open format for giving AI coding agents reusable, shareable capabilities. Installing a Polyvia skill teaches your agent how to ingest documents, query your knowledge base, and manage groups — in any editor or agent framework that supports the format.
## Install
```bash theme={null}
npx skills add polyvia-ai/skills
```
Works with Claude Code, Cursor, GitHub Copilot, Windsurf, and any other agent client that supports the [Agent Skills](https://skills.sh) format. The CLI prompts you to choose which clients to install for.
Install a specific skill:
```bash theme={null}
npx skills add polyvia-ai/skills@polyvia # Python SDK workflows
npx skills add polyvia-ai/skills@polyvia-mcp # MCP server setup
```
***
## Available Skills
### `polyvia`
Teaches your agent to use the Polyvia Python SDK — ingesting documents, polling status, querying across the workspace or scoped to a group, and managing documents and groups.
Activated when you ask your agent to ingest files, search documents, or build document-aware workflows in code.
```bash theme={null}
pip install polyvia
export POLYVIA_API_KEY=poly_
```
### `polyvia-mcp`
Teaches your agent to connect any AI client to the Polyvia hosted MCP server (`https://app.polyvia.ai/mcp`) — covering Claude Desktop, the Anthropic beta MCP client, OpenAI Responses API, and the OpenAI Agents SDK.
Activated when you ask your agent to set up an MCP connection or configure a client to use Polyvia tools.
***
## Claude Code & Cursor: plugin install
For Claude Code and Cursor, the Polyvia plugin bundles both skills **plus** the MCP server connection into a single command — no config files to edit:
```
/plugin add polyvia-ai/skills
```
Set `POLYVIA_API_KEY=poly_` in your environment first. The plugin wires up the MCP server automatically using it, making all 10 Polyvia tools available in your editor immediately.
***
## How skills work
Skills are plain Markdown files (`SKILL.md`) with a short frontmatter description. Your agent reads the description to decide when to activate a skill, then uses the body as instructions for how to complete the task.
The Polyvia skills live at [github.com/polyvia-ai/skills](https://github.com/polyvia-ai/skills) and are installed locally into your project at `.agents/skills/` or globally at `~/.agents/skills/`.
***
## Next Steps
Manual MCP setup and all available tools
Build document-aware agents in code
Browse the Agent Skills directory
# Supported Formats
Source: https://docs.polyvia.ai/products/supported-formats
Document, image, and audio formats Polyvia can ingest and index
Polyvia ingests text, visual, and audio source material into the same unified knowledge graph. You don't need to pre-classify or pre-convert your files — drop in whatever your team produces and Polyvia routes each file through the right parser automatically.
## Modalities
Live today — **Visual Document Intelligence + Audio**, ingesting now:
* Charts
* Graphs & plots
* Infographics
* Complex, multi-page tables
* Slides & decks
* Reports & filings
* Scanned & photographed pages
* Invoices & forms
* Handwriting & annotations
* Diagrams & flowcharts
* Photos & images
* Audio (calls, meetings, recordings)
**Coming next:**
* Healthcare scans / EHR
* Chemical & molecular data
* CAD & technical drawings
* Video
* Heatmaps
## Overview
PDF, DOCX, PPTX
TXT, Markdown
PNG, JPG, screenshots, scans
WAV, MP3, M4A, and more
## Documents
Reports, filings, contracts, research papers, scanned books, invoices. Polyvia reads everything on the page — not just the text layer:
* **Text** — body text, headings, footnotes, and multi-column layouts
* **Tables** — including complex, multi-page, and nested tables
* **Charts & graphs** — bar, line, pie, scatter, and combo charts read back as data
* **Infographics & diagrams** — flowcharts, org charts, schematics, maps
* **Figures & images** — embedded photos, logos, and screenshots
* **Scanned & photographed pages** — OCR over image-only PDFs
* **Handwriting** — handwritten notes and annotations
* **Forms & invoices** — fields, line items, stamps, and signatures
Page numbers and layout are preserved for citations.
**Extensions:** `.pdf`
Memos, proposals, internal docs. Headings, lists, and tables are preserved so structure-aware queries (e.g. "what does section 3.2 say about…") work correctly.
**Extensions:** `.docx`, `.doc`
Pitch decks, board decks, training material. Slide order is preserved and per-slide visuals (charts, images, diagrams) are extracted alongside speaker notes.
**Extensions:** `.pptx`, `.ppt`
## Text
Logs, transcripts, notes, raw exports. Indexed line-by-line with no formatting loss.
**Extensions:** `.txt`
READMEs, wikis, internal docs, AI-generated reports. Headings, code blocks, and lists are parsed natively, so structural citations point to the right section.
**Extensions:** `.md`
## Images
Product photography, architecture diagrams, whiteboards, screenshots, scanned receipts. Polyvia runs visual understanding to extract text, structure, and entities — citations point to the image with bounding boxes when applicable.
**Extensions:** `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.bmp`
## Audio
Sales calls, interviews, podcasts, meeting recordings. Polyvia transcribes with timestamps and speaker turns — citations link directly to the utterance, and queries can return clickable timestamps that seek the player to the cited moment.
**Extensions:** `.wav`, `.mp3`, `.m4a`
## How ingestion works
Regardless of format, every file flows through the same pipeline:
1. **Parse** — format-specific parser extracts raw content (text, page layout, frames, transcript).
2. **Extract** — VLM and LLM passes pull out facts, tables, charts, and entities.
3. **Index** — facts are linked into the ontology graph with source provenance (document, page, bounding box, or timestamp).
4. **Query** — agents can search across all formats in the same call; citations point back to the exact source location.
## Uploading
You can ingest any supported format through any of our interfaces:
Single or batch upload via `/api/v1/ingest`.
Python and TypeScript SDKs with batch helpers.
**Need a format we don't list?** Video, HTML, and email ingestion are on the roadmap. Email [mgierlach5@gmail.com](mailto:mgierlach5@gmail.com) and tell us what you'd like to throw at Polyvia.
# Quickstart
Source: https://docs.polyvia.ai/quickstart
Ingest your first document and query it in a couple of minutes
Polyvia turns visual & multimodal documents into a queryable knowledge graph.
This guide takes you from zero to a cited answer.
## 1. Get an API key
Open the **Polyvia Platform** and sign up or log in.
Open **API** in the sidebar of the **Polyvia Platform**, then **Create API Key**. Copy it — it's shown only once. All
keys start with `poly_`.
In the snippets below you can paste your key straight into `api_key` to get
going. For real projects, keep it out of code instead — run
`export POLYVIA_API_KEY=poly_` and call `Polyvia()` (or `new Polyvia()`)
with no arguments; the SDK reads the variable automatically. An explicit
`api_key` always takes precedence over the environment variable.
## 2. Install an SDK
```bash pip theme={null}
pip install polyvia
```
```bash npm theme={null}
npm install polyvia
```
Prefer raw HTTP? Every example here maps 1:1 to the REST
[API Reference](/api-reference/introduction). No SDK required.
## 3a. Ingest & query — a single file
Ingestion is asynchronous: upload returns a `task_id`, then you poll (or
`wait`) until it's `completed`. Once indexed, query in natural language and get
an answer grounded in the exact source page.
```python Python theme={null}
from polyvia import Polyvia
client = Polyvia(api_key="poly_")
result = client.ingest.file("q4-report.pdf")
client.ingest.wait(result.task_id) # blocks until completed
answer = client.query("What was Q4 revenue, and which chart shows it?")
print(answer.answer)
```
```ts TypeScript theme={null}
import { Polyvia } from "polyvia";
const client = new Polyvia({ apiKey: "poly_" });
const result = await client.ingest.file("q4-report.pdf");
await client.ingest.wait(result.task_id); // resolves when completed
const answer = await client.query("What was Q4 revenue, and which chart shows it?");
console.log(answer.answer);
```
## 3b. Ingest & query — across many documents
The power of Polyvia is querying a **whole corpus** jointly. Ingest a batch into
a **group**, then ask one question across all of it.
```python Python theme={null}
from polyvia import Polyvia
client = Polyvia(api_key="poly_")
# Ingest several files into a group (created on first use)
items = client.ingest.batch(
["q1.pdf", "q2.pdf", "q3.pdf", "q4.pdf"],
group="FY24 Earnings",
)
for item in items:
client.ingest.wait(item.task_id)
# Ask once, across the group — answers cite the exact page in each doc
print(client.query("How did revenue trend across the four quarters?",
group="FY24 Earnings").answer)
```
```ts TypeScript theme={null}
import { Polyvia } from "polyvia";
const client = new Polyvia({ apiKey: "poly_" });
const items = await client.ingest.batch(
["q1.pdf", "q2.pdf", "q3.pdf", "q4.pdf"],
{ group: "FY24 Earnings" },
);
await Promise.all(items.map((i) => client.ingest.wait(i.task_id)));
const answer = await client.query(
"How did revenue trend across the four quarters?",
{ group: "FY24 Earnings" },
);
console.log(answer.answer);
```
## Next steps
Documents, groups, ingestion, citations.
Async client, error handling, agent tools.
Use Polyvia from Claude, Cursor and agents.
**Need help?** Email [mgierlach5@gmail.com](mailto:mgierlach5@gmail.com).
# Releases & changelog
Source: https://docs.polyvia.ai/versions
What's new in Polyvia — release notes and versions
**Polyvia 1** ships as two products:
The developer product — REST API, Python & TypeScript SDKs, MCP server, and
agent skills.
Search & Exploration over multimodal docs — ingest, chat, and explore the
knowledge graph.
***
## Polyvia 1
Polyvia 1 ships as two products: the **Polyvia API** (Multimodal Document
Retrieval API, for developers of AI agents) and the **Polyvia Platform**
(Search & Exploration over multimodal docs, for knowledge workers in
enterprises).
### Polyvia API
* **REST API v1** — `ingest`, `documents`, `groups`, `query`, `usage`, and
`rate-limits`. Async ingestion with task polling; query workspace-wide, by
group, or per document, with grounded citations.
* **Python SDK** — `pip install polyvia`. Typed sync **and** async clients,
batch ingestion, idempotent groups, and structured error handling.
* **TypeScript SDK** — `npm install polyvia`. Fully-typed, ESM/CJS, for Node
and modern JS.
* **MCP server + agent tools** — connect Polyvia to Claude, Cursor and other
MCP clients; first-class helpers for agent frameworks and LangChain.
### Polyvia Platform
* **Better document processing** — large documents process reliably and much
faster, with live uploading & parsing progress on each card while ingestion
runs, and bigger uploads (up to 50 MB).
* **Better chat** — smooth streaming and reliable citation links.
* **Office & Google docs + images** — upload Word, PowerPoint and Excel
(DOCX / PPTX / XLSX) alongside PDFs and images.
* **Faster, smoother viewer** — PDFs open quickly and scroll without stutter.
* **Knowledge graph view** — see entities, claims and evidence extracted from
your docs. Filter by group, and click any claim to jump to its source page in
a side panel.
* **Settings + API access** — a Usage tab showing pages, queries and API calls
(this month and lifetime).
* **Integrations (WIP)** — connect Google Drive, Dropbox, OneDrive / SharePoint,
Amazon S3, Notion and Slack. Notion and Slack use per-user OAuth, so each
teammate signs in with their own account. All sources appear in a unified
**Import** dropdown on the Documents page.
Index your first document and query it in a couple of minutes.
***
**On the roadmap:** more integrations out of WIP and expanded ingest types
(CAD, heatmaps, molecular). Follow along on the [blog](https://polyvia.ai/blog).