Documentation

Uploading Images

Upload images with automatic AI description generation — via Python SDK or REST API

Automatic Workflow

The SDK handles the complete upload workflow automatically — streaming upload for files up to 100MB, with automatic fallback to presigned URLs for larger files. The REST API offers both streaming and presigned URL endpoints directly.

Supported File Types

.jpg, .jpeg, .png, .webp, and .gif. Unsupported file types are automatically filtered when uploading directories.

Quick Start

Upload a Single Image

Upload an image and get an AI-generated description

async with AionVision(api_key="aion_...") as client:
# Upload a single file - returns UploadResult directly
result = await client.upload_one("photo.jpg")
print(f"Image ID: {result.image_id}")
print(f"Description: {result.description}")
print(f"Tags: {result.tags}")
print(f"Visible Text: {result.visible_text}")
print(f"Confidence: {result.confidence_score}")

Streaming Upload

Single request for files up to 100MB — use this for almost all uploads

Single and Batch

The SDK handles method selection automatically. The REST API has separate single and batch endpoints.

# Files ≤100MB automatically use streaming upload
# Files >100MB automatically use presigned URL workflow
# No code changes needed!
result = await client.upload_one("photo.jpg") # Automatic method selection
print(f"Image ID: {result.image_id}")
print(f"Description: {result.description}")

Presigned URL Upload (advanced — files >100MB only)

3-step process: request URL → upload to S3 → confirm. The SDK handles this automatically for large files.

# SDK handles presigned URLs automatically for large files
# No code changes needed — just upload as normal
result = await client.upload_one("large_photo.jpg")
# For manual control, use the low-level API:
presigned = await client.uploads.request_presigned_url(
filename="photo.jpg",
content_type="image/jpeg",
size_bytes=1024000,
)
# Upload to presigned.upload_url, then:
confirmed = await client.uploads.confirm_upload(
object_key=presigned.object_key,
size_bytes=1024000,
)

Batch Upload

Up to 10,000 Files (SDK)

The SDK supports uploading up to 10,000 files in a single call. Large batches are automatically chunked into 100-file batches. The REST API supports up to 50 files per streaming batch request.

Upload Multiple Files

# Just pass a list - automatically uses optimized batch workflow
results = await client.upload(
["img1.jpg", "img2.jpg", "img3.jpg"],
wait_for_descriptions=True,
)
for result in results:
print(f"{result.filename}: {result.description[:50]}...")

Directory Upload

SDK only — no REST equivalent

Upload Entire Directory

Pass a directory path to upload all supported images inside it

# Upload all images in a directory (recursive by default)
results = await client.upload("/path/to/photos")
print(f"Uploaded {len(results)} images")
for result in results:
print(f" {result.filename}: {result.description[:50]}...")

Directory Options

# Non-recursive (only top-level files)
results = await client.upload(
"/path/to/photos",
recursive=False # Don't include subdirectories
)
# Include hidden files and directories
results = await client.upload(
"/path/to/photos",
include_hidden=True # Include .hidden files and .cache/ dirs
)
# Mix files and directories
results = await client.upload([
"/path/to/folder1", # All images in folder1
"/path/to/folder2", # All images in folder2
"/path/to/specific_image.jpg" # Plus this specific file
])

Automatic Filtering

When uploading directories, only supported image files (.jpg, .jpeg, .png, .webp, .gif) are included. Hidden files and directories (starting with .) are excluded by default. Other file types like .pdf, .txt, or .mp4 are silently skipped.

Progress Callbacks

SDK only

Track Upload Progress

from aion import AionVision
def on_progress(event: AionVision.UploadProgressEvent):
print(f"File {event.file_index}: {event.progress_percent:.1f}%")
# Also available: event.uploaded_bytes, event.total_bytes, event.filename
def on_file_complete(event: AionVision.FileCompleteEvent):
print(f"File {event.file_index} complete: {event.result.image_id}")
async with AionVision(api_key="aion_...") as client:
results = await client.upload(
["img1.jpg", "img2.jpg", "img3.jpg"],
on_progress=on_progress,
on_file_complete=on_file_complete,
)

Custom S3 Storage (BYOB)

Upload to your own S3 bucket instead of Aionvision's default storage

# First, configure your S3 bucket (one-time setup)
await client.settings.configure_custom_s3(
access_key_id="AKIAIOSFODNN7EXAMPLE",
secret_access_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
bucket_name="my-company-uploads",
region="us-east-1"
)
# Upload to your custom bucket
result = await client.upload(
"photo.jpg",
storage_target="custom" # Use your configured S3 bucket
)

Error Handling

Handle Upload Errors

from aion import AionVision
results = await client.upload(
files,
raise_on_failure=False, # Don't raise, handle manually
)
# Check status using enum
for r in results:
if r.description_status == AionVision.DescriptionStatus.FAILED:
print(f"Failed: {r.image_id}")
# Use convenience methods
if results.has_failures:
print(f"Summary: {results.summary()}")
for r in results.retryable():
print(f"Can retry: {r.image_id}")

UploadResult Type

Fields available on the SDK upload result

@dataclass(frozen=True)
class UploadResult:
# Core fields
image_id: str # Unique identifier
filename: str # Original filename
object_key: str # S3 object key
# AI description fields
description: Optional[str] # AI-generated description
tags: Optional[list[str]] # Extracted tags
visible_text: Optional[str] # OCR extracted text
confidence_score: Optional[float] # Description confidence
description_status: DescriptionStatus # PENDING | QUEUED | PROCESSING | COMPLETED | FAILED | SKIPPED
# Media fields
thumbnail_url: Optional[str]
created_at: Optional[datetime]
# Error fields (when description_status == DescriptionStatus.FAILED)
description_error: Optional[str]
description_error_type: Optional[str]
description_is_retryable: Optional[bool]
# Properties
@property
def is_failed(self) -> bool
@property
def is_completed(self) -> bool
@property
def is_pending(self) -> bool
# Serialization
def to_dict(self, exclude_none: bool = True) -> dict[str, Any]