fix(api): make OpenAPI spec ASCII-only so Schemathesis can load it (#344)

The nightly Schemathesis job failed with a schema-loading error:
'unacceptable character #x0080: control characters are not allowed'. The
served openapi.yaml contained 215 em dashes (U+2014) and box-drawing
section dividers (U+2500); Schemathesis's strict YAML parser mis-decodes
those multi-byte UTF-8 sequences as C1 control chars and refuses to load
the schema, so no fuzz checks ran. (PyYAML is lenient, which is why the
local yaml.safe_load check passed.)

Replace every non-ASCII char with ASCII '-'. This also clears an
em-dash style-rule violation. Add a docs.test.ts guard asserting the
served spec is ASCII-only, with a clear message, so this can't regress.

Pre-existing issue (the dashes predate this branch); surfaced while
verifying CI is green.
This commit is contained in:
SnapOtter
2026-06-24 17:14:10 +08:00
committed by GitHub
parent b2104fcde6
commit c2ae334c81
2 changed files with 243 additions and 228 deletions
+228 -228
View File
@@ -9,9 +9,9 @@ info:
Most endpoints require authentication via one of two methods:
1. **Session cookie** Call `POST /api/auth/login` with username and password. The response includes a `token` field. Pass it as `Authorization: Bearer <token>` on subsequent requests.
1. **Session cookie** - Call `POST /api/auth/login` with username and password. The response includes a `token` field. Pass it as `Authorization: Bearer <token>` on subsequent requests.
2. **API key** Generate a key via the Settings UI or `POST /api/v1/api-keys`. Keys are prefixed with `si_`. Pass as `Authorization: Bearer si_...`.
2. **API key** - Generate a key via the Settings UI or `POST /api/v1/api-keys`. Keys are prefixed with `si_`. Pass as `Authorization: Bearer si_...`.
Endpoints marked with a lock icon require authentication. Admin-only endpoints are noted in their description.
license:
@@ -267,11 +267,11 @@ paths:
type: string
description: |
JSON string with options:
- `width` (number, optional) Target width in pixels
- `height` (number, optional) Target height in pixels
- `fit` (string, default "contain") One of: contain, cover, fill, inside, outside
- `withoutEnlargement` (boolean, default false) Prevent upscaling
- `percentage` (number, optional) Scale by percentage instead of fixed dimensions
- `width` (number, optional) - Target width in pixels
- `height` (number, optional) - Target height in pixels
- `fit` (string, default "contain") - One of: contain, cover, fill, inside, outside
- `withoutEnlargement` (boolean, default false) - Prevent upscaling
- `percentage` (number, optional) - Scale by percentage instead of fixed dimensions
responses:
"200":
description: Processed image
@@ -316,12 +316,12 @@ paths:
type: string
description: |
JSON string with options:
- `width` (number, optional) Target width in pixels
- `height` (number, optional) Target height in pixels
- `protectFaces` (boolean, default false) Detect and protect faces from distortion
- `blurRadius` (number 0-20, default 4) Blur radius for energy map
- `sobelThreshold` (number 1-20, default 2) Edge detection threshold
- `square` (boolean, default false) Crop to square aspect ratio
- `width` (number, optional) - Target width in pixels
- `height` (number, optional) - Target height in pixels
- `protectFaces` (boolean, default false) - Detect and protect faces from distortion
- `blurRadius` (number 0-20, default 4) - Blur radius for energy map
- `sobelThreshold` (number 1-20, default 2) - Edge detection threshold
- `square` (boolean, default false) - Crop to square aspect ratio
At least one of `width`, `height`, or `square` must be provided.
responses:
@@ -375,11 +375,11 @@ paths:
type: string
description: |
JSON string with options:
- `left` (number, required) Left offset in pixels (min 0)
- `top` (number, required) Top offset in pixels (min 0)
- `width` (number, required) Width of crop region in pixels
- `height` (number, required) Height of crop region in pixels
- `unit` (string, optional) One of: px, percent
- `left` (number, required) - Left offset in pixels (min 0)
- `top` (number, required) - Top offset in pixels (min 0)
- `width` (number, required) - Width of crop region in pixels
- `height` (number, required) - Height of crop region in pixels
- `unit` (string, optional) - One of: px, percent
responses:
"200":
description: Processed image
@@ -424,9 +424,9 @@ paths:
type: string
description: |
JSON string with options:
- `angle` (number, default 0) Rotation angle in degrees
- `horizontal` (boolean, default false) Flip horizontally
- `vertical` (boolean, default false) Flip vertically
- `angle` (number, default 0) - Rotation angle in degrees
- `horizontal` (boolean, default false) - Flip horizontally
- `vertical` (boolean, default false) - Flip vertically
responses:
"200":
description: Processed image
@@ -471,8 +471,8 @@ paths:
type: string
description: |
JSON string with options:
- `format` (string, required) One of: jpg, png, webp, avif, tiff, gif, heic, heif, jxl, bmp, ico, jp2, qoi, psd
- `quality` (number 1-100, optional) Output quality
- `format` (string, required) - One of: jpg, png, webp, avif, tiff, gif, heic, heif, jxl, bmp, ico, jp2, qoi, psd
- `quality` (number 1-100, optional) - Output quality
responses:
"200":
description: Processed image
@@ -517,9 +517,9 @@ paths:
type: string
description: |
JSON string with options:
- `mode` (string, default "quality") One of: quality, targetSize
- `quality` (number 1-100, optional) Compression quality level
- `targetSizeKb` (number, optional) Target file size in kilobytes
- `mode` (string, default "quality") - One of: quality, targetSize
- `quality` (number 1-100, optional) - Compression quality level
- `targetSizeKb` (number, optional) - Target file size in kilobytes
responses:
"200":
description: Processed image
@@ -564,11 +564,11 @@ paths:
type: string
description: |
JSON string with options:
- `stripExif` (boolean, default false) Remove EXIF data
- `stripGps` (boolean, default false) Remove GPS data
- `stripIcc` (boolean, default false) Remove ICC profile
- `stripXmp` (boolean, default false) Remove XMP data
- `stripAll` (boolean, default true) Remove all metadata
- `stripExif` (boolean, default false) - Remove EXIF data
- `stripGps` (boolean, default false) - Remove GPS data
- `stripIcc` (boolean, default false) - Remove ICC profile
- `stripXmp` (boolean, default false) - Remove XMP data
- `stripAll` (boolean, default true) - Remove all metadata
responses:
"200":
description: Processed image
@@ -613,17 +613,17 @@ paths:
type: string
description: |
JSON string with options:
- `borderWidth` (number 0-2000, default 10) Border thickness in pixels
- `borderColor` (hex string, default "#000000") Border color
- `padding` (number 0-200, default 0) Inner padding in pixels
- `paddingColor` (hex string, default "#FFFFFF") Padding fill color
- `cornerRadius` (number 0-2000, default 0) Corner rounding radius
- `shadow` (boolean, default false) Enable drop shadow
- `shadowBlur` (number 1-200, default 15) Drop shadow blur radius
- `shadowOffsetX` (number -50 to 50, default 0) Shadow horizontal offset
- `shadowOffsetY` (number -50 to 50, default 5) Shadow vertical offset
- `shadowColor` (hex string, default "#000000") Drop shadow color
- `shadowOpacity` (number 0-100, default 40) Shadow opacity percentage
- `borderWidth` (number 0-2000, default 10) - Border thickness in pixels
- `borderColor` (hex string, default "#000000") - Border color
- `padding` (number 0-200, default 0) - Inner padding in pixels
- `paddingColor` (hex string, default "#FFFFFF") - Padding fill color
- `cornerRadius` (number 0-2000, default 0) - Corner rounding radius
- `shadow` (boolean, default false) - Enable drop shadow
- `shadowBlur` (number 1-200, default 15) - Drop shadow blur radius
- `shadowOffsetX` (number -50 to 50, default 0) - Shadow horizontal offset
- `shadowOffsetY` (number -50 to 50, default 5) - Shadow vertical offset
- `shadowColor` (hex string, default "#000000") - Drop shadow color
- `shadowOpacity` (number 0-100, default 40) - Shadow opacity percentage
responses:
"200":
description: Processed image
@@ -1794,12 +1794,12 @@ paths:
type: string
description: |
JSON string with options:
- `text` (string 1-500, required) Watermark text
- `fontSize` (number 8-1000, default 48) Font size in pixels
- `color` (hex string, default "#000000") Text color
- `opacity` (number 0-100, default 50) Opacity percentage
- `position` (string, default "center") One of: center, top-left, top-right, bottom-left, bottom-right, tiled
- `rotation` (number -360 to 360, default 0) Text rotation angle
- `text` (string 1-500, required) - Watermark text
- `fontSize` (number 8-1000, default 48) - Font size in pixels
- `color` (hex string, default "#000000") - Text color
- `opacity` (number 0-100, default 50) - Opacity percentage
- `position` (string, default "center") - One of: center, top-left, top-right, bottom-left, bottom-right, tiled
- `rotation` (number -360 to 360, default 0) - Text rotation angle
responses:
"200":
description: Processed image
@@ -1844,13 +1844,13 @@ paths:
type: string
description: |
JSON string with options:
- `text` (string 1-500, required) Overlay text
- `fontSize` (number 8-200, default 48) Font size in pixels
- `color` (hex string, default "#FFFFFF") Text color
- `position` (string, default "bottom") One of: top, center, bottom
- `backgroundBox` (boolean, default false) Show background box behind text
- `backgroundColor` (hex string, default "#000000") Background box color
- `shadow` (boolean, default true) Add text shadow
- `text` (string 1-500, required) - Overlay text
- `fontSize` (number 8-200, default 48) - Font size in pixels
- `color` (hex string, default "#FFFFFF") - Text color
- `position` (string, default "bottom") - One of: top, center, bottom
- `backgroundBox` (boolean, default false) - Show background box behind text
- `backgroundColor` (hex string, default "#000000") - Background box color
- `shadow` (boolean, default true) - Add text shadow
responses:
"200":
description: Processed image
@@ -1895,10 +1895,10 @@ paths:
type: string
description: |
JSON string with options:
- `sourceColor` (hex string, default "#FF0000") Color to replace
- `targetColor` (hex string, default "#00FF00") Replacement color
- `makeTransparent` (boolean, default false) Make matched pixels transparent instead
- `tolerance` (number 0-255, default 30) Color matching tolerance
- `sourceColor` (hex string, default "#FF0000") - Color to replace
- `targetColor` (hex string, default "#00FF00") - Replacement color
- `makeTransparent` (boolean, default false) - Make matched pixels transparent instead
- `tolerance` (number 0-255, default 30) - Color matching tolerance
responses:
"200":
description: Processed image
@@ -1988,23 +1988,23 @@ paths:
type: string
description: |
JSON string with options:
- `mode` (string, default "resize") One of: resize, optimize, speed, reverse, extract, rotate
- `width` (number 1-16384, optional) Target width in pixels (resize mode)
- `height` (number 1-16384, optional) Target height in pixels (resize mode)
- `percentage` (number 1-500, optional) Scale by percentage (resize mode)
- `colors` (number 2-256, default 256) Color palette size (optimize mode)
- `dither` (number 0-1, default 1.0) Dither amount (optimize mode)
- `effort` (number 1-10, default 7) Compression effort (optimize mode)
- `speedFactor` (number 0.1-10, default 1.0) Speed multiplier (speed mode)
- `extractMode` (string, default "single") One of: single, range, all (extract mode)
- `frameNumber` (number, default 0) Frame index to extract (extract/single mode)
- `frameStart` (number, default 0) Start frame index (extract/range mode)
- `frameEnd` (number, optional) End frame index (extract/range mode)
- `extractFormat` (string, default "png") One of: png, webp (extract mode)
- `angle` (number, optional) Rotation angle, one of: 90, 180, 270 (rotate mode)
- `flipH` (boolean, default false) Flip horizontally (rotate mode)
- `flipV` (boolean, default false) Flip vertically (rotate mode)
- `loop` (number 0-100, default 0) Loop count, 0 = infinite
- `mode` (string, default "resize") - One of: resize, optimize, speed, reverse, extract, rotate
- `width` (number 1-16384, optional) - Target width in pixels (resize mode)
- `height` (number 1-16384, optional) - Target height in pixels (resize mode)
- `percentage` (number 1-500, optional) - Scale by percentage (resize mode)
- `colors` (number 2-256, default 256) - Color palette size (optimize mode)
- `dither` (number 0-1, default 1.0) - Dither amount (optimize mode)
- `effort` (number 1-10, default 7) - Compression effort (optimize mode)
- `speedFactor` (number 0.1-10, default 1.0) - Speed multiplier (speed mode)
- `extractMode` (string, default "single") - One of: single, range, all (extract mode)
- `frameNumber` (number, default 0) - Frame index to extract (extract/single mode)
- `frameStart` (number, default 0) - Start frame index (extract/range mode)
- `frameEnd` (number, optional) - End frame index (extract/range mode)
- `extractFormat` (string, default "png") - One of: png, webp (extract mode)
- `angle` (number, optional) - Rotation angle, one of: 90, 180, 270 (rotate mode)
- `flipH` (boolean, default false) - Flip horizontally (rotate mode)
- `flipV` (boolean, default false) - Flip vertically (rotate mode)
- `loop` (number 0-100, default 0) - Loop count, 0 = infinite
responses:
"200":
description: Processed image
@@ -2103,18 +2103,18 @@ paths:
type: string
description: |
JSON string with options:
- `mode` (string, default "subject") One of: subject, face, trim, attention (alias for subject), content (alias for trim)
- `strategy` (string, default "attention") One of: attention, entropy (subject mode)
- `width` (integer, optional) Target width in pixels
- `height` (integer, optional) Target height in pixels
- `padding` (integer 0-50, default 0) Padding percentage around focus area
- `facePreset` (string, default "head-shoulders") One of: closeup, head-shoulders, upper-body, half-body (face mode)
- `sensitivity` (number 0-1, default 0.5) Face detection sensitivity (face mode)
- `threshold` (integer 0-255, default 30) Trim tolerance (trim mode)
- `padToSquare` (boolean, default false) Pad to square after trimming (trim mode)
- `padColor` (string, default "#ffffff") Hex color for padding (trim mode)
- `targetSize` (integer, optional) Target size for padded output (trim mode)
- `quality` (integer 1-100, optional) Output quality
- `mode` (string, default "subject") - One of: subject, face, trim, attention (alias for subject), content (alias for trim)
- `strategy` (string, default "attention") - One of: attention, entropy (subject mode)
- `width` (integer, optional) - Target width in pixels
- `height` (integer, optional) - Target height in pixels
- `padding` (integer 0-50, default 0) - Padding percentage around focus area
- `facePreset` (string, default "head-shoulders") - One of: closeup, head-shoulders, upper-body, half-body (face mode)
- `sensitivity` (number 0-1, default 0.5) - Face detection sensitivity (face mode)
- `threshold` (integer 0-255, default 30) - Trim tolerance (trim mode)
- `padToSquare` (boolean, default false) - Pad to square after trimming (trim mode)
- `padColor` (string, default "#ffffff") - Hex color for padding (trim mode)
- `targetSize` (integer, optional) - Target size for padded output (trim mode)
- `quality` (integer 1-100, optional) - Output quality
responses:
"200":
description: Processed image
@@ -2211,12 +2211,12 @@ paths:
type: string
description: |
JSON string with options:
- `width` (number 1-65536, optional) Output width in pixels
- `height` (number 1-65536, optional) Output height in pixels
- `dpi` (number 36-2400, default 300) Render density for SVG rasterization
- `quality` (number 1-100, default 90) Output quality for lossy formats
- `backgroundColor` (hex string, default "#00000000") Background color
- `outputFormat` (string, default "png") One of: png, jpg, webp, avif, tiff, gif, heif, jxl
- `width` (number 1-65536, optional) - Output width in pixels
- `height` (number 1-65536, optional) - Output height in pixels
- `dpi` (number 36-2400, default 300) - Render density for SVG rasterization
- `quality` (number 1-100, default 90) - Output quality for lossy formats
- `backgroundColor` (hex string, default "#00000000") - Background color
- `outputFormat` (string, default "png") - One of: png, jpg, webp, avif, tiff, gif, heif, jxl
responses:
"200":
description: Processed image
@@ -2318,14 +2318,14 @@ paths:
type: string
description: |
JSON string with options:
- `pageSize` (string, default "A4") One of: A4, Letter, A3, A5
- `orientation` (string, default "portrait") One of: portrait, landscape
- `margin` (number 0-500, default 20) Page margin in points
- `targetSize` (object, optional) Target output file size
- `value` (number, required) Positive number (decimals allowed)
- `unit` (string, required) One of: KB, MB
- `pageSize` (string, default "A4") - One of: A4, Letter, A3, A5
- `orientation` (string, default "portrait") - One of: portrait, landscape
- `margin` (number 0-500, default 20) - Page margin in points
- `targetSize` (object, optional) - Target output file size
- `value` (number, required) - Positive number (decimals allowed)
- `unit` (string, required) - One of: KB, MB
Minimum effective target is 50KB. When set, images are compressed as JPEG with quality auto-tuned to meet the target.
- `collate` (boolean, default true) When true, all images go into one PDF. When false, each image becomes a separate PDF and the result is a ZIP.
- `collate` (boolean, default true) - When true, all images go into one PDF. When false, each image becomes a separate PDF and the result is a ZIP.
responses:
"200":
description: Generated PDF (downloadUrl points to .pdf file)
@@ -2395,11 +2395,11 @@ paths:
type: string
description: |
JSON string with options:
- `format` (string, default "png") Output format: png, jpg, webp, avif, tiff, gif, heic, heif, jxl
- `dpi` (number 36-2400, default 150) Resolution in dots per inch
- `quality` (number 1-100, default 85) Output quality
- `colorMode` (string, default "color") One of: color, grayscale, bw
- `pages` (string, default "all") Page selection, e.g. "all", "1-3", "1,3,5"
- `format` (string, default "png") - Output format: png, jpg, webp, avif, tiff, gif, heic, heif, jxl
- `dpi` (number 36-2400, default 150) - Resolution in dots per inch
- `quality` (number 1-100, default 85) - Output quality
- `colorMode` (string, default "color") - One of: color, grayscale, bw
- `pages` (string, default "all") - Page selection, e.g. "all", "1-3", "1,3,5"
responses:
"200":
description: Converted pages
@@ -2558,12 +2558,12 @@ paths:
type: string
description: |
JSON string with options:
- `columns` (number 1-100, default 3) Number of columns
- `rows` (number 1-100, default 3) Number of rows
- `tileWidth` (number, min 10, optional) Fixed tile width in pixels
- `tileHeight` (number, min 10, optional) Fixed tile height in pixels
- `outputFormat` (string, default "original") One of: original, png, jpg, webp, avif, jxl
- `quality` (number 1-100, default 90) Output quality
- `columns` (number 1-100, default 3) - Number of columns
- `rows` (number 1-100, default 3) - Number of rows
- `tileWidth` (number, min 10, optional) - Fixed tile width in pixels
- `tileHeight` (number, min 10, optional) - Fixed tile height in pixels
- `outputFormat` (string, default "original") - One of: original, png, jpg, webp, avif, jxl
- `quality` (number 1-100, default 90) - Output quality
responses:
"200":
description: ZIP archive with results
@@ -2611,8 +2611,8 @@ paths:
type: string
description: |
JSON string with options:
- `pattern` (string 1-1000, default "image-{{index}}") Naming pattern template
- `startIndex` (number, default 1) Starting index number
- `pattern` (string 1-1000, default "image-{{index}}") - Naming pattern template
- `startIndex` (number, default 1) - Starting index number
responses:
"200":
description: ZIP archive with results
@@ -2703,9 +2703,9 @@ paths:
type: string
description: |
JSON string with options:
- `position` (string, default "bottom-right") One of: center, top-left, top-right, bottom-left, bottom-right
- `opacity` (number 0-100, default 50) Watermark opacity percentage
- `scale` (number 1-100, default 25) Watermark size as percentage of base image
- `position` (string, default "bottom-right") - One of: center, top-left, top-right, bottom-left, bottom-right
- `opacity` (number 0-100, default 50) - Watermark opacity percentage
- `scale` (number 1-100, default 25) - Watermark size as percentage of base image
responses:
"200":
description: Processed image
@@ -2754,10 +2754,10 @@ paths:
type: string
description: |
JSON string with options:
- `x` (number, default 0) Horizontal offset of overlay
- `y` (number, default 0) Vertical offset of overlay
- `opacity` (number 0-100, default 100) Overlay opacity percentage
- `blendMode` (string, default "over") One of: over, multiply, screen, overlay, darken, lighten, hard-light, soft-light, difference, exclusion
- `x` (number, default 0) - Horizontal offset of overlay
- `y` (number, default 0) - Vertical offset of overlay
- `opacity` (number 0-100, default 100) - Overlay opacity percentage
- `blendMode` (string, default "over") - One of: over, multiply, screen, overlay, darken, lighten, hard-light, soft-light, difference, exclusion
responses:
"200":
description: Processed image
@@ -3260,14 +3260,14 @@ paths:
type: string
description: |
JSON string with options:
- `templateId` (string, required) Template ID defining the grid layout
- `cells` (array, optional) Per-cell configuration overrides
- `gap` (number 0-500, default 8) Gap between images in pixels
- `cornerRadius` (number 0-500, default 0) Corner rounding radius
- `backgroundColor` (string, default "#FFFFFF") Background fill color
- `aspectRatio` (string, default "free") Aspect ratio constraint
- `outputFormat` (string, default "png") One of: png, jpeg, webp, avif, jxl
- `quality` (number 1-100, default 90) Output quality
- `templateId` (string, required) - Template ID defining the grid layout
- `cells` (array, optional) - Per-cell configuration overrides
- `gap` (number 0-500, default 8) - Gap between images in pixels
- `cornerRadius` (number 0-500, default 0) - Corner rounding radius
- `backgroundColor` (string, default "#FFFFFF") - Background fill color
- `aspectRatio` (string, default "free") - Aspect ratio constraint
- `outputFormat` (string, default "png") - One of: png, jpeg, webp, avif, jxl
- `quality` (number 1-100, default 90) - Output quality
responses:
"200":
description: Processed image
@@ -3314,16 +3314,16 @@ paths:
type: string
description: |
JSON string with options:
- `direction` (string, default "horizontal") One of: horizontal, vertical, grid
- `gridColumns` (integer 2-100, default 2) Columns when direction is grid
- `resizeMode` (string, default "fit") One of: fit, original, stretch, crop
- `alignment` (string, default "center") One of: start, center, end
- `gap` (number 0-1000, default 0) Gap between images in pixels
- `border` (number 0-500, default 0) Border width in pixels
- `cornerRadius` (number 0-500, default 0) Corner radius in pixels
- `backgroundColor` (hex string, default "#FFFFFF") Hex color for background and gap fill
- `format` (string, default "png") One of: png, jpeg, webp, avif, jxl
- `quality` (number 1-100, default 90) Output quality
- `direction` (string, default "horizontal") - One of: horizontal, vertical, grid
- `gridColumns` (integer 2-100, default 2) - Columns when direction is grid
- `resizeMode` (string, default "fit") - One of: fit, original, stretch, crop
- `alignment` (string, default "center") - One of: start, center, end
- `gap` (number 0-1000, default 0) - Gap between images in pixels
- `border` (number 0-500, default 0) - Border width in pixels
- `cornerRadius` (number 0-500, default 0) - Corner radius in pixels
- `backgroundColor` (hex string, default "#FFFFFF") - Hex color for background and gap fill
- `format` (string, default "png") - One of: png, jpeg, webp, avif, jxl
- `quality` (number 1-100, default 90) - Output quality
responses:
"200":
description: Stitched image
@@ -3368,16 +3368,16 @@ paths:
type: string
description: |
JSON string with options:
- `model` (string, optional) AI model name
- `backgroundType` (string, optional) One of: transparent, color, gradient, blur, image
- `backgroundColor` (string, optional) Hex color to replace removed background with
- `gradientColor1` (string, optional) First gradient color
- `gradientColor2` (string, optional) Second gradient color
- `gradientAngle` (number, optional) Gradient angle in degrees
- `blurEnabled` (boolean, optional) Enable background blur effect
- `blurIntensity` (number 0-100, optional) Blur strength
- `shadowEnabled` (boolean, optional) Enable drop shadow
- `shadowOpacity` (number 0-100, optional) Shadow opacity
- `model` (string, optional) - AI model name
- `backgroundType` (string, optional) - One of: transparent, color, gradient, blur, image
- `backgroundColor` (string, optional) - Hex color to replace removed background with
- `gradientColor1` (string, optional) - First gradient color
- `gradientColor2` (string, optional) - Second gradient color
- `gradientAngle` (number, optional) - Gradient angle in degrees
- `blurEnabled` (boolean, optional) - Enable background blur effect
- `blurIntensity` (number 0-100, optional) - Blur strength
- `shadowEnabled` (boolean, optional) - Enable drop shadow
- `shadowOpacity` (number 0-100, optional) - Shadow opacity
clientJobId:
type: string
description: Client-provided job ID for SSE progress tracking
@@ -3445,17 +3445,17 @@ paths:
type: string
description: |
JSON string with options:
- `jobId` (string, required) Job ID from the initial remove-background call
- `filename` (string, required) Original filename
- `backgroundType` (string) One of: transparent, color, gradient, blur, image
- `backgroundColor` (string) Hex color for solid background
- `gradientColor1` (string) First gradient color
- `gradientColor2` (string) Second gradient color
- `gradientAngle` (number) Gradient angle in degrees
- `blurEnabled` (boolean) Enable background blur effect
- `blurIntensity` (number 0-100) Blur strength
- `shadowEnabled` (boolean) Enable drop shadow
- `shadowOpacity` (number 0-100) Shadow opacity
- `jobId` (string, required) - Job ID from the initial remove-background call
- `filename` (string, required) - Original filename
- `backgroundType` (string) - One of: transparent, color, gradient, blur, image
- `backgroundColor` (string) - Hex color for solid background
- `gradientColor1` (string) - First gradient color
- `gradientColor2` (string) - Second gradient color
- `gradientAngle` (number) - Gradient angle in degrees
- `blurEnabled` (boolean) - Enable background blur effect
- `blurIntensity` (number 0-100) - Blur strength
- `shadowEnabled` (boolean) - Enable drop shadow
- `shadowOpacity` (number 0-100) - Shadow opacity
responses:
"200":
description: Image with applied effects
@@ -3512,9 +3512,9 @@ paths:
type: string
description: |
JSON string with options:
- `defringe` (number 0-100, default 30) Edge defringe strength to remove color contamination
- `outputFormat` (string, default "png") One of: png, webp
- `removeWatermark` (boolean, default false) Apply median filter to reduce watermark artifacts before matting
- `defringe` (number 0-100, default 30) - Edge defringe strength to remove color contamination
- `outputFormat` (string, default "png") - One of: png, webp
- `removeWatermark` (boolean, default false) - Apply median filter to reduce watermark artifacts before matting
clientJobId:
type: string
description: Client-provided job ID for SSE progress tracking
@@ -3580,12 +3580,12 @@ paths:
type: string
description: |
JSON string with options:
- `scale` (number, default 2) Upscale factor
- `model` (string, default "auto") AI model name
- `faceEnhance` (boolean, default false) Enable face enhancement
- `denoise` (number, default 0) Denoise strength
- `format` (string, default "auto") Output format (auto, png, jpg, jpeg, webp, tiff, gif, avif, heic, heif, jxl)
- `quality` (number, default 95) Output quality (1-100)
- `scale` (number, default 2) - Upscale factor
- `model` (string, default "auto") - AI model name
- `faceEnhance` (boolean, default false) - Enable face enhancement
- `denoise` (number, default 0) - Denoise strength
- `format` (string, default "auto") - Output format (auto, png, jpg, jpeg, webp, tiff, gif, avif, heic, heif, jxl)
- `quality` (number, default 95) - Output quality (1-100)
clientJobId:
type: string
description: Client-provided job ID for SSE progress tracking
@@ -3651,8 +3651,8 @@ paths:
type: string
description: |
JSON string with options:
- `blurRadius` (number 1-100, default 30) Blur strength radius
- `sensitivity` (number 0-1, default 0.5) Face detection sensitivity
- `blurRadius` (number 1-100, default 30) - Blur strength radius
- `sensitivity` (number 0-1, default 0.5) - Face detection sensitivity
clientJobId:
type: string
description: Client-provided job ID for SSE progress tracking
@@ -3718,10 +3718,10 @@ paths:
type: string
description: |
JSON string with options:
- `quality` (string, default "balanced") One of: fast, balanced, best
- `language` (string, default "auto") One of: auto, en, de, fr, es, zh, ja, ko
- `enhance` (boolean, default true) Pre-process image for better recognition
- `engine` (string, optional) One of: tesseract, paddleocr (backward compat)
- `quality` (string, default "balanced") - One of: fast, balanced, best
- `language` (string, default "auto") - One of: auto, en, de, fr, es, zh, ja, ko
- `enhance` (boolean, default true) - Pre-process image for better recognition
- `engine` (string, optional) - One of: tesseract, paddleocr (backward compat)
clientJobId:
type: string
responses:
@@ -3938,7 +3938,7 @@ paths:
type: string
description: |
JSON string with options:
- `tryHarder` (boolean, default true) Enable more aggressive barcode detection
- `tryHarder` (boolean, default true) - Enable more aggressive barcode detection
responses:
"200":
description: Decoded barcode data
@@ -4032,7 +4032,7 @@ paths:
type: string
description: |
JSON string with options:
- `threshold` (number 0-20, default 8) Hamming distance threshold for duplicate detection
- `threshold` (number 0-20, default 8) - Hamming distance threshold for duplicate detection
responses:
"200":
description: Duplicate groups
@@ -4331,28 +4331,28 @@ paths:
type: string
description: |
JSON string with options:
- `title` (string) Image title
- `author` (string) Author name
- `artist` (string) Artist or author name
- `copyright` (string) Copyright notice
- `imageDescription` (string) Image description
- `software` (string) Software used
- `dateTime` (string) Date/time string
- `dateTimeOriginal` (string) Original capture date/time
- `clearGps` (boolean, default false) Remove all GPS data
- `fieldsToRemove` (string[]) Specific metadata fields to remove
- `gpsLatitude` (number -90 to 90) GPS latitude
- `gpsLongitude` (number -180 to 180) GPS longitude
- `gpsAltitude` (number) GPS altitude in meters
- `keywords` (string[]) Keywords or tags
- `keywordsMode` (string, default "add") One of: add, set
- `dateShift` (string) Shift dates by offset, e.g. "+2:00" or "-1:30"
- `setAllDates` (string) Set all date fields to this value
- `iptcTitle` (string) IPTC title
- `iptcHeadline` (string) IPTC headline
- `iptcCity` (string) IPTC city
- `iptcState` (string) IPTC state or province
- `iptcCountry` (string) IPTC country
- `title` (string) - Image title
- `author` (string) - Author name
- `artist` (string) - Artist or author name
- `copyright` (string) - Copyright notice
- `imageDescription` (string) - Image description
- `software` (string) - Software used
- `dateTime` (string) - Date/time string
- `dateTimeOriginal` (string) - Original capture date/time
- `clearGps` (boolean, default false) - Remove all GPS data
- `fieldsToRemove` (string[]) - Specific metadata fields to remove
- `gpsLatitude` (number -90 to 90) - GPS latitude
- `gpsLongitude` (number -180 to 180) - GPS longitude
- `gpsAltitude` (number) - GPS altitude in meters
- `keywords` (string[]) - Keywords or tags
- `keywordsMode` (string, default "add") - One of: add, set
- `dateShift` (string) - Shift dates by offset, e.g. "+2:00" or "-1:30"
- `setAllDates` (string) - Set all date fields to this value
- `iptcTitle` (string) - IPTC title
- `iptcHeadline` (string) - IPTC headline
- `iptcCity` (string) - IPTC city
- `iptcState` (string) - IPTC state or province
- `iptcCountry` (string) - IPTC country
responses:
"200":
description: Image with updated metadata
@@ -4420,7 +4420,7 @@ paths:
schema:
$ref: "#/components/schemas/Error"
# ─── Batch ────────────────────────────────────────────────────────────────
# --- Batch ----------------------------------------------------------------
/api/v1/tools/{section}/{toolId}/batch:
post:
@@ -4488,7 +4488,7 @@ paths:
schema:
$ref: "#/components/schemas/UnauthorizedError"
# ─── Pipelines ────────────────────────────────────────────────────────────
# --- Pipelines ------------------------------------------------------------
/api/v1/pipeline/execute:
post:
@@ -4769,9 +4769,9 @@ paths:
type: string
description: |
JSON string with pipeline definition:
- `steps` (array, required, 1-20 items) Pipeline steps
- `steps[].toolId` (string, required) Tool ID for this step
- `steps[].settings` (object, optional) Tool-specific settings
- `steps` (array, required, 1-20 items) - Pipeline steps
- `steps[].toolId` (string, required) - Tool ID for this step
- `steps[].settings` (object, optional) - Tool-specific settings
clientJobId:
type: string
description: Optional client-provided job ID for progress tracking via SSE
@@ -4805,7 +4805,7 @@ paths:
schema:
$ref: "#/components/schemas/UnauthorizedError"
# ─── Files ────────────────────────────────────────────────────────────────
# --- Files ----------------------------------------------------------------
/api/v1/upload:
post:
@@ -5380,7 +5380,7 @@ paths:
type: string
example: en
# ─── Auth ─────────────────────────────────────────────────────────────────
# --- Auth -----------------------------------------------------------------
/api/auth/login:
post:
@@ -5831,7 +5831,7 @@ paths:
schema:
$ref: "#/components/schemas/ForbiddenError"
# ─── API Keys ─────────────────────────────────────────────────────────────
# --- API Keys -------------------------------------------------------------
/api/v1/api-keys:
post:
@@ -5981,7 +5981,7 @@ paths:
schema:
$ref: "#/components/schemas/UnauthorizedError"
# ─── Settings ─────────────────────────────────────────────────────────────
# --- Settings -------------------------------------------------------------
/api/v1/settings:
get:
@@ -6098,7 +6098,7 @@ paths:
schema:
$ref: "#/components/schemas/Error"
# ─── Teams ────────────────────────────────────────────────────────────────
# --- Teams ----------------------------------------------------------------
/api/v1/teams:
get:
@@ -6298,7 +6298,7 @@ paths:
schema:
$ref: "#/components/schemas/Error"
# ─── System (additional) ──────────────────────────────────────────────────
# --- System (additional) --------------------------------------------------
/api/v1/config/auth:
get:
@@ -6376,7 +6376,7 @@ paths:
type: string
description: Error details for failed files (batch mode)
# ─── Admin ───────────────────────────────────────────────────────────────
# --- Admin ---------------------------------------------------------------
/api/v1/admin/health:
get:
@@ -6408,7 +6408,7 @@ paths:
schema:
$ref: "#/components/schemas/ForbiddenError"
# ─── Analytics ───────────────────────────────────────────────────────────
# --- Analytics -----------------------------------------------------------
/api/v1/config/analytics:
get:
@@ -6440,7 +6440,7 @@ paths:
instanceId:
type: string
# ─── Features ────────────────────────────────────────────────────────────
# --- Features ------------------------------------------------------------
/api/v1/features:
get:
@@ -6642,7 +6642,7 @@ paths:
schema:
$ref: "#/components/schemas/ForbiddenError"
# ─── Audit Log ───────────────────────────────────────────────────────────
# --- Audit Log -----------------------------------------------------------
/api/v1/audit-log:
get:
@@ -6747,7 +6747,7 @@ paths:
schema:
$ref: "#/components/schemas/ForbiddenError"
# ─── Roles ───────────────────────────────────────────────────────────────
# --- Roles ---------------------------------------------------------------
/api/v1/roles:
get:
@@ -10068,7 +10068,7 @@ paths:
type: string
description: |
JSON string with options:
- `format` (string, required) One of: docx, odt, rtf, txt
- `format` (string, required) - One of: docx, odt, rtf, txt
clientJobId:
type: string
description: Client-provided job ID for SSE progress tracking
@@ -10122,7 +10122,7 @@ paths:
type: string
description: |
JSON string with options:
- `format` (string, required) One of: pptx, odp
- `format` (string, required) - One of: pptx, odp
clientJobId:
type: string
description: Client-provided job ID for SSE progress tracking
@@ -10176,7 +10176,7 @@ paths:
type: string
description: |
JSON string with options:
- `format` (string, required) One of: xlsx, ods, csv
- `format` (string, required) - One of: xlsx, ods, csv
clientJobId:
type: string
description: Client-provided job ID for SSE progress tracking
@@ -10555,7 +10555,7 @@ paths:
type: string
description: |
JSON string with options:
- `format` (string, required) One of: pdf, docx, html, md
- `format` (string, required) - One of: pdf, docx, html, md
clientJobId:
type: string
description: Client-provided job ID for SSE progress tracking
@@ -10698,8 +10698,8 @@ paths:
type: string
description: |
JSON string with options:
- `terms` (string[], required) Text strings to redact (1-50 terms, each up to 200 characters)
- `caseSensitive` (boolean, default false) Whether matching is case-sensitive
- `terms` (string[], required) - Text strings to redact (1-50 terms, each up to 200 characters)
- `caseSensitive` (boolean, default false) - Whether matching is case-sensitive
responses:
"200":
description: Processed PDF
@@ -10845,10 +10845,10 @@ paths:
type: string
description: |
JSON string with options:
- `title` (string, optional, max 500) Document title
- `author` (string, optional, max 500) Document author
- `subject` (string, optional, max 500) Document subject
- `keywords` (string, optional, max 500) Document keywords
- `title` (string, optional, max 500) - Document title
- `author` (string, optional, max 500) - Document author
- `subject` (string, optional, max 500) - Document subject
- `keywords` (string, optional, max 500) - Document keywords
responses:
"200":
description: Processed PDF with updated metadata
@@ -10902,9 +10902,9 @@ paths:
type: string
description: |
JSON string with options:
- `quality` (string, default "balanced") One of: fast, balanced, best
- `language` (string, default "auto") One of: auto, en, de, fr, es, zh, ja, ko
- `pages` (string, default "all") Page selection, e.g. "all", "1-3", "1,3,5"
- `quality` (string, default "balanced") - One of: fast, balanced, best
- `language` (string, default "auto") - One of: auto, en, de, fr, es, zh, ja, ko
- `pages` (string, default "all") - Page selection, e.g. "all", "1-3", "1,3,5"
clientJobId:
type: string
description: Client-provided job ID for SSE progress tracking
+15
View File
@@ -23,6 +23,21 @@ describe("API docs", () => {
expect(res.body).toContain("SnapOtter API");
});
it("serves an ASCII-only spec (strict YAML parsers reject high-byte chars)", async () => {
// Schemathesis (and other strict YAML parsers) mis-decode multi-byte UTF-8
// sequences as C1 control characters and refuse to load the schema. Keep the
// spec ASCII-only: use '-' instead of em dashes, plain ASCII section dividers.
const res = await testApp.app.inject({ method: "GET", url: "/api/v1/openapi.yaml" });
const offending = [...res.body].find((ch) => ch.charCodeAt(0) > 0x7f);
const hint = offending
? `OpenAPI spec has non-ASCII char U+${offending
.charCodeAt(0)
.toString(16)
.padStart(4, "0")} (${JSON.stringify(offending)}); replace it with ASCII.`
: "ok";
expect(hint).toBe("ok");
});
it("serves the Scalar docs page without auth", async () => {
// Scalar redirects /api/docs -> /api/docs/ (trailing slash)
const redirect = await testApp.app.inject({