Image Inputs
The Responses API accepts images alongside text in a single turn, using the OpenAI-compatible
input_image content part. Images are stored in the Data Platform rather than in the Responses API,
so a conversation that contains images spans two services, which matters for how you export and
delete them.
Supplying an image
Supported image types are PNG, JPEG and WebP. The type is detected from the image bytes; the
data: header is ignored. Bytes that are not a supported image fail with 415 image_mime_not_allowed,
whatever the header claims, and so does any other type (GIF, SVG, PDF…). A supported image with the
wrong header is accepted under its real type: PNG bytes sent as data:image/jpeg are treated as PNG.
The same check, including the IMAGE_ALLOWED_MIME_TYPES allowlist, applies to a file referenced by
file_id, using the type Sherlock recorded at upload (compared case-insensitively, ignoring
parameters such as ; charset=binary). A referenced file with no recorded type is rejected.
An input_image part takes exactly one source:
| Field | Meaning |
|---|---|
|
A |
|
A reference to a file that already exists in the Data Platform. |
|
Requested vision fidelity: |
|
External URLs are rejected
A fully-qualified |
-
curl
-
Python (OpenAI SDK)
curl -X POST https://your-deployment-url/v1/responses \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AA_TOKEN" \
-d '{
"model": "qwen3-32b-tool",
"input": [
{
"role": "user",
"content": [
{ "type": "input_text", "text": "What is in this image?" },
{
"type": "input_image",
"image_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...",
"detail": "auto"
}
]
}
]
}'
import base64
from openai import OpenAI
client = OpenAI(base_url="https://your-deployment-url/v1", api_key=AA_TOKEN)
with open("chart.png", "rb") as fh:
encoded = base64.b64encode(fh.read()).decode()
response = client.responses.create(
model="qwen3-32b-tool",
input=[
{
"role": "user",
"content": [
{"type": "input_text", "text": "What is in this image?"},
{
"type": "input_image",
"image_url": f"data:image/png;base64,{encoded}",
"detail": "auto",
},
],
}
],
)
print(response.output_text)
Using an image already stored in Sherlock
Instead of sending base64 bytes on every request, you can upload an image to the Data Platform
(Sherlock) once and pass its file_id. The service checks the file exists and is readable by you,
and never uploads, copies, or deletes it: a referenced file belongs to you.
Both calls must use the same identity. The Responses API forwards your Authorization bearer
to Sherlock, so the file must live in a search store you can view (can_view).
1. Create a search store (or reuse one)
curl -X POST "$SHERLOCK_URL/api/v1/search-stores" \
-H "Authorization: Bearer $AA_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "my-images",
"models": [
{
"embedding_model": "text-embedding-3-small",
"type": "dense",
"dimension": 1536,
"distance": "cosine",
"modality": "text"
}
]
}'
Keep the returned id; it is the search_store_id. The store is private by default.
2. Upload the image
curl -X POST "$SHERLOCK_URL/api/v1/search-stores/$STORE_ID/files" \
-H "Authorization: Bearer $AA_TOKEN" \
-F "files=@dog.png;type=image/png"
The response is a list with one entry per uploaded file; keep its id. Uploads are deduplicated by
content hash per store, so uploading the same bytes again returns the existing file.
3. Reference it from the Responses API
Pass the Sherlock file id as file_id and name its store in the x-alephalpha-search-store-id
header:
-
curl
-
Python (OpenAI SDK)
curl -X POST https://your-deployment-url/v1/responses \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AA_TOKEN" \
-H "x-alephalpha-search-store-id: $STORE_ID" \
-d '{
"model": "qwen3-32b-tool",
"input": [
{
"role": "user",
"content": [
{ "type": "input_text", "text": "What animal is this?" },
{ "type": "input_image", "file_id": "'"$FILE_ID"'" }
]
}
]
}'
response = client.responses.create(
model="qwen3-32b-tool",
input=[
{
"role": "user",
"content": [
{"type": "input_text", "text": "What animal is this?"},
{"type": "input_image", "file_id": file_id},
],
}
],
extra_headers={"x-alephalpha-search-store-id": store_id},
)
Without the header, a bare file_id cannot be located and the request fails with 400
(image_search_store_not_specified). The header also applies to inline data: images: when it is
set, uploads go into that store instead of an auto-created one.
You can skip the header by passing the structured reference the service itself uses, a JSON string naming both the store and the file:
{
"type": "input_image",
"file_id": "{\"search_store_id\":\"<store id>\",\"file_id\":\"<file id>\"}"
}
Stored responses always carry file_id in this structured form, so you can copy an input_image
part from a previous response into a new request unchanged.
| Error | Cause |
|---|---|
|
A |
|
The file does not exist in that store, or you cannot view it. |
|
The file is not a supported image type, or its type is not in |
A file_id that does not parse as a structured reference (not JSON, or missing search_store_id /
file_id) is treated as a bare file ID. With the x-alephalpha-search-store-id header, the whole
string is looked up in that store, which normally fails with 400 image_not_found. Without the
header, the request fails with 400 image_search_store_not_specified.
|
Deleting your response does not delete your file
Only images the service uploaded itself (from |
Sharing an image with a Sherlock public URL
Sherlock can mint a time-limited download link for any file you can view:
curl -X POST "$SHERLOCK_URL/api/v1/search-stores/$STORE_ID/files/$FILE_ID/public-urls?ttl=1h" \
-H "Authorization: Bearer $AA_TOKEN"
{
"url": "https://<sherlock-host>/api/v1/search-stores/<store>/files/<file>/download?token=<opaque>",
"expires_at": "2026-08-25T13:00:00Z"
}
-
ttlis optional: it defaults to1hand must be between1mand24h. -
The token is a bearer credential bound to that one file: anyone holding the URL can download it until it expires or is revoked. Treat it as a secret.
-
Downloading still needs an authenticated call (Sherlock requires an identity on every request). A plain browser navigation without credentials fails with
401.
|
Do not pass a public URL as
image_urlThe Responses API refuses every non- |
See the Sherlock API reference (docs/sherlock/api.md in the sherlock-holmes repository, sections
Upload Files and Create a Public Download URL) for the full upload and public URL contracts.
How It Works
-
A
data:image is uploaded to the Data Platform, and the service records which response, conversation and user it belongs to. -
On each turn the worker fetches the image from the Data Platform and inlines it for the backend as a
data:URL. The backend never receives a URL to fetch. -
A
file_idsupplied directly is resolved the same way, with no upload step.
Images and compaction
Images are never described on upload. When a chain is compacted (manually via
/v1/responses/compact or automatically), the service:
-
attaches an
ImageAIworkflow namedimage-descriptionto every search store referenced by the chain’s images (auto-created and caller-provided alike; if it is already attached, it is reused) and runs it over those images, one store per run, in parallel. Each run writes one description document per image into its store; -
copies the projected context and removes the image parts from the copy;
-
puts each image’s description in its place as an
input_textpart; -
summarises the copy. The stored history is not changed.
Every Data Platform call authenticates with the caller’s Authorization bearer. For automatic
compaction, the bearer of the request that crossed the threshold travels with the compaction job
(stored in the Redis compaction stream, like request headers on the work stream), so a token that
has expired by the time the worker runs makes those images fall back to the placeholder.
Inline images that were never stored, and images in a store whose attach or run fails or times out,
are replaced by a short "no description available"
placeholder, so compaction still completes (a failed run is logged at WARNING). Set IMAGE_DESCRIBE_FOR_COMPACTION=false to send the
images to the summariser unchanged instead.
Where the bytes live
Image bytes are held by the Data Platform, not by the Responses API (see Data Flows). The Responses API stores only the provenance, which response, conversation, user, store and file an image belongs to.
Two consequences worth planning for:
-
Export. Retrieving a conversation through
GET /v1/conversations/{id}/responsesreturns the image references, not the bytes. A complete export needs the corresponding Data Platform files as well. -
Deletion. Images are purged by the retention sweep in step with the conversation hard-delete, so their lifetime is bounded by the same retention policy (see Managing Responses). The sweep authenticates with a service-account token, because it runs on a schedule and has no caller
Authorizationto forward.
|
Retention requires a service-account token
If |
Configuration
Image input is enabled implicitly whenever an image store base URL resolves, a deployment that already has file search configured gets image input without further settings.
When it is not enabled, any request carrying an input_image part (a data: image or a
file_id) is refused with 400 image_input_disabled, before anything is stored or sent to the
model. Without an image store the image could not be replaced by a Data Platform reference, so it
would reach the model raw and no guardrail image stage could check it.
| Variable | Default | Description |
|---|---|---|
|
falls back to |
Data Platform base URL for image files. Image input is on when this resolves to a non-empty value. |
|
|
Per-image size cap. |
|
|
Reserved for a future public-URL path; currently unused, the worker always inlines. |
|
|
Comma-separated MIME allowlist for both inline uploads and |
|
|
Describe images via the store’s workflow before compaction and summarise the descriptions instead of the images. |
|
|
Max wait for one store’s image-description workflow run. |
|
|
Interval between workflow-run status polls. |
|
- |
Service-account bearer the retention sweep uses to delete Data Platform images. Unset, images are not purged. |