Skip to content

Commit c02bf8c

Browse files
authored
Merge pull request #17 from mozex/files-resource
Add Files API resource
2 parents 62dd0d7 + b491b68 commit c02bf8c

25 files changed

Lines changed: 1372 additions & 1 deletion

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ A community-maintained PHP SDK for the [Anthropic API](https://platform.claude.c
2525
- [Token Counting](https://mozex.dev/docs/anthropic-php/v1/usage/token-counting)
2626
- [Models](https://mozex.dev/docs/anthropic-php/v1/usage/models)
2727
- [Batches](https://mozex.dev/docs/anthropic-php/v1/usage/batches)
28+
- [Files](https://mozex.dev/docs/anthropic-php/v1/usage/files)
2829
- [Completions](https://mozex.dev/docs/anthropic-php/v1/usage/completions)
2930
- Reference
3031
- [Configuration](https://mozex.dev/docs/anthropic-php/v1/reference/configuration)

docs/reference/testing.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ Every response class has a `fake()` method:
5151
| `Batches\BatchListResponse::fake()` | Batch list |
5252
| `Batches\BatchResultResponse::fake()` | Batch results |
5353
| `Batches\DeletedBatchResponse::fake()` | Batch delete |
54+
| `Files\FileResponse::fake()` | Files upload/retrieve metadata |
55+
| `Files\FileListResponse::fake()` | Files list |
56+
| `Files\DeletedFileResponse::fake()` | Files delete |
5457

5558
## Overriding response fields
5659

docs/usage/files.md

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
---
2+
title: Files
3+
weight: 10
4+
---
5+
6+
The Files API lets you upload documents once and reference them by `file_id` in any later Messages call. Anthropic currently flags this endpoint as beta on their side; the SDK sends the required header for you, so there's nothing to configure.
7+
8+
## Uploading a file
9+
10+
Pass any readable resource under the `file` key. The client streams it to `/v1/files` as `multipart/form-data` and gives you back the metadata:
11+
12+
```php
13+
$response = $client->files()->upload([
14+
'file' => fopen('/path/to/document.pdf', 'r'),
15+
]);
16+
17+
$response->id; // 'file_011CNha8iCJcU1wXNR6q4V8w'
18+
$response->type; // 'file'
19+
$response->filename; // 'document.pdf'
20+
$response->mimeType; // 'application/pdf'
21+
$response->sizeBytes; // 1024000
22+
$response->createdAt; // '2025-01-01T00:00:00Z'
23+
$response->downloadable; // false
24+
```
25+
26+
Supported types map to content blocks like this:
27+
28+
| File type | MIME | Use it in |
29+
|-----------|------|-----------|
30+
| PDF | `application/pdf` | `document` block |
31+
| Plain text | `text/plain` | `document` block |
32+
| Images | `image/jpeg`, `image/png`, `image/gif`, `image/webp` | `image` block |
33+
| Code execution inputs | varies (CSV, XLSX, JSON, etc.) | `container_upload` block |
34+
35+
Max file size is 500 MB. The storage ceiling is 500 GB per organization.
36+
37+
Keep the `id` wherever you'd normally keep a file path. You'll hand it back to the Messages API as a `file_id`.
38+
39+
## Referencing a file in a message
40+
41+
Once you have an `id`, drop it into a content block. For a PDF or text file, that's a `document` block with `source.type` set to `file`. Note the `betas` key on the Messages call: the Messages endpoint only accepts `source.type: file` when the `files-api-2025-04-14` beta header is on that specific request. The SDK auto-injects it on `$client->files()` calls but doesn't know whether any given Messages call references a file_id, so you pass it explicitly here:
42+
43+
```php
44+
$response = $client->messages()->create([
45+
'model' => 'claude-opus-4-6',
46+
'max_tokens' => 1024,
47+
'betas' => ['files-api-2025-04-14'],
48+
'messages' => [
49+
[
50+
'role' => 'user',
51+
'content' => [
52+
['type' => 'text', 'text' => 'Summarise this document.'],
53+
[
54+
'type' => 'document',
55+
'source' => [
56+
'type' => 'file',
57+
'file_id' => 'file_011CNha8iCJcU1wXNR6q4V8w',
58+
],
59+
],
60+
],
61+
],
62+
],
63+
]);
64+
```
65+
66+
If every Messages call in your app references uploaded files, skip the per-call `betas` and set the header globally on the factory instead:
67+
68+
```php
69+
$client = Anthropic::factory()
70+
->withApiKey('your-api-key')
71+
->withHttpHeader('anthropic-beta', 'files-api-2025-04-14')
72+
->make();
73+
```
74+
75+
For images, swap `document` for `image`:
76+
77+
```php
78+
[
79+
'type' => 'image',
80+
'source' => [
81+
'type' => 'file',
82+
'file_id' => 'file_011CPMxVD3fHLUhvTqtsQA5w',
83+
],
84+
]
85+
```
86+
87+
Same file can be referenced from any number of requests, which is the point of the whole API.
88+
89+
## Listing files
90+
91+
`list()` returns a cursor-paginated page of files in the workspace tied to your API key:
92+
93+
```php
94+
$response = $client->files()->list(['limit' => 20]);
95+
96+
foreach ($response->data as $file) {
97+
$file->id;
98+
$file->filename;
99+
$file->sizeBytes;
100+
}
101+
102+
$response->firstId; // 'file_011CNha8iCJcU1wXNR6q4V8w'
103+
$response->lastId; // 'file_011CPMxVD3fHLUhvTqtsQA5w'
104+
$response->hasMore; // false
105+
```
106+
107+
Pagination works like the Models and Batches endpoints. `limit` goes up to 1000 and defaults to 20. Use `after_id` with the previous page's `lastId` to walk forward, or `before_id` with `firstId` to walk backward:
108+
109+
```php
110+
$page1 = $client->files()->list(['limit' => 100]);
111+
112+
while ($page1->hasMore) {
113+
$page1 = $client->files()->list([
114+
'limit' => 100,
115+
'after_id' => $page1->lastId,
116+
]);
117+
}
118+
```
119+
120+
There's also an optional `scope_id` parameter to filter by session scope, which matters if you're using files inside a session.
121+
122+
## Retrieving metadata
123+
124+
`retrieveMetadata()` is a GET on a single file. Same response shape as upload:
125+
126+
```php
127+
$file = $client->files()->retrieveMetadata('file_011CNha8iCJcU1wXNR6q4V8w');
128+
129+
$file->filename; // 'document.pdf'
130+
$file->mimeType; // 'application/pdf'
131+
$file->sizeBytes; // 1024000
132+
$file->downloadable; // false
133+
```
134+
135+
For session-scoped files (created by the code execution tool or Skills API), `scope` tells you where it came from:
136+
137+
```php
138+
$file->scope?->type; // 'session'
139+
$file->scope?->id; // 'session_01AbCdEfGhIjKlMnOpQrStUv'
140+
```
141+
142+
## Downloading a file
143+
144+
Only files created by the [code execution tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/code-execution-tool) or [Skills](https://platform.claude.com/docs/en/build-with-claude/skills-guide) can be downloaded. Files you upload yourself can't be read back. `downloadable` on the metadata tells you which is which.
145+
146+
`download()` returns the raw bytes as a string:
147+
148+
```php
149+
$bytes = $client->files()->download('file_011CPMxVD3fHLUhvTqtsQA5w');
150+
151+
file_put_contents('output.png', $bytes);
152+
```
153+
154+
For a large download, wrap the write in something that writes to disk as it goes rather than holding the whole thing in memory. If you're building on top of Guzzle or Symfony, you can swap the transporter out via the factory and stream directly.
155+
156+
## Deleting a file
157+
158+
```php
159+
$response = $client->files()->delete('file_011CNha8iCJcU1wXNR6q4V8w');
160+
161+
$response->id; // 'file_011CNha8iCJcU1wXNR6q4V8w'
162+
$response->type; // 'file_deleted'
163+
```
164+
165+
Deletes are permanent. Files still being referenced in an in-flight Messages call may keep working briefly, but new requests using that `file_id` will fail with a 404.
166+
167+
## Errors to expect
168+
169+
The common ones, from the [Anthropic docs](https://platform.claude.com/docs/en/build-with-claude/files):
170+
171+
- `404` if the `file_id` doesn't exist or belongs to another workspace.
172+
- `400` if you use the wrong block type for the file (e.g., image file inside a `document` block).
173+
- `400` if the filename breaks the rules (1 to 255 characters, no `< > : " | ? * \ /` or control characters).
174+
- `413` if the file is over 500 MB.
175+
- `403` if your organization is over the 500 GB total.
176+
177+
All of these surface as `Anthropic\Exceptions\ErrorException` with the usual `message` and `type` on the payload.
178+
179+
## Rate limits
180+
181+
Anthropic caps file-related calls at roughly 100 per minute. Normal messages and token usage rate limits still apply on top.
182+
183+
File operations themselves (upload, list, retrieve, download, delete) are free. Tokens are only charged when the file is actually used inside a Messages call.
184+
185+
---
186+
187+
For the full API reference, see the [Files API guide](https://platform.claude.com/docs/en/build-with-claude/files) and the [Files endpoint reference](https://platform.claude.com/docs/en/api/files-list) on the Anthropic docs.

src/Client.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Anthropic\Contracts\TransporterContract;
99
use Anthropic\Resources\Batches;
1010
use Anthropic\Resources\Completions;
11+
use Anthropic\Resources\Files;
1112
use Anthropic\Resources\Messages;
1213
use Anthropic\Resources\Models;
1314

@@ -61,4 +62,14 @@ public function batches(): Batches
6162
{
6263
return new Batches($this->transporter);
6364
}
65+
66+
/**
67+
* Upload, list, retrieve, download, and delete files.
68+
*
69+
* @see https://platform.claude.com/docs/en/build-with-claude/files
70+
*/
71+
public function files(): Files
72+
{
73+
return new Files($this->transporter);
74+
}
6475
}

src/Contracts/ClientContract.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Anthropic\Contracts\Resources\BatchesContract;
66
use Anthropic\Contracts\Resources\CompletionsContract;
7+
use Anthropic\Contracts\Resources\FilesContract;
78
use Anthropic\Contracts\Resources\MessagesContract;
89
use Anthropic\Contracts\Resources\ModelsContract;
910

@@ -37,4 +38,11 @@ public function models(): ModelsContract;
3738
* @see https://platform.claude.com/docs/en/api/messages/batches/create
3839
*/
3940
public function batches(): BatchesContract;
41+
42+
/**
43+
* Upload, list, retrieve, download, and delete files.
44+
*
45+
* @see https://platform.claude.com/docs/en/build-with-claude/files
46+
*/
47+
public function files(): FilesContract;
4048
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace Anthropic\Contracts\Resources;
4+
5+
use Anthropic\Responses\Files\DeletedFileResponse;
6+
use Anthropic\Responses\Files\FileListResponse;
7+
use Anthropic\Responses\Files\FileResponse;
8+
9+
interface FilesContract
10+
{
11+
/**
12+
* Uploads a file to be referenced in future API calls.
13+
*
14+
* @see https://platform.claude.com/docs/en/api/files-create
15+
*
16+
* @param array<string, mixed> $parameters
17+
*/
18+
public function upload(array $parameters): FileResponse;
19+
20+
/**
21+
* Lists files belonging to the workspace of the API key.
22+
*
23+
* @see https://platform.claude.com/docs/en/api/files-list
24+
*
25+
* @param array<string, mixed> $parameters
26+
*/
27+
public function list(array $parameters = []): FileListResponse;
28+
29+
/**
30+
* Retrieves metadata for a specific file.
31+
*
32+
* @see https://platform.claude.com/docs/en/api/files-metadata
33+
*/
34+
public function retrieveMetadata(string $fileId): FileResponse;
35+
36+
/**
37+
* Downloads the contents of a file created by skills or the code execution tool.
38+
*
39+
* @see https://platform.claude.com/docs/en/api/files-content
40+
*/
41+
public function download(string $fileId): string;
42+
43+
/**
44+
* Deletes a file.
45+
*
46+
* @see https://platform.claude.com/docs/en/api/files-delete
47+
*/
48+
public function delete(string $fileId): DeletedFileResponse;
49+
}

src/Resources/Files.php

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Anthropic\Resources;
6+
7+
use Anthropic\Contracts\Resources\FilesContract;
8+
use Anthropic\Responses\Files\DeletedFileResponse;
9+
use Anthropic\Responses\Files\FileListResponse;
10+
use Anthropic\Responses\Files\FileResponse;
11+
use Anthropic\ValueObjects\Transporter\Payload;
12+
use Anthropic\ValueObjects\Transporter\Response;
13+
14+
final class Files implements FilesContract
15+
{
16+
use Concerns\Transportable;
17+
18+
/**
19+
* The beta header required for every Files API request. Auto-injected on every method.
20+
*/
21+
private const BETA = 'files-api-2025-04-14';
22+
23+
/**
24+
* Uploads a file to be referenced in future API calls.
25+
*
26+
* @see https://platform.claude.com/docs/en/api/files-create
27+
*
28+
* @param array<string, mixed> $parameters
29+
*/
30+
public function upload(array $parameters): FileResponse
31+
{
32+
$payload = Payload::upload('files', $parameters)->withBetas([self::BETA]);
33+
34+
/** @var Response<array{id: string, type: string, filename: string, mime_type: string, size_bytes: int, created_at: string, downloadable?: bool, scope?: array{id: string, type: string}}> $response */
35+
$response = $this->transporter->requestObject($payload);
36+
37+
return FileResponse::from($response->data(), $response->meta());
38+
}
39+
40+
/**
41+
* Lists files belonging to the workspace of the API key.
42+
*
43+
* @see https://platform.claude.com/docs/en/api/files-list
44+
*
45+
* @param array<string, mixed> $parameters
46+
*/
47+
public function list(array $parameters = []): FileListResponse
48+
{
49+
$payload = Payload::list('files', $parameters)->withBetas([self::BETA]);
50+
51+
/** @var Response<array{data: array<int, array{id: string, type: string, filename: string, mime_type: string, size_bytes: int, created_at: string, downloadable?: bool, scope?: array{id: string, type: string}}>, first_id?: ?string, last_id?: ?string, has_more?: bool}> $response */
52+
$response = $this->transporter->requestObject($payload);
53+
54+
return FileListResponse::from($response->data(), $response->meta());
55+
}
56+
57+
/**
58+
* Retrieves metadata for a specific file.
59+
*
60+
* @see https://platform.claude.com/docs/en/api/files-metadata
61+
*/
62+
public function retrieveMetadata(string $fileId): FileResponse
63+
{
64+
$payload = Payload::retrieve('files', $fileId)->withBetas([self::BETA]);
65+
66+
/** @var Response<array{id: string, type: string, filename: string, mime_type: string, size_bytes: int, created_at: string, downloadable?: bool, scope?: array{id: string, type: string}}> $response */
67+
$response = $this->transporter->requestObject($payload);
68+
69+
return FileResponse::from($response->data(), $response->meta());
70+
}
71+
72+
/**
73+
* Downloads the contents of a file created by skills or the code execution tool.
74+
*
75+
* @see https://platform.claude.com/docs/en/api/files-content
76+
*/
77+
public function download(string $fileId): string
78+
{
79+
$payload = Payload::retrieveContent('files', $fileId)->withBetas([self::BETA]);
80+
81+
return $this->transporter->requestContent($payload);
82+
}
83+
84+
/**
85+
* Deletes a file.
86+
*
87+
* @see https://platform.claude.com/docs/en/api/files-delete
88+
*/
89+
public function delete(string $fileId): DeletedFileResponse
90+
{
91+
$payload = Payload::delete('files', $fileId)->withBetas([self::BETA]);
92+
93+
/** @var Response<array{id: string, type: string}> $response */
94+
$response = $this->transporter->requestObject($payload);
95+
96+
return DeletedFileResponse::from($response->data(), $response->meta());
97+
}
98+
}

0 commit comments

Comments
 (0)