Document Management
Once a document is ingested into a Grill project, three admin endpoints let you manage it:
| Endpoint | Purpose |
|---|---|
GET /grill/docs | List all documents in the project namespace. |
GET /grill/docs/{docId} | Get metadata for one document. |
DELETE /grill/docs/{docId} | Remove a document's vectors and storage from the namespace. |
All three require a project API key (Authentication).
List documents
curl -sS "$GRILL/grill/docs" \
-H "authorization: Bearer $GRILL_KEY" | jq .Response (ListDocsResponse):
{
"namespace": "project_docs_rag_4f2",
"total_documents": 3,
"documents": [
{
"doc_id": "annual-report-2025",
"title": "Annual Report 2025",
"language": "en",
"filename": "annual-report.pdf",
"pages": 84,
"chunkset_count": 142,
"chunk_count": 612,
"image_count": 38,
"table_count": 14,
"ingested_at": "2026-04-30T10:00:00Z",
"source_job_id": "550e8400-…"
},
…
]
}The documents array is the authoritative source for doc_id values. Use the exact doc_id string in doc_filter (on /grill/search) and as the {docId} path parameter on the per-doc endpoints.
source_job_id ties the document back to the /grill/ingest job that created it — useful for audit and incident replay.
Inspect one document
curl -sS "$GRILL/grill/docs/annual-report-2025" \
-H "authorization: Bearer $GRILL_KEY" | jq .Response (DocInfo):
{
"doc_id": "annual-report-2025",
"title": "Annual Report 2025",
"language": "en",
"filename": "annual-report.pdf",
"pages": 84,
"chunkset_count": 142,
"chunk_count": 612,
"image_count": 38,
"table_count": 14,
"ingested_at": "2026-04-30T10:00:00Z",
"source_job_id": "550e8400-…"
}The same shape as a single entry from GET /grill/docs. Field semantics:
| Field | Meaning |
|---|---|
doc_id | Stable identifier within this project namespace. |
title | Detected from document metadata when present, otherwise from filename. |
language | Detected primary language (BCP-47 short code). |
filename | The original Content-Disposition filename at ingest. |
pages | Source page count (when applicable). |
chunkset_count / chunk_count | How many chunksets and chunks were extracted. Useful for sanity-checking the pipeline. |
image_count / table_count | Number of figures and tables Grill identified. |
ingested_at | UTC timestamp the ingest job completed. |
source_job_id | The job_id returned by the original POST /grill/ingest. |
Delete a document
curl -sS -X DELETE "$GRILL/grill/docs/annual-report-2025" \
-H "authorization: Bearer $GRILL_KEY" | jq .Response (DeleteDocResponse):
{
"doc_id": "annual-report-2025",
"vectors_deleted": 612,
"storage_deleted": true
}DELETE is destructive and scoped:
- Destructive. All vectors and storage for this document are removed. Subsequent
/grill/searchcalls will not surface its content. There is no soft-delete or recycle bin. - Scoped. Only the document is removed. The project, project API key, and all other documents are left untouched.
If you want to reset the entire project namespace at once, delete and recreate the project — see Create a Grill project.
Common patterns
Garbage-collect old docs by ingest date.
curl -sS "$GRILL/grill/docs" -H "authorization: Bearer $GRILL_KEY" \
| jq -r '.documents
| map(select(.ingested_at < "2026-01-01T00:00:00Z"))
| .[].doc_id' \
| xargs -I{} curl -sS -X DELETE "$GRILL/grill/docs/{}" \
-H "authorization: Bearer $GRILL_KEY"Replace a doc with a fresh version.
# 1. delete the old version
curl -sS -X DELETE "$GRILL/grill/docs/annual-report-2025" \
-H "authorization: Bearer $GRILL_KEY"
# 2. ingest the new file
curl -sS -X POST "$GRILL/grill/ingest" \
-H "authorization: Bearer $GRILL_KEY" \
-H "content-type: application/octet-stream" \
-H 'content-disposition: attachment; filename="annual-report-2025.pdf"' \
--data-binary @annual-report-2025.pdfThis pattern is safer than relying on re-ingest replacement, because the docId derivation is filename-sensitive — explicit delete-then-ingest avoids surprises if the filename changes between versions.
From the Python SDK
All three admin operations are typed methods on the Grill client:
from poma import Grill
g = Grill()
# List
docs = g.list_docs() # list[DocInfo]
for d in docs:
print(d.doc_id, d.filename, d.pages, d.source_job_id)
# Inspect one
info = g.get_doc("annual-report-2025") # DocInfo
# Delete one
result = g.delete_doc("annual-report-2025") # DeleteDocResponse
print(result.vectors_deleted, result.storage_deleted)DocInfo and DeleteDocResponse are dataclasses — the fields match the JSON shapes documented above. To garbage-collect old docs:
from datetime import datetime, timezone
from poma import Grill
cutoff = datetime(2026, 1, 1, tzinfo=timezone.utc)
with Grill() as g:
for d in g.list_docs():
if datetime.fromisoformat(d.ingested_at) < cutoff:
g.delete_doc(d.doc_id)Full surface: Grill.list_docs / Grill.get_doc / Grill.delete_doc. Async equivalents on AsyncGrill.
Errors
| Status | Cause | Notes |
|---|---|---|
400 | Empty docId path parameter | Provide the exact doc_id from DocInfo. |
401 | Missing/invalid Bearer token | Use a project API key. |
403 | Project lacks Grill access | Create or switch to a Grill project. |
404 | Doc is not in this project namespace | Confirm with GET /grill/docs. Document is project-scoped. |
502 / 503 | Upstream Grill / proxy error | Retry with backoff. |
Next
- API reference — full request/response shapes for every
/grill/*endpoint.