Image understanding
Add vision to your workflow. Send images alongside a text prompt and get grounded text back you can crop, measure, or render on.
How it works
Send one or more images alongside text in a Responses API or chat completion request. Muse Spark reads the visuals and returns text. Provide each image one of three ways:
- Public URL — a fully qualified
http/httpsimage link. - Base64 data URL — the image bytes inline, no hosting required.
- Uploaded file — a
file_idfrom the Files API.
Use it for:
- Describing scenes: generate detailed descriptions of what appears in an image.
- Answering questions: respond to specific queries about objects, people, or actions in an image.
- Extracting information: pull text, data, or key elements from charts, diagrams, or documents.
- Analyzing content: identify objects, understand relationships, and categorize visual information.
- Localizing objects: report where objects are as coordinates you can crop, measure, or draw overlays with. See perception grounding.
Image understanding with the Responses API
Send images as input_image content blocks inside a user message. The image_url field is a plain string — a public URL or a base64 data: URL — or set file_id to reference an image uploaded through the Files API.
pythonimport osfrom openai import OpenAIclient = OpenAI(base_url="https://api.meta.ai/v1",api_key=os.environ["MODEL_API_KEY"],)response = client.responses.create(model="muse-spark-1.3",input=[{"type": "message","role": "user","content": [{"type": "input_text","text": "What is in this image?",},{"type": "input_image","image_url": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c9/Avocado_Hass_-_single_and_halved.jpg/1280px-Avocado_Hass_-_single_and_halved.jpg",},],},],)print(response.model_dump_json(indent=2))
typescriptimport OpenAI from 'openai';const apiKey = process.env.MODEL_API_KEY;if (!apiKey) {throw new Error('MODEL_API_KEY is not set');}const client = new OpenAI({baseURL: 'https://api.meta.ai/v1',apiKey,});const response = await client.responses.create({model: 'muse-spark-1.3',input: [{type: 'message',role: 'user',content: [{type: 'input_text',text: 'What is in this image?',},{type: 'input_image',image_url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/c/c9/Avocado_Hass_-_single_and_halved.jpg/1280px-Avocado_Hass_-_single_and_halved.jpg',},],},],});console.log(JSON.stringify(response, null, 2));
pythonimport jsonimport osimport requestsresponse = requests.post("https://api.meta.ai/v1/responses",headers={"Authorization": f"Bearer {os.environ['MODEL_API_KEY']}","Content-Type": "application/json",},json={"model": "muse-spark-1.3","input": [{"type": "message","role": "user","content": [{"type": "input_text","text": "What is in this image?",},{"type": "input_image","image_url": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c9/Avocado_Hass_-_single_and_halved.jpg/1280px-Avocado_Hass_-_single_and_halved.jpg",},],},],},)response.raise_for_status()print(json.dumps(response.json(), indent=2))
shellcurl -X POST "https://api.meta.ai/v1/responses" \-H "Authorization: Bearer $MODEL_API_KEY" \-H "Content-Type: application/json" \-d '{"model": "muse-spark-1.3","input": [{"type": "message","role": "user","content": [{"type": "input_text","text": "What is in this image?"},{"type": "input_image","image_url": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c9/Avocado_Hass_-_single_and_halved.jpg/1280px-Avocado_Hass_-_single_and_halved.jpg"}]}]}'
To send a local image without hosting it, pass a base64 data: URL as the same image_url string (for example, "data:image/jpeg;base64,<encoded bytes>").
Reference an uploaded file
Upload an image through the Files API, then reference it by ID with an input_file (or input_image) block.
Python (OpenAI SDK)import osfrom openai import OpenAIclient = OpenAI(base_url="https://api.meta.ai/v1",api_key=os.environ["MODEL_API_KEY"],)# Upload the imagewith open("photo.png", "rb") as f:file = client.files.create(file=f,purpose="user_data",)# Reference the uploaded file in a responses requestresponse = client.responses.create(model="muse-spark-1.3",input=[{"type": "message","role": "user","content": [{"type": "input_text", "text": "What is in this image?"},{"type": "input_file", "file_id": file.id},],}],)print(response.output_text)
Image understanding with Chat Completions
If your application or agent harness is built on Chat Completions, image understanding is fully supported. The only structural difference from the Responses API is that Chat Completions wraps the image in an image_url object (whose url is a public URL or a base64 data URL), rather than the plain string the Responses API uses.
Include the image in the content array of a user message:
pythonimport osfrom openai import OpenAIclient = OpenAI(base_url="https://api.meta.ai/v1",api_key=os.environ["MODEL_API_KEY"],)response = client.chat.completions.create(model="muse-spark-1.3",messages=[{"role": "user","content": [{"type": "text","text": "Describe what you see in this image.",},{"type": "image_url","image_url": {"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c9/Avocado_Hass_-_single_and_halved.jpg/1280px-Avocado_Hass_-_single_and_halved.jpg",},},],},],)print(response.model_dump_json(indent=2))
typescriptimport OpenAI from 'openai';const apiKey = process.env.MODEL_API_KEY;if (!apiKey) {throw new Error('MODEL_API_KEY is not set');}const client = new OpenAI({baseURL: 'https://api.meta.ai/v1',apiKey,});const response = await client.chat.completions.create({model: 'muse-spark-1.3',messages: [{role: 'user',content: [{type: 'text',text: 'Describe what you see in this image.',},{type: 'image_url',image_url: {url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/c/c9/Avocado_Hass_-_single_and_halved.jpg/1280px-Avocado_Hass_-_single_and_halved.jpg',},},],},],});console.log(JSON.stringify(response, null, 2));
pythonimport jsonimport osimport requestsresponse = requests.post("https://api.meta.ai/v1/chat/completions",headers={"Authorization": f"Bearer {os.environ['MODEL_API_KEY']}","Content-Type": "application/json",},json={"model": "muse-spark-1.3","messages": [{"role": "user","content": [{"type": "text","text": "Describe what you see in this image.",},{"type": "image_url","image_url": {"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c9/Avocado_Hass_-_single_and_halved.jpg/1280px-Avocado_Hass_-_single_and_halved.jpg",},},],},],},)response.raise_for_status()print(json.dumps(response.json(), indent=2))
shellcurl -X POST "https://api.meta.ai/v1/chat/completions" \-H "Authorization: Bearer $MODEL_API_KEY" \-H "Content-Type: application/json" \-d '{"model": "muse-spark-1.3","messages": [{"role": "user","content": [{"type": "text","text": "Describe what you see in this image."},{"type": "image_url","image_url": {"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c9/Avocado_Hass_-_single_and_halved.jpg/1280px-Avocado_Hass_-_single_and_halved.jpg"}}]}]}'
Example response
json{"id": "chatcmpl-abc123","object": "chat.completion","created": 1714502400,"model": "muse-spark-1.3","choices": [{"index": 0,"message": {"role": "assistant","content": "A whole dark-skinned avocado is placed next to a halved avocado showing its green flesh and brown pit on a white reflective surface."},"finish_reason": "stop"}],"usage": {"prompt_tokens": 272,"completion_tokens": 22,"total_tokens": 294}}
To send a local image, base64-encode it and pass it as the url inside the image_url object:
Python (OpenAI SDK)import osimport base64from openai import OpenAIclient = OpenAI(base_url="https://api.meta.ai/v1",api_key=os.environ["MODEL_API_KEY"],)def image_to_base64(image_path):with open(image_path, "rb") as img:return base64.b64encode(img.read()).decode("utf-8")base64_image = image_to_base64("photo.jpg")response = client.chat.completions.create(model="muse-spark-1.3",messages=[{"role": "user","content": [{"type": "text", "text": "What does this image contain?"},{"type": "image_url","image_url": {"url": f"data:image/jpeg;base64,{base64_image}"},},],}],)print(response.choices[0].message.content)
Multiple images
Send multiple images in one request by adding several image content blocks — input_image blocks on the Responses API, or image_url items on Chat Completions. You can include up to 50 images; more than 50 returns HTTP 400 (request contains <n> images, exceeding the maximum of 50 allowed per request). Requests are also bounded by payload size and the context window. A PDF upload contributes at most its first 50 page-images toward this budget; see Files API: PDF handling.
pythonimport osfrom openai import OpenAIclient = OpenAI(base_url="https://api.meta.ai/v1",api_key=os.environ["MODEL_API_KEY"],)response = client.chat.completions.create(model="muse-spark-1.3",messages=[{"role": "user","content": [{"type": "text","text": "What do these two images have in common?",},{"type": "image_url","image_url": {"url": "https://example.com/image1.jpg",},},{"type": "image_url","image_url": {"url": "https://example.com/image2.jpg",},},],},],)print(response.model_dump_json(indent=2))
Localize objects with perception grounding
Perception grounding adds location to understanding. Alongside describing an image, Muse Spark reports where objects are. Name the objects you care about, ask for coordinates, and use the positions to crop regions, measure layouts, seed a downstream detector, or draw labeled overlays on the original image.
Coordinate system
Muse Spark reports positions on a normalized 0–1000 grid based on the image you sent:
(0, 0)is the top-left corner;(1000, 1000)is the bottom-right.- The first axis (
x) increases left to right; the second (y) increases top to bottom. - The grid is resolution-independent: it applies whether you sent a 640px or a 4000px image, so output stays stable if you resize.
Convert normalized values back to pixels using the dimensions of the exact image you sent:
pythondef to_pixels(x_norm, y_norm, width, height):x = round(x_norm * width / 1000)y = round(y_norm * height / 1000)return x, y
Prompt for structured coordinates
Muse Spark fills the coordinate schema you define. State the exact fields and axis order in your prompt and ask for JSON. Pair the instruction with structured output when you need the response to parse on the first try.
Python (OpenAI SDK)import osimport base64from openai import OpenAIclient = OpenAI(base_url="https://api.meta.ai/v1",api_key=os.environ["MODEL_API_KEY"],)def image_to_base64(image_path):with open(image_path, "rb") as img:return base64.b64encode(img.read()).decode("utf-8")base64_image = image_to_base64("kitchen.jpg")prompt = ("Locate every piece of fruit in the image. Return a JSON array where each "'element has "label" (the object name) and "box", a bounding box as '"[x_min, y_min, x_max, y_max] on a normalized 0-1000 grid with (0,0) at the ""top-left. Return only the JSON.")response = client.chat.completions.create(model="muse-spark-1.3",messages=[{"role": "user","content": [{"type": "text", "text": prompt},{"type": "image_url","image_url": {"url": f"data:image/jpeg;base64,{base64_image}"},},],}],)print(response.choices[0].message.content)
json[{"label": "avocado", "box": [412, 530, 588, 690]},{"label": "lemon", "box": [640, 486, 742, 604]}]
Parse the array, then run each box through the to_pixels helper to crop or annotate the region. Points work the same way: ask for a single [x, y] per object when you need a location rather than an extent.
Some grounding modes: point, box, and count
Muse Spark grounds most reliably when your prompt asks for one specific output shape and lists the objects to find. Three shapes worth reaching for first are point, box, and count — they cover most tasks, but they aren't the only options; you can define your own output shape the same way (see Prompt for structured coordinates above). In each of these, coordinates use the normalized 0–1000 grid described above, the model returns a JSON array with one entry per object, and any object it can't find is omitted from the results.
Pick the shape from what you need to do with the result:
| Mode | Use when | Returns per object |
|---|---|---|
| Point | You need a location, not an extent — "where is", "point to", "find", "show me where" | {"x": int, "y": int} |
| Box | You want to highlight, outline, detect, or crop a region or a large object | {"bbox": [{"x_min": int, "y_min": int, "x_max": int, "y_max": int}]} |
| Count | You need "how many" — dense crowds, repeated or small items, overlapping or occluded instances | {"points": [{"x": int, "y": int}, ...], "count": int} |
Point. Ask the model to point at each object and reply as a JSON array:
Point promptPoint to each of the following objects in the image: "the power button", "the water tank". For each object, answer in the format {"object_name": "<name>", "x": <int>, "y": <int>}. The coordinates should be in the 0-1000 range. Return a JSON array of results. If you cannot find an object, omit it from the results.
Box. For regions and larger objects, ask for bounding boxes. Leading with "You are an object grounding expert" and telling the model not to miss any objects measurably improves recall:
Bounding-box promptYou are an object grounding expert. Provide the bounding box coordinates of the objects in the image: "the coffee mug", "the laptop". Ensure the objects accurately match the request and do not miss any objects. For each object, answer in the format {"object_name": "<name>", "bbox": [{"x_min": <int>, "y_min": <int>, "x_max": <int>, "y_max": <int>}]}. The coordinates should be in the 0-1000 range. Return a JSON array of results. If you cannot find an object, omit it from the results.
Count. To count instances, ask the model to point at every instance and return the total. Pointing at each one before counting handles overlapping, clustered, and partially occluded objects far better than asking for a bare number:
Count promptPoint to every instance of each of the following objects in the image and count them: "person", "car". For each object, answer in the format {"object_name": "<name>", "points": [{"x": <int>, "y": <int>}, ...], "count": <int>}. The coordinates should be in the 0-1000 range. Return a JSON array of results. If you cannot find any instances of an object, omit it from the results.
Render annotation overlays
Normalized coordinates map directly to the image at display time, so you can ask for a ready-to-view annotation instead of raw numbers. Ask for a self-contained HTML page that draws each labeled box over the image, and you get the overlay without writing rendering code:
pythonprompt = ("Locate every piece of fruit in the image. Return a complete, self-contained ""HTML page that displays the image and overlays a labeled box on each item. ""Position each box with CSS percentages by dividing the normalized 0-1000 ""coordinates by 10. Return only the HTML.")# Send prompt + image as in the previous example, then save the result:with open("annotated.html", "w") as f:f.write(response.choices[0].message.content)
Dividing normalized coordinates by 10 yields CSS percentages, so a box positioned with percentage left / top / width / height lines up at any display size. Embed the same base64 data URL you sent, or point the page's <img> at your hosted image.
Supported formats and limits
| Constraint | Value |
|---|---|
Max file size per image (inline image_url) | 50 MB (50,000,000 bytes) |
Max file size per image (input_file via Files API) | 1 GiB (1,073,741,824 bytes) |
| Max images per request | 50, enforced. More than 50 returns HTTP 400. Also bounded by payload size and the context window. |
| PDF page-images retained | First 50 per PDF |
| Supported MIME types | image/jpeg, image/jpg, image/png, image/gif, image/webp, image/x-icon |
Estimating token usage
For an exact count of any request (including images and files), call POST /v1/responses/input_tokens with the same input. It returns the input token total without generating a response, and is the authoritative way to estimate cost.
Image cost scales with resolution, not a fixed per-image amount. Measured via input_tokens:
- A very small image adds only a few tokens (a 1x1 image ≈ 3 tokens).
- Cost rises with resolution: a ~1280px image adds roughly 1,300–1,500 tokens.
Next steps
- Upload images efficiently: use the Files API to reference images by
file_idinstead of base64 or public URLs. - Go multimodal and multi-turn: use the Responses API for multimodal, multi-turn interactions.
- Make localization reliable: enforce your coordinate schema with structured output so responses parse on the first try.
- Wire up the rest of the call: explore generation controls in chat completion and see the Responses API reference for the full parameter schema.