# 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).