HTTP API contract¶
The API is intended for local tools, agents, SDKs, and authenticated private
network clients. Version 1 evolves additively; breaking wire changes use /v2.
Core endpoints¶
| Method | Route | Purpose | Success |
|---|---|---|---|
GET |
/health/live |
Process liveness | 200 |
GET |
/health/ready |
Detail-free cached provider readiness | 200 ready, 503 otherwise |
GET |
/metrics |
Opt-in Prometheus exposition | 200 when enabled |
GET |
/v1/providers |
Cursor-paginated provider inventory | 200 |
GET |
/v1/providers/{name}/capabilities |
Dynamic model capabilities | 200 |
GET |
/v1/diagnostics |
Authenticated redaction-safe operator state | 200 |
POST |
/v1/images |
Lossless normalized generation/edit request | 200 |
POST |
/v1/images/stream |
Bounded native SSE lifecycle | 200 |
POST |
/v1/images/generations |
OpenAI-familiar generation compatibility | 200 |
POST |
/v1/images/edits |
Multipart edit compatibility | 200 |
POST |
/v1/jobs |
Create a durable artifact-backed operation | 202 |
GET |
/v1/jobs |
Cursor-paginated job history | 200 |
GET |
/v1/jobs/{id} |
Complete durable job detail | 200 |
DELETE |
/v1/jobs/{id} |
Request cancellation | 200 |
PATCH |
/v1/jobs/{id} |
Favorite, soft-delete, or restore history | 200 |
GET |
/v1/presets |
Cursor-paginated preset collection | 200 |
POST |
/v1/presets |
Create a named preset | 201 |
GET |
/v1/presets/{name} |
Read a preset | 200 |
PUT |
/v1/presets/{name} |
Fully replace a preset | 200 |
DELETE |
/v1/presets/{name} |
Delete a preset | 204 |
GET |
/v1/artifacts/{id} |
Ownership-verified image delivery | 200 |
GET |
/v1/artifacts/{id}/thumbnail |
Bounded PNG thumbnail | 200 |
GET |
/v1/openapi.json |
Checked OpenAPI 3.1 contract | 200 |
Native generation accepts the versioned ImageRequest schema and returns
ImageResponse. Idempotency-Key may be supplied for POST requests. The bridge
returns an x-request-id response header for every request.
Synchronous requests use the runtime deadline (runtime.default_timeout_ms,
overridable per request) rather than the socket write-stall timeout. The default
socket read-stall timeout is disabled so a valid long generation is not closed
while its handler is still working.
The SSE handler emits started, bounded provider progress/partial_image
events when available, then completed or error, with heartbeat comments,
backpressure, and disconnect cancellation.
Capability responses distinguish the logical bridge contract from one upstream
call. count is the accepted request range. batching.mode=fan_out means the
bridge divides a larger request into calls bounded by batching.native_count;
batching.max_parallel_outputs is the effective provider-wide simultaneous-call
capacity. max_parallel_outputs = "auto" resolves to the configured logical
output ceiling, so all requested outputs can start together; a numeric setting
is an operator-selected cap.
Output and partial-image indices are remapped into the original request order.
Persistent and explicit-thread app-server batches run sequentially, while
isolated batches may use the advertised parallelism.
GET /v1/diagnostics returns only aggregate operational facts: bridge version,
listener scope, whether bridge authentication is required, configuration field
origins without values, bounded runtime queue depths, provider readiness, job
status counts, configured retention/admission limits, and a newest-first
in-memory ring of 256 redacted API events. Events contain only a generated
sequence, timestamp, normalized method, fixed route template, status, and
duration; overwritten entries are counted. It never returns
credential values, prompts, account IDs, input data, artifact/database paths, or
job/session identifiers, queries, headers, or payloads. SQLite storage is
reported only as an aggregate byte count. The route follows the same bridge
bearer policy as other /v1/** endpoints.
Durable jobs and history¶
POST /v1/jobs accepts the native ImageRequest, validates it before
persistence, forces output.response_format=artifact, and returns an
ImageJob with status queued. GET /v1/jobs/{id} returns the retained
request plus a verified result or structured terminal error. List responses
contain only ImageJobSummary records, so inline request images are not copied
into history pages.
Idempotency-Key on job creation is durable rather than process-local. For the
retention lifetime, the same authorization scope, key, and generation request
returns the original job ID without scheduling another provider call. Reusing
that key with a different generation request returns 409. The key is hashed
before persistence and omitted from the stored request. Job ownership is
enforced in SQLite for detail, list, cancellation, history updates, and partial
previews; another scope receives the same 404 as an unknown ID. Bearer token
rotation intentionally creates a new history scope. Rows created by older
schema versions are quarantined as legacy-unowned instead of being assigned
silently to a newly configured bearer.
The queue is bounded by server.jobs.max_pending; workers are independently
bounded by server.jobs.max_running. Cancellation is persisted before an
active provider token is signaled. Queued jobs are immediately terminal;
running jobs settle after cooperative cancellation. On restart, queued jobs
resume, while previously running jobs become interrupted. They are never
retried automatically because provider completion and billing may be
ambiguous. Retention is bounded by both age and terminal-record count.
server.jobs.max_retained_bytes also bounds the logical size of ordinary
terminal history, while server.jobs.max_database_bytes is an admission budget
covering queued, running, terminal, hidden, and favorite rows. Active jobs
reserve bounded result-metadata space before acceptance, so completion cannot
silently escape the global budget. Diagnostics report both logical accounting
and the physical main SQLite file size; WAL pages and reusable free pages mean
those two values are intentionally not identical.
GET /v1/jobs?limit=20&cursor=...&status=succeeded uses an opaque, stable
newest-first cursor. limit is 1..=100. Optional visibility selects
active (default), hidden, or all; favorite=true|false and the
case-insensitive literal prompt substring search are applied before cursor
pagination. The deprecated include_deleted=true alias still means
visibility=all for existing clients. The current lifecycle values are
queued, running, succeeded, failed, cancelled, and interrupted.
PATCH /v1/jobs/{id} accepts favorite and/or deleted booleans. Deletion is
soft, terminal-only, hidden from ordinary lists, and reversible. It preserves
the job evidence. Favorite job records are explicitly exempt from automatic
job-history pruning until unfavorited; artifact bytes still follow the separate
artifact-retention policy.
Artifact routes never resolve caller-supplied filenames. They look up the
opaque ID through the bridge ownership record, re-check the checksum and full
image decode, and return only verified PNG/JPEG/WebP bytes. Thumbnail requests
run off the async reactor, accept a 32..=2048 maximum edge, preserve aspect
ratio, and always return a verified PNG with private immutable caching.
Presets¶
Presets are named, durable ImagePresetTemplate values stored in the same
configured SQLite database as jobs. A template covers prompt defaults,
generation parameters, routing/fallbacks, session behavior, output settings,
request policies, timeout, and user metadata. It intentionally cannot contain
source images, masks, reference images, an idempotency key, or provider result
data. Applying a preset remains a client operation: the caller reads the
template, supplies any required image inputs, and posts the resulting native
request.
Names are 1-64 portable ASCII characters, begin with a letter or number, and
may subsequently contain letters, numbers, ., _, or -. Descriptions are
optional and bounded. Creation returns 409 for an existing name; read,
replace, and delete return 404 for a missing preset. Listing uses the same
opaque cursor style and 1..=100 limit as other collections. Presets share the
bridge bearer boundary and are intentionally global within one bridge
instance, so its CLI, dashboard, and SDK clients see the same set.
Embedded dashboard¶
When durable jobs are enabled, GET /dashboard serves a static HTML, CSS, and
native JavaScript application embedded in the server binary. It adds no runtime
process, package manager, CDN request, or writable static directory. The UI can
submit generation and edit requests, attach local edit/reference images as data
URLs, discover provider capabilities, poll durable jobs, show the latest
verified partial preview, and manage favorite, hidden, restored, and cancelled
states. Partial previews are bounded to one latest 16 MiB image per active job,
fully decoded before exposure, retained only in memory, and removed when the job
becomes terminal. Artifact and partial previews are fetched as blobs through
authenticated requests, so bearer tokens never appear in image URLs.
Result details can copy the portable artifact name, including its relative
directory when one exists. This avoids copying an unhelpful . for root-level
artifacts; the API still does not expose the server's configured artifact root
or offer a remote file-manager action.
The dashboard shell is intentionally public because browser navigation cannot
attach an Authorization header. It contains no prompt, history, provider result,
credential, or artifact data. Every data API and artifact request remains under
the normal bridge bearer policy. A token entered in the Connection dialog is
stored only in the tab's sessionStorage. Responses use a self-only content
security policy, deny framing, disable referrers, and do not permit inline script
or style execution. Protected routes reject Sec-Fetch-Site cross-site and
same-site requests. When a browser sends Origin, its authority must match
Host exactly. Requests from CLI and SDK clients remain valid without either
browser header. Disabling server.jobs.enabled removes all dashboard routes.
Native multi-image requests accept parameters.failure_policy as fail_fast
or best_effort. Results retain the requested index and optional
generation_ms; best-effort responses add structured per-index failures and
the partial_output_failure warning. If every output fails, the request still
returns an error rather than an empty success.
Artifact and URL delivery accept portable output.directory and
output.filename controls below the server's configured artifact root. An
exact filename requires parameters.n=1; its extension may be omitted or must
match parameters.output_format. Publication is atomic and never overwrites.
output.collision defaults to error and may be suffix to allocate a
deterministic -2, -3, … name. The suffix policy is valid only with an exact
filename. Filesystem paths are never accepted by the HTTP contract and absolute
server paths are never returned to clients.
output.metadata is none by default. sidecar is accepted only with
artifact delivery and writes a bounded JSON object next to every image. The
sidecar includes version/request identity, completion timestamp, a path-free
operation summary, original/effective/negative prompts, effective policies,
requested/effective parameters, normalizations, revised prompt, provider/model,
ordered provider attempts, usage, session, timings, warnings, and verified
per-image dimensions, format, byte count and checksum. Its portable relative
path is returned in each image's optional metadata_name. Sidecar JSON is
independently checksummed in
the ownership record and participates in conservative retention cleanup. It is
an explicit privacy choice: deployments that publicly serve the artifact root
must treat sidecars as equally public.
embedded stores a compact version of the same generation contract inside the
returned image. It is supported for PNG, JPEG, and WebP, uses conventional XMP
containers, and inserts metadata without decoding or re-encoding pixels. The
bridge fully decodes and verifies the result, then returns the final byte count
and SHA-256. The embedded record is a bounded JSON object containing prompts,
operation summary, requested/effective parameters, normalization and warnings,
provider/model, revised prompt, usage/session, queue/provider/per-image timing,
format and dimensions. It deliberately omits a checksum of its own final image,
which would be self-referential. embedded works with b64_json, artifact,
or bridge-hosted url output; it is rejected for metadata-only output.
sidecar_and_embedded writes both and therefore requires artifact delivery.
Embedding is limited to 40 KiB of JSON so JPEG APP1 remains portable. Combined
original and negative prompt text is limited to 12 KiB and rejected before any
provider work. An unchanged effective prompt is omitted as redundant. If
response-only optional fields would exceed the container, the record removes
them in a stable order and names every removed field in omitted_fields; prompt
text is never silently truncated. All metadata modes are explicit opt-ins
because prompts and session identifiers become part of copied or publicly
served files.
parameters.background=transparent describes the requested result.
output.transparency.mode selects auto, native, or chroma_key; auto uses
native provider alpha when declared and otherwise performs local chroma-key
removal. Optional key_color, transparent_threshold, opaque_threshold, and
despill fields tune the deterministic matte. Final native and emulated alpha
are independently decoded and validated. JPEG is not an alpha-capable output.
routing.fallbacks is an ordered list of {provider, model?} routes and
routing.fallback_policy is on_unavailable or on_error. Fallbacks are
allowed only for isolated sessions. The runtime never reroutes safety,
permission, cancellation, session, idempotency, or unknown-outcome failures.
Successful routed responses include redaction-safe attempts; exhausted
errors carry the same trace in details.attempts.
parameters.input_fidelity is optional and accepts low or high only when
the selected model advertises it. parameters.action is auto, generate, or
edit; intrinsic operation conflicts fail before provider work. Provider
capabilities expose the exact accepted fidelity/action sets. The compatible
multipart edit route accepts the official input_fidelity field. Masks remain
present in the contract but both Codex providers currently advertise them as
unsupported and reject them before generation.
Codex contextual-edit integration¶
Codex consumers should use the existing version-1 provider capability document
instead of maintaining a second bridge-specific matrix. For a selected model,
query GET /v1/providers/{name}/capabilities?model=... and derive the operation
flags as follows:
| Consumer flag | Authoritative version-1 fields |
|---|---|
generate |
generation |
contextualEdit |
edits and edit_images.support != unsupported |
rasterMaskEdit |
masks.support != unsupported and masks.max_count > 0 |
referenceImages |
reference_images.support != unsupported |
sourceTransport |
conversation_attachment for both current Codex transports |
codex-responses sends each verified source/reference as an input_image data
URL in the current Codex message. codex-app-server stages each verified input
under bridge-owned storage and sends it as a localImage item in the current
turn. These are contextual attachments: neither transport accepts an arbitrary
OneDay path, asset identifier, story identifier, or branch identifier.
A contextual edit request uses the native POST /v1/images contract with a
source image and no mask. Callers should request the explicit edit action
when the selected transport advertises it:
{
"version": "1",
"operation": "edit",
"prompt": "Replace the blue square with a red circle",
"images": [
{
"type": "data_url",
"data_url": "data:image/png;base64,...",
"filename": "source.png"
}
],
"parameters": {
"action": "edit"
},
"routing": {
"provider": "codex-responses",
"model": "gpt-image-2"
}
}
When a request includes mask, capability negotiation stops before source
materialization or Codex dispatch. The error remains the stable version-1
unsupported_capability classification and includes the Codex-specific detail
"codex_code": "CODEX_RASTER_MASK_UNSUPPORTED". Callers must not reinterpret
that rejection as prompt-guided inpainting or silently retry without the mask.
Authentication¶
When server.bearer_token_env is configured, /v1/** and the opt-in
/metrics endpoint require Authorization: Bearer …. The token is unrelated
to Codex OAuth and is never included in configuration dumps or diagnostics.
Health endpoints remain available for container orchestration and reveal no
secret values.
Observability¶
server.tracing.enabled defaults to true for the standalone CLI server. It
emits newline-delimited JSON to stderr at INFO level. Image-operation events
contain only the generated request ID, a registered provider name, stable error
code, and retryability; prompts, negative prompts, session keys, account IDs,
paths, image bodies, upstream bodies, and authentication data are never fields.
There is intentionally no supported content-logging switch. A future diagnostic
mode that exposes prompts or image content would require an explicit dangerous
configuration name, warnings, isolation, and separate security review.
server.metrics.enabled defaults to false. When enabled, GET /metrics
exports in-process Prometheus text metrics for request outcomes, operation,
provider and queue time, verified generated bytes, explicit normalizations,
current bounded queue depth, and supervised provider restarts. Labels are
limited to registered provider names, fixed scopes/results, and the bridge's
stable error taxonomy. The endpoint is covered by bridge bearer authentication
when configured and otherwise follows the listener's network trust boundary.
Errors¶
Errors never use a successful HTTP status:
{
"error": {
"message": "request validation failed",
"type": "invalid_request_error",
"param": "prompt",
"code": "invalid_request",
"imagegen_bridge": {
"code": "invalid_request",
"retryable": false,
"details": { "field": "prompt" }
}
},
"request_id": "019f..."
}
The four standard fields under error are consumable by OpenAI clients.
error.code is the compatibility discriminator; image safety blocks use
type=image_generation_user_error and code=moderation_blocked. The
imagegen_bridge extension always preserves the original bridge error code,
retryability, safe provider/upstream IDs, and redaction-safe structured detail.
Safety rejections include safety_category=content_policy,
recovery=revise_prompt_or_inputs, retry_same_request=false, the requested
moderation mode when available, and whether input images were present. The
bridge does not automatically weaken the prompt or moderation setting.
When Codex returns public moderation details, the bridge preserves only the
documented input|output|unknown stage and coarse allow-listed categories;
unknown/internal classifier labels are discarded.
The codex-responses adapter may repeat a provider call only for an explicitly
retryable pre-output result such as HTTP 429/5xx, a declared transient
Responses failure, or response.completed without an image item. The default
limit is two total attempts. Errors marked with outcome=unknown, as well as
safety, authentication, permission, cancellation, timeout-after-dispatch, and
protocol failures, are not repeated. A recovered call adds the response warning
transient_provider_retry_used; a final error includes provider_attempts.
If a completed response has no image, the error may also include bounded,
allow-listed shape diagnostics such as output_item_types,
message_content_types, and image_call_statuses. These fields contain only
documented enum-like labels, never response text, prompts, image data, or raw
provider payloads. A completed response containing a refusal is classified as
a non-retryable safety rejection instead of a transient missing-image result.
The top-level request ID also appears in the x-request-id response header.
Validation/input errors map to 400/422, missing authentication to 401,
permission failures to 403, conflicts to 409, rate limits to 429, capacity
or readiness failures to 503, deadlines to 504, and unexpected bridge or
upstream failures to 500/502.
Every native error includes an ordered, redaction-safe suggestions array. The
bridge derives it from the stable error taxonomy; overload, rate-limit,
timeout, authentication, configuration and protocol failures therefore carry
specific inspection or configuration steps instead of only a status code.
The checked-in OpenAPI 3.1 document includes native and compatibility request,
response, error, extension, multipart, provider, session, job, readiness, and SSE
schemas with examples. It is generated from the Rust contract and verified for
drift by CI and imagegen-bridge schema --kind openapi --check FILE.
Provider pagination¶
GET /v1/providers?limit=20&cursor=... accepts 1..=100. Cursors are opaque and
stable for the immutable provider registry. The response contains items and
an optional next_cursor. Each descriptor includes a models inventory. Query
each entry through /v1/providers/{name}/capabilities?model=...; provider/model
differences are authoritative and unsupported models return a structured error.