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:

  1. SubmitPOST /analyze queues the image and returns an image_id immediately.
  2. PollGET /status/{image_id} returns live progress and results as services complete.
  3. RetrieveGET /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 /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.