# Files

Source: https://staging-docs.aiand.com/api/files/

The Files API lets you upload an asset once and reference it in inference requests via a `file_id`, instead of re-sending the bytes (or base64) on every turn. Fully compatible with the [OpenAI Files API](https://platform.openai.com/docs/api-reference/files) shape, and dual-emits the [Anthropic Files API](https://docs.claude.com/en/docs/build-with-claude/files) shape when the request carries an `anthropic-version` header — so both the official `openai` and `anthropic` SDKs work against it without any extension.

```http
POST   /v1/files
GET    /v1/files
GET    /v1/files/{file_id}
GET    /v1/files/{file_id}/content
DELETE /v1/files/{file_id}
```

<Aside type="note">
  v1 supports `purpose: "vision"` (images), `purpose: "video"` (video clips), `purpose: "audio"` (audio clips), and `purpose: "document"` (PDFs).
</Aside>

## Limits

| Purpose    | Single-shot `/v1/files` | Total via [`/v1/uploads`](/api/uploads/) | Allowed mime types                                                                                                              |
|------------|-------------------------|------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------|
| `vision`   | 100 MB                  | 100 MB                                   | `image/png`, `image/jpeg`, `image/webp`, `image/gif`                                                                            |
| `video`    | 100 MB                  | 8 GB                                     | `video/mp4`, `video/webm`, `video/quicktime`                                                                                    |
| `audio`    | 100 MB                  | 1 GB                                     | `audio/wav`, `audio/x-wav`, `audio/mpeg`, `audio/mp3`, `audio/flac`, `audio/ogg`, `audio/webm`, `audio/mp4`, `audio/x-m4a`      |
| `document` | 100 MB                  | 1 GB                                     | `application/pdf`                                                                                                               |

<Aside type="tip">
  For files larger than 100 MB, use the [Uploads API](/api/uploads/) — it splits the file into ≤ 64 MB parts. The official `openai` SDK's `client.uploads.upload(...)` helper drives the chunked flow in one call.
</Aside>

<Aside type="caution">
  `document` files are converted to per-page images at upload time, so the upload call is synchronous. PDFs are capped at 100 pages — larger uploads return `400 invalid_request_error`. At inference time the model sees one image per page, so the target model must have the `vision` capability.
</Aside>

Uploads default to a 30-day expiry — `expires_at` is returned on every file object. After expiry, references will fail with `410 file_expired`. Files are also subject to soft delete via `DELETE`.

## Upload a file

```http
POST /v1/files
```

Multipart form upload.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `file` | file | Yes | The asset bytes |
| `purpose` | string | No | One of `vision`, `video`, `audio`, `document`. Determines limits + which models can reference it. If omitted, inferred from the file's MIME type — Anthropic SDK uploads (which don't send `purpose`) work transparently |

### Example

<Tabs>
  <TabItem label="Python">
    ```python
    from openai import OpenAI

    client = OpenAI(
        api_key="sk-your-api-key",
        base_url="https://api.aiand.com/v1",
    )

    with open("photo.png", "rb") as f:
        file = client.files.create(file=f, purpose="vision")

    print(file.id)  # → file-abc123...
    ```
  </TabItem>
  <TabItem label="JavaScript">
    ```javascript
    import OpenAI from "openai";
    import fs from "node:fs";

    const client = new OpenAI({
      apiKey: "sk-your-api-key",
      baseURL: "https://api.aiand.com/v1",
    });

    const file = await client.files.create({
      file: fs.createReadStream("photo.png"),
      purpose: "vision",
    });

    console.log(file.id); // → file-abc123...
    ```
  </TabItem>
  <TabItem label="curl">
    ```bash
    curl https://api.aiand.com/v1/files \
      -H "Authorization: Bearer sk-your-api-key" \
      -F "file=@photo.png" \
      -F "purpose=vision"
    ```
  </TabItem>
</Tabs>

### Response

```json
{
  "id": "file-abc123def456",
  "object": "file",
  "bytes": 482301,
  "purpose": "vision",
  "filename": "photo.png",
  "created_at": 1719450000,
  "expires_at": 1722042000
}
```

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Stable id with `file-` prefix. Use this in chat completion content parts. |
| `object` | string | Always `"file"` |
| `bytes` | integer | File size in bytes |
| `purpose` | string | What you uploaded it for |
| `filename` | string \| null | Original filename if provided |
| `created_at` | integer | Unix timestamp |
| `expires_at` | integer \| null | Unix timestamp; defaults to 30 days from upload |
| `page_count` | integer | Only present on `document` uploads — number of pages in the PDF |

## Reference a file in chat completions

Use a `type: "file"` content part inside a user message. Your client only ever sends the id; the bytes never leave the server again.

```json
{
  "model": "<vision-capable-model>",
  "messages": [{
    "role": "user",
    "content": [
      { "type": "text", "text": "What's in this image?" },
      { "type": "file", "file": { "file_id": "file-abc123def456" } }
    ]
  }]
}
```

<Tabs>
  <TabItem label="Python">
    ```python
    completion = client.chat.completions.create(
        model="<vision-capable-model>",
        messages=[{
            "role": "user",
            "content": [
                {"type": "text", "text": "What's in this image?"},
                {"type": "file", "file": {"file_id": file.id}},
            ],
        }],
    )

    print(completion.choices[0].message.content)
    ```
  </TabItem>
  <TabItem label="JavaScript">
    ```javascript
    const completion = await client.chat.completions.create({
      model: "<vision-capable-model>",
      messages: [{
        role: "user",
        content: [
          { type: "text", text: "What's in this image?" },
          { type: "file", file: { file_id: file.id } },
        ],
      }],
    });

    console.log(completion.choices[0].message.content);
    ```
  </TabItem>
</Tabs>

<Aside type="caution">
  The model must have the matching capability for the file's purpose: `purpose: "vision"` requires a `vision`-capable model, `purpose: "video"` requires a `video`-capable model, `purpose: "audio"` requires an `audio`-capable model, `purpose: "document"` requires a `vision`-capable model (the model receives one image per page). Otherwise the request returns `400 invalid_request_error`. See [`GET /v1/models`](/api/models/) for each model's capabilities.
</Aside>

### Video example

```json
{
  "model": "<video-capable-model>",
  "messages": [{
    "role": "user",
    "content": [
      { "type": "text", "text": "Describe what happens in this clip." },
      { "type": "file", "file": { "file_id": "file-vid789..." } }
    ]
  }]
}
```

The wire shape is identical to images — you always send `type: "file"` and the file's `purpose` determines how the model receives it.

### Audio example

```json
{
  "model": "<audio-capable-model>",
  "messages": [{
    "role": "user",
    "content": [
      { "type": "text", "text": "Transcribe and summarize this clip." },
      { "type": "file", "file": { "file_id": "file-aud456..." } }
    ]
  }]
}
```

Same shape. If you prefer to inline audio rather than upload it first, you can also send an OpenAI-shape `{ "type": "input_audio", "input_audio": { "data": "<base64>", "format": "wav" } }` part directly.

### Document (PDF) example

```json
{
  "model": "<vision-capable-model>",
  "messages": [{
    "role": "user",
    "content": [
      { "type": "text", "text": "Summarize this paper." },
      { "type": "file", "file": { "file_id": "file-doc456..." } }
    ]
  }]
}
```

The wire shape is the same `type: "file"` reference. The model receives one image per page, so it must have the `vision` capability. Page count is available as `page_count` on `GET /v1/files/{file_id}`.

PDF processing happens at upload time, so inference latency is unaffected by document size.

## List files

```http
GET /v1/files
```

| Query param | Type | Description |
|-------------|------|-------------|
| `purpose` | string | Filter by purpose |
| `limit` | integer | Page size, 1–100 (default 20) |
| `after` | string | Cursor: pass the `id` of the last item from the previous page |

Returns files in reverse-chronological order (newest first).

### Example

```bash
curl "https://api.aiand.com/v1/files?purpose=vision&limit=50" \
  -H "Authorization: Bearer sk-your-api-key"
```

### Response

```json
{
  "object": "list",
  "data": [
    { "id": "file-abc...", "object": "file", "bytes": 482301, "purpose": "vision", "filename": "photo.png", "created_at": 1719450000, "expires_at": 1722042000 }
  ],
  "has_more": false,
  "first_id": "file-abc...",
  "last_id": "file-abc..."
}
```

When `has_more: true`, pass `last_id` as the next request's `after`. SDK paging (`client.files.list().auto_paging_iter()`) works automatically.

## Retrieve file metadata

```http
GET /v1/files/{file_id}
```

Returns the same shape as the upload response.

```bash
curl https://api.aiand.com/v1/files/file-abc123 \
  -H "Authorization: Bearer sk-your-api-key"
```

## Download file content

```http
GET /v1/files/{file_id}/content
```

Returns the raw bytes with `Content-Type` and `Content-Disposition` headers set from the upload metadata.

<Tabs>
  <TabItem label="Python">
    ```python
    content = client.files.content("file-abc123")
    with open("downloaded.png", "wb") as f:
        f.write(content.read())
    ```
  </TabItem>
  <TabItem label="curl">
    ```bash
    curl https://api.aiand.com/v1/files/file-abc123/content \
      -H "Authorization: Bearer sk-your-api-key" \
      -o downloaded.png
    ```
  </TabItem>
</Tabs>

## Delete a file

```http
DELETE /v1/files/{file_id}
```

Deletes the file. Subsequent inference requests referencing the id will fail.

```bash
curl -X DELETE https://api.aiand.com/v1/files/file-abc123 \
  -H "Authorization: Bearer sk-your-api-key"
```

### Response

```json
{
  "id": "file-abc123",
  "object": "file",
  "deleted": true
}
```

Returns `404` if the file is missing or already deleted (idempotent semantics).

## Errors

| Status | `error.code` | When |
|--------|--------------|------|
| `400` | `invalid_request_error` | Missing `file`/`purpose`, unknown `purpose`, mime not in allowlist, file exceeds size cap |
| `400` | `model_capability_mismatch` | Chat completion references a file whose `purpose` requires a capability the model lacks |
| `401` | `invalid_api_key` | Missing or invalid `Authorization` header |
| `404` | `file_not_found` | Unknown id, or id belongs to a different organization |
| `410` | `file_expired` | File past its `expires_at` |
| `500` | `server_error` | Internal failure |

## Notes

- File ids are scoped to the **organization** that uploaded them. Cross-org references return `404`.
- For one-off small images you can still inline base64 via the standard `{type: "image_url", image_url: {url: "data:..."}}` content part — but multi-turn conversations and large assets benefit from upload-once + reference.
