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

# Upload File

> POST /v1/files — Upload a file and get an id the Expert can read in its sandbox

Upload a file to the platform, get a file id, then pass it to [Run Expert](/en/developers/api/agents-run) as `attachment_file_ids`. The platform materializes the file in the Expert's sandbox (`/home/user/uploads/`), where the Expert can `read` / `grep` it — including PDF, Word, and Excel.

<Note>
  **Why ids and not URLs**: the platform never fetches a link you hand it. That means (1) you never have to publish a readable link to a private document, and (2) the platform cannot be used as a proxy to reach your internal network. The read URL is minted by the platform at invoke time and only travels inside the platform.
</Note>

## Quick Start

One call with the SDK (which wraps the three steps below):

<CodeGroup>
  ```python Python theme={null}
  from profy import Profy

  async with Profy(api_key="sk-pro-your-key") as client:
      file = await client.files.create("./customs-declaration.pdf")

      result = await client.agents.run(
          "my-expert",
          "Read this declaration and list the line items",
          attachment_file_ids=[file.id],
      )
      print(result.text)
  ```

  ```typescript TypeScript theme={null}
  import { Profy } from "@profy-ai/sdk";
  import { readFile } from "node:fs/promises";

  const client = new Profy({ apiKey: "sk-pro-your-key" });

  const file = await client.files.create({
    data: await readFile("./customs-declaration.pdf"),
    filename: "customs-declaration.pdf",
  });

  const result = await client.agents.run(
    "my-expert",
    "Read this declaration and list the line items",
    { attachmentFileIds: [file.id] },
  );
  console.log(result.text);
  ```
</CodeGroup>

## Upload Flow

Without the SDK, uploading is three steps: get an upload ticket, PUT the bytes to storage, register the object.

The bytes go straight to object storage rather than through the platform's API pods, so large files are not bounded by a request body limit.

### 1. Get an Upload Ticket

```
POST https://api.profy.cn/v1/files/upload-url
```

<ParamField body="filename" type="string" required>
  File name. Must not contain path separators (`/`, `\`); max 255 characters.
</ParamField>

<ParamField body="content_type" type="string" required>
  MIME type, e.g. `application/pdf`. Must be on the platform's allowlist.
</ParamField>

<ParamField body="size" type="integer" required>
  Size in bytes, max 100 MB. Used here for a fail-fast storage quota check.
</ParamField>

```json Response theme={null}
{
  "code": 0,
  "message": "ok",
  "data": {
    "upload_url": "https://cos.example.com/...&X-Amz-Signature=...",
    "file_key": "platform-api/3f2c.../customs-declaration.pdf",
    "content_type": "application/pdf",
    "expires_in": 600
  }
}
```

### 2. PUT the Bytes to Storage

Send a `PUT` to `upload_url` with the file bytes as the body.

<Warning>
  `Content-Type` must exactly match the `content_type` from step 1 — the signature covers that header, and a mismatch is a 403.

  Do **not** send an `Authorization` header on this `PUT`: object storage would switch to header-based auth and ignore the query-string signature.
</Warning>

```bash theme={null}
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/pdf" \
  --data-binary @customs-declaration.pdf
```

### 3. Register the File

```
POST https://api.profy.cn/v1/files
```

<ParamField body="file_key" type="string" required>
  The `file_key` returned in step 1.
</ParamField>

<ParamField body="filename" type="string">
  File name. Defaults to the name embedded in `file_key`.
</ParamField>

<ParamField body="content_type" type="string">
  MIME type. Defaults to what object storage recorded.
</ParamField>

The platform confirms the object was really uploaded (and uses the size reported by storage rather than the size declared in the request), then returns the file id:

```json Response theme={null}
{
  "code": 0,
  "message": "ok",
  "data": {
    "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "name": "customs-declaration.pdf",
    "type": "application/pdf",
    "size": 284913
  }
}
```

## Delete a File

```
DELETE https://api.profy.cn/v1/files/{file_id}
```

Soft-deletes the record and purges the stored object. You can only delete your own files; deleting an unknown id also succeeds (idempotent).

<CodeGroup>
  ```python Python theme={null}
  await client.files.delete(file.id)
  ```

  ```typescript TypeScript theme={null}
  await client.files.delete(file.id);
  ```
</CodeGroup>

## Limits

| Item                   | Limit                                                                                    |
| ---------------------- | ---------------------------------------------------------------------------------------- |
| File size              | 100 MB                                                                                   |
| Attachments per run    | 20                                                                                       |
| Upload ticket lifetime | 10 minutes                                                                               |
| File types             | Platform MIME allowlist (PDF / Office / text / code / images / audio / video / archives) |
| Storage quota          | Per account plan; 403 when exceeded                                                      |

## Error Codes

| HTTP Status Code | Description                                                                                                |
| ---------------- | ---------------------------------------------------------------------------------------------------------- |
| `400`            | Missing or invalid parameters (path separator in filename, disallowed type, over size limit, empty object) |
| `401`            | Authentication failed                                                                                      |
| `403`            | Storage quota exceeded                                                                                     |
| `404`            | No object at `file_key` (not uploaded yet, or the ticket expired)                                          |

## Next Steps

<CardGroup cols={2}>
  <Card title="Run Expert" icon="robot" href="/en/developers/api/agents-run">
    Pass the file to an Expert via `attachment_file_ids`
  </Card>

  <Card title="Python SDK" icon="python" href="/en/developers/sdk-python">
    Complete Python SDK guide
  </Card>
</CardGroup>
