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

# Files

> Upload media once and reuse it across requests.

Upload media to the Files API, then reference its `file-...` ID in requests. Reusing a file ID avoids uploading the same bytes again. Each occurrence still has its own position in the request's [asset ordering](/perceptron-mk1.5/guides/multiple-assets); file IDs and `asset_idx` serve different purposes.

## Upload and use a local image

Install `perceptron>=0.4.0` and set `PERCEPTRON_API_KEY` as shown in the [quickstart](/perceptron-mk1.5/index). Replace `./image.png` with a local PNG image. The SDK's `files.upload()` sends the bytes to the Files API and returns a `File`; pass that object to `image()` to reference its ID in a completion.

<CodeGroup>
  ```python Python theme={null}
  from perceptron import Client, image

  client = Client(provider="perceptron")
  uploaded = client.files.upload("./image.png", purpose="vision")

  response = client.chat.completions.create(
      model="perceptron-mk1.5",
      messages=[{
          "role": "user",
          "content": [
              image(uploaded),
              {"type": "text", "text": "Describe the scene and its main objects."},
          ],
      }],
      max_completion_tokens=1024,
  )
  if not response.complete or response.tool_calls:
      raise RuntimeError(f"Incomplete answer: {response.finish_reason}")
  print(response.text)
  print("Reusable file ID:", uploaded.id)
  ```

  ```bash curl theme={null}
  set -euo pipefail

  file_id="$(
    curl --fail-with-body --silent --show-error \
      https://api.perceptron.inc/v1/files \
      -H "Authorization: Bearer $PERCEPTRON_API_KEY" \
      -F 'purpose=vision' \
      -F 'file=@./image.png;type=image/png' \
      | jq -er '.id'
  )"

  jq -n --arg file_id "$file_id" '{
    model: "perceptron-mk1.5",
    messages: [{
      role: "user",
      content: [
        {type: "image_file_id", image_file_id: {file_id: $file_id}},
        {type: "text", text: "Describe the scene and its main objects."}
      ]
    }],
    max_completion_tokens: 1024
  }' | curl --fail-with-body --silent --show-error \
    https://api.perceptron.inc/v1/chat/completions \
    -H "Authorization: Bearer $PERCEPTRON_API_KEY" \
    -H 'Content-Type: application/json' \
    --data-binary @-
  ```
</CodeGroup>

For a file already uploaded, use `image(file_id="file-...")` instead. Calling `image("./image.png")` directly embeds the local image in the completion request; it does not upload to the Files API. The same distinction applies to `video()` and `audio()`.

The curl example also needs Bash and `jq`. The upload uses multipart form data; let `curl` set its content type and boundary. The completion response has the same shape as any chat completion: read `choices[0].message.content` and check `choices[0].finish_reason` before treating the answer as complete.

## Upload and use a local video

Use `video(uploaded)` to send a `video_file_id` content part. Replace `./video.mp4` with a local video:

```python theme={null}
from perceptron import Client, video

client = Client(provider="perceptron")
uploaded = client.files.upload("./video.mp4", purpose="vision")
response = client.chat.completions.create(
    model="perceptron-mk1.5",
    messages=[{
        "role": "user",
        "content": [
            video(uploaded),
            {"type": "text", "text": "Describe the main actions in chronological order."},
        ],
    }],
    max_completion_tokens=1024,
)
if not response.complete or response.tool_calls:
    raise RuntimeError(f"Incomplete answer: {response.finish_reason}")
print(response.text)
```

The soundtrack is disabled by default. To analyze it alongside the frames, explicitly pass `vision_config={"enable_audio_in_video": True}` to the completion request. See [Video Q\&A](/perceptron-mk1.5/capabilities/video-qa) for soundtrack behavior.

## Upload and use local audio

Audio uploads support these canonical content types:

| Format | Content type | Accepted aliases            |
| ------ | ------------ | --------------------------- |
| WAV    | `audio/wav`  | `audio/x-wav`, `audio/wave` |
| MP3    | `audio/mpeg` | `audio/mp3`                 |
| FLAC   | `audio/flac` | `audio/x-flac`              |

Use the content type matching the file's actual bytes. Renaming a file does not convert its format. Replace `./recording.wav` with a local WAV recording:

<CodeGroup>
  ```python Python theme={null}
  from perceptron import Client, audio

  client = Client(provider="perceptron")
  uploaded = client.files.upload("./recording.wav", purpose="vision")
  response = client.chat.completions.create(
      model="perceptron-mk1.5",
      messages=[{
          "role": "user",
          "content": [
              audio(uploaded),
              {"type": "text", "text": "Transcribe the speech. Mark unclear words instead of guessing."},
          ],
      }],
      reasoning_effort="none",
      max_completion_tokens=2048,
  )
  if not response.complete or response.tool_calls:
      raise RuntimeError(f"Incomplete answer: {response.finish_reason}")
  print(response.text)
  ```

  ```bash curl theme={null}
  set -euo pipefail

  file_id="$(
    curl --fail-with-body --silent --show-error \
      https://api.perceptron.inc/v1/files \
      -H "Authorization: Bearer $PERCEPTRON_API_KEY" \
      -F 'purpose=vision' \
      -F 'file=@./recording.wav;type=audio/wav' \
      | jq -er '.id'
  )"

  jq -n --arg file_id "$file_id" '{
    model: "perceptron-mk1.5",
    messages: [{
      role: "user",
      content: [
        {type: "audio_file_id", audio_file_id: {file_id: $file_id}},
        {type: "text", text: "Transcribe the speech. Mark unclear words instead of guessing."}
      ]
    }],
    reasoning_effort: "none",
    max_completion_tokens: 2048
  }' | curl --fail-with-body --silent --show-error \
    https://api.perceptron.inc/v1/chat/completions \
    -H "Authorization: Bearer $PERCEPTRON_API_KEY" \
    -H 'Content-Type: application/json' \
    --data-binary @-
  ```
</CodeGroup>

`purpose=vision` is the accepted Files API purpose for media uploads, including audio. An `audio_file_id` input is analyzed without enabling video soundtracks. For an uploaded video's soundtrack, use `video_file_id` and set `vision_config.enable_audio_in_video: true` on the completion request.

A successful upload does not guarantee that the recording fits the model's audio or context limits. Each audio item is limited to 16,384 encoder tokens, approximately 21.8 minutes at 750 tokens per minute; timestamp tokens and other request content also consume context. See [Audio](/perceptron-mk1.5/capabilities/audio) and [tokenization](/perceptron-mk1.5/guides/tokenization) before submitting a long recording.

## Manage uploaded files

The SDK also provides `client.files.list()` for one page of files, `client.files.iter()` to iterate across pages, `client.files.retrieve(file_id)` for metadata, and `client.files.download(file_id, path)` to save the bytes locally. Call `client.files.delete(file_id)` when you no longer need a file; subsequent requests cannot use the deleted ID.

See [Upload a file](/perceptron-mk1.5/api-reference/files/upload-a-file) for accepted formats and [Delete a file](/perceptron-mk1.5/api-reference/files/delete-a-file) for the deletion endpoint.

## Use presigned media uploads

`POST /v1/media/upload-urls` also accepts WAV, MP3, and FLAC with the content types above. This is a separate upload flow from the Files API:

1. Send `files` entries containing `file_name`, `content_type`, and the exact byte `content_length` to `/v1/media/upload-urls`, using your Perceptron API key.
2. Upload the bytes with `PUT` to the returned `upload_url`, using the same `Content-Type` you declared. Do not send your Perceptron API key to the presigned URL.
3. Send the returned `object_key` in an `object_keys` array to `POST /v1/media/download-urls`, using your Perceptron API key. Use the returned `download_url` in an `audio_url` content part while it remains valid.

An `object_key` is not a Files API `file-...` ID. Use `/v1/files` when you want to reference the recording with `audio_file_id`.

## Referencing a file in a request

Reference an uploaded file in a chat completion by id with an `image_file_id` / `video_file_id` content part, or by passing its `/v1/files/{file_id}/content` URL as a standard `image_url` / `video_url`. All four forms are equivalent.

<CodeGroup>
  ```json Image (file_id) theme={null}
  { "type": "image_file_id", "image_file_id": { "file_id": "file-abc123" } }
  ```

  ```json Video (file_id) theme={null}
  { "type": "video_file_id", "video_file_id": { "file_id": "file-abc123" } }
  ```

  ```json Image (URL) theme={null}
  { "type": "image_url",
    "image_url": { "url": "https://api.perceptron.inc/v1/files/file-abc123/content" } }
  ```

  ```json Video (URL) theme={null}
  { "type": "video_url",
    "video_url": { "url": "https://api.perceptron.inc/v1/files/file-abc123/content" } }
  ```
</CodeGroup>

## Limits

Files are capped at **128 MiB** each, with **100 GiB** of total storage per organization. Per-endpoint rate limits are listed on each API reference page.

Need higher limits? [Contact us](mailto:support@perceptron.inc).
