API Reference
The Ice9 API accepts image uploads and returns structured analysis from a coordinated ensemble of machine learning services. Submit an image, receive an image_id, poll until processing is complete, then retrieve the results.
Access: All endpoints require an API key. To request access, reach out via Discord or GitHub.
Python SDK
Recommended for Python users. The ice9 SDK handles authentication, polling, retries, and results parsing for you:
pip install ice9
from ice9 import Ice9
client = Ice9(api_key="ice9_...")
result = client.analyze("photo.jpg")
print(result.nudenet) # content moderation
print(result.colors) # dominant colors
print(result.metadata) # EXIF and file info
The SDK accepts file paths, URLs, or file objects. It polls automatically until results are ready, with configurable timeouts and retries. For async/await support, use AsyncIce9. See examples on GitHub for Discord bots, batch processing, and streaming results.
Note: The SDK is in active development (v0.0.1). The API and examples below work with or without the SDK.
Overview
Ice9 routes each image through multiple specialized ML services in parallel — object detection, image captioning, segmentation, color analysis, and more. Because these services run asynchronously, the API follows a submit-then-poll pattern:
- Submit —
POST /analyzequeues the image and returns animage_idimmediately. - Poll —
GET /status/{image_id}returns live progress and results as services complete. - Retrieve —
GET /results/{image_id}returns the complete payload once all services are done.
Results are available progressively — you can render partial output as each service finishes rather than waiting for everything.
Authentication
Every request must include your API key in the X-API-Key header. Keys are never embedded in query parameters or request bodies.
X-API-Key: ice9_your_key_here
A missing or invalid key returns 401 with the same message in both cases — no information is leaked about whether a key exists. Keys are rate-limited individually; see each endpoint for limits.
Quickstart
With Python SDK
from ice9 import Ice9
client = Ice9(api_key="ice9_...")
result = client.analyze("photo.jpg") # Polls automatically
print(f"Analysis complete. Image ID: {result.image_id}")
print(f"Services: {result.services_submitted}")
print(f"Nudenet detections: {result.nudenet.detections}")
The SDK handles submission, polling, and result parsing in a single call. See Python SDK for installation and async support.
With curl
A complete end-to-end example using the raw API:
1. Submit an image
curl -X POST https://app.ice9.ai/analyze \
-H "X-API-Key: ice9_your_key_here" \
-F "file=@photo.jpg"
{
"image_id": 4821,
"trace_id": "a3f7c2d1-...",
"services_submitted": ["blip", "moondream", "yolo_v8", "..."],
"image_width": 1920,
"image_height": 1080
}
2. Poll for completion
curl https://app.ice9.ai/status/4821 \
-H "X-API-Key: ice9_your_key_here"
Repeat until is_complete is true. Results populate progressively as services finish.
3. Get the final results
curl https://app.ice9.ai/results/4821 \
-H "X-API-Key: ice9_your_key_here"
POST /analyze
Endpoint: POST /analyze
Rate limit: 60 requests / minute
Submit an image for analysis. The image is validated, normalized, and dispatched to the configured set of ML services. Returns immediately with an image_id — processing happens asynchronously.
Request
Content-Type must be multipart/form-data.
| Field | Type | Required | Description |
|---|---|---|---|
| file | file | required | Image to analyze. Accepted formats: JPEG, PNG, WebP, HEIC/HEIF. Maximum size 16 MB. Images larger than 2048px on the longest dimension are resized proportionally. EXIF orientation is automatically corrected. |
| tier | string | optional | Analysis tier controlling which services run and the flat per-image price. Defaults to free. See the tier table below. Unknown tier names return a 400 with the list of valid options. |
| services | string | optional | Comma-separated list of services to run. For internal keys only — overrides tier entirely. Unknown service names return a 400 with the list of valid options. |
| image_group | string | optional | A label applied to the image for grouping and filtering in the database. Defaults to "api". |
Image handling: Image bytes are processed entirely in memory and are never written to disk. The original bytes are not stored — only metadata (filename, dimensions, perceptual hash) is persisted.
Tiers
| Tier | Services | Price |
|---|---|---|
free |
colors, metadata, ocr, nudenet | $0.00 |
basic |
colors, metadata, ocr, nudenet, yolo_v8, blip, moondream, ollama, qwen + background removal | $0.05 |
premium |
colors, metadata, ocr, nudenet, yolo_v8, blip, moondream, ollama, qwen, haiku, gemini, gpt_nano + background removal | $0.10 |
cloud |
colors, metadata, ocr, nudenet, yolo_v8, haiku, gemini, gpt_nano + background removal. Cloud-hosted VLMs only — no local models. | $0.05 |
SDK usage:
result = client.analyze("photo.jpg", tier="basic")
# Or check available tiers
tiers = client.tiers() # {"free": [...], "basic": [...], ...}
Response — 202 Accepted
| Field | Type | Description |
|---|---|---|
| image_id | integer | Unique identifier for this submission. Use this to poll /status and retrieve /results. |
| trace_id | string | UUID assigned to this submission for tracing across the processing pipeline. |
| tier | string | Effective tier applied to this submission. |
| services_submitted | array | Names of the services the image was dispatched to. |
| image_width | integer | Width of the normalized image in pixels. |
| image_height | integer | Height of the normalized image in pixels. |
GET /status/{image_id}
Endpoint: GET /status/{image_id}
Rate limit: 300 requests / minute
Returns current processing state plus all results available so far. Call this repeatedly until is_complete is true. Results are included progressively — you can render useful output before all services have finished.
Path parameter
| Parameter | Type | Description |
|---|---|---|
| image_id | integer | The image_id returned by /analyze. |
Response — 200 OK
Includes all image metadata, per-service completion state, and the full results payload (see Response fields). Status-specific fields:
| Field | Type | Description |
|---|---|---|
| image_id | integer | Echo of the requested ID. |
| image_filename | string | Original filename from the upload. |
| image_group | string | Group label set at submission. |
| image_created | string | ISO 8601 timestamp of when the image was registered. |
| services_submitted | array | Services the image was sent to. |
| vlm_services | array | Subset of submitted services that are vision-language models. |
| services_completed | object | Per-service status, completion time, and processing duration. |
| services_pending | array | Services not yet complete. |
| services_failed | object | Services that failed or were dead-lettered, keyed by service name with a reason string. Empty object if no failures. |
| progress | string | Completion fraction, e.g. "3/7". |
| is_complete | boolean | true when all submitted services have finished. Stop polling here. |
| noun_consensus_complete | boolean | true when noun consensus has been computed. |
| verb_consensus_complete | boolean | true when verb consensus has been computed. |
| caption_summary_complete | boolean | true when the caption summary has been generated. |
| sam3_complete | boolean | true when SAM3 segmentation has finished. |
| content_analysis_complete | boolean | true when content analysis has finished. |
GET /results/{image_id}
Endpoint: GET /results/{image_id}
Rate limit: 300 requests / minute
Returns the complete analysis payload for an image. Functionally equivalent to the final /status response but without the polling state fields. Call this after is_complete is true, or use it for already-processed images.
Path parameter
| Parameter | Type | Description |
|---|---|---|
| image_id | integer | The image_id returned by /analyze. |
Returns the same results payload described in Response fields below, plus image metadata fields.
Response fields
Both /status and /results include a results payload. Fields are null until the corresponding stage has completed — check the *_complete flags on /status to know what is ready.
service_results
Type: object — keyed by service name
Raw output from each ML service, indexed by service name. Each entry includes data (service-specific payload), processing_time (seconds), and result_created (ISO 8601 timestamp). Only services with a "success" status are included.
noun_consensus
Type: object | null
Detected objects and concepts aggregated across all captioning and detection services. nouns contains items with confidence above 0.5 or those explicitly promoted; nouns_all contains the full unfiltered list. category_tally summarizes detections by semantic category. service_count indicates how many services contributed.
verb_consensus
Type: object | null
Actions and verbs extracted from captions and aggregated across services. verbs is a list of detected actions. svo_triples contains subject-verb-object relationships derived from captions. service_count indicates how many services contributed.
caption_summary
Type: object | null
A single synthesized natural-language description of the image, generated by distilling captions from multiple vision-language models. summary_caption is the final text. model identifies which model produced the summary. services_present lists which VLMs contributed input captions.
sam3
Type: object | null
Segmentation results from SAM3 (Segment Anything Model 3). Triggered automatically after noun consensus completes. results is a dictionary keyed by noun, each containing a list of instances with segmentation masks and bounding boxes. nouns_queried lists the nouns that were passed to the segmentation model. instance_count is the total number of segmented instances across all nouns.
content_analysis
Type: object | null
Scene-level classification including scene_type, detected activities, people_count, and related fields. Produced by a dedicated content analysis stage that runs after primary services complete.
rembg
Type: object | null
Background removal result. png_b64 is a base64-encoded RGBA PNG with the subject isolated on a transparent background. shape is [height, width]. Available on basic, premium, and cloud tiers. rembg_complete in /status indicates when it is ready. Included in the per-image tier price — no separate fee.
postprocessing
Type: array
Per-region analysis results (colors, face attributes, pose estimation) applied to bounding box crops identified during detection. Each entry includes service, data, and source_bbox for mapping results back to image coordinates.
Polling pattern
Processing time varies based on image complexity and which services are running.
With Python SDK
The SDK polls automatically with exponential backoff:
from ice9 import Ice9
client = Ice9(api_key="ice9_...", timeout=120)
result = client.analyze("photo.jpg") # Blocks until complete (or timeout)
# Or stream results as services finish
for partial in client.analyze("photo.jpg", stream=True):
if partial.is_complete:
print("Done!")
result = partial
else:
print(f"Progress: {partial.progress}")
The SDK handles polling, retries, and timeout logic for you.
Manual polling (raw API)
If you're not using the SDK, implement polling with exponential backoff:
# Python example with requests
import time, requests
BASE = "https://app.ice9.ai"
HEADERS = {"X-API-Key": "ice9_your_key_here"}
# 1. Submit
resp = requests.post(f"{BASE}/analyze", headers=HEADERS,
files={"file": open("photo.jpg", "rb")})
image_id = resp.json()["image_id"]
# 2. Poll
interval = 1.5
while True:
data = requests.get(f"{BASE}/status/{image_id}", headers=HEADERS).json()
if data["is_complete"]:
break
time.sleep(interval)
interval = min(interval * 1.3, 10) # cap at 10s
# 3. Use results (or read them from the final status response above)
results = requests.get(f"{BASE}/results/{image_id}", headers=HEADERS).json()
The final
/statusresponse already contains the complete results payload — you only need to call/resultsseparately if you want a clean response without the polling fields, or to retrieve results for a previously-processed image.
Error codes
All error responses include a JSON body with an "error" string field.
| Status | Meaning |
|---|---|
| 400 | Bad request — missing file, invalid image, unknown service name, or wrong content type. |
| 401 | Missing or invalid API key. The same message is returned regardless of which case applies. |
| 404 | The requested image_id does not exist. |
| 413 | Payload too large. Maximum upload size is 16 MB. |
| 429 | Rate limit exceeded. Slow down and retry after a short wait. |
| 500 | Internal server error — database or processing failure. |
| 502 | Upstream API unavailable. |