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 selectionprint(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 normalresult = 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 workflowresults = 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 directoriesresults = await client.upload( "/path/to/photos", include_hidden=True # Include .hidden files and .cache/ dirs)
# Mix files and directoriesresults = 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 bucketresult = 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 enumfor r in results: if r.description_status == AionVision.DescriptionStatus.FAILED: print(f"Failed: {r.image_id}")
# Use convenience methodsif 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]