> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://documents.pyramid-ai.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://documents.pyramid-ai.com/_mcp/server.

# Document Processing

## Overview

Getting a document into Pyramid AI is a three-step process. This design keeps uploads fast and reliable — even for large files — by separating the upload from the processing.

```
┌─────────────┐      ┌──────────────┐      ┌─────────────────┐
│  1. Reserve  │ ───> │  2. Upload   │ ───> │  3. Process     │
│  (API call)  │      │  (direct PUT)│      │  (API call)     │
└─────────────┘      └──────────────┘      └─────────────────┘
    Returns:              File goes           Starts:
    - document_id         directly to         - Text extraction
    - upload_url          cloud storage       - Chunking
    - expiry time                             - Indexing
```

## Step 1: Reserve an upload slot

Tell the API you're about to upload a file. You provide the file name, type, and size. The API returns a **presigned upload URL** — a temporary, secure link where you'll send the actual file.

```bash
curl -X POST https://api.pyramid-ai.com/api/v2/documents \
  -H "Authorization: Bearer pai_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "file_name": "safety-plan.pdf",
    "content_type": "application/pdf",
    "size_bytes": 2048576,
    "project_id": "YOUR_PROJECT_ID"
  }'
```

**Response:**

```json
{
  "success": true,
  "data": {
    "id": "doc_abc123",
    "status": "awaiting_upload",
    "upload": {
      "url": "https://storage.supabase.co/...",
      "method": "PUT",
      "recommended_content_type": "application/pdf",
      "expires_at": "2026-05-22T14:00:00Z"
    }
  }
}
```

> **Note**
>
> The upload URL expires after **2 hours**. If it expires before you upload, call the reserve endpoint again to get a fresh URL.

### Why presigned URLs?

The file goes directly from your system to cloud storage — it never passes through the Pyramid API server. This means:

* **No file size limits** from the API layer (Vercel's 4.5 MB body limit is bypassed)
* **Faster uploads** — direct connection to storage
* **More reliable** — fewer hops means fewer failure points

## Step 2: Upload the file

Send the actual file bytes to the presigned URL using a `PUT` request. This is a direct upload to cloud storage — no API key needed (the URL itself contains the authorization).

```bash
curl -X PUT "PRESIGNED_URL_FROM_STEP_1" \
  -H "Content-Type: application/pdf" \
  --data-binary @safety-plan.pdf
```

> **Warning**
>
> Use `--data-binary` (not `-d`) to preserve the file's binary content. Using `-d` will corrupt binary files like PDFs.

### In code (JavaScript)

```javascript
const file = document.getElementById('fileInput').files[0];

await fetch(uploadUrl, {
  method: 'PUT',
  headers: { 'Content-Type': file.type },
  body: file,
});
```

### In code (Python)

```python
with open('safety-plan.pdf', 'rb') as f:
    requests.put(
        upload_url,
        data=f,
        headers={'Content-Type': 'application/pdf'}
    )
```

## Step 3: Trigger processing

Once the file is uploaded, tell the API to start processing it. You can submit multiple document IDs at once.

```bash
curl -X POST https://api.pyramid-ai.com/api/v2/documents/process \
  -H "Authorization: Bearer pai_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"document_ids": ["doc_abc123"]}'
```

**Response:**

```json
{
  "success": true,
  "data": {
    "processed": ["doc_abc123"],
    "skipped": [],
    "failed": []
  }
}
```

| Result      | Meaning                                       |
| ----------- | --------------------------------------------- |
| `processed` | Successfully started processing               |
| `skipped`   | Already processed, or not in the right state  |
| `failed`    | Something went wrong (error message included) |

## What happens during processing

Behind the scenes, the processing pipeline does the following:

#### Text extraction

The system reads the file and extracts all text content. For PDFs, this includes OCR (optical character recognition) for scanned documents.

#### Chunking

The extracted text is split into overlapping chunks — typically a few paragraphs each. Overlap ensures context isn't lost at chunk boundaries.

#### Embedding

Each chunk is converted into a mathematical representation (a vector embedding) that captures its meaning. This is what allows semantic search — finding relevant content by meaning, not just keywords.

#### Indexing

The embeddings are stored in a searchable index. When you ask a question, the AI compares your question's embedding against all chunk embeddings to find the most relevant passages.

## Checking processing status

Poll the document endpoint to check when processing is complete:

```bash
curl https://api.pyramid-ai.com/api/v2/documents/doc_abc123 \
  -H "Authorization: Bearer pai_live_YOUR_KEY"
```

Watch the `status` field:

| Status            | Next action                                |
| ----------------- | ------------------------------------------ |
| `awaiting_upload` | Upload the file (Step 2)                   |
| `processing`      | Wait and poll again                        |
| `completed`       | Ready to query                             |
| `failed`          | Check `error_message`, re-upload if needed |

> **Note**
>
> Processing typically takes 10–60 seconds for a standard PDF. Large documents (100+ pages) or scanned PDFs with OCR may take longer.

## Error handling

Common processing failures:

| Error                        | Cause                                                       | Fix                                   |
| ---------------------------- | ----------------------------------------------------------- | ------------------------------------- |
| `size_mismatch`              | Uploaded file size doesn't match declared `size_bytes`      | Re-reserve with correct size          |
| `content_type_mismatch`      | File content doesn't match declared `content_type`          | Ensure the file matches the MIME type |
| `extraction_failed`          | Unable to extract text (corrupted file, password-protected) | Re-upload a valid file                |
| `upload_url_creation_failed` | Storage service temporarily unavailable                     | Retry the reserve step                |

## Batch uploads

For uploading many files at once, repeat the reserve → upload → process cycle for each file, then call process with all document IDs:

```bash
# Reserve 3 files, upload each one, then process all at once
curl -X POST https://api.pyramid-ai.com/api/v2/documents/process \
  -H "Authorization: Bearer pai_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "document_ids": ["doc_1", "doc_2", "doc_3"]
  }'
```

This is more efficient than processing one at a time, as the pipeline can parallelize the work.