> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anonalabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Documents

> Upload files into a space so their content becomes searchable memory.

A document is parsed, split into chunks, and stored as memory, so the same
[Retrieve](/api-reference/retrieve) and [Reason](/api-reference/reason) calls surface
file content alongside anything you recorded conversationally.

| Category           | Formats                  | How it is extracted       |
| ------------------ | ------------------------ | ------------------------- |
| Documents          | PDF, DOCX, DOC           | Text extraction           |
| Office             | PPTX, PPT, XLSX, XLS     | Text and table extraction |
| Web and plain text | HTML, TXT, Markdown, CSV | Direct parsing            |
| Images             | JPG, PNG                 | OCR                       |
| Audio              | MP3, WAV                 | Transcription             |

## Upload files

```http theme={null}
POST /v1/spaces/{space_id}/documents
Authorization: Bearer anona_live_YOUR_KEY
Content-Type: multipart/form-data
```

| Form field   | Type    | Required | Description                                                                                                                                         |
| ------------ | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `files`      | file(s) | Yes      | One or more files to upload.                                                                                                                        |
| `tags`       | string  | No       | Comma-separated tags applied to every file, so retrieval can scope to them later. Tags starting with `anona:` are reserved and rejected with `422`. |
| `user_id`    | string  | No       | End user these documents belong to. See [Scoping](/api-reference/retrieve#scoping-within-a-space).                                                  |
| `agent_id`   | string  | No       | Owning agent.                                                                                                                                       |
| `session_id` | string  | No       | Owning session.                                                                                                                                     |
| `strategy`   | string  | No       | How the file is retained. Defaults to storing it as retrieval chunks; pass a named strategy to override.                                            |

<Warning>
  If you use scoping, upload with the same scope you retrieve with. Scoped search
  is strict, so a document uploaded **without** `user_id` is not returned to a
  search **with** one: it is only reachable from an unscoped search.
</Warning>

| Limit             | Value |
| ----------------- | ----- |
| Files per request | 20    |
| Size per file     | 25 MB |
| Total per request | 50 MB |

Ingestion is always asynchronous. The call returns immediately with a `job_ids` list;
poll each with the [job status](/api-reference/memories#job-status) endpoint. When a job
reaches `completed`, that file's content is searchable.

**Response** `202 Accepted`

```json theme={null}
{
  "job_ids": ["job_conv_7f1a", "job_conv_7f1b"]
}
```

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.anonalabs.com/v1/spaces/customer-support-bot/documents \
    -H "Authorization: Bearer anona_live_YOUR_KEY" \
    -F "files=@handbook.pdf" \
    -F "tags=hr,policy" \
    -F "user_id=alice"
  ```

  ```python Python SDK theme={null}
  job = client.upload_file(
      space_id="customer-support-bot",
      file="handbook.pdf",
      tags=["hr", "policy"],
  )
  print(job["job_ids"])  # poll each with client.get_job(...)
  ```
</CodeGroup>

<Note>
  Parsing and embedding a document is billed on completion, not at upload. Larger files
  and richer formats, such as scanned PDFs and audio, cost more because more content is
  processed.
</Note>

## List documents

```http theme={null}
GET /v1/spaces/{space_id}/documents?limit=100&offset=0
Authorization: Bearer anona_live_YOUR_KEY
```

| Query parameter | Type    | Description                               |
| --------------- | ------- | ----------------------------------------- |
| `limit`         | integer | Maximum results. Defaults to 100.         |
| `offset`        | integer | Pagination offset.                        |
| `q`             | string  | Optional substring filter on document id. |

**Response** `200 OK`

```json theme={null}
{
  "documents": [
    {
      "document_id": "file_9c2f1a",
      "source": "file",
      "created_at": "2026-07-22T10:30:00Z",
      "updated_at": "2026-07-22T10:30:04Z",
      "text_length": 18240,
      "memory_count": 37,
      "tags": ["hr", "policy"]
    }
  ],
  "total": 1,
  "limit": 100,
  "offset": 0
}
```

`memory_count` is how many searchable memories were extracted from the document.

### What counts as a document

This list contains everything ingested into the space, not only uploaded files. Storing
a memory with `POST /v1/record` also creates a document row, so `source` tells you which
is which.

| `source` | Meaning                                                                                                                         |
| -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `file`   | Uploaded through this endpoint.                                                                                                 |
| `custom` | You passed your own `document_id` on a write, deliberately grouping content under it.                                           |
| `memory` | Created implicitly by a write that supplied no `document_id`. It holds a single stored memory rather than an uploaded document. |

To group your writes into meaningful documents, such as a conversation, a ticket, or a
session, pass your own `document_id` when recording. Reusing an id replaces that
document's memories, which is how you keep a document current.

<Note>
  The dashboard's Documents tab hides `memory` rows by default for this reason, with a
  toggle to show everything ingested.
</Note>

```python theme={null}
docs = client.list_documents(space_id="customer-support-bot")
```

## Get a document

```http theme={null}
GET /v1/spaces/{space_id}/documents/{document_id}
Authorization: Bearer anona_live_YOUR_KEY
```

Returns the document's metadata and ingestion status, in the same shape as one item in
the list above.

## Delete a document

Removes the document and every memory extracted from it.

```http theme={null}
DELETE /v1/spaces/{space_id}/documents/{document_id}
Authorization: Bearer anona_live_YOUR_KEY
```

```python theme={null}
client.delete_document(space_id="customer-support-bot", document_id="file_9c2f1a")
```

**Response** `204 No Content`.

## Error responses

| Status | Code               | Cause                                        |
| ------ | ------------------ | -------------------------------------------- |
| 400    | `too_many_files`   | More than 20 files in one request.           |
| 401    | `unauthorized`     | Missing or invalid API key.                  |
| 403    | `forbidden`        | The space is not owned by your organization. |
| 413    | `file_too_large`   | A single file exceeds 25 MB.                 |
| 413    | `upload_too_large` | The total upload exceeds 50 MB.              |
| 429    | `rate_limited`     | Credit quota or rate limit exceeded.         |

See the full [error reference](/api-reference/errors).

## Next steps

<CardGroup cols={2}>
  <Card title="Retrieve API" icon="magnifying-glass" href="/api-reference/retrieve">
    Search across recorded memory and uploaded documents.
  </Card>

  <Card title="Memories API" icon="database" href="/api-reference/memories">
    Record memories and poll ingestion jobs.
  </Card>
</CardGroup>
