openapi: 3.0.3
info:
title: Truss Image Server API
version: 0.12.0
description: |
HTTP API for the Truss image toolkit.
Successful transform requests return image bytes directly as `image/*` or
`image/svg+xml`; they do not return JSON envelopes. Failures are reported as
RFC 7807 `application/problem+json`.
Choose an endpoint based on how the source image is supplied:
- `GET /images/by-path`: public, cache-friendly delivery for files under the
configured storage root.
- `GET /images/by-url`: public, cache-friendly delivery for remote HTTP(S)
sources.
- `POST /images:transform`: private server-to-server transforms using a JSON body.
- `POST /images`: private uploads using `multipart/form-data`.
Common transform options are shared across endpoints. When `format` is omitted,
the server first tries `Accept` negotiation and otherwise keeps the input format.
**Watermark compositing** is available on all transform endpoints. Public GET
endpoints accept `watermarkUrl`, `watermarkPosition`, `watermarkOpacity`, and
`watermarkMargin` query parameters. The JSON endpoint accepts a `watermark`
object. The multipart endpoint accepts `watermark`, `watermark_position`,
`watermark_opacity`, and `watermark_margin` form fields.
license:
name: MIT
url: https://opensource.org/licenses/MIT
servers:
- url: http://localhost:8080
description: Local development
tags:
- name: Transform
description: Endpoints that fetch or accept an image, apply transform options, and return transformed bytes.
- name: Health
description: Lightweight probes for liveness and readiness checks.
- name: Observability
description: Operator-facing metrics and runtime diagnostics.
paths:
/images/by-path:
get:
tags: [Transform]
summary: Transform an image by storage path
description: |
Public, cache-friendly transform endpoint for files identified by path.
Also supports `HEAD` requests, which return the same headers without a body.
When the storage backend is `filesystem` (default), the `path` query
parameter is resolved relative to `TRUSS_STORAGE_ROOT`; path traversal
and out-of-root access are rejected. When the backend is `s3`, `gcs`,
or `azure`, the path is used as the object key (leading `/` is stripped
automatically).
This endpoint requires signed URL authentication through `keyId`,
`expires`, and `signature`.
Output selection rules:
- When `format` is present, that format is requested explicitly and the
`Accept` header is ignored.
- When `format` is omitted, the server tries `Accept` negotiation and
otherwise keeps the input format.
- When both `width` and `height` are present without `fit`, `contain`
is applied.
operationId: transformImageByPath
security:
- SignedUrlAuth: []
parameters:
- $ref: '#/components/parameters/SourcePath'
- $ref: '#/components/parameters/SourceVersion'
- $ref: '#/components/parameters/KeyId'
- $ref: '#/components/parameters/Expires'
- $ref: '#/components/parameters/Signature'
- $ref: '#/components/parameters/Width'
- $ref: '#/components/parameters/Height'
- $ref: '#/components/parameters/Fit'
- $ref: '#/components/parameters/Position'
- $ref: '#/components/parameters/Format'
- $ref: '#/components/parameters/Quality'
- $ref: '#/components/parameters/Optimize'
- $ref: '#/components/parameters/TargetQuality'
- $ref: '#/components/parameters/Background'
- $ref: '#/components/parameters/Rotate'
- $ref: '#/components/parameters/AutoOrient'
- $ref: '#/components/parameters/StripMetadata'
- $ref: '#/components/parameters/PreserveExif'
- $ref: '#/components/parameters/Crop'
- $ref: '#/components/parameters/Blur'
- $ref: '#/components/parameters/Sharpen'
- $ref: '#/components/parameters/WatermarkUrl'
- $ref: '#/components/parameters/WatermarkPosition'
- $ref: '#/components/parameters/WatermarkOpacity'
- $ref: '#/components/parameters/WatermarkMargin'
- $ref: '#/components/parameters/Preset'
responses:
'200':
$ref: '#/components/responses/ImageBinary'
'304':
$ref: '#/components/responses/NotModified'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'406':
$ref: '#/components/responses/NotAcceptable'
'413':
$ref: '#/components/responses/PayloadTooLarge'
'415':
$ref: '#/components/responses/UnsupportedMediaType'
'422':
$ref: '#/components/responses/UnprocessableEntity'
'500':
$ref: '#/components/responses/InternalError'
'501':
$ref: '#/components/responses/NotImplemented'
'502':
$ref: '#/components/responses/BadGateway'
'503':
$ref: '#/components/responses/ServiceUnavailable'
'508':
$ref: '#/components/responses/LoopDetected'
'429':
$ref: '#/components/responses/TooManyRequests'
head:
tags: [Transform]
summary: Inspect transformed image headers by storage path
description: |
Same contract as `GET /images/by-path`, but returns headers only and no body.
operationId: headTransformImageByPath
security:
- SignedUrlAuth: []
parameters:
- $ref: '#/components/parameters/SourcePath'
- $ref: '#/components/parameters/SourceVersion'
- $ref: '#/components/parameters/KeyId'
- $ref: '#/components/parameters/Expires'
- $ref: '#/components/parameters/Signature'
- $ref: '#/components/parameters/Width'
- $ref: '#/components/parameters/Height'
- $ref: '#/components/parameters/Fit'
- $ref: '#/components/parameters/Position'
- $ref: '#/components/parameters/Format'
- $ref: '#/components/parameters/Quality'
- $ref: '#/components/parameters/Optimize'
- $ref: '#/components/parameters/TargetQuality'
- $ref: '#/components/parameters/Background'
- $ref: '#/components/parameters/Rotate'
- $ref: '#/components/parameters/AutoOrient'
- $ref: '#/components/parameters/StripMetadata'
- $ref: '#/components/parameters/PreserveExif'
- $ref: '#/components/parameters/Crop'
- $ref: '#/components/parameters/Blur'
- $ref: '#/components/parameters/Sharpen'
- $ref: '#/components/parameters/WatermarkUrl'
- $ref: '#/components/parameters/WatermarkPosition'
- $ref: '#/components/parameters/WatermarkOpacity'
- $ref: '#/components/parameters/WatermarkMargin'
- $ref: '#/components/parameters/Preset'
responses:
'200':
$ref: '#/components/responses/ImageBinary'
'304':
$ref: '#/components/responses/NotModified'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'406':
$ref: '#/components/responses/NotAcceptable'
'413':
$ref: '#/components/responses/PayloadTooLarge'
'415':
$ref: '#/components/responses/UnsupportedMediaType'
'422':
$ref: '#/components/responses/UnprocessableEntity'
'500':
$ref: '#/components/responses/InternalError'
'501':
$ref: '#/components/responses/NotImplemented'
'502':
$ref: '#/components/responses/BadGateway'
'503':
$ref: '#/components/responses/ServiceUnavailable'
'508':
$ref: '#/components/responses/LoopDetected'
'429':
$ref: '#/components/responses/TooManyRequests'
/images/by-url:
get:
tags: [Transform]
summary: Transform an image from a remote URL
description: |
Public, cache-friendly transform endpoint for remote HTTP(S) sources.
Also supports `HEAD` requests, which return the same headers without a body.
Use this when the source image is hosted elsewhere and should be fetched by
Truss on demand. Remote fetches are subject to URL validation, redirect
limits, and source size limits.
This endpoint requires signed URL authentication through `keyId`,
`expires`, and `signature`.
Output selection rules:
- When `format` is present, that format is requested explicitly and the
`Accept` header is ignored.
- When `format` is omitted, the server tries `Accept` negotiation and
otherwise keeps the input format.
- When both `width` and `height` are present without `fit`, `contain`
is applied.
operationId: transformImageByUrl
security:
- SignedUrlAuth: []
parameters:
- $ref: '#/components/parameters/SourceUrl'
- $ref: '#/components/parameters/SourceVersion'
- $ref: '#/components/parameters/KeyId'
- $ref: '#/components/parameters/Expires'
- $ref: '#/components/parameters/Signature'
- $ref: '#/components/parameters/Width'
- $ref: '#/components/parameters/Height'
- $ref: '#/components/parameters/Fit'
- $ref: '#/components/parameters/Position'
- $ref: '#/components/parameters/Format'
- $ref: '#/components/parameters/Quality'
- $ref: '#/components/parameters/Optimize'
- $ref: '#/components/parameters/TargetQuality'
- $ref: '#/components/parameters/Background'
- $ref: '#/components/parameters/Rotate'
- $ref: '#/components/parameters/AutoOrient'
- $ref: '#/components/parameters/StripMetadata'
- $ref: '#/components/parameters/PreserveExif'
- $ref: '#/components/parameters/Crop'
- $ref: '#/components/parameters/Blur'
- $ref: '#/components/parameters/Sharpen'
- $ref: '#/components/parameters/WatermarkUrl'
- $ref: '#/components/parameters/WatermarkPosition'
- $ref: '#/components/parameters/WatermarkOpacity'
- $ref: '#/components/parameters/WatermarkMargin'
- $ref: '#/components/parameters/Preset'
responses:
'200':
$ref: '#/components/responses/ImageBinary'
'304':
$ref: '#/components/responses/NotModified'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'406':
$ref: '#/components/responses/NotAcceptable'
'413':
$ref: '#/components/responses/PayloadTooLarge'
'415':
$ref: '#/components/responses/UnsupportedMediaType'
'422':
$ref: '#/components/responses/UnprocessableEntity'
'500':
$ref: '#/components/responses/InternalError'
'501':
$ref: '#/components/responses/NotImplemented'
'502':
$ref: '#/components/responses/BadGateway'
'503':
$ref: '#/components/responses/ServiceUnavailable'
'508':
$ref: '#/components/responses/LoopDetected'
'429':
$ref: '#/components/responses/TooManyRequests'
head:
tags: [Transform]
summary: Inspect transformed image headers by remote URL
description: |
Same contract as `GET /images/by-url`, but returns headers only and no body.
operationId: headTransformImageByUrl
security:
- SignedUrlAuth: []
parameters:
- $ref: '#/components/parameters/SourceUrl'
- $ref: '#/components/parameters/SourceVersion'
- $ref: '#/components/parameters/KeyId'
- $ref: '#/components/parameters/Expires'
- $ref: '#/components/parameters/Signature'
- $ref: '#/components/parameters/Width'
- $ref: '#/components/parameters/Height'
- $ref: '#/components/parameters/Fit'
- $ref: '#/components/parameters/Position'
- $ref: '#/components/parameters/Format'
- $ref: '#/components/parameters/Quality'
- $ref: '#/components/parameters/Optimize'
- $ref: '#/components/parameters/TargetQuality'
- $ref: '#/components/parameters/Background'
- $ref: '#/components/parameters/Rotate'
- $ref: '#/components/parameters/AutoOrient'
- $ref: '#/components/parameters/StripMetadata'
- $ref: '#/components/parameters/PreserveExif'
- $ref: '#/components/parameters/Crop'
- $ref: '#/components/parameters/Blur'
- $ref: '#/components/parameters/Sharpen'
- $ref: '#/components/parameters/WatermarkUrl'
- $ref: '#/components/parameters/WatermarkPosition'
- $ref: '#/components/parameters/WatermarkOpacity'
- $ref: '#/components/parameters/WatermarkMargin'
- $ref: '#/components/parameters/Preset'
responses:
'200':
$ref: '#/components/responses/ImageBinary'
'304':
$ref: '#/components/responses/NotModified'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'406':
$ref: '#/components/responses/NotAcceptable'
'413':
$ref: '#/components/responses/PayloadTooLarge'
'415':
$ref: '#/components/responses/UnsupportedMediaType'
'422':
$ref: '#/components/responses/UnprocessableEntity'
'500':
$ref: '#/components/responses/InternalError'
'501':
$ref: '#/components/responses/NotImplemented'
'502':
$ref: '#/components/responses/BadGateway'
'503':
$ref: '#/components/responses/ServiceUnavailable'
'508':
$ref: '#/components/responses/LoopDetected'
'429':
$ref: '#/components/responses/TooManyRequests'
/images:transform:
post:
tags: [Transform]
summary: Transform an image by reference
description: |
Private server-to-server transform endpoint.
The request body identifies the source image as a storage path, a remote
URL, or an object-storage reference (bucket + key) and provides an
optional `options` object. This is the
most convenient endpoint for backend callers that want to authenticate with a
Bearer token instead of generating signed public URLs.
When `options.format` is omitted, the server tries `Accept` negotiation and
otherwise keeps the input format.
operationId: transformImageByReference
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TransformImageRequest'
example:
source:
kind: path
path: /photos/hero.jpg
version: v3
options:
width: 1600
format: webp
quality: 82
optimize: auto
targetQuality: ssim:0.98
autoOrient: true
stripMetadata: true
responses:
'200':
$ref: '#/components/responses/ImageBinary'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'406':
$ref: '#/components/responses/NotAcceptable'
'413':
$ref: '#/components/responses/PayloadTooLarge'
'415':
$ref: '#/components/responses/UnsupportedMediaType'
'422':
$ref: '#/components/responses/UnprocessableEntity'
'500':
$ref: '#/components/responses/InternalError'
'501':
$ref: '#/components/responses/NotImplemented'
'502':
$ref: '#/components/responses/BadGateway'
'503':
$ref: '#/components/responses/ServiceUnavailable'
'508':
$ref: '#/components/responses/LoopDetected'
'429':
$ref: '#/components/responses/TooManyRequests'
/images:
post:
tags: [Transform]
summary: Transform an uploaded image
description: |
Private upload-and-transform endpoint.
Send a `multipart/form-data` body with a required `file` part containing the
source image or SVG, plus an optional `options` part containing JSON that
matches `ImageTransformOptions`.
This endpoint is useful when the caller already has the source bytes in hand
and does not want to make the server read from a path or remote URL.
operationId: transformUploadedImage
security:
- BearerAuth: []
requestBody:
required: true
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/UploadImageTransformRequest'
encoding:
options:
contentType: application/json
responses:
'200':
$ref: '#/components/responses/ImageBinary'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'406':
$ref: '#/components/responses/NotAcceptable'
'413':
$ref: '#/components/responses/PayloadTooLarge'
'415':
$ref: '#/components/responses/UnsupportedMediaType'
'422':
$ref: '#/components/responses/UnprocessableEntity'
'500':
$ref: '#/components/responses/InternalError'
'501':
$ref: '#/components/responses/NotImplemented'
'503':
$ref: '#/components/responses/ServiceUnavailable'
'429':
$ref: '#/components/responses/TooManyRequests'
/health:
get:
tags: [Health]
summary: Comprehensive health diagnostic
description: |
Human-oriented diagnostic endpoint that returns version information,
process uptime, and detailed readiness checks.
Also supports `HEAD` requests.
This endpoint always returns 200 OK, even when individual checks fail,
because it is designed for dashboards and operators who need to see the
full picture.
operationId: getHealth
security:
- {}
- BearerAuth: []
responses:
'200':
description: Comprehensive health diagnostic
content:
application/json:
schema:
$ref: '#/components/schemas/HealthDiagnosticResponse'
example:
status: ok
service: truss
version: 0.12.0
uptimeSeconds: 3600
checks:
- name: storageRoot
status: ok
- name: cacheDiskFree
status: ok
freeBytes: 53687091200
thresholdBytes: 1073741824
- name: transformCapacity
status: ok
current: 2
max: 64
- name: memoryUsage
status: ok
rssBytes: 134217728
thresholdBytes: 536870912
maxInputPixels: 40000000
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
head:
tags: [Health]
summary: Inspect comprehensive health diagnostic headers
description: |
Same contract as `GET /health`, but returns headers only and no body.
operationId: headHealth
security:
- {}
- BearerAuth: []
responses:
'200':
description: Comprehensive health diagnostic
content:
application/json:
schema:
$ref: '#/components/schemas/HealthDiagnosticResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
/health/live:
get:
tags: [Health]
summary: Liveness probe
description: |
Lightweight liveness probe for container orchestrators (e.g. Kubernetes
`livenessProbe`). Returns immediately with minimal information confirming
the process is running and can serve HTTP. Does not check external
dependencies. Also supports `HEAD` requests.
operationId: getLiveHealth
responses:
'200':
description: Process is alive
content:
application/json:
schema:
$ref: '#/components/schemas/LivenessResponse'
example:
status: ok
service: truss
version: 0.12.0
'429':
$ref: '#/components/responses/TooManyRequests'
head:
tags: [Health]
summary: Inspect liveness probe headers
description: |
Same contract as `GET /health/live`, but returns headers only and no body.
operationId: headLiveHealth
responses:
'200':
description: Process is alive
content:
application/json:
schema:
$ref: '#/components/schemas/LivenessResponse'
'429':
$ref: '#/components/responses/TooManyRequests'
/health/ready:
get:
tags: [Health]
summary: Readiness probe
description: |
Readiness probe for container orchestrators (e.g. Kubernetes
`readinessProbe`). Also supports `HEAD` requests. Checks that the server
is able to accept traffic by verifying the storage root is accessible,
the cache root (if configured) is accessible, the object-storage backend
(if configured) is network-reachable, cache disk free space is above the
configured threshold (`TRUSS_HEALTH_CACHE_MIN_FREE_BYTES`), process memory
usage is below the configured limit (`TRUSS_HEALTH_MAX_MEMORY_BYTES`),
and transform capacity is not exhausted.
During graceful shutdown (after SIGTERM/SIGINT), this endpoint
immediately returns 503 with a `draining` check set to `fail`, even
before the drain period (`TRUSS_SHUTDOWN_DRAIN_SECS`, default 10s)
expires. This allows load balancers and CDN edges to remove the
instance from rotation before the server stops accepting connections.
**Platform note:** RSS memory and cache disk free space are read on
Linux only. On other platforms `TRUSS_HEALTH_MAX_MEMORY_BYTES` and
`TRUSS_HEALTH_CACHE_MIN_FREE_BYTES` are effectively no-ops — those
checks are omitted from the response and the remaining checks
(object-storage reachability, transform capacity, etc.) still apply.
The object-storage reachability check (S3, GCS, or Azure) confirms
that the configured bucket/container exists and the service responds to
requests. It does **not** verify IAM permissions for individual
objects — a 403 AccessDenied response still counts as "reachable"
because it proves the service is up and the bucket name is correct.
Permission errors on actual object reads are surfaced as 403 on the
transform endpoints.
Returns 503 Service Unavailable when any check fails, signalling that
traffic should be routed elsewhere.
operationId: getReadyHealth
responses:
'200':
description: Server is ready to accept traffic
content:
application/json:
schema:
$ref: '#/components/schemas/ReadinessResponse'
examples:
filesystem:
summary: Filesystem backend
value:
status: ok
checks:
- name: storageRoot
status: ok
s3:
summary: S3 backend
value:
status: ok
checks:
- name: storageRoot
status: ok
- name: storageBackend
status: ok
gcs:
summary: GCS backend
value:
status: ok
checks:
- name: storageRoot
status: ok
- name: storageBackend
status: ok
azure:
summary: Azure Blob Storage backend
value:
status: ok
checks:
- name: storageRoot
status: ok
- name: storageBackend
status: ok
'503':
description: Server is not ready to accept traffic
content:
application/json:
schema:
$ref: '#/components/schemas/ReadinessResponse'
examples:
storageRootFail:
summary: Storage root inaccessible
value:
status: fail
checks:
- name: storageRoot
status: fail
draining:
summary: Graceful shutdown in progress
value:
status: fail
checks:
- name: draining
status: fail
'429':
$ref: '#/components/responses/TooManyRequests'
head:
tags: [Health]
summary: Inspect readiness probe headers
description: |
Same contract as `GET /health/ready`, but returns headers only and no body.
operationId: headReadyHealth
responses:
'200':
description: Server is ready to accept traffic
content:
application/json:
schema:
$ref: '#/components/schemas/ReadinessResponse'
'503':
description: Server is not ready to accept traffic
content:
application/json:
schema:
$ref: '#/components/schemas/ReadinessResponse'
'429':
$ref: '#/components/responses/TooManyRequests'
/metrics:
get:
tags: [Observability]
summary: Prometheus metrics
description: |
Returns metrics in Prometheus text exposition format (version 0.0.4).
Also supports `HEAD` requests.
By default, the endpoint does not require authentication. Operators can
restrict access via `TRUSS_METRICS_TOKEN` (Bearer token) or disable the
endpoint entirely via `TRUSS_DISABLE_METRICS`.
operationId: getMetrics
security:
- {}
- BearerAuth: []
responses:
'200':
description: Prometheus text exposition format
content:
text/plain:
schema:
type: string
example: |
# HELP truss_process_up Whether the process is running.
# TYPE truss_process_up gauge
truss_process_up 1
# HELP truss_http_requests_total Total HTTP requests handled.
# TYPE truss_http_requests_total counter
truss_http_requests_total 42
# HELP truss_http_request_duration_seconds HTTP request duration in seconds.
# TYPE truss_http_request_duration_seconds histogram
truss_http_request_duration_seconds_bucket{route="/health",le="0.005"} 10
truss_http_request_duration_seconds_bucket{route="/health",le="+Inf"} 10
truss_http_request_duration_seconds_sum{route="/health"} 0.002
truss_http_request_duration_seconds_count{route="/health"} 10
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'503':
$ref: '#/components/responses/ServiceUnavailable'
'429':
$ref: '#/components/responses/TooManyRequests'
head:
tags: [Observability]
summary: Inspect Prometheus metrics headers
description: |
Same contract as `GET /metrics`, but returns headers only and no body.
operationId: headMetrics
security:
- {}
- BearerAuth: []
responses:
'200':
description: Prometheus text exposition format
content:
text/plain:
schema:
type: string
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'503':
$ref: '#/components/responses/ServiceUnavailable'
'429':
$ref: '#/components/responses/TooManyRequests'
components:
securitySchemes:
SignedUrlAuth:
type: apiKey
in: query
name: signature
description: |
HMAC-SHA256 signed URL authentication.
Requires `keyId`, `expires`, and `signature` query parameters on public
image endpoints.
The canonical form is:
`METHOD\nAUTHORITY\nREQUEST_PATH\nCANONICAL_QUERY`
In practice, callers usually generate these URLs with `truss sign` or an
equivalent implementation that matches the server's canonicalization rules.
BearerAuth:
type: http
scheme: bearer
bearerFormat: opaque
description: |
Bearer token authentication for private server-to-server and upload endpoints.
parameters:
SourcePath:
name: path
in: query
required: true
description: |
Source path resolved relative to `TRUSS_STORAGE_ROOT`.
A leading slash is allowed, but the final resolved path must stay under the
storage root.
schema:
type: string
minLength: 1
example: /photos/hero.jpg
SourceUrl:
name: url
in: query
required: true
description: |
Remote HTTP(S) URL to fetch the source image from.
Private-network and loopback targets may be rejected depending on server configuration.
schema:
type: string
format: uri
example: https://example.com/images/hero.png
SourceVersion:
name: version
in: query
required: false
description: |
Optional cache-busting version tag for the source.
This value becomes part of the cache key but is not fetched from the origin.
schema:
type: string
minLength: 1
example: v3
KeyId:
name: keyId
in: query
required: true
description: Key identifier that selects the shared secret used for signed URL verification.
schema:
type: string
minLength: 1
example: public-demo
Expires:
name: expires
in: query
required: true
description: |
Expiration time as a Unix timestamp in seconds.
Requests are rejected after this instant.
schema:
type: integer
format: int64
minimum: 1
example: 1893456000
Signature:
name: signature
in: query
required: true
description: Lowercase hex-encoded HMAC-SHA256 signature
schema:
type: string
minLength: 1
example: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
Width:
name: width
in: query
required: false
description: |
Requested output width in pixels.
When `height` is omitted, the image is resized proportionally.
schema:
type: integer
minimum: 1
example: 1200
Height:
name: height
in: query
required: false
description: |
Requested output height in pixels.
When `width` is omitted, the image is resized proportionally.
schema:
type: integer
minimum: 1
example: 630
Fit:
name: fit
in: query
required: false
description: |
Resize strategy. Only meaningful when both width and height are set.
Defaults to `contain`.
- `contain`: fit inside the box without cropping
- `cover`: fill the box, cropping excess
- `fill`: stretch to the exact box
- `inside`: like contain, but never upscale
schema:
type: string
enum: [contain, cover, fill, inside]
default: contain
example: cover
Position:
name: position
in: query
required: false
description: |
Crop anchor for cover mode and padding placement.
Only meaningful when both width and height are set.
Defaults to `center`.
schema:
type: string
enum: [center, top, right, bottom, left, top-left, top-right, bottom-left, bottom-right]
default: center
example: top
Format:
name: format
in: query
required: false
description: |
Output image format.
When present, the `Accept` header is ignored.
`svg` output is only valid for SVG input.
schema:
type: string
enum: [jpeg, png, webp, avif, bmp, tiff, svg]
example: webp
Quality:
name: quality
in: query
required: false
description: |
Encoding quality for lossy formats (jpeg, webp, avif).
Returns 400 for lossless formats.
schema:
type: integer
minimum: 1
maximum: 100
example: 82
Optimize:
name: optimize
in: query
required: false
description: |
Optimization mode for the final encoding stage.
`none` preserves the existing behavior.
`auto` picks a format-appropriate strategy.
`lossless` uses only pixel-preserving techniques.
`lossy` may reduce quality to save more bytes.
schema:
type: string
enum: [none, auto, lossless, lossy]
default: none
example: auto
TargetQuality:
name: targetQuality
in: query
required: false
description: |
Perceptual target used by lossy optimization, for example `ssim:0.98`
or `psnr:42`. Requires `optimize=auto` or `optimize=lossy`.
SSIM must be in `(0,1]`; PSNR must be greater than `0`.
schema:
oneOf:
- type: string
pattern: '^ssim:(?:0\.[0-9]*[1-9][0-9]*|1(?:\.0+)?)$'
- type: string
pattern: '^psnr:(?:0\.[0-9]*[1-9][0-9]*|[1-9][0-9]*(?:\.[0-9]+)?)$'
example: ssim:0.98
Background:
name: background
in: query
required: false
description: |
Background color as RRGGBB or RRGGBBAA hex (no leading #).
Mainly visible when padding or flattening transparency into opaque formats.
schema:
type: string
pattern: '^[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$'
example: ffffff
Rotate:
name: rotate
in: query
required: false
description: Rotation in degrees, applied after EXIF auto-orient
schema:
type: integer
enum: [0, 90, 180, 270]
default: 0
example: 90
AutoOrient:
name: autoOrient
in: query
required: false
description: |
Apply EXIF orientation when the input provides it, then reset the orientation tag.
Most useful for JPEG camera images.
schema:
type: boolean
default: true
example: true
StripMetadata:
name: stripMetadata
in: query
required: false
description: |
Remove metadata from the output when `true`.
Set this to `false` to keep supported metadata where the output encoder can retain it.
schema:
type: boolean
default: true
example: true
PreserveExif:
name: preserveExif
in: query
required: false
description: |
Preserve EXIF data only. Requires `stripMetadata=false`.
When `stripMetadata=false` and `preserveExif=false`, the server keeps all metadata
that the selected output encoder supports.
Returns 400 when output format is SVG.
schema:
type: boolean
default: false
example: false
Crop:
name: crop
in: query
required: false
description: |
Explicit pixel crop as `x,y,width,height`.
Applied after auto-orient and rotation but before resize.
The region must fit within the (possibly rotated) source dimensions.
Not supported for SVG inputs.
schema:
type: string
pattern: '^\d+,\d+,[1-9]\d*,[1-9]\d*$'
example: "100,50,800,600"
Preset:
name: preset
in: query
required: false
description: |
Name of a server-side transform preset.
Presets are defined via `TRUSS_PRESETS` or `TRUSS_PRESETS_FILE`.
Explicit query parameters override preset values.
schema:
type: string
minLength: 1
example: thumbnail
Blur:
name: blur
in: query
required: false
description: |
Apply Gaussian blur with the given sigma value.
Valid range is 0.1 to 100.0. Higher values produce a stronger blur.
Not supported for SVG inputs.
schema:
type: number
format: float
minimum: 0.1
maximum: 100.0
example: 3.0
Sharpen:
name: sharpen
in: query
required: false
description: |
Apply unsharp-mask (sharpen) filter with the given sigma value.
Valid range is 0.1 to 100.0. Higher values produce stronger sharpening.
Not supported for SVG inputs.
schema:
type: number
format: float
minimum: 0.1
maximum: 100.0
example: 2.0
WatermarkUrl:
name: watermarkUrl
in: query
required: false
description: |
HTTP(S) URL of a watermark image to composite onto the output.
Must be a raster format (PNG, JPEG, WebP, BMP, TIFF); SVG is not supported.
Subject to SSRF protection and the remote watermark size limit
(`TRUSS_MAX_WATERMARK_BYTES`, default 10 MB).
For multipart uploads the `watermark` file field has a fixed 10 MB cap.
schema:
type: string
format: uri
example: https://example.com/logo.png
WatermarkPosition:
name: watermarkPosition
in: query
required: false
description: |
Where to place the watermark on the output image.
Requires `watermarkUrl` to be set; requests are rejected otherwise.
schema:
type: string
enum: [center, top, right, bottom, left, top-left, top-right, bottom-left, bottom-right]
default: bottom-right
example: bottom-right
WatermarkOpacity:
name: watermarkOpacity
in: query
required: false
description: |
Watermark opacity (1–100).
Requires `watermarkUrl` to be set; requests are rejected otherwise.
schema:
type: integer
minimum: 1
maximum: 100
default: 50
example: 50
WatermarkMargin:
name: watermarkMargin
in: query
required: false
description: |
Margin in pixels from the nearest edge.
Requires `watermarkUrl` to be set; requests are rejected otherwise.
schema:
type: integer
minimum: 0
default: 10
example: 10
schemas:
PathImageSource:
type: object
description: Source image stored under the server's configured storage root.
additionalProperties: false
required: [kind, path]
properties:
kind:
type: string
enum: [path]
path:
type: string
minLength: 1
version:
type: string
minLength: 1
example:
kind: path
path: /photos/hero.jpg
version: v3
UrlImageSource:
type: object
description: Source image fetched from a remote HTTP(S) URL.
additionalProperties: false
required: [kind, url]
properties:
kind:
type: string
enum: [url]
url:
type: string
format: uri
version:
type: string
minLength: 1
example:
kind: url
url: https://example.com/images/hero.png
version: v3
StorageImageSource:
type: object
description: |
Source image stored in an S3-compatible, GCS, or Azure Blob Storage
backend. Requires the `s3`, `gcs`, or `azure` feature and the
corresponding `TRUSS_STORAGE_BACKEND` value.
additionalProperties: false
required: [kind, key]
properties:
kind:
type: string
enum: [storage]
bucket:
type: string
minLength: 1
description: >-
Bucket name (or Azure container name). Defaults to
`TRUSS_S3_BUCKET`, `TRUSS_GCS_BUCKET`, or `TRUSS_AZURE_CONTAINER`
when omitted.
key:
type: string
minLength: 1
description: Object key within the bucket (or Azure container).
version:
type: string
minLength: 1
example:
kind: storage
key: photos/hero.jpg
ImageSource:
description: >-
Exactly one source descriptor: a storage path, a remote URL, or an
object-storage reference.
oneOf:
- $ref: '#/components/schemas/PathImageSource'
- $ref: '#/components/schemas/UrlImageSource'
- $ref: '#/components/schemas/StorageImageSource'
discriminator:
propertyName: kind
mapping:
path: '#/components/schemas/PathImageSource'
url: '#/components/schemas/UrlImageSource'
storage: '#/components/schemas/StorageImageSource'
ImageTransformOptions:
type: object
description: |
Shared transform options used by the JSON and multipart endpoints.
The same semantics also apply to equivalent query parameters on public GET endpoints.
additionalProperties: false
properties:
width:
type: integer
minimum: 1
description: Requested output width in pixels.
height:
type: integer
minimum: 1
description: Requested output height in pixels.
fit:
type: string
enum: [contain, cover, fill, inside]
default: contain
description: Resize strategy used when both width and height are present.
position:
type: string
enum: [center, top, right, bottom, left, top-left, top-right, bottom-left, bottom-right]
default: center
description: Crop anchor or padding placement for bounded resizes.
format:
type: string
enum: [jpeg, png, webp, avif, bmp, tiff, svg]
description: Explicit output format. When omitted, the server negotiates or keeps the input format.
quality:
type: integer
minimum: 1
maximum: 100
description: Quality for lossy outputs (`jpeg`, `webp`, `avif`).
optimize:
type: string
enum: [none, auto, lossless, lossy]
default: none
description: Optimization mode for the final encoding stage.
targetQuality:
description: >-
Perceptual target used by lossy optimization, for example
`ssim:0.98` or `psnr:42`. SSIM must be in `(0,1]`; PSNR must be
greater than `0`. Requires `optimize=auto` or `optimize=lossy`.
oneOf:
- type: string
pattern: '^ssim:(?:0\.[0-9]*[1-9][0-9]*|1(?:\.0+)?)$'
- type: string
pattern: '^psnr:(?:0\.[0-9]*[1-9][0-9]*|[1-9][0-9]*(?:\.[0-9]+)?)$'
background:
type: string
pattern: '^[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$'
description: Background color for padding or flattening transparency.
rotate:
type: integer
enum: [0, 90, 180, 270]
default: 0
description: Extra clockwise rotation applied after auto-orientation.
autoOrient:
type: boolean
default: true
description: Apply EXIF orientation when present.
stripMetadata:
type: boolean
default: true
description: Remove metadata from the output when `true`.
preserveExif:
type: boolean
default: false
description: Preserve EXIF only. Requires `stripMetadata=false`.
crop:
type: string
pattern: '^\d+,\d+,[1-9]\d*,[1-9]\d*$'
description: >-
Explicit pixel crop as `x,y,width,height`. Applied after auto-orient
and rotation but before resize. Not supported for SVG inputs.
blur:
type: number
format: float
minimum: 0.1
maximum: 100.0
description: >-
Gaussian blur sigma (0.1–100.0). Higher values produce a stronger blur.
Not supported for SVG inputs; the server will reject the request
with an error if blur is specified for an SVG source.
sharpen:
type: number
format: float
minimum: 0.1
maximum: 100.0
description: >-
Sharpen sigma (0.1–100.0). Higher values produce stronger sharpening.
Not supported for SVG inputs; the server will reject the request
with an error if sharpen is specified for an SVG source.
preset:
type: string
minLength: 1
description: >-
Name of a server-side transform preset. Explicit fields override preset values.
example:
width: 1600
height: 900
fit: cover
position: center
format: webp
quality: 82
optimize: auto
targetQuality: ssim:0.98
autoOrient: true
stripMetadata: true
WatermarkPayload:
type: object
description: |
Watermark overlay configuration for the JSON transform endpoint.
The `url` field is required and must point to a raster image (PNG, JPEG, WebP, BMP, TIFF).
SVG watermarks are not supported.
additionalProperties: false
required: [url]
properties:
url:
type: string
format: uri
description: HTTP(S) URL of the watermark image. Subject to SSRF protection.
position:
type: string
enum: [center, top, right, bottom, left, top-left, top-right, bottom-left, bottom-right]
default: bottom-right
description: Where to place the watermark on the output image.
opacity:
type: integer
minimum: 1
maximum: 100
default: 50
description: Watermark opacity (1–100).
margin:
type: integer
minimum: 0
default: 10
description: Margin in pixels from the nearest edge.
example:
url: https://example.com/logo.png
position: bottom-right
opacity: 50
margin: 10
TransformImageRequest:
type: object
description: JSON request body for transforming a path- or URL-based source.
additionalProperties: false
required: [source]
properties:
source:
$ref: '#/components/schemas/ImageSource'
options:
$ref: '#/components/schemas/ImageTransformOptions'
watermark:
$ref: '#/components/schemas/WatermarkPayload'
example:
source:
kind: path
path: /photos/hero.jpg
options:
width: 1600
format: webp
quality: 82
optimize: auto
UploadImageTransformRequest:
type: object
description: |
Multipart request body containing the source file, optional transform options,
and optional watermark overlay fields.
additionalProperties: false
required: [file]
properties:
file:
type: string
format: binary
description: The image or SVG file to transform.
options:
$ref: '#/components/schemas/ImageTransformOptions'
watermark:
type: string
format: binary
description: Watermark image file (PNG, JPEG, WebP, BMP, or TIFF). SVG is not supported.
watermark_position:
type: string
enum: [center, top, right, bottom, left, top-left, top-right, bottom-left, bottom-right]
default: bottom-right
description: Where to place the watermark on the output image. Requires a `watermark` file field.
watermark_opacity:
type: integer
minimum: 1
maximum: 100
default: 50
description: Watermark opacity (1–100). Requires a `watermark` file field.
watermark_margin:
type: integer
minimum: 0
default: 10
description: Margin in pixels from the nearest edge. Requires a `watermark` file field.
LivenessResponse:
type: object
description: Minimal liveness payload confirming the process is running.
additionalProperties: false
required: [status, service, version]
properties:
status:
type: string
enum: [ok]
service:
type: string
enum: [truss]
version:
type: string
description: Semantic version of the running binary.
example: "0.12.0"
ReadinessResponse:
type: object
description: Readiness payload with individual dependency checks.
additionalProperties: false
required: [status, checks]
properties:
status:
type: string
enum: [ok, fail]
checks:
type: array
items:
$ref: '#/components/schemas/HealthCheck'
HealthDiagnosticResponse:
type: object
description: Comprehensive diagnostic payload with version, uptime, and checks.
additionalProperties: false
required: [status, service, version, uptimeSeconds, checks, maxInputPixels]
properties:
status:
type: string
enum: [ok, fail]
service:
type: string
enum: [truss]
version:
type: string
description: Semantic version of the running binary.
example: "0.12.0"
uptimeSeconds:
type: integer
format: int64
minimum: 0
description: Seconds since the server process started.
checks:
type: array
items:
$ref: '#/components/schemas/HealthCheck'
maxInputPixels:
type: integer
format: int64
minimum: 1
description: Maximum input image pixel count allowed before decode.
HealthCheck:
type: object
description: Individual dependency check result.
additionalProperties: false
required: [name, status]
properties:
name:
type: string
description: Name of the checked dependency.
example: storageRoot
status:
type: string
enum: [ok, fail]
freeBytes:
type: integer
format: int64
minimum: 0
description: Free bytes on the cache disk (cacheDiskFree check only).
thresholdBytes:
type: integer
format: int64
minimum: 1
description: >-
Configured threshold in bytes. Present on cacheDiskFree and
memoryUsage checks when a threshold is configured.
rssBytes:
type: integer
format: int64
minimum: 0
description: Process resident set size in bytes (memoryUsage check only, Linux).
current:
type: integer
format: int64
minimum: 0
description: Current number of in-flight transforms (transformCapacity check only).
max:
type: integer
format: int64
minimum: 1
description: Maximum concurrent transforms allowed (transformCapacity check only).
recovering:
type: boolean
description: Present and `true` while a hysteresis-tracked resource check remains in its recovery margin.
ProblemDetail:
type: object
description: RFC 7807 Problem Details
required: [type, title, status, detail]
properties:
type:
type: string
example: "about:blank"
title:
type: string
example: "Bad Request"
status:
type: integer
example: 400
detail:
type: string
example: "missing required query parameter: path"
responses:
ImageBinary:
description: |
Transformed image or sanitized SVG bytes.
The exact response media type depends on the selected output format.
headers:
Cache-Control:
description: |
`public, max-age=3600, stale-while-revalidate=60` for public GET endpoints.
`no-store` for private endpoints.
schema:
type: string
ETag:
description: SHA-256 based entity tag in the form `"sha256-<hex>"`
schema:
type: string
Vary:
description: Set to `Accept` when content negotiation was used
schema:
type: string
Cache-Status:
description: RFC 9211 cache status (`"truss"; hit` or `"truss"; fwd=miss`)
schema:
type: string
Age:
description: Seconds since the response was cached (only on cache hits)
schema:
type: integer
X-Content-Type-Options:
description: Always set to `nosniff`
schema:
type: string
Content-Disposition:
description: Always `inline; filename="truss.<ext>"`
schema:
type: string
Content-Security-Policy:
description: Set to `sandbox` for SVG output only
schema:
type: string
content:
image/jpeg:
schema:
type: string
format: binary
image/png:
schema:
type: string
format: binary
image/webp:
schema:
type: string
format: binary
image/avif:
schema:
type: string
format: binary
image/bmp:
schema:
type: string
format: binary
image/tiff:
schema:
type: string
format: binary
image/svg+xml:
schema:
type: string
format: binary
NotModified:
description: ETag matched the If-None-Match header (GET and HEAD)
headers:
ETag:
schema:
type: string
Cache-Control:
schema:
type: string
Cache-Status:
schema:
type: string
BadRequest:
description: Invalid request parameters or body
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
example:
type: about:blank
title: Bad Request
status: 400
detail: fit requires both width and height
Unauthorized:
description: Authentication failed or missing
headers:
WWW-Authenticate:
description: Set to `Bearer` for Bearer token endpoints
schema:
type: string
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
Forbidden:
description: Access denied (e.g. path traversal attempt)
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
NotFound:
description: Source file or route not found
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
NotAcceptable:
description: |
`Accept` header negotiation failed because none of the requested response media types
matched the formats supported by the server.
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
PayloadTooLarge:
description: Request body or remote source exceeds size limit
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
UnprocessableEntity:
description: Input image exceeds the configured pixel limit (`TRUSS_MAX_INPUT_PIXELS`)
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
example:
type: about:blank
title: Unprocessable Entity
status: 422
detail: "input image has 50000000 pixels, server limit is 40000000"
UnsupportedMediaType:
description: |
Unsupported input format, unsupported output format, or wrong request `Content-Type`.
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
InternalError:
description: Internal server error
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
NotImplemented:
description: Requested transform is not supported
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
BadGateway:
description: Remote origin fetch failed
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
ServiceUnavailable:
description: Server overloaded or required configuration missing
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
TooManyRequests:
description: Request was rejected by the per-IP rate limiter
headers:
Retry-After:
description: Seconds to wait before retrying the request
schema:
type: integer
X-Request-Id:
schema:
type: string
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
LoopDetected:
description: Too many redirects when fetching remote source
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'