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

# Documents

Documents are files uploaded to a project. Once processed, the AI can search through them to answer questions and verify compliance. See [Document Processing](/concepts/document-processing) for a detailed explanation of the upload flow.

---

## POST /api/v2/documents

**Reserve a document upload slot.** Step 1 of the upload flow. Returns a presigned URL for direct file upload to storage.

**Authentication:** Bearer token required

**Request body:**

| Field          | Type    | Required | Description                                               |
| -------------- | ------- | -------- | --------------------------------------------------------- |
| `file_name`    | string  | Yes      | File name (no path separators). 1–500 characters          |
| `content_type` | string  | Yes      | MIME type (e.g., `application/pdf`, `application/msword`) |
| `size_bytes`   | integer | Yes      | File size in bytes (max 500 MB)                           |
| `project_id`   | uuid    | No       | Project to associate with. Omit for org-level documents   |

**Example request:**

```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-Management-Plan-2026.pdf",
    "content_type": "application/pdf",
    "size_bytes": 2048576,
    "project_id": "7f9c8d2a-1b3e-4c5d-8e9f-0a1b2c3d4e5f"
  }'
```

**Example response** `201 Created`:

```json
{
  "success": true,
  "data": {
    "id": "9d11d31d-17ce-4b9f-8c13-63d7058f5fd3",
    "org_id": "iPwoNDP1ribwqCTkvlEyiA6DtGZdYe0k",
    "project_id": "7f9c8d2a-1b3e-4c5d-8e9f-0a1b2c3d4e5f",
    "file_name": "Safety-Management-Plan-2026.pdf",
    "content_type": "application/pdf",
    "size_bytes": null,
    "status": "awaiting_upload",
    "chunk_count": null,
    "error_message": null,
    "created_at": "2026-05-22T10:00:00Z",
    "updated_at": "2026-05-22T10:00:00Z",
    "upload": {
      "url": "https://storage.supabase.co/...",
      "method": "PUT",
      "recommended_content_type": "application/pdf",
      "expires_at": "2026-05-22T12:00:00Z"
    }
  },
  "request_id": "..."
}
```

After receiving the response, upload your file directly to `upload.url`:

```bash
curl -X PUT "UPLOAD_URL_FROM_RESPONSE" \
  -H "Content-Type: application/pdf" \
  --data-binary @Safety-Management-Plan-2026.pdf
```

> **Warning**
>
> The upload URL expires after approximately 2 hours. If it expires before you upload, call this endpoint again to get a fresh URL.

---

## POST /api/v2/documents/process

**Trigger processing for uploaded documents.** Step 2 of the upload flow. After uploading file bytes to the presigned URL, call this to start text extraction, chunking, and indexing.

**Authentication:** Bearer token required

**Request body:**

| Field          | Type    | Required | Description                              |
| -------------- | ------- | -------- | ---------------------------------------- |
| `document_ids` | uuid\[] | Yes      | Array of document IDs to process (1–100) |

**Example request:**

```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": ["9d11d31d-17ce-4b9f-8c13-63d7058f5fd3"]}'
```

**Example response** `200 OK`:

```json
{
  "success": true,
  "data": {
    "processed": ["9d11d31d-17ce-4b9f-8c13-63d7058f5fd3"],
    "skipped": [],
    "failed": []
  },
  "request_id": "..."
}
```

| Field       | Type      | Description                                                              |
| ----------- | --------- | ------------------------------------------------------------------------ |
| `processed` | uuid\[]   | Documents that started processing successfully                           |
| `skipped`   | object\[] | Documents skipped with a `reason` (already processed, wrong state, etc.) |
| `failed`    | object\[] | Documents that failed with an `error` and `message`                      |

**Skipped reasons:**

| Reason                  | Description                                              |
| ----------------------- | -------------------------------------------------------- |
| `not_found`             | Document ID doesn't exist or belongs to another org      |
| `not_awaiting_upload`   | Document already past the upload stage                   |
| `upload_not_found`      | File bytes not found in storage                          |
| `size_mismatch`         | Uploaded file size doesn't match declared `size_bytes`   |
| `content_type_mismatch` | Uploaded file type doesn't match declared `content_type` |

---

## GET /api/v2/documents

List documents with optional filters. Supports pagination.

**Authentication:** Bearer token required

**Query parameters:**

| Parameter      | Type    | Default | Description                                                                       |
| -------------- | ------- | ------- | --------------------------------------------------------------------------------- |
| `project_id`   | uuid    | —       | Filter by project                                                                 |
| `document_ids` | string  | —       | Comma-separated list of document UUIDs                                            |
| `status`       | string  | —       | Filter by status: `awaiting_upload`, `processing`, `ready`, `completed`, `failed` |
| `limit`        | integer | 50      | Maximum results per page (max 200)                                                |
| `offset`       | integer | 0       | Number of results to skip                                                         |

**Example request:**

```bash
curl "https://api.pyramid-ai.com/api/v2/documents?project_id=7f9c8d2a-1b3e-4c5d-8e9f-0a1b2c3d4e5f&status=completed" \
  -H "Authorization: Bearer pai_live_YOUR_KEY"
```

**Example response** `200 OK`:

```json
{
  "success": true,
  "data": [
    {
      "id": "9d11d31d-17ce-4b9f-8c13-63d7058f5fd3",
      "org_id": "iPwoNDP1ribwqCTkvlEyiA6DtGZdYe0k",
      "project_id": "7f9c8d2a-1b3e-4c5d-8e9f-0a1b2c3d4e5f",
      "file_name": "Safety-Management-Plan-2026.pdf",
      "content_type": "application/pdf",
      "size_bytes": 2048576,
      "status": "completed",
      "chunk_count": 85,
      "error_message": null,
      "created_at": "2026-05-22T10:00:00Z",
      "updated_at": "2026-05-22T10:05:30Z"
    }
  ],
  "request_id": "...",
  "pagination": {
    "limit": 50,
    "offset": 0,
    "total": 1
  }
}
```

---

## GET /api/v2/documents/\{id}/download

**Get a presigned download URL for a document.** Returns a short-lived URL (10-minute expiry) for downloading the original source file. Use this to trace AI-generated citations back to the source document — critical for compliance audit trails.

**Authentication:** Bearer token required

**Path parameters:**

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id`      | uuid | Document ID |

**Example request:**

```bash
curl https://api.pyramid-ai.com/api/v2/documents/9d11d31d-17ce-4b9f-8c13-63d7058f5fd3/download \
  -H "Authorization: Bearer pai_live_YOUR_KEY"
```

**Example response** `200 OK`:

```json
{
  "success": true,
  "data": {
    "document_id": "9d11d31d-17ce-4b9f-8c13-63d7058f5fd3",
    "file_name": "Safety-Management-Plan-2026.pdf",
    "content_type": "application/pdf",
    "size_bytes": 2048576,
    "download_url": "https://storage.supabase.co/object/sign/uploads/orgs/...?token=...",
    "expires_at": "2026-05-22T10:10:00Z"
  },
  "request_id": "..."
}
```

**Response fields:**

| Field          | Type            | Description                                                    |
| -------------- | --------------- | -------------------------------------------------------------- |
| `document_id`  | uuid            | The document this download URL is for                          |
| `file_name`    | string          | Original file name                                             |
| `content_type` | string          | MIME type of the file                                          |
| `size_bytes`   | integer \| null | File size in bytes                                             |
| `download_url` | string          | Presigned URL — use to download the file directly              |
| `expires_at`   | string          | When the URL expires (ISO 8601). Generate a new one if expired |

> **Warning**
>
> The download URL expires after approximately 10 minutes. If it expires, call this endpoint again to get a fresh URL.

**Error responses:**

| Status | Code        | When                                                                      |
| ------ | ----------- | ------------------------------------------------------------------------- |
| `404`  | `NOT_FOUND` | Document doesn't exist, belongs to another org, or file is not in storage |

---

## GET /api/v2/documents/\{id}

Get details for a single document, including its processing status.

**Authentication:** Bearer token required

**Path parameters:**

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id`      | uuid | Document ID |

**Example request:**

```bash
curl https://api.pyramid-ai.com/api/v2/documents/9d11d31d-17ce-4b9f-8c13-63d7058f5fd3 \
  -H "Authorization: Bearer pai_live_YOUR_KEY"
```

**Example response** `200 OK`:

```json
{
  "success": true,
  "data": {
    "id": "9d11d31d-17ce-4b9f-8c13-63d7058f5fd3",
    "org_id": "iPwoNDP1ribwqCTkvlEyiA6DtGZdYe0k",
    "project_id": "7f9c8d2a-1b3e-4c5d-8e9f-0a1b2c3d4e5f",
    "file_name": "Safety-Management-Plan-2026.pdf",
    "content_type": "application/pdf",
    "size_bytes": 2048576,
    "status": "completed",
    "chunk_count": 85,
    "error_message": null,
    "created_at": "2026-05-22T10:00:00Z",
    "updated_at": "2026-05-22T10:05:30Z"
  },
  "request_id": "..."
}
```

---

## DELETE /api/v2/documents

Delete one or more documents by ID. The AI will no longer reference these documents.

**Authentication:** Bearer token required

**Request body:**

| Field          | Type    | Required | Description                             |
| -------------- | ------- | -------- | --------------------------------------- |
| `document_ids` | uuid\[] | Yes      | Array of document IDs to delete (1–100) |

**Example request:**

```bash
curl -X DELETE https://api.pyramid-ai.com/api/v2/documents \
  -H "Authorization: Bearer pai_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"document_ids": ["9d11d31d-17ce-4b9f-8c13-63d7058f5fd3"]}'
```

**Example response** `200 OK`:

```json
{
  "success": true,
  "data": {
    "deleted": ["9d11d31d-17ce-4b9f-8c13-63d7058f5fd3"]
  },
  "request_id": "..."
}
```

**Error responses:**

| Status | Code        | When                                                        |
| ------ | ----------- | ----------------------------------------------------------- |
| `403`  | `FORBIDDEN` | One or more document IDs belong to a different organization |

**Document fields (all endpoints):**

| Field           | Type            | Description                                                                        |
| --------------- | --------------- | ---------------------------------------------------------------------------------- |
| `id`            | uuid            | Unique document identifier                                                         |
| `org_id`        | string          | Organization the document belongs to                                               |
| `project_id`    | uuid \| null    | Project the document is associated with                                            |
| `file_name`     | string          | Original file name                                                                 |
| `content_type`  | string          | MIME type (e.g., `application/pdf`)                                                |
| `size_bytes`    | integer \| null | File size in bytes                                                                 |
| `status`        | string          | Processing status: `awaiting_upload`, `processing`, `completed`, `ready`, `failed` |
| `chunk_count`   | integer \| null | Number of searchable chunks created from this document                             |
| `error_message` | string \| null  | Error details if status is `failed`                                                |
| `created_at`    | string          | When the document was created (ISO 8601)                                           |
| `updated_at`    | string          | When the document was last modified (ISO 8601)                                     |