Skip to main content

Files API

PREVIEW MODE

This API is currently in preview mode. Some routes are still subject to potential changes.

The Files API handles uploading, storing, and managing files for use across Nebul Inference API endpoints. Upload a document once, then reference it by ID in OCR, vision, and other model requests without re-uploading.

There are two upload methods: a simple single-request upload for small files, and a multipart upload for large files.

Base URL

1
https://api.inference.nebul.io

All endpoints require an Authorization header:

1
Authorization: Bearer <YOUR_API_KEY>

Simple Upload

POST /v1/files: Upload a file

Upload a file in a single request. Use this for files up to 50 MB.

12
POST /v1/files
Content-Type: multipart/form-data
FieldTypeRequiredDescription
fileFileYesThe file to upload.
purposeStringYesIntended use. One of: assistants, batch, fine-tune, vision, user_data, evals, modelweights, logo, avatar, ocr.
python
12345678910111213
import httpx
API_URL = "https://api.inference.nebul.io"
HEADERS = {"Authorization": f"Bearer <YOUR_API_KEY>"}
with open("document.pdf", "rb") as f:
upload = httpx.post(
f"{API_URL}/v1/files",
files={"file": ("document.pdf", f, "application/pdf")},
data={"purpose": "ocr"},
headers=HEADERS,
)
file_id = upload.json()["id"]
bash
1234
curl -X POST https://api.inference.nebul.io/v1/files \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-F "file=@document.pdf" \
-F "purpose=ocr"

Response:

json
12345678910
{
"id": "0193a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"filename": "document.pdf",
"org_id": "0191a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"project_id": "0192a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"purpose": "ocr",
"file_size": 245678,
"status": "complete",
"created_at": "2025-01-15T10:30:00Z"
}

GET /v1/files: List files

List files you have uploaded. Optional query parameters filter the results.

ParameterInTypeRequiredDescription
purposequeryStringNoFilter by file purpose (e.g. ocr, vision).
created_byqueryUUIDNoFilter by the user who uploaded the file.
python
12345
files = httpx.get(
f"{API_URL}/v1/files",
params={"purpose": "ocr"},
headers=HEADERS,
).json()
bash
12
curl https://api.inference.nebul.io/v1/files?purpose=ocr \
-H "Authorization: Bearer <YOUR_API_KEY>"

Response:

json
12345678910111213
{
"object": "list",
"data": [
{
"id": "0193a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"filename": "document.pdf",
"purpose": "ocr",
"file_size": 245678,
"status": "complete",
"created_at": "2025-01-15T10:30:00Z"
}
]
}

GET /v1/files/{file_id}: Retrieve file metadata

Get metadata for a specific file by its ID.

python
1234
file_meta = httpx.get(
f"{API_URL}/v1/files/{file_id}",
headers=HEADERS,
).json()
bash
12
curl https://api.inference.nebul.io/v1/files/0193a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b \
-H "Authorization: Bearer <YOUR_API_KEY>"

GET /v1/files/{file_id}/content: Download file content

Download the raw file content. Returns the file bytes directly.

python
123456
content = httpx.get(
f"{API_URL}/v1/files/{file_id}/content",
headers=HEADERS,
)
with open("downloaded.pdf", "wb") as f:
f.write(content.content)
bash
123
curl https://api.inference.nebul.io/v1/files/0193a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b/content \
-H "Authorization: Bearer <YOUR_API_KEY>" \
--output downloaded.pdf

DELETE /v1/files/{file_id}: Delete a file

Remove a file and its stored content. Returns a confirmation with the deleted file ID.

python
1234
httpx.delete(
f"{API_URL}/v1/files/{file_id}",
headers=HEADERS,
)
bash
12
curl -X DELETE https://api.inference.nebul.io/v1/files/0193a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b \
-H "Authorization: Bearer <YOUR_API_KEY>"

Response:

json
12345
{
"id": "0193a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"object": "file",
"deleted": true
}

Multipart Upload

For files larger than 50 MB, use the multipart upload flow. This splits the upload into parts that can be sent independently and reassembled server-side.

Step 1: Create an upload session

12
POST /v1/uploads
Content-Type: application/json
FieldTypeRequiredDescription
filenameStringYesName of the file to upload.
purposeStringYesIntended use (same values as simple upload).
bytesIntegerYesTotal file size in bytes.
python
12345678910
upload_session = httpx.post(
f"{API_URL}/v1/uploads",
json={
"filename": "large-document.pdf",
"purpose": "ocr",
"bytes": 524288000,
},
headers={**HEADERS, "Content-Type": "application/json"},
).json()
upload_id = upload_session["id"]
bash
1234
curl -X POST https://api.inference.nebul.io/v1/uploads \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{"filename": "large-document.pdf", "purpose": "ocr", "bytes": 524288000}'

Response:

json
12345678910
{
"id": "0194a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"object": "upload",
"bytes": 524288000,
"bytes_uploaded": 0,
"created_at": "2025-01-15T10:30:00Z",
"status": "pending",
"filename": "large-document.pdf",
"purpose": "ocr"
}

Step 2: Upload parts

Upload each chunk of the file as a separate part.

12
POST /v1/uploads/{upload_id}/parts
Content-Type: multipart/form-data
FieldTypeRequiredDescription
part_numberIntegerYes1-based index of this part.
dataFileYesThe raw bytes for this part.
python
1234567
part = httpx.post(
f"{API_URL}/v1/uploads/{upload_id}/parts",
files={"data": ("part", chunk_bytes, "application/octet-stream")},
data={"part_number": str(part_number)},
headers=HEADERS,
).json()
part_ids.append(part["id"])
bash
1234
curl -X POST https://api.inference.nebul.io/v1/uploads/$UPLOAD_ID/parts \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-F "part_number=1" \
-F "data=@part1.bin"

Response:

json
123456
{
"id": "part-abc123",
"object": "upload.part",
"created_at": "2025-01-15T10:31:00Z",
"upload_id": "0194a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
}

Step 3: Complete the upload

Once all parts are uploaded, finalize the upload by providing the ordered list of part IDs.

12
POST /v1/uploads/{upload_id}/complete
Content-Type: application/json
FieldTypeRequiredDescription
part_idsString[]YesOrdered list of part IDs from step 2.
python
12345
httpx.post(
f"{API_URL}/v1/uploads/{upload_id}/complete",
json={"part_ids": part_ids},
headers={**HEADERS, "Content-Type": "application/json"},
)
bash
1234
curl -X POST https://api.inference.nebul.io/v1/uploads/$UPLOAD_ID/complete \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{"part_ids": ["part-abc123", "part-def456"]}'

Cancel an upload

Abort an in-progress multipart upload. Already-uploaded parts are discarded.

python
1234
httpx.post(
f"{API_URL}/v1/uploads/{upload_id}/cancel",
headers=HEADERS,
)

Check upload status

python
1234
status = httpx.get(
f"{API_URL}/v1/uploads/{upload_id}",
headers=HEADERS,
).json()

Upload status values: pending, in_progress, completed, cancelled, failed.

File Object

Every file operation returns a file object with these fields:

FieldTypeDescription
idUUIDUnique file identifier.
filenameStringOriginal filename.
org_idUUIDOrganization that owns the file.
project_idUUIDProject the file belongs to.
purposeStringWhy the file was uploaded.
file_sizeIntegerSize in bytes.
checksumStringSHA-256 checksum (null during upload).
statusStringLifecycle state: pending, complete, cancelled, failed, deleted.
created_atDateTimeUpload timestamp.

Using Files with OCR

After uploading a file, pass the file_id to the OCR endpoint:

python
123456
result = httpx.post(
f"{API_URL}/v1/files/{file_id}/ocr",
json={"model": "deepseek-ai/DeepSeek-OCR"},
headers={**HEADERS, "Content-Type": "application/json"},
timeout=120.0,
).json()

See the OCR page for the full OCR API reference.

Errors

StatusCause
400Invalid input: unsupported format, missing required field
401Missing or invalid API key
404File or upload not found
413File exceeds maximum size
415Unsupported file type
422Validation error in request parameters

Troubleshooting

422 with "Field required" on upload

If you get a 422 error where both file and purpose are reported as missing, the most likely cause is a manually set Content-Type header overriding the auto-generated multipart/form-data boundary.

When using httpx (or similar clients), the files parameter automatically sets the correct Content-Type: multipart/form-data; boundary=.... If your default headers include Content-Type: application/json, it overrides this boundary and the server cannot parse the body.

python
123456789101112131415
# Wrong: Content-Type: application/json overwrites the multipart boundary
response = await client.post(
f"{API_URL}/v1/files",
files={"file": (filename, file_bytes, file_type)},
data={"purpose": "ocr"},
headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}, # ← breaks multipart
)
# Correct: omit Content-Type, let httpx set it
response = await client.post(
f"{API_URL}/v1/files",
files={"file": (filename, file_bytes, file_type)},
data={"purpose": "ocr"},
headers={"Authorization": f"Bearer {API_KEY}"},
)

If you use a shared headers dict, filter out Content-Type for multipart requests:

python
1
headers = {k: v for k, v in default_headers.items() if k.lower() != "content-type"}