Source: http://www.poma-ai.com/docs/grill/concepts/document-management

# 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](/grill/getting-started/authentication)).

## List documents

```bash
curl -sS "$GRILL/docs" \
  -H "authorization: Bearer $GRILL_KEY" | jq .
```

Response (`ListDocsResponse`):

```json
{
  "namespace": "project_docs_rag_4f2",
  "total_documents": 3,
  "documents": [
    {
      "doc_id": "annual-report-2025",
      "title": "Annual Report 2025",
      "language": "en",
      "filename": "annual-report.pdf",
      "canonical_url": "https://investors.example.com/2025/annual-report",
      "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-…",
      "bm25_state": "ready"
    },
    …
  ]
}
```

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

```bash
curl -sS "$GRILL/docs/annual-report-2025" \
  -H "authorization: Bearer $GRILL_KEY" | jq .
```

Response (`DocInfo`):

```json
{
  "doc_id": "annual-report-2025",
  "title": "Annual Report 2025",
  "language": "en",
  "filename": "annual-report.pdf",
  "canonical_url": "https://investors.example.com/2025/annual-report",
  "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-…",
  "bm25_state": "ready"
}
```

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. |
| `canonical_url` | Citation URL stamped by Core at ingest — e.g. the resolved target when ingesting from a short link / redirect. Empty when not applicable. |
| `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`. |
| `bm25_state` | State of the document's BM25 (lexical) index. |

## Delete a document

```bash
curl -sS -X DELETE "$GRILL/docs/annual-report-2025" \
  -H "authorization: Bearer $GRILL_KEY" | jq .
```

Response (`DeleteDocResponse`):

```json
{
  "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/search` calls 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](/grill/getting-started/projects).

## Common patterns

**Garbage-collect old docs by ingest date.**

```bash
curl -sS "$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/docs/{}" \
                -H "authorization: Bearer $GRILL_KEY"
```

**Replace a doc with a fresh version.**

```bash
# 1. delete the old version
curl -sS -X DELETE "$GRILL/docs/annual-report-2025" \
  -H "authorization: Bearer $GRILL_KEY"

# 2. ingest the new file
curl -sS -X POST "$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.pdf
```

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

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

```python
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`](/sdk/reference/grill#list_docs) / [`Grill.get_doc`](/sdk/reference/grill#get_doc) / [`Grill.delete_doc`](/sdk/reference/grill#delete_doc). Async equivalents on [`AsyncGrill`](/sdk/reference/async-grill).

## 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](/grill/reference/api) — full request/response shapes for every `/grill/*` endpoint.