> For clean Markdown content of this page, append .md to this URL. For the complete documentation index, see https://docs.agentmail.to/llms.txt.

# Create File

POST https://api.agentmail.to/v0/inboxes/{inbox_id}/files
Content-Type: application/json

Starts an upload: creates a pending file at `path` and returns an `upload` to `PUT` its bytes to. The bytes go
straight to storage, not through the API. Then call Complete File to make the file `ready`.

Retry-safe: sending the identical request again (same `path`, `size`, `sha256`, `content_type` and
`expires_at`) returns the existing file with `200` instead of `201`: a pending one with a fresh `upload`, or
a `ready` one without. If its bytes were uploaded but the upload was never completed, it is completed then. A
different request at a path whose file is still pending and has no bytes replaces that file; at any other
taken path it returns `409`.

The file counts against the organization's Drive quota from creation, pending or not: 1 GiB and 10,000 files
by default. A file that doesn't fit returns `403` with code `limit_exceeded`. Deleting a file frees its
space within seconds. Expired files count until a create needs their space: a create that doesn't fit first
gives back what expired files hold, a batch at a time, so retry a refusal while many expired files remain.

Requires the `file_create` permission, which an unverified organization's keys don't have at any scope until
it completes agent verification. A key without `file_read` can still create files, but gets the plain `409` at
any taken path, even for an identical request at a file it didn't create.

Agent Drive is in AgentMail Labs, available in US production (`api.agentmail.to`) but not yet in EU
production (`api.agentmail.eu`). Organizations that have not joined Labs receive a `403`.

Reference: https://agentmail.to/docs/api-reference/inboxes/files/create

## Authentication

- `Authorization` header (bearer token, required) — Bearer authentication of the form `Bearer <token>`, where token is your auth token.

## Servers

- `https://api.agentmail.to` (prod, default)
- `https://x402.api.agentmail.to` (prod-x402)
- `https://mpp.api.agentmail.to` (prod-mpp)
- `https://api.agentmail.eu` (eu-prod)

## Request

### Path parameters

- `inbox_id` (string, required) — The ID of the inbox.

### Body (application/json)

This endpoint expects an object.

- `path` (string, required) — Where the file lives in its inbox's drive, such as `reports/q3.pdf`. Unique within the inbox. Segments are separated by `/`, with no leading or trailing `/`, no empty, `.` or `..` segment, no `\` and no control or invisible characters. At most 1,024 bytes, and at most 255 bytes per segment. Stored in Unicode NFC, so two spellings of one name are one path. Case is kept.
- `sha256` (string, required) — The base64 encoding of the file's 32-byte SHA-256 digest, as S3's `x-amz-checksum-sha256` takes it (44 characters, ending in `=`). S3 refuses an upload whose bytes don't match.
- `size` (integer, required) — Size of the file in bytes. At most 104,857,600 (100 MiB).
- `content_type` (string, optional) — The file's MIME type, such as `application/pdf`. Defaults to `application/octet-stream`.
- `expires_at` (datetime, optional) — When the file expires, in the future. Rounded up to a whole second and returned in UTC. On a retry, send the `expires_at` the first response returned.

## Response

### 200

- `content_type` (string, required) — The file's MIME type, such as `application/pdf`. Defaults to `application/octet-stream`.
- `created_at` (datetime, required) — When the file was created.
- `email` (string, required) — Email address of the inbox.
- `file_id` (string, required) — ID of the file. A UUID.
- `inbox_id` (string, required) — The ID of the inbox.
- `name` (string, required) — The path's last segment. A download saves under this name.
- `path` (string, required) — Where the file lives in its inbox's drive, such as `reports/q3.pdf`. Unique within the inbox. Segments are separated by `/`, with no leading or trailing `/`, no empty, `.` or `..` segment, no `\` and no control or invisible characters. At most 1,024 bytes, and at most 255 bytes per segment. Stored in Unicode NFC, so two spellings of one name are one path. Case is kept.
- `sha256` (string, required) — The base64 encoding of the file's 32-byte SHA-256 digest, as S3's `x-amz-checksum-sha256` takes it (44 characters, ending in `=`). S3 refuses an upload whose bytes don't match.
- `size` (integer, required) — Size of the file in bytes. At most 104,857,600 (100 MiB).
- `status` (enum, required) — `pending` until its bytes are uploaded and the upload is completed, then `ready`. Only a `ready` file has a download URL. A pending file is deleted if it isn't completed within a day.
  - Allowed values: `pending`, `ready`
- `updated_at` (datetime, required) — When the file last changed, such as when its upload was completed.
- `download_expires_at` (datetime, optional) — When `download_url` stops working. Never after the file's own expiry.
- `download_url` (string, optional) — A signed URL that downloads the file's bytes, named by `name`. No API key is needed to use it, so treat it as a secret. It works until `download_expires_at`, at most an hour, even after the file is deleted. Get the file again for a fresh one.
- `expires_at` (datetime, optional) — When the file expires. After it, the file is gone from get and list and its path is free; its bytes are deleted soon after. Absent for a file that never expires.
- `upload` (FileUpload, optional) — Where to upload a pending file's bytes: one HTTP `PUT` to `url` with exactly `headers` and the file's bytes as the body. Every header is signed, so S3 refuses the upload if any is missing or different, and refuses bytes whose size or SHA-256 doesn't match the file. Then call Complete File.

## Errors

### 400 Validation Error

- `name` (string, required) — Name of error.
- `errors` (any, required) — Validation errors. Each entry has a path and a message identifying the invalid field.
- `code` (string, optional) — Stable, machine-readable error code in snake_case (for example, not_found or missing_permission). Branch on this rather than the message text.
- `message` (string, optional) — Error message.
- `fix` (string, optional) — The concrete next action that resolves the error.
- `docs` (string, optional) — Link to the error reference entry for this code.

### 403 File Forbidden Error

`code` says why: `forbidden` when the organization hasn't joined AgentMail Labs; `missing_permission` when the credential lacks the file permission the endpoint needs, including `file_create` in an organization that hasn't completed agent verification, and every file permission for an organization- or pod-scoped key under a content-access policy; `account_suspended`, `pod_suspended` or `inbox_suspended` when the organization, pod or inbox is suspended; and, for Create File only, `limit_exceeded` when the file doesn't fit the organization's Drive quota.

- `name` (string, required) — Name of error.
- `message` (string, required) — Error message.
- `code` (string, optional) — Stable, machine-readable error code in snake_case (for example, not_found or missing_permission). Branch on this rather than the message text.
- `fix` (string, optional) — The concrete next action that resolves the error.
- `docs` (string, optional) — Link to the error reference entry for this code.

### 404 Not Found Error

- `name` (string, required) — Name of error.
- `message` (string, required) — Error message.
- `code` (string, optional) — Stable, machine-readable error code in snake_case (for example, not_found or missing_permission). Branch on this rather than the message text.
- `fix` (string, optional) — The concrete next action that resolves the error.
- `docs` (string, optional) — Link to the error reference entry for this code.

### 409 Conflict Error

- `name` (string, required) — Name of error.
- `message` (string, required) — Error message.
- `code` (string, optional) — Stable, machine-readable error code in snake_case (for example, not_found or missing_permission). Branch on this rather than the message text.
- `fix` (string, optional) — The concrete next action that resolves the error.
- `docs` (string, optional) — Link to the error reference entry for this code.

## Types

### FileUpload

Where to upload a pending file's bytes: one HTTP `PUT` to `url` with exactly `headers` and the file's bytes as the body. Every header is signed, so S3 refuses the upload if any is missing or different, and refuses bytes whose size or SHA-256 doesn't match the file. Then call Complete File.

- `method` ("PUT", required) — Always `PUT`.
- `url` (string, required) — The presigned S3 URL. No API key is needed to use it.
- `headers` (map from string to string, required) — The headers to send with the `PUT`, exactly as given.
- `expires_at` (datetime, required) — When `url` stops working, at most an hour after it was issued. Send the identical create request again for a fresh one.

## Examples

**Request**

```json
{
  "path": "reports/q3.pdf",
  "sha256": "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=",
  "size": 48213,
  "content_type": "application/pdf"
}
```

**Response**

```json
{
  "content_type": "application/pdf",
  "created_at": "2026-10-14T16:20:00Z",
  "email": "reports@agentmail.to",
  "file_id": "6f1c2f4e-8d3b-4a5e-9c7d-1b2a3c4d5e6f",
  "inbox_id": "reports@agentmail.to",
  "name": "q3.pdf",
  "path": "reports/q3.pdf",
  "sha256": "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=",
  "size": 48213,
  "status": "pending",
  "updated_at": "2026-10-14T16:20:00Z",
  "upload": {
    "method": "PUT",
    "url": "https://file-bucket-8c41a7e.s3.us-east-1.amazonaws.com/files/6f1c2f4e-8d3b-4a5e-9c7d-1b2a3c4d5e6f?X-Amz-Signature=...",
    "headers": {
      "Content-Length": "48213",
      "Content-Type": "application/pdf",
      "If-None-Match": "*",
      "x-amz-checksum-sha256": "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=",
      "x-amz-tagging": "upload-state=pending"
    },
    "expires_at": "2026-10-14T17:20:00Z"
  }
}
```

**SDK Code**

```python
import requests

url = "https://api.agentmail.to/v0/inboxes/reports%40agentmail.to/files"

payload = {
    "path": "reports/q3.pdf",
    "sha256": "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=",
    "size": 48213,
    "content_type": "application/pdf"
}
headers = {
    "Authorization": "Bearer <api_key>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.agentmail.to/v0/inboxes/reports%40agentmail.to/files';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <api_key>', 'Content-Type': 'application/json'},
  body: '{"path":"reports/q3.pdf","sha256":"n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=","size":48213,"content_type":"application/pdf"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.agentmail.to/v0/inboxes/reports%40agentmail.to/files"

	payload := strings.NewReader("{\n  \"path\": \"reports/q3.pdf\",\n  \"sha256\": \"n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=\",\n  \"size\": 48213,\n  \"content_type\": \"application/pdf\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <api_key>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.agentmail.to/v0/inboxes/reports%40agentmail.to/files")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <api_key>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"path\": \"reports/q3.pdf\",\n  \"sha256\": \"n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=\",\n  \"size\": 48213,\n  \"content_type\": \"application/pdf\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.agentmail.to/v0/inboxes/reports%40agentmail.to/files")
  .header("Authorization", "Bearer <api_key>")
  .header("Content-Type", "application/json")
  .body("{\n  \"path\": \"reports/q3.pdf\",\n  \"sha256\": \"n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=\",\n  \"size\": 48213,\n  \"content_type\": \"application/pdf\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.agentmail.to/v0/inboxes/reports%40agentmail.to/files', [
  'body' => '{
  "path": "reports/q3.pdf",
  "sha256": "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=",
  "size": 48213,
  "content_type": "application/pdf"
}',
  'headers' => [
    'Authorization' => 'Bearer <api_key>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.agentmail.to/v0/inboxes/reports%40agentmail.to/files");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <api_key>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"path\": \"reports/q3.pdf\",\n  \"sha256\": \"n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=\",\n  \"size\": 48213,\n  \"content_type\": \"application/pdf\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <api_key>",
  "Content-Type": "application/json"
]
let parameters = [
  "path": "reports/q3.pdf",
  "sha256": "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=",
  "size": 48213,
  "content_type": "application/pdf"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.agentmail.to/v0/inboxes/reports%40agentmail.to/files")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```