curl --request POST \
--url https://api.infery.ai/v1/files \
--header 'Authorization: <api-key>' \
--header 'Content-Type: multipart/form-data' \
--form file='@example-file'import requests
url = "https://api.infery.ai/v1/files"
files = { "file": ("example-file", open("example-file", "rb")) }
headers = {"Authorization": "<api-key>"}
response = requests.post(url, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('file', '<string>');
const options = {method: 'POST', headers: {Authorization: '<api-key>'}};
options.body = form;
fetch('https://api.infery.ai/v1/files', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.infery.ai/v1/files",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: <api-key>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.infery.ai/v1/files"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.infery.ai/v1/files")
.header("Authorization", "<api-key>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.infery.ai/v1/files")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<api-key>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "file_1hR9xTPZqK4mVLc2nJ7fY5wB",
"object": "file",
"bytes": 245192,
"created_at": 1713204900,
"filename": "report.pdf",
"purpose": "assistants",
"status": "processed"
}{
"error": {
"message": "Model not found",
"type": "invalid_request_error",
"code": "model_not_found",
"param": "model",
"job_id": "job_1hR9xTPZqK4mVLc2nJ7fY5wB"
}
}{
"error": {
"message": "Model not found",
"type": "invalid_request_error",
"code": "model_not_found",
"param": "model",
"job_id": "job_1hR9xTPZqK4mVLc2nJ7fY5wB"
}
}{
"error": {
"message": "Model not found",
"type": "invalid_request_error",
"code": "model_not_found",
"param": "model",
"job_id": "job_1hR9xTPZqK4mVLc2nJ7fY5wB"
}
}Upload a file
POST /v1/files — upload a file and get back a reusable file_id.
curl --request POST \
--url https://api.infery.ai/v1/files \
--header 'Authorization: <api-key>' \
--header 'Content-Type: multipart/form-data' \
--form file='@example-file'import requests
url = "https://api.infery.ai/v1/files"
files = { "file": ("example-file", open("example-file", "rb")) }
headers = {"Authorization": "<api-key>"}
response = requests.post(url, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('file', '<string>');
const options = {method: 'POST', headers: {Authorization: '<api-key>'}};
options.body = form;
fetch('https://api.infery.ai/v1/files', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.infery.ai/v1/files",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: <api-key>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.infery.ai/v1/files"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.infery.ai/v1/files")
.header("Authorization", "<api-key>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.infery.ai/v1/files")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<api-key>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "file_1hR9xTPZqK4mVLc2nJ7fY5wB",
"object": "file",
"bytes": 245192,
"created_at": 1713204900,
"filename": "report.pdf",
"purpose": "assistants",
"status": "processed"
}{
"error": {
"message": "Model not found",
"type": "invalid_request_error",
"code": "model_not_found",
"param": "model",
"job_id": "job_1hR9xTPZqK4mVLc2nJ7fY5wB"
}
}{
"error": {
"message": "Model not found",
"type": "invalid_request_error",
"code": "model_not_found",
"param": "model",
"job_id": "job_1hR9xTPZqK4mVLc2nJ7fY5wB"
}
}{
"error": {
"message": "Model not found",
"type": "invalid_request_error",
"code": "model_not_found",
"param": "model",
"job_id": "job_1hR9xTPZqK4mVLc2nJ7fY5wB"
}
}file_id, reference it in chat completions.
Upload
curl https://api.infery.ai/v1/files \
-H "Authorization: Bearer $INFERY_API_KEY" \
-F purpose=assistants \
-F file=@./report.pdf
from openai import OpenAI
client = OpenAI(api_key=API_KEY, base_url="https://api.infery.ai/v1")
f = client.files.create(file=open("report.pdf", "rb"), purpose="assistants")
print(f.id) # file_abc123...
const f = await client.files.create({
file: fs.createReadStream('./report.pdf'),
purpose: 'assistants',
});
console.log(f.id);
{
"id": "file_1hR9xTPZqK4mVLc2nJ7fY5wB",
"object": "file",
"bytes": 245192,
"created_at": 1713204900,
"filename": "report.pdf",
"purpose": "assistants",
"status": "processed"
}
Purposes
| purpose | Used for |
|---|---|
assistants | General-purpose attachment (most common) |
vision | Image inputs |
user_data | Per-user documents (RAG, analysis) |
batch | Batch inference inputs (coming soon) |
Reference in chat
{
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "Summarise this"},
{"type": "file", "file_id": "file_abc123..."}
]
}]
}
Idempotency
Idempotency-Key: my-unique-request-2026-04-15
file_id instead of uploading twice. Key format: [A-Za-z0-9_.-:]{1,255}.
Security
- MIME sniffing via magic bytes — we don’t trust
Content-Type. Mismatch → 400. - Workspace isolation — cross-workspace lookup returns 404 (we don’t leak existence).
- Private storage — GCS objects have
Cache-Control: private, max-age=0. Download only via our signed proxy. - Size and count caps — plan-based, see Plans.
- Filename sanitisation — basename stripped, control chars removed, max 255 chars.
Quotas
Your plan caps:max_file_size_bytes— single uploadmax_files_per_workspace— total countmax_storage_bytes_per_workspace— total size
X-Storage-Used-Bytes, X-Storage-Limit-Bytes, X-Files-Used, X-Files-Limit.
max_file_size_bytes caps one upload. Using several uploaded files in a
single chat request has its own, separate cap on their combined size — see
Total attachment size per request.Authorizations
API key in format: Bearer inf_***
Headers
Same key within 24h returns the originally created file instead of uploading twice. Format: [A-Za-z0-9_.-:]{1,255}
Body
The file contents. Exactly one file part per request; a second is rejected with 400 too_many_files.
Intended use of the file. It is stored verbatim and can be filtered on in GET /v1/files; it does not change validation, storage or quota treatment. pipeline_artifact and media_artifact are the values our own workers stamp on generated files — accepted here, but not what a caller normally uploads with.
assistants, vision, user_data, batch, pipeline_artifact, media_artifact Response
File uploaded successfully.
File handle, file_ followed by a base62 id. Use it on every other /v1/files route.
"file_1hR9xTPZqK4mVLc2nJ7fY5wB"
Always file.
"file"
Stored size in bytes. Counts against the workspace storage quota.
245192
Unix timestamp, in seconds, of when the file was stored.
1713204900
Sanitized basename. Any directory component of the uploaded name is stripped.
"report.pdf"
What the file is for. pipeline_artifact and media_artifact are stamped on files our own workflow and media-job workers produce; they appear in listings even though a caller rarely uploads with them.
assistants, vision, user_data, batch, pipeline_artifact, media_artifact "assistants"
Always processed — a constant kept for OpenAI parity. There is no asynchronous post-upload processing, so this never reports anything else and is not worth branching on.
"processed"