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.
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
A complete end-to-end example using curl:
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
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 | 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". |
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 |
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}
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}
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.
data (service-specific payload), processing_time (seconds),
and result_created (ISO 8601 timestamp).
Only services with a "success" status are included.
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.
verbs is a list of detected actions.
svo_triples contains subject-verb-object relationships derived from captions.
service_count indicates how many services contributed.
summary_caption is the final text.
model identifies which model produced the summary.
services_present lists which VLMs contributed input captions.
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.
scene_type, detected activities,
people_count, and related fields. Produced by a dedicated content analysis
stage that runs after primary services complete.
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.
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. A reasonable polling strategy is to start at 1–2 seconds and back off gradually.
# Python example 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()
/status response already contains the complete results payload —
you only need to call /results separately 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. |