Skip to content

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.