docs: sync api documentation

This commit is contained in:
SnapOtter
2026-07-06 08:09:22 +08:00
parent 48494afc1e
commit b69c7362e1
37 changed files with 1302 additions and 107 deletions
+2 -2
View File
@@ -51,11 +51,11 @@ For the production Compose stack, NVIDIA GPU acceleration, and configuration, se
- **Local AI:** Remove backgrounds, upscale images, restore and colorize old photos, erase objects, blur faces, enhance faces, extract text (OCR from images and PDFs), transcribe audio, auto-generate video subtitles, expand canvas, and fix transparency. All on your hardware, no internet required - **Local AI:** Remove backgrounds, upscale images, restore and colorize old photos, erase objects, blur faces, enhance faces, extract text (OCR from images and PDFs), transcribe audio, auto-generate video subtitles, expand canvas, and fix transparency. All on your hardware, no internet required
- **OIDC / SSO:** Login with Google, GitHub, Okta, or any OpenID Connect provider - **OIDC / SSO:** Login with Google, GitHub, Okta, or any OpenID Connect provider
- **21 languages:** English, Arabic, Chinese (Simplified & Traditional), Dutch, French, German, Hindi, Indonesian, Italian, Japanese, Korean, Polish, Portuguese, Russian, Spanish, Swedish, Thai, Turkish, Ukrainian, Vietnamese. RTL support for Arabic - **21 languages:** English, Arabic, Chinese (Simplified & Traditional), Dutch, French, German, Hindi, Indonesian, Italian, Japanese, Korean, Polish, Portuguese, Russian, Spanish, Swedish, Thai, Turkish, Ukrainian, Vietnamese. RTL support for Arabic
- **Pipelines:** Chain tools into reusable workflows with unlimited steps. Import/export as JSON. Batch process unlimited files at once - **Pipelines:** Chain tools into reusable workflows with 20 steps by default. Import/export as JSON. Batch process up to 100 files by default
- **REST API:** Every tool available via API with API key auth. Interactive docs at `/api/docs` - **REST API:** Every tool available via API with API key auth. Interactive docs at `/api/docs`
- **Self-hosted:** one `docker run` for a single-container quick start (embedded Postgres 17 + Redis 8), or the same Postgres 17 + Redis 8 as a Compose stack for production. No external SaaS dependencies - **Self-hosted:** one `docker run` for a single-container quick start (embedded Postgres 17 + Redis 8), or the same Postgres 17 + Redis 8 as a Compose stack for production. No external SaaS dependencies
- **Multi-arch:** Runs on AMD64 and ARM64 (Intel, Apple Silicon, Raspberry Pi) - **Multi-arch:** Runs on AMD64 and ARM64 (Intel, Apple Silicon, Raspberry Pi)
- **Privacy first:** Your files never leave your network. Basic analytics help us catch bugs and improve tools -- disable anytime by rebuilding with `SNAPOTTER_ANALYTICS=off` ([Here's how to do it](https://docs.snapotter.com/guide/deployment.html#analytics)) - **Privacy first:** Your files never leave your network. Basic analytics help us catch bugs and improve tools -- disable at build time with `SNAPOTTER_ANALYTICS=off` or at runtime with the in-app admin opt-out ([Here's how to do it](https://docs.snapotter.com/guide/deployment.html#analytics))
## Deployment ## Deployment
+1013 -15
View File
File diff suppressed because it is too large Load Diff
+44 -2
View File
@@ -37,12 +37,19 @@ function isPublic(op: PathOperation): boolean {
function generateLlmsTxt(spec: OpenAPISpec): string { function generateLlmsTxt(spec: OpenAPISpec): string {
const lines: string[] = []; const lines: string[] = [];
const customModes: Record<string, string> = {
ocr: "sync-json",
"content-aware-resize": "sync",
"passport-photo": "two-phase",
};
lines.push(`# ${spec.info.title}`); lines.push(`# ${spec.info.title}`);
lines.push(""); lines.push("");
lines.push( lines.push(
"> Self-hosted file processing API with 200+ tools across image, video, audio, document, and data. Convert, compress, edit, transcribe, OCR, and more.", "> Self-hosted file processing API with 241 catalog tool routes across image, video, audio, document, and file workflows. Convert, compress, edit, transcribe, OCR, and more.",
); );
lines.push(""); lines.push("");
lines.push("Base URL: `/api/v1`");
lines.push("");
lines.push("## Docs"); lines.push("## Docs");
lines.push("- [Interactive API Reference](/api/docs): Full interactive API documentation"); lines.push("- [Interactive API Reference](/api/docs): Full interactive API documentation");
lines.push("- [OpenAPI Spec](/api/v1/openapi.yaml): OpenAPI 3.1 specification (YAML)"); lines.push("- [OpenAPI Spec](/api/v1/openapi.yaml): OpenAPI 3.1 specification (YAML)");
@@ -65,7 +72,7 @@ function generateLlmsTxt(spec: OpenAPISpec): string {
const tools = TOOLS.filter((tool) => toolSection(tool) === section.id); const tools = TOOLS.filter((tool) => toolSection(tool) === section.id);
lines.push(`- ${section.name} (${tools.length} tools)`); lines.push(`- ${section.name} (${tools.length} tools)`);
for (const tool of tools) { for (const tool of tools) {
const mode = tool.executionHint === "long" ? "async" : "sync"; const mode = customModes[tool.id] ?? (tool.executionHint === "long" ? "async" : "sync");
lines.push(` - ${tool.name} - ${tool.description} (${tool.id}, ${mode})`); lines.push(` - ${tool.name} - ${tool.description} (${tool.id}, ${mode})`);
} }
} }
@@ -74,6 +81,41 @@ function generateLlmsTxt(spec: OpenAPISpec): string {
lines.push("## Authentication"); lines.push("## Authentication");
lines.push("- Session token via `POST /api/auth/login` -> `Authorization: Bearer <token>`"); lines.push("- Session token via `POST /api/auth/login` -> `Authorization: Bearer <token>`");
lines.push("- API key (prefixed `si_`) -> `Authorization: Bearer si_...`"); lines.push("- API key (prefixed `si_`) -> `Authorization: Bearer si_...`");
lines.push(
"- MFA login challenges return `requiresMfa` and must be completed through `/api/auth/mfa/complete`.",
);
lines.push("");
lines.push("## Processing Contract");
lines.push(
"- Tool requests use `multipart/form-data` with `file`, optional JSON `settings`, optional `clientJobId`, and optional `fileId`.",
);
lines.push(
"- Fast tools usually return `200` with `jobId`, `downloadUrl`, `originalSize`, and `processedSize`.",
);
lines.push(
"- Any queued tool can return `202` with `jobId` and `async: true` when it is long-running or exceeds the synchronous wait window.",
);
lines.push(
'- Progress streams from `GET /api/v1/jobs/:jobId/progress` as SSE frames with `type: "single"` or `type: "batch"`.',
);
lines.push(
"- Missing AI bundles return `501` with code `FEATURE_NOT_INSTALLED`, feature id, feature name, and estimated size.",
);
lines.push("");
lines.push("## Surrounding APIs");
lines.push("- Auth: local login, OIDC, SAML, MFA, user administration, sessions.");
lines.push(
"- Files: upload, library versions, downloads, thumbnails, file previews, URL import.",
);
lines.push(
"- Workflows: batch routes, pipeline execute/save/list/delete, job cancel and progress.",
);
lines.push(
"- Admin: health, readiness, metrics, log level, support bundle, usage, backup status, feature bundles.",
);
lines.push(
"- Enterprise: audit export, config import/export, IP allowlist, legal hold, SCIM, SIEM, webhooks, GDPR lifecycle, upgrade checks.",
);
return lines.join("\n"); return lines.join("\n");
} }
+1 -1
View File
@@ -238,7 +238,7 @@ export async function registerScimRoutes(app: FastifyInstance): Promise<void> {
async (_request: FastifyRequest, reply: FastifyReply) => { async (_request: FastifyRequest, reply: FastifyReply) => {
return reply.send({ return reply.send({
schemas: ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"], schemas: ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
documentationUri: "https://docs.snapotter.com/enterprise/scim", documentationUri: "https://docs.snapotter.com/guide/scim",
patch: { supported: true }, patch: { supported: true },
bulk: { supported: false, maxOperations: 0, maxPayloadSize: 0 }, bulk: { supported: false, maxOperations: 0, maxPayloadSize: 0 },
filter: { supported: true, maxResults: 200 }, filter: { supported: true, maxResults: 200 },
+3 -3
View File
@@ -52,9 +52,9 @@ Each AI tool requires a model bundle to be installed before use. Bundles are ins
| `background-removal` | 4-5 GB | remove-background, passport-photo, transparency-fixer, background-replace, blur-background | | `background-removal` | 4-5 GB | remove-background, passport-photo, transparency-fixer, background-replace, blur-background |
| `face-detection` | 200-300 MB | blur-faces, red-eye-removal, smart-crop | | `face-detection` | 200-300 MB | blur-faces, red-eye-removal, smart-crop |
| `object-eraser-colorize` | 1-2 GB | erase-object, colorize, ai-canvas-expand | | `object-eraser-colorize` | 1-2 GB | erase-object, colorize, ai-canvas-expand |
| `upscale-enhance` | 4-5 GB | upscale, enhance-faces, noise-removal | | `upscale-enhance` | 5-6 GB | upscale, enhance-faces, noise-removal |
| `photo-restoration` | 800 MB - 1 GB | restore-photo | | `photo-restoration` | 4-5 GB | restore-photo |
| `ocr` | 3-4 GB | ocr, ocr-pdf | | `ocr` | 5-6 GB | ocr, ocr-pdf |
| `transcription` | ~600 MB | transcribe-audio, auto-subtitles | | `transcription` | ~600 MB | transcribe-audio, auto-subtitles |
--- ---
+124 -18
View File
@@ -62,6 +62,18 @@ Keys are prefixed `si_` and stored as scrypt hashes - the raw key is shown once
| `POST` | `/api/auth/users/:id/reset-password` | Admin | Reset user's password | | `POST` | `/api/auth/users/:id/reset-password` | Admin | Reset user's password |
| `DELETE` | `/api/auth/users/:id` | Admin | Delete a user | | `DELETE` | `/api/auth/users/:id` | Admin | Delete a user |
| `GET` | `/api/v1/config/auth` | Public | Check if authentication is enabled (`{ authEnabled: bool }`) | | `GET` | `/api/v1/config/auth` | Public | Check if authentication is enabled (`{ authEnabled: bool }`) |
| `POST` | `/api/auth/mfa/enroll` | Auth | Start TOTP MFA enrollment. Requires the enterprise `mfa` feature |
| `POST` | `/api/auth/mfa/verify` | Auth | Confirm MFA enrollment with a TOTP code |
| `POST` | `/api/auth/mfa/complete` | Public | Complete a pending MFA login challenge |
| `POST` | `/api/auth/mfa/disable` | Auth | Disable MFA for the current user |
| `POST` | `/api/auth/users/:id/mfa/reset` | Admin (`users:manage`) | Reset MFA for a user |
| `GET` | `/api/auth/oidc/login` | Public | Start OIDC login when OIDC is enabled |
| `GET` | `/api/auth/oidc/callback` | Public | OIDC authorization callback |
| `GET` | `/api/auth/saml/metadata` | Public | SAML SP metadata XML when SAML is enabled |
| `GET` | `/api/auth/saml/login` | Public | Start SAML login |
| `POST` | `/api/auth/saml/callback` | Public | SAML assertion consumer service |
When MFA is enabled for a user, `POST /api/auth/login` returns `{"requiresMfa":true,"mfaToken":"...","mfaRequired":true|false}` instead of a session token. Send that `mfaToken` plus a TOTP or recovery code to `/api/auth/mfa/complete`.
### Permissions ### Permissions
@@ -79,6 +91,7 @@ Keys are prefixed `si_` and stored as scrypt hashes - the raw key is shown once
| Method | Path | Access | Description | | Method | Path | Access | Description |
|--------|------|--------|-------------| |--------|------|--------|-------------|
| `GET` | `/api/v1/health` | Public | Basic health check. Returns `{"status":"healthy","version":"..."}` with 200, or `{"status":"unhealthy"}` with 503 if the database is unreachable. | | `GET` | `/api/v1/health` | Public | Basic health check. Returns `{"status":"healthy","version":"..."}` with 200, or `{"status":"unhealthy"}` with 503 if the database is unreachable. |
| `GET` | `/api/v1/readyz` | Public | Readiness probe. Checks PostgreSQL, Redis, disk space, and S3 when configured. Returns 503 when the instance should not receive traffic. |
| `GET` | `/api/v1/admin/health` | Admin (`system:health`) | Detailed diagnostics including uptime, storage mode, database status, queue state, and GPU availability. | | `GET` | `/api/v1/admin/health` | Admin (`system:health`) | Detailed diagnostics including uptime, storage mode, database status, queue state, and GPU availability. |
## Using Tools ## Using Tools
@@ -104,9 +117,11 @@ curl -X POST http://localhost:1349/api/v1/tools/<section>/<toolId>/batch \
- Upload is `multipart/form-data`. - Upload is `multipart/form-data`.
- `settings` is a JSON string with tool-specific options. - `settings` is a JSON string with tool-specific options.
- **Fast tools** (200) return JSON: `{"jobId":"...","downloadUrl":"/api/v1/download/<jobId>/<filename>","originalSize":1234,"processedSize":567}`. Fetch the processed file from `downloadUrl`. - `clientJobId` is an optional form field for caller-supplied progress correlation.
- **Long-running tools** (202) return JSON: `{"jobId":"...","async":true}`. Connect to SSE for progress, then download when complete (see [Progress Tracking](#progress-tracking)). - `fileId` is an optional form field referencing an existing file library item. When present, the processed output is saved as a new version and the response includes `savedFileId`.
- **Batch** returns a ZIP archive streamed directly (with `X-Job-Id` header). - **Fast tools** usually return 200 JSON: `{"jobId":"...","downloadUrl":"/api/v1/download/<jobId>/<filename>","originalSize":1234,"processedSize":567}`. Fetch the processed file from `downloadUrl`.
- **Any queued tool** can return 202 JSON if it is long-running or exceeds the synchronous wait window: `{"jobId":"...","async":true}`. Connect to SSE for progress, then download when complete (see [Progress Tracking](#progress-tracking)).
- **Batch** routes return a ZIP archive streamed directly (with `X-Job-Id` header) for tools registered in the generic batch registry.
## Tools Reference ## Tools Reference
@@ -222,14 +237,13 @@ All AI tools run on your hardware: CPU by default, or NVIDIA CUDA when a support
| `svg-to-raster` | SVG to Raster | `format` (png/jpeg/webp/avif/tiff/gif/heif), `width`, `height`, `scale`, `dpi`, `background` | | `svg-to-raster` | SVG to Raster | `format` (png/jpeg/webp/avif/tiff/gif/heif), `width`, `height`, `scale`, `dpi`, `background` |
| `vectorize` | Image to SVG | `colorMode` (bw/color), `threshold`, `colorPrecision`, `filterSpeckle`, `pathMode` (none/polygon/spline) | | `vectorize` | Image to SVG | `colorMode` (bw/color), `threshold`, `colorPrecision`, `filterSpeckle`, `pathMode` (none/polygon/spline) |
| `gif-tools` | GIF Tools | `action` (resize/optimize/reverse/speed/extract-frames/rotate/add-text), action-specific params | | `gif-tools` | GIF Tools | `action` (resize/optimize/reverse/speed/extract-frames/rotate/add-text), action-specific params |
| `pdf-to-image` | PDF to Image | `pages` (all/range), `format`, `dpi`, `quality` |
| `gif-webp` | GIF/WebP Converter | `quality` (1-100), `lossless` (bool), `resizePercent` (10-100) | | `gif-webp` | GIF/WebP Converter | `quality` (1-100), `lossless` (bool), `resizePercent` (10-100) |
### Video Tools ### Video Tools
| Tool ID | Name | Key settings | | Tool ID | Name | Key settings |
|---------|------|-------------| |---------|------|-------------|
| `convert-video` | Convert Video | `format` (mp4/mov/webm), `quality` (high/balanced/small) | | `convert-video` | Convert Video | `format` (mp4/mov/webm/avi/mkv), `quality` (high/balanced/small) |
| `compress-video` | Compress Video | `quality` (light/balanced/strong), `resolution` (original/1080p/720p/480p) | | `compress-video` | Compress Video | `quality` (light/balanced/strong), `resolution` (original/1080p/720p/480p) |
| `trim-video` | Trim Video | `startS`, `endS`, `precise` (bool, frame-accurate cut) | | `trim-video` | Trim Video | `startS`, `endS`, `precise` (bool, frame-accurate cut) |
| `mute-video` | Mute Video | - | | `mute-video` | Mute Video | - |
@@ -246,7 +260,7 @@ All AI tools run on your hardware: CPU by default, or NVIDIA CUDA when a support
| `blur-pad` | Blur Pad | `target` (16:9/9:16/1:1/4:3/3:4), `blur` (2-50) | | `blur-pad` | Blur Pad | `target` (16:9/9:16/1:1/4:3/3:4), `blur` (2-50) |
| `watermark-video` | Watermark Video | `text`, `position`, `fontSize`, `opacity`, `color` | | `watermark-video` | Watermark Video | `text`, `position`, `fontSize`, `opacity`, `color` |
| `stabilize-video` | Stabilize Video | `smoothing` (5-60, in frames) | | `stabilize-video` | Stabilize Video | `smoothing` (5-60, in frames) |
| `gif-to-video` | GIF to Video | `format` (mp4/webm) | | `gif-to-video` | GIF to Video | `format` (mp4/webm/mov) |
| `video-to-webp` | Video to WebP | `fps`, `width`, `quality`, `loop` (bool) | | `video-to-webp` | Video to WebP | `fps`, `width`, `quality`, `loop` (bool) |
| `video-to-frames` | Video to Frames | `mode` (all/nth/timestamps), `n`, `timestamps`, `format` (png/jpg) | | `video-to-frames` | Video to Frames | `mode` (all/nth/timestamps), `n`, `timestamps`, `format` (png/jpg) |
| `merge-videos` | Merge Videos | - (multi-file, normalized to first video's resolution) | | `merge-videos` | Merge Videos | - (multi-file, normalized to first video's resolution) |
@@ -257,7 +271,7 @@ All AI tools run on your hardware: CPU by default, or NVIDIA CUDA when a support
| `images-to-video` | Images to Video | `secondsPerImage` (0.5-10), `resolution` (1080p/720p/square), `fps` - multi-file | | `images-to-video` | Images to Video | `secondsPerImage` (0.5-10), `resolution` (1080p/720p/square), `fps` - multi-file |
| `video-metadata` | Clean Video Metadata | - | | `video-metadata` | Clean Video Metadata | - |
| `auto-subtitles` | Auto Subtitles (AI) | `language` (auto/en/de/fr/es/zh/ja/ko/id/th/vi), `format` (srt/vtt) | | `auto-subtitles` | Auto Subtitles (AI) | `language` (auto/en/de/fr/es/zh/ja/ko/id/th/vi), `format` (srt/vtt) |
| `extract-audio` | Extract Audio | `format` (mp3/wav/m4a) | | `extract-audio` | Extract Audio | `format` (mp3/wav/m4a/ogg) |
### Audio Tools ### Audio Tools
@@ -322,6 +336,10 @@ All AI tools run on your hardware: CPU by default, or NVIDIA CUDA when a support
| `epub-convert` | Convert EPUB | `format` (pdf/docx/html/md) | | `epub-convert` | Convert EPUB | `format` (pdf/docx/html/md) |
| `to-epub` | Convert to EPUB | - (accepts .docx, .md, .html, .txt) | | `to-epub` | Convert to EPUB | - (accepts .docx, .md, .html, .txt) |
| `ocr-pdf` | PDF OCR (AI) | `quality` (fast/balanced/best), `language` (auto/en/de/fr/es/zh/ja/ko), `pages` | | `ocr-pdf` | PDF OCR (AI) | `quality` (fast/balanced/best), `language` (auto/en/de/fr/es/zh/ja/ko), `pages` |
| `pdf-to-image` | PDF to Image | `pages` (all/range), `format`, `dpi`, `quality` |
| `pdf-to-jpg` | PDF to JPG | `pages`, `dpi`, `quality`, `colorMode` |
| `pdf-to-png` | PDF to PNG | `pages`, `dpi`, `quality`, `colorMode` |
| `pdf-to-tiff` | PDF to TIFF | `pages`, `dpi`, `quality`, `colorMode` |
### File Tools ### File Tools
@@ -335,6 +353,7 @@ All AI tools run on your hardware: CPU by default, or NVIDIA CUDA when a support
| `merge-csvs` | Merge CSVs | - (multi-file, matching columns) | | `merge-csvs` | Merge CSVs | - (multi-file, matching columns) |
| `yaml-json` | YAML / JSON | - (bidirectional) | | `yaml-json` | YAML / JSON | - (bidirectional) |
| `xml-to-csv` | XML to CSV | - (auto-finds repeating elements) | | `xml-to-csv` | XML to CSV | - (auto-finds repeating elements) |
| `excel-to-csv` | Excel to CSV | dedicated conversion preset backed by `convert-spreadsheet` |
| `create-zip` | Create ZIP | - (multi-file, 2-50 files) | | `create-zip` | Create ZIP | - (multi-file, 2-50 files) |
| `extract-zip` | Extract ZIP | - (bomb-protected) | | `extract-zip` | Extract ZIP | - (bomb-protected) |
@@ -391,13 +410,19 @@ Some tools expose additional endpoints beyond the standard `POST /api/v1/tools/<
| `POST` | `/api/v1/tools/image/gif-tools/info` | Get GIF metadata (frame count, dimensions, duration) | | `POST` | `/api/v1/tools/image/gif-tools/info` | Get GIF metadata (frame count, dimensions, duration) |
| `POST` | `/api/v1/tools/pdf/pdf-to-image/info` | Get PDF metadata (page count, dimensions) | | `POST` | `/api/v1/tools/pdf/pdf-to-image/info` | Get PDF metadata (page count, dimensions) |
| `POST` | `/api/v1/tools/pdf/pdf-to-image/preview` | Generate a preview of a specific PDF page | | `POST` | `/api/v1/tools/pdf/pdf-to-image/preview` | Generate a preview of a specific PDF page |
| `POST` | `/api/v1/tools/pdf/pdf-to-jpg/info` | Get PDF metadata for the dedicated JPG preset |
| `POST` | `/api/v1/tools/pdf/pdf-to-jpg/preview` | Generate a JPG preset PDF page preview |
| `POST` | `/api/v1/tools/pdf/pdf-to-png/info` | Get PDF metadata for the dedicated PNG preset |
| `POST` | `/api/v1/tools/pdf/pdf-to-png/preview` | Generate a PNG preset PDF page preview |
| `POST` | `/api/v1/tools/pdf/pdf-to-tiff/info` | Get PDF metadata for the dedicated TIFF preset |
| `POST` | `/api/v1/tools/pdf/pdf-to-tiff/preview` | Generate a TIFF preset PDF page preview |
| `POST` | `/api/v1/tools/image/svg-to-raster/batch` | Batch convert multiple SVGs to raster | | `POST` | `/api/v1/tools/image/svg-to-raster/batch` | Batch convert multiple SVGs to raster |
| `POST` | `/api/v1/tools/image/image-enhancement/analyze` | Analyze image quality and return enhancement recommendations | | `POST` | `/api/v1/tools/image/image-enhancement/analyze` | Analyze image quality and return enhancement recommendations |
| `POST` | `/api/v1/tools/image/optimize-for-web/preview` | Lightweight preview for live parameter tuning. Returns optimized image with size headers. | | `POST` | `/api/v1/tools/image/optimize-for-web/preview` | Lightweight preview for live parameter tuning. Returns optimized image with size headers. |
## Batch Processing ## Batch Processing
Apply any tool to multiple files at once. Returns a ZIP archive. Apply a generic batch-enabled tool to multiple files at once. Returns a ZIP archive. Custom multi-file or multi-step routes, such as PDF signing, PDF OCR, and PDF-to-image preset routes, use their own endpoint contract instead of the generic `/batch` route.
```bash ```bash
curl -X POST http://localhost:1349/api/v1/tools/image/compress/batch \ curl -X POST http://localhost:1349/api/v1/tools/image/compress/batch \
@@ -433,7 +458,7 @@ curl -X POST http://localhost:1349/api/v1/pipeline/batch \
-F 'pipeline={"steps":[{"toolId":"resize","settings":{"width":800}}]}' -F 'pipeline={"steps":[{"toolId":"resize","settings":{"width":800}}]}'
``` ```
Each step's output is the next step's input. Unlimited steps per pipeline by default (configurable via `MAX_PIPELINE_STEPS`). Each step's output is the next step's input. Pipelines allow 20 steps by default, configurable via `MAX_PIPELINE_STEPS`. Set `MAX_PIPELINE_STEPS=0` to remove the limit.
### Save and manage pipelines ### Save and manage pipelines
@@ -446,20 +471,22 @@ Each step's output is the next step's input. Unlimited steps per pipeline by def
## Progress Tracking ## Progress Tracking
Long-running jobs (AI tools, batch, pipelines) emit real-time progress via Server-Sent Events: Long-running jobs, queued tools, batch jobs, and pipelines emit real-time progress via Server-Sent Events. The progress stream is public and keyed by job ID, so clients do not need to send an Authorization header to read it.
```bash ```bash
# Connect to the SSE stream (jobId is in the JSON response body from the tool endpoint) # Connect to the SSE stream (jobId is in the JSON response body from the tool endpoint)
curl -N http://localhost:1349/api/v1/jobs/<jobId>/progress \ curl -N http://localhost:1349/api/v1/jobs/<jobId>/progress
-H "Authorization: Bearer <token>"
``` ```
Event format: Event format:
``` ```
data: {"progress":42,"status":"processing","message":"Upscaling frame 2/5"} data: {"jobId":"...","type":"single","phase":"processing","stage":"Upscaling","percent":42}
data: {"progress":100,"status":"completed"} data: {"jobId":"...","type":"single","phase":"complete","percent":100,"result":{"downloadUrl":"/api/v1/download/..."}}
data: {"jobId":"...","type":"batch","status":"processing","completedFiles":2,"totalFiles":5,"failedFiles":0,"errors":[]}
``` ```
You can request cancellation for a queued or running job with `POST /api/v1/jobs/:jobId/cancel`. The response is `{"canceled":true|false}`.
## File Library ## File Library
Persistent file storage with version history. Persistent file storage with version history.
@@ -476,9 +503,11 @@ Persistent file storage with version history.
| `DELETE` | `/api/v1/files` | Bulk delete files and their version chains (body: `{ ids: [...] }`) | | `DELETE` | `/api/v1/files` | Bulk delete files and their version chains (body: `{ ids: [...] }`) |
| `POST` | `/api/v1/fetch-urls` | Fetch remote URLs into the workspace for URL-based imports | | `POST` | `/api/v1/fetch-urls` | Fetch remote URLs into the workspace for URL-based imports |
| `POST` | `/api/v1/preview` | Generate a browser-compatible WebP preview (for HEIC/HEIF/RAW formats) | | `POST` | `/api/v1/preview` | Generate a browser-compatible WebP preview (for HEIC/HEIF/RAW formats) |
| `GET` | `/api/v1/files/:id/preview` | Stream a cached or generated browser-compatible preview for a saved PDF, office document, video, or audio file |
| `POST` | `/api/v1/preview/generate` | Generate an on-demand MP4 or MP3 preview for an uploaded media file without saving it first |
| `GET` | `/api/v1/download/:jobId/:filename` | Download a processed file from a workspace | | `GET` | `/api/v1/download/:jobId/:filename` | Download a processed file from a workspace |
To auto-save a tool result to the library, include `fileId` in the settings payload referencing an existing library file. The processed result will be saved as a new version. To auto-save a tool result to the library, include `fileId` as a multipart form field referencing an existing library file. The processed result will be saved as a new version.
## API Key Management ## API Key Management
@@ -509,6 +538,15 @@ Runtime key-value configuration (read by any authenticated user, write by admin
Known keys: `disabledTools` (JSON array of tool IDs), `enableExperimentalTools` (bool string), `loginAttemptLimit` (number). Known keys: `disabledTools` (JSON array of tool IDs), `enableExperimentalTools` (bool string), `loginAttemptLimit` (number).
## Preferences
Per-user preferences are separate from instance settings. Any authenticated user can read and update their own preference map.
| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/api/v1/preferences` | Get the current user's preferences as `{ "preferences": { ... } }` |
| `PUT` | `/api/v1/preferences` | Upsert one or more preference keys for the current user |
## Roles ## Roles
Custom role management with granular permissions. Custom role management with granular permissions.
@@ -516,9 +554,9 @@ Custom role management with granular permissions.
| Method | Path | Access | Description | | Method | Path | Access | Description |
|--------|------|--------|-------------| |--------|------|--------|-------------|
| `GET` | `/api/v1/roles` | Admin (`audit:read`) | List all roles with user counts | | `GET` | `/api/v1/roles` | Admin (`audit:read`) | List all roles with user counts |
| `POST` | `/api/v1/roles` | Admin (`users:manage`) | Create a custom role (`name`, `description`, `permissions`) | | `POST` | `/api/v1/roles` | Admin (`security:manage`) | Create a custom role (`name`, `description`, `permissions`) |
| `PUT` | `/api/v1/roles/:id` | Admin (`users:manage`) | Update a custom role (cannot modify built-in roles) | | `PUT` | `/api/v1/roles/:id` | Admin (`security:manage`) | Update a custom role (cannot modify built-in roles) |
| `DELETE` | `/api/v1/roles/:id` | Admin (`users:manage`) | Delete a custom role (cannot delete built-in roles; affected users revert to `user` role) | | `DELETE` | `/api/v1/roles/:id` | Admin (`security:manage`) | Delete a custom role (cannot delete built-in roles; affected users revert to `user` role) |
Available permissions (17): `tools:use`, `files:own`, `files:all`, `apikeys:own`, `apikeys:all`, `pipelines:own`, `pipelines:all`, `settings:read`, `settings:write`, `users:manage`, `teams:manage`, `features:manage`, `system:health`, `audit:read`, `compliance:manage`, `webhooks:manage`, `security:manage`. Available permissions (17): `tools:use`, `files:own`, `files:all`, `apikeys:own`, `apikeys:all`, `pipelines:own`, `pipelines:all`, `settings:read`, `settings:write`, `users:manage`, `teams:manage`, `features:manage`, `system:health`, `audit:read`, `compliance:manage`, `webhooks:manage`, `security:manage`.
@@ -537,6 +575,7 @@ Query parameters:
| `page` | Page number (default: 1) | | `page` | Page number (default: 1) |
| `limit` | Entries per page (default: 50, max: 100) | | `limit` | Entries per page (default: 50, max: 100) |
| `action` | Filter by action type (e.g. `ROLE_CREATED`, `ROLE_DELETED`) | | `action` | Filter by action type (e.g. `ROLE_CREATED`, `ROLE_DELETED`) |
| `ip` | Filter by source IP address |
| `from` | Filter entries after this ISO 8601 date | | `from` | Filter entries after this ISO 8601 date |
| `to` | Filter entries before this ISO 8601 date | | `to` | Filter entries before this ISO 8601 date |
@@ -558,6 +597,71 @@ Manage AI feature bundles (install/uninstall AI model packages in the Docker env
| `POST` | `/api/v1/admin/features/:bundleId/install` | Admin (`features:manage`) | Install a feature bundle (async, returns `jobId` for progress tracking) | | `POST` | `/api/v1/admin/features/:bundleId/install` | Admin (`features:manage`) | Install a feature bundle (async, returns `jobId` for progress tracking) |
| `POST` | `/api/v1/admin/features/:bundleId/uninstall` | Admin (`features:manage`) | Uninstall a feature bundle and clean up model files | | `POST` | `/api/v1/admin/features/:bundleId/uninstall` | Admin (`features:manage`) | Uninstall a feature bundle and clean up model files |
| `GET` | `/api/v1/admin/features/disk-usage` | Admin (`features:manage`) | Get total disk usage of AI models | | `GET` | `/api/v1/admin/features/disk-usage` | Admin (`features:manage`) | Get total disk usage of AI models |
| `POST` | `/api/v1/admin/features/import` | Admin (`features:manage`) | Import an offline AI bundle archive |
## Admin Operations
Operational endpoints for observability, support, usage reporting, and backup status.
| Method | Path | Access | Description |
|--------|------|--------|-------------|
| `GET` | `/api/v1/admin/log-level` | Admin (`settings:write`) | Read the current runtime log level |
| `POST` | `/api/v1/admin/log-level` | Admin (`settings:write`) | Change the runtime log level (`fatal`, `error`, `warn`, `info`, `debug`, `trace`, or `silent`) |
| `GET` | `/api/v1/metrics` | Admin (`system:health`) | Prometheus metrics in text format |
| `GET` | `/api/v1/admin/support-bundle` | Admin (`system:health`) | Download a redacted diagnostic support bundle ZIP |
| `GET` | `/api/v1/admin/usage` | Admin (`audit:read`) | Usage dashboard data, with optional `days` query parameter |
| `GET` | `/api/v1/admin/backup-status` | Admin (`system:health`) | Read last backup metadata and freshness status |
| `POST` | `/api/v1/admin/backup-status` | Admin (`system:health`) | Record a completed backup (`type`, optional `sizeBytes`, optional `notes`) |
## Enterprise APIs
These routes are license-gated by their related enterprise feature. They still require the listed SnapOtter permission.
| Method | Path | Access | Description |
|--------|------|--------|-------------|
| `GET` | `/api/v1/enterprise/audit/export` | Admin (`audit:read`) | Export audit entries as JSON or CSV with filters |
| `GET` | `/api/v1/enterprise/config/export` | Admin (`system:health`) | Export redacted instance config, custom roles, and teams |
| `POST` | `/api/v1/enterprise/config/import` | Admin (`system:health`) | Import config, with optional dry run |
| `GET` | `/api/v1/enterprise/ip-allowlist` | Admin (`security:manage`) | Read configured CIDR allowlist |
| `PUT` | `/api/v1/enterprise/ip-allowlist` | Admin (`security:manage`) | Update CIDR allowlist with self-lockout prevention |
| `GET` | `/api/v1/enterprise/legal-hold` | Admin (`compliance:manage`) | List user and team legal holds |
| `PUT` | `/api/v1/enterprise/legal-hold` | Admin (`compliance:manage`) | Apply or release a legal hold on a user or team |
| `POST` | `/api/v1/enterprise/scim/token` | Admin (`users:manage`) | Generate a SCIM bearer token, returned once |
| `DELETE` | `/api/v1/enterprise/scim/token` | Admin (`users:manage`) | Revoke the current SCIM bearer token |
| `GET` | `/api/v1/enterprise/siem/config` | Admin (`webhooks:manage`) | Read SIEM forwarding config |
| `PUT` | `/api/v1/enterprise/siem/config` | Admin (`webhooks:manage`) | Update SIEM forwarding config |
| `GET` | `/api/v1/enterprise/webhooks` | Admin (`webhooks:manage`) | List webhook destinations |
| `POST` | `/api/v1/enterprise/webhooks` | Admin (`webhooks:manage`) | Create a webhook destination |
| `PUT` | `/api/v1/enterprise/webhooks/:index` | Admin (`webhooks:manage`) | Update a webhook destination |
| `DELETE` | `/api/v1/enterprise/webhooks/:index` | Admin (`webhooks:manage`) | Delete a webhook destination |
| `POST` | `/api/v1/enterprise/webhooks/:index/test` | Admin (`webhooks:manage`) | Send a test webhook payload |
| `POST` | `/api/v1/enterprise/users/:id/export` | Admin (`compliance:manage`) | Start a GDPR user export job |
| `GET` | `/api/v1/enterprise/users/:id/export/:jobId` | Admin (`compliance:manage`) | Read GDPR export status and download URL |
| `DELETE` | `/api/v1/enterprise/users/:id/purge` | Admin (`compliance:manage`) | Permanently purge a user's data after confirmation |
| `DELETE` | `/api/v1/enterprise/teams/:id/purge` | Admin (`compliance:manage`) | Permanently purge a team's data after confirmation |
| `GET` | `/api/v1/admin/version` | Admin (`system:health`) | Read app, build, Node, and schema version metadata |
| `GET` | `/api/v1/admin/migrations/pending` | Admin (`system:health`) | Compare packaged migrations with applied migrations |
| `GET` | `/api/v1/admin/upgrade-check` | Admin (`system:health`) | Run upgrade readiness checks |
### SCIM 2.0
SCIM discovery endpoints are public. User and group endpoints require the SCIM bearer token generated above.
| Method | Path | Access | Description |
|--------|------|--------|-------------|
| `GET` | `/api/v1/scim/v2/ServiceProviderConfig` | Public | SCIM server capabilities |
| `GET` | `/api/v1/scim/v2/Schemas` | Public | SCIM schema discovery |
| `GET` | `/api/v1/scim/v2/ResourceTypes` | Public | SCIM resource type discovery |
| `GET` | `/api/v1/scim/v2/Users` | SCIM token | List users, with optional SCIM filter |
| `POST` | `/api/v1/scim/v2/Users` | SCIM token | Create a user |
| `GET` | `/api/v1/scim/v2/Users/:id` | SCIM token | Get a user |
| `PUT` | `/api/v1/scim/v2/Users/:id` | SCIM token | Replace a user |
| `DELETE` | `/api/v1/scim/v2/Users/:id` | SCIM token | Soft deactivate a user |
| `GET` | `/api/v1/scim/v2/Groups` | SCIM token | List teams as SCIM groups |
| `POST` | `/api/v1/scim/v2/Groups` | SCIM token | Create a team |
| `GET` | `/api/v1/scim/v2/Groups/:id` | SCIM token | Get a team |
| `PUT` | `/api/v1/scim/v2/Groups/:id` | SCIM token | Replace a team and group membership |
| `DELETE` | `/api/v1/scim/v2/Groups/:id` | SCIM token | Delete a team |
## Meme Templates ## Meme Templates
@@ -588,5 +692,7 @@ All errors return JSON:
| 403 | Insufficient permissions | | 403 | Insufficient permissions |
| 404 | Resource not found | | 404 | Resource not found |
| 413 | File too large (see `MAX_UPLOAD_SIZE_MB`) | | 413 | File too large (see `MAX_UPLOAD_SIZE_MB`) |
| 422 | Processing failed after validation |
| 429 | Rate limited (see `RATE_LIMIT_PER_MIN`) | | 429 | Rate limited (see `RATE_LIMIT_PER_MIN`) |
| 501 | Required AI feature bundle is not installed (`FEATURE_NOT_INSTALLED`) |
| 500 | Internal server error | | 500 | Internal server error |
+3 -3
View File
@@ -49,7 +49,7 @@ Shared TypeScript types, constants (like `APP_VERSION` and tool definitions), an
### API (`apps/api`) ### API (`apps/api`)
A Fastify v5 server exposing 240 tool routes across five modalities (image, video, audio, document, data) that handles: A Fastify v5 server exposing 241 tool routes across five modalities (image, video, audio, document, file) that handles:
- File uploads, temporary workspace management, and persistent file storage - File uploads, temporary workspace management, and persistent file storage
- User file library with version chains (`user_files` table) - each processed result links back to its source file and records which tool was applied, with auto-generated thumbnails for the Files page - User file library with version chains (`user_files` table) - each processed result links back to its source file and records which tool was applied, with auto-generated thumbnails for the Files page
- Tool execution (routes each tool request to the image engine or AI bridge) - Tool execution (routes each tool request to the image engine or AI bridge)
@@ -58,8 +58,8 @@ A Fastify v5 server exposing 240 tool routes across five modalities (image, vide
- User authentication, RBAC (admin/user roles with a full permission set), API key management, and rate limiting - User authentication, RBAC (admin/user roles with a full permission set), API key management, and rate limiting
- Teams management - admin-only CRUD; users are assigned to a team via the `team` field on their profile - Teams management - admin-only CRUD; users are assigned to a team via the `team` field on their profile
- Runtime settings - a key-value store in the `settings` table that controls `disabledTools`, `enableExperimentalTools`, `loginAttemptLimit`, and other operational knobs without redeploying - Runtime settings - a key-value store in the `settings` table that controls `disabledTools`, `enableExperimentalTools`, `loginAttemptLimit`, and other operational knobs without redeploying
- Custom branding - logo upload endpoint; the uploaded image is stored at `data/branding/logo.png` and served to the frontend - Custom branding and runtime preferences through database-backed settings
- Swagger/OpenAPI documentation at `/api/docs` - Scalar/OpenAPI documentation at `/api/docs`
- Serving the built frontend as a SPA in production - Serving the built frontend as a SPA in production
Key dependencies: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod for validation. Key dependencies: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod for validation.
+1 -1
View File
@@ -62,7 +62,7 @@ Telemetry note: embedded mode inherits the image's analytics default like any ot
| `MAX_MEGAPIXELS` | `0` (unlimited) | Maximum image resolution allowed in megapixels. Set to 0 for unlimited. | | `MAX_MEGAPIXELS` | `0` (unlimited) | Maximum image resolution allowed in megapixels. Set to 0 for unlimited. |
| `MAX_WORKER_THREADS` | `0` (auto) | Maximum worker threads for image processing. Set to 0 to auto-detect based on available CPU cores. | | `MAX_WORKER_THREADS` | `0` (auto) | Maximum worker threads for image processing. Set to 0 to auto-detect based on available CPU cores. |
| `PROCESSING_TIMEOUT_S` | `0` (no limit) | Maximum processing time per request in seconds. Set to 0 for no timeout. | | `PROCESSING_TIMEOUT_S` | `0` (no limit) | Maximum processing time per request in seconds. Set to 0 for no timeout. |
| `MAX_PIPELINE_STEPS` | `0` (no limit) | Maximum number of steps in a pipeline. Set to 0 for no limit. | | `MAX_PIPELINE_STEPS` | `20` | Maximum number of steps in a pipeline. Set to 0 for no limit. |
| `MAX_CANVAS_PIXELS` | `0` (no limit) | Maximum canvas size in pixels for output images. Set to 0 for no limit. | | `MAX_CANVAS_PIXELS` | `0` (no limit) | Maximum canvas size in pixels for output images. Set to 0 for no limit. |
| `MAX_SVG_SIZE_MB` | `0` (unlimited) | Maximum SVG file size in megabytes. Set to 0 for unlimited. | | `MAX_SVG_SIZE_MB` | `0` (unlimited) | Maximum SVG file size in megabytes. Set to 0 for unlimited. |
| `MAX_SPLIT_GRID` | `100` | Maximum grid dimension for the image split tool. | | `MAX_SPLIT_GRID` | `100` | Maximum grid dimension for the image split tool. |
+7 -7
View File
@@ -226,7 +226,7 @@ These numbers come from benchmarks run across four systems (Apple M2 Max, AMD Ry
| Disk | 3 GB (image) + 1 GB (data volume) | | Disk | 3 GB (image) + 1 GB (data volume) |
| GPU | Not required | | GPU | Not required |
All 138 non-AI tools (image resize/crop/convert, video trim/merge, audio normalize/convert, PDF merge/split/compress, data format conversion, and more) run on any hardware. Most operations complete in under 1 second even on a single core. The exception is AVIF encoding, which takes ~27s on 1 core but drops to ~5s on 4 cores. All 222 non-AI catalog tools (image resize/crop/convert, video trim/merge, audio normalize/convert, PDF merge/split/compress, file format conversion, conversion presets, and more) run on any hardware. Most operations complete in under 1 second even on a single core. The exception is AVIF encoding, which takes ~27s on 1 core but drops to ~5s on 4 cores.
```yaml ```yaml
deploy: deploy:
@@ -242,7 +242,7 @@ deploy:
|---|---| |---|---|
| CPU | 4 cores | | CPU | 4 cores |
| RAM | 4 GB | | RAM | 4 GB |
| Disk | 3 GB (image) + 14 GB (AI models) + workspace | | Disk | 3 GB (image) + 24 GB (AI models) + workspace |
| GPU | Not required (CPU fallback) | | GPU | Not required (CPU fallback) |
AI tools work on CPU but are significantly slower. Some tools are practical on CPU, others are not: AI tools work on CPU but are significantly slower. Some tools are practical on CPU, others are not:
@@ -259,13 +259,13 @@ AI model download sizes:
| Bundle | Disk Size | | Bundle | Disk Size |
|---|---| |---|---|
| Background removal | 3-4 GB | | Background removal | 4-5 GB |
| Upscale + Face enhance + Noise removal | 4-5 GB | | Upscale + Face enhance + Noise removal | 5-6 GB |
| Face detection | 200-300 MB | | Face detection | 200-300 MB |
| Object eraser + Colorize | 1-2 GB | | Object eraser + Colorize | 1-2 GB |
| OCR | 3-4 GB | | OCR | 5-6 GB |
| Photo restoration | 800 MB - 1 GB | | Photo restoration | 4-5 GB |
| **All bundles** | **~14 GB** | | **All bundles** | **~24 GB** |
```yaml ```yaml
deploy: deploy:
+6 -6
View File
@@ -111,14 +111,14 @@ pnpm dev
## What You Can Do ## What You Can Do
### File Processing (200+ Tools) ### File Processing (241 Tools)
| Modality | Count | Example Tools | | Modality | Count | Example Tools |
|----------|-------|---------------| |----------|-------|---------------|
| **Image** | 64 | Resize, Crop, Compress, Convert, Remove Background, Upscale, OCR, Watermark, Collage, Colorize, GIF Tools | | **Image** | 105 | Resize, Crop, Compress, Convert, Remove Background, Upscale, OCR, Watermark, Collage, Colorize, GIF Tools, format presets |
| **Video** | 29 | Trim, Crop, Compress, Convert, Merge, Extract Audio, Auto Subtitles, Video to GIF, Resize, Stabilize | | **Video** | 57 | Trim, Crop, Compress, Convert, Merge, Extract Audio, Auto Subtitles, Video to GIF, Resize, Stabilize, format presets |
| **Audio** | 17 | Trim, Merge, Convert, Normalize, Noise Reduction, Transcribe, Pitch Shift, Fade, Ringtone Maker | | **Audio** | 27 | Trim, Merge, Convert, Normalize, Noise Reduction, Transcribe, Pitch Shift, Fade, Ringtone Maker, format presets |
| **PDF / Document** | 37 | Merge, Split, Compress, OCR, Watermark, Redact, Word to PDF, Excel to PDF, Rotate, Protect, Repair | | **PDF / Document** | 42 | Merge, Split, Compress, OCR, Watermark, Redact, Word to PDF, Excel to PDF, Rotate, Protect, Repair |
| **Files** | 10 | CSV to JSON, JSON to XML, Merge CSVs, Split CSV, Create ZIP, Extract ZIP, Chart Maker, YAML/JSON | | **Files** | 10 | CSV to JSON, JSON to XML, Merge CSVs, Split CSV, Create ZIP, Extract ZIP, Chart Maker, YAML/JSON |
### Pipelines ### Pipelines
@@ -130,7 +130,7 @@ Chain tools into multi-step workflows and apply them to one image or a whole bat
3. Run on a single file - or an entire batch at once. 3. Run on a single file - or an entire batch at once.
4. Save the pipeline for later reuse. 4. Save the pipeline for later reuse.
Pipelines have unlimited steps by default. Pipelines allow 20 steps by default. Set `MAX_PIPELINE_STEPS=0` to make the limit unlimited.
### File Library ### File Library
+1 -1
View File
@@ -14,7 +14,7 @@ SCIM provisioning requires an **enterprise** license with the `scim` feature. It
- A running SnapOtter instance reachable at a public URL - A running SnapOtter instance reachable at a public URL
- An enterprise license key with the `scim` feature - An enterprise license key with the `scim` feature
- Admin access to SnapOtter (the `users:manage` permission is required to generate a SCIM token) - Admin access to SnapOtter (the `users:manage` permission is required to generate or revoke a SCIM token)
- Admin access to your identity provider's provisioning settings - Admin access to your identity provider's provisioning settings
## Quick start ## Quick start
+1 -1
View File
@@ -296,7 +296,7 @@ docker run --rm -v SnapOtter-data:/data -v $(pwd)/backup:/backup \
--exclude='ai' --exclude='venv' -C /data . --exclude='ai' --exclude='venv' -C /data .
``` ```
AI models total up to 14 GB across all bundles. Since they are re-downloadable, exclude `/data/ai/` and `/data/venv/` from backups to save space. Only the database and user files are critical. AI models total up to about 24 GB across all bundles. Since they are re-downloadable, exclude `/data/ai/` and `/data/venv/` from backups to save space. Only the database and user files are critical.
## Compliance Artifacts ## Compliance Artifacts
+4 -4
View File
@@ -14,7 +14,7 @@ SnapOtter ships three built-in roles, 17 granular permissions, and support for c
### Creating users ### Creating users
Admins can create users through the admin panel or the `POST /api/auth/users` endpoint. Each user has a username, role, team assignment, and an optional email address. Admins can create users through the admin panel or the `POST /api/auth/register` endpoint. Each user has a username, role, team assignment, and an optional email address.
### Default admin ### Default admin
@@ -90,12 +90,12 @@ All 17 permissions. Full control over the instance.
## Custom roles ## Custom roles
Admins with the `users:manage` permission can create custom roles through the admin panel or the roles API. Admins with the `security:manage` permission can create custom roles through the admin panel or the roles API. Listing roles requires `audit:read`.
### Creating a custom role ### Creating a custom role
```bash ```bash
curl -X POST http://localhost:13490/api/v1/roles \ curl -X POST http://localhost:1349/api/v1/roles \
-H "Authorization: Bearer si_..." \ -H "Authorization: Bearer si_..." \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
@@ -167,7 +167,7 @@ Users can generate API keys for programmatic access. Each key uses the `si_` pre
API keys can optionally carry a `permissions` array. When set, the effective permissions for a request are the **intersection** of the user's role permissions and the key's scoped permissions. This means an API key can never escalate beyond the user's own permissions. API keys can optionally carry a `permissions` array. When set, the effective permissions for a request are the **intersection** of the user's role permissions and the key's scoped permissions. This means an API key can never escalate beyond the user's own permissions.
```bash ```bash
curl -X POST http://localhost:13490/api/v1/api-keys \ curl -X POST http://localhost:1349/api/v1/api-keys \
-H "Authorization: Bearer si_..." \ -H "Authorization: Bearer si_..." \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
+1 -1
View File
@@ -43,4 +43,4 @@ curl -X POST http://localhost:1349/api/v1/tools/audio/audio-channels \
- `stereo-to-mono` mixes both channels into a single mono track. - `stereo-to-mono` mixes both channels into a single mono track.
- `mono-to-stereo` duplicates the mono channel to both left and right. - `mono-to-stereo` duplicates the mono channel to both left and right.
- `swap` exchanges the left and right channels of a stereo file. - `swap` exchanges the left and right channels of a stereo file.
- Output format matches the input format. - Output usually keeps the input container. AAC input is written as M4A, and unsupported decode-only inputs fall back to MP3.
+1 -1
View File
@@ -42,4 +42,4 @@ curl -X POST http://localhost:1349/api/v1/tools/audio/audio-speed \
- A factor of `0.25` plays at quarter speed (4x longer). A factor of `4` plays at quadruple speed (4x shorter). - A factor of `0.25` plays at quarter speed (4x longer). A factor of `4` plays at quadruple speed (4x shorter).
- Pitch is preserved while speed changes (time-stretch). Use pitch-shift to adjust pitch independently. - Pitch is preserved while speed changes (time-stretch). Use pitch-shift to adjust pitch independently.
- Output format matches the input format. - Output usually keeps the input container. AAC input is written as M4A, and unsupported decode-only inputs fall back to MP3.
+1 -1
View File
@@ -43,4 +43,4 @@ curl -X POST http://localhost:1349/api/v1/tools/audio/fade-audio \
- Set either value to `0` to skip that fade direction. At least one must be greater than 0. - Set either value to `0` to skip that fade direction. At least one must be greater than 0.
- The fade duration is clamped to the audio length if it exceeds it. - The fade duration is clamped to the audio length if it exceeds it.
- Output format matches the input format. - Output usually keeps the input container. AAC input is written as M4A, and unsupported decode-only inputs fall back to MP3.
+1 -1
View File
@@ -42,4 +42,4 @@ curl -X POST http://localhost:1349/api/v1/tools/audio/noise-reduction \
- `light` preserves more detail but removes less noise. `strong` removes more noise but may introduce subtle artifacts. - `light` preserves more detail but removes less noise. `strong` removes more noise but may introduce subtle artifacts.
- Best results on recordings with consistent background noise (fan hum, air conditioning, static). - Best results on recordings with consistent background noise (fan hum, air conditioning, static).
- Output format matches the input format. - Output usually keeps the input container. AAC input is written as M4A, and unsupported decode-only inputs fall back to MP3.
+1 -1
View File
@@ -40,4 +40,4 @@ curl -X POST http://localhost:1349/api/v1/tools/audio/normalize-audio \
- Uses the EBU R128 loudness standard, targeting -16 LUFS. - Uses the EBU R128 loudness standard, targeting -16 LUFS.
- Ideal for podcasts, audiobooks, and broadcast content where consistent loudness is important. - Ideal for podcasts, audiobooks, and broadcast content where consistent loudness is important.
- The source sample rate is preserved in the output. - The source sample rate is preserved in the output.
- Output format matches the input format. - Output usually keeps the input container. AAC input is written as M4A, and unsupported decode-only inputs fall back to MP3.
+1 -1
View File
@@ -43,4 +43,4 @@ curl -X POST http://localhost:1349/api/v1/tools/audio/pitch-shift \
- Positive values raise the pitch; negative values lower it. - Positive values raise the pitch; negative values lower it.
- A shift of 12 semitones equals one octave up; -12 equals one octave down. - A shift of 12 semitones equals one octave up; -12 equals one octave down.
- Playback duration stays the same regardless of the shift amount. - Playback duration stays the same regardless of the shift amount.
- Output format matches the input format. - Output usually keeps the input container. AAC input is written as M4A, and unsupported decode-only inputs fall back to MP3.
+1 -1
View File
@@ -38,4 +38,4 @@ curl -X POST http://localhost:1349/api/v1/tools/audio/reverse-audio \
## Notes ## Notes
- The full audio track is reversed from end to start. - The full audio track is reversed from end to start.
- Output format matches the input format. - Output usually keeps the input container. AAC input is written as M4A, and unsupported decode-only inputs fall back to MP3.
+1 -1
View File
@@ -44,4 +44,4 @@ curl -X POST http://localhost:1349/api/v1/tools/audio/silence-removal \
- A higher (less negative) threshold is more aggressive and removes quieter passages as well as true silence. - A higher (less negative) threshold is more aggressive and removes quieter passages as well as true silence.
- Increase `minSilenceS` to only strip longer pauses while keeping short natural gaps. - Increase `minSilenceS` to only strip longer pauses while keeping short natural gaps.
- Useful for cleaning up podcast recordings, lectures, and voice memos. - Useful for cleaning up podcast recordings, lectures, and voice memos.
- Output format matches the input format. - Output usually keeps the input container. AAC input is written as M4A, and unsupported decode-only inputs fall back to MP3.
+1 -1
View File
@@ -43,7 +43,7 @@ Track progress via SSE at `GET /api/v1/jobs/{jobId}/progress`. When the job comp
## Notes ## Notes
- Requires the **transcription** feature bundle to be installed. Returns `501 Not Implemented` if the bundle is not available. - Requires the **transcription** feature bundle to be installed. Returns `501` with code `FEATURE_NOT_INSTALLED`, the missing `feature`, `featureName`, and `estimatedSize` if the bundle is not available.
- Uses faster-whisper for transcription. Language `auto` detects the spoken language automatically. - Uses faster-whisper for transcription. Language `auto` detects the spoken language automatically.
- `srt` and `vtt` formats include timestamps for each segment, suitable for subtitles. - `srt` and `vtt` formats include timestamps for each segment, suitable for subtitles.
- `txt` format returns plain text without timestamps. - `txt` format returns plain text without timestamps.
+1 -1
View File
@@ -44,4 +44,4 @@ curl -X POST http://localhost:1349/api/v1/tools/audio/trim-audio \
- Times are specified in seconds and can include decimals (e.g. `10.5`). - Times are specified in seconds and can include decimals (e.g. `10.5`).
- The `endS` value must be greater than `startS`. - The `endS` value must be greater than `startS`.
- If `endS` exceeds the audio duration, the file is trimmed to the end. - If `endS` exceeds the audio duration, the file is trimmed to the end.
- Output format matches the input format. - Output usually keeps the input container. AAC input is written as M4A, and unsupported decode-only inputs fall back to MP3.
+1 -1
View File
@@ -42,4 +42,4 @@ curl -X POST http://localhost:1349/api/v1/tools/audio/volume-adjust \
- Positive values increase volume; negative values decrease it. - Positive values increase volume; negative values decrease it.
- Large positive gains can cause clipping. Use normalize-audio for loudness-safe leveling. - Large positive gains can cause clipping. Use normalize-audio for loudness-safe leveling.
- Output format matches the input format. - Output usually keeps the input container. AAC input is written as M4A, and unsupported decode-only inputs fall back to MP3.
+1 -1
View File
@@ -10,7 +10,7 @@ SnapOtter exposes 83 dedicated conversion preset endpoints in addition to the ba
`POST /api/v1/tools/<section>/<presetId>` `POST /api/v1/tools/<section>/<presetId>`
Send `multipart/form-data` with a `file` part and optional `settings` JSON string. Fast presets return `200` with a `downloadUrl`; long-running presets return `202` and progress streams from `/api/v1/jobs/<jobId>/progress`. Send `multipart/form-data` with a `file` part and optional `settings` JSON string. Presets follow the response contract of the base tool. Fast presets usually return `200` with a `downloadUrl`, but can return `202` if they exceed the synchronous wait window. Video presets and long file/document presets return `202` and progress streams from `/api/v1/jobs/<jobId>/progress`. PDF-to-image presets return page download URLs plus a ZIP URL.
## Image Presets ## Image Presets
@@ -51,7 +51,7 @@ curl -X POST http://localhost:1349/api/v1/tools/image/content-aware-resize \
## Notes ## Notes
- This tool returns a synchronous response (not 202 async). Processing happens inline. - This custom route currently returns a synchronous 200 response.
- Uses the `caire` seam carving library for content-aware resizing. - Uses the `caire` seam carving library for content-aware resizing.
- Only reduces dimensions (removes seams). Cannot expand an image beyond its original size. - Only reduces dimensions (removes seams). Cannot expand an image beyond its original size.
- The `protectFaces` option uses AI face detection to mark face regions as high-energy, preventing seams from passing through faces. - The `protectFaces` option uses AI face detection to mark face regions as high-energy, preventing seams from passing through faces.
+2 -2
View File
@@ -12,7 +12,7 @@ Restore and enhance faces in images using AI models (GFPGAN/CodeFormer).
**Processing:** Asynchronous (returns 202, poll `/api/v1/jobs/{jobId}/progress` for status via SSE) **Processing:** Asynchronous (returns 202, poll `/api/v1/jobs/{jobId}/progress` for status via SSE)
**Model bundle:** `upscale-enhance` (4-5 GB) **Model bundles:** `upscale-enhance` (5-6 GB) and `face-detection` (200-300 MB)
## Parameters ## Parameters
@@ -74,7 +74,7 @@ data: {"phase":"processing","stage":"Enhancing faces...","percent":60}
## Notes ## Notes
- Requires the `upscale-enhance` model bundle to be installed (4-5 GB). - Requires both the `upscale-enhance` model bundle (5-6 GB) and the `face-detection` model bundle (200-300 MB).
- GFPGAN produces more aggressive enhancement; CodeFormer better preserves identity. `auto` selects the best model for the input. - GFPGAN produces more aggressive enhancement; CodeFormer better preserves identity. `auto` selects the best model for the input.
- Output is always PNG format for maximum quality. - Output is always PNG format for maximum quality.
- A WebP preview is generated alongside the full-resolution output for faster frontend display. - A WebP preview is generated alongside the full-resolution output for faster frontend display.
+1 -1
View File
@@ -12,7 +12,7 @@ One-click auto-improve with smart analysis. Analyzes the image and applies expos
**Processing:** Synchronous (uses `createToolRoute` factory, returns result directly) **Processing:** Synchronous (uses `createToolRoute` factory, returns result directly)
**Model bundle:** None required for basic enhancement. The `upscale-enhance` bundle (4-5 GB) is used only when `deepEnhance` is enabled (for AI noise removal via SCUNet). **Model bundle:** None required for basic enhancement. The `upscale-enhance` bundle (5-6 GB) is used only when `deepEnhance` is enabled (for AI noise removal via SCUNet).
## Parameters ## Parameters
+2 -2
View File
@@ -12,7 +12,7 @@ AI-powered noise and grain removal with multi-tier quality options, using the Py
**Processing:** Asynchronous (returns 202, poll `/api/v1/jobs/{jobId}/progress` for status via SSE) **Processing:** Asynchronous (returns 202, poll `/api/v1/jobs/{jobId}/progress` for status via SSE)
**Model bundle:** `upscale-enhance` (4-5 GB) **Model bundle:** `upscale-enhance` (5-6 GB)
## Parameters ## Parameters
@@ -69,7 +69,7 @@ data: {"phase":"processing","stage":"Denoising...","percent":65}
## Notes ## Notes
- Requires the `upscale-enhance` model bundle to be installed (4-5 GB). - Requires the `upscale-enhance` model bundle to be installed (5-6 GB).
- Quality tiers trade speed for quality: `quick` is fastest with basic denoising, `maximum` uses the most thorough multi-pass approach. - Quality tiers trade speed for quality: `quick` is fastest with basic denoising, `maximum` uses the most thorough multi-pass approach.
- The `detailPreservation` parameter is critical for textured subjects (fabric, hair, foliage). Higher values prevent the denoiser from smoothing away fine detail. - The `detailPreservation` parameter is critical for textured subjects (fabric, hair, foliage). Higher values prevent the denoiser from smoothing away fine detail.
- When `format` is set to `"original"`, the output format matches the input file format. - When `format` is set to `"original"`, the output format matches the input file format.
+5 -5
View File
@@ -10,9 +10,9 @@ Extract text from images using AI-powered optical character recognition. Support
`POST /api/v1/tools/image/ocr` `POST /api/v1/tools/image/ocr`
**Processing:** Synchronous (returns extracted text directly, though progress is reported via SSE if a `clientJobId` is provided) **Processing:** Synchronous JSON response. If `clientJobId` is provided, progress is also reported through SSE.
**Model bundle:** `ocr` (3-4 GB) **Model bundle:** `ocr` (5-6 GB)
## Parameters ## Parameters
@@ -45,7 +45,7 @@ curl -X POST http://localhost:1349/api/v1/tools/image/ocr \
### Progress (SSE, optional) ### Progress (SSE, optional)
If a `clientJobId` is provided, progress events are streamed: If a `clientJobId` form field is provided, progress events are streamed:
``` ```
event: progress event: progress
@@ -54,8 +54,8 @@ data: {"phase":"processing","stage":"Recognizing text...","percent":50}
## Notes ## Notes
- Requires the `ocr` model bundle to be installed (3-4 GB). - Requires the `ocr` model bundle to be installed (5-6 GB).
- Unlike most AI tools, OCR returns a synchronous JSON response with extracted text (not an image download URL). - OCR returns extracted text directly rather than an image download URL.
- Uses a fallback chain: if a higher-quality tier crashes (e.g., PaddleOCR segfault), it automatically retries with the next lower tier. - Uses a fallback chain: if a higher-quality tier crashes (e.g., PaddleOCR segfault), it automatically retries with the next lower tier.
- If a tier returns empty text without crashing, it also falls back to the next tier. - If a tier returns empty text without crashing, it also falls back to the next tier.
- Quality tiers map to engines: `fast` = Tesseract, `balanced` = PaddleOCR v5, `best` = PaddleOCR VL. - Quality tiers map to engines: `fast` = Tesseract, `balanced` = PaddleOCR v5, `best` = PaddleOCR VL.
+2 -2
View File
@@ -12,7 +12,7 @@ Fix scratches, tears, and damage on old photos using a multi-step AI pipeline. C
**Processing:** Asynchronous (returns 202, poll `/api/v1/jobs/{jobId}/progress` for status via SSE) **Processing:** Asynchronous (returns 202, poll `/api/v1/jobs/{jobId}/progress` for status via SSE)
**Model bundle:** `photo-restoration` (800 MB - 1 GB) **Model bundle:** `photo-restoration` (4-5 GB)
## Parameters ## Parameters
@@ -83,7 +83,7 @@ data: {"phase":"processing","stage":"Enhancing faces...","percent":60}
## Notes ## Notes
- Requires the `photo-restoration` model bundle to be installed (800 MB - 1 GB). - Requires the `photo-restoration` model bundle to be installed (4-5 GB).
- The pipeline runs multiple AI steps sequentially: scratch repair, face enhancement (GFPGAN), denoising, and optionally colorization. - The pipeline runs multiple AI steps sequentially: scratch repair, face enhancement (GFPGAN), denoising, and optionally colorization.
- The `steps` array in the result shows which processing steps were actually executed. - The `steps` array in the result shows which processing steps were actually executed.
- `scratchCoverage` is an estimated percentage of the image area that had scratch damage. - `scratchCoverage` is an estimated percentage of the image area that had scratch damage.
+2 -2
View File
@@ -12,7 +12,7 @@ AI super-resolution enhancement using Real-ESRGAN. Upscales images 2x-4x while p
**Processing:** Asynchronous (returns 202, poll `/api/v1/jobs/{jobId}/progress` for status via SSE) **Processing:** Asynchronous (returns 202, poll `/api/v1/jobs/{jobId}/progress` for status via SSE)
**Model bundle:** `upscale-enhance` (4-5 GB) **Model bundle:** `upscale-enhance` (5-6 GB)
## Parameters ## Parameters
@@ -73,7 +73,7 @@ data: {"phase":"processing","stage":"Upscaling...","percent":60}
## Notes ## Notes
- Requires the `upscale-enhance` model bundle to be installed (4-5 GB). - Requires the `upscale-enhance` model bundle to be installed (5-6 GB).
- Uses Real-ESRGAN when available; falls back to Lanczos interpolation if the AI model is unavailable. - Uses Real-ESRGAN when available; falls back to Lanczos interpolation if the AI model is unavailable.
- The `faceEnhance` option applies GFPGAN face restoration during upscaling for better face quality. - The `faceEnhance` option applies GFPGAN face restoration during upscaling for better face quality.
- For non-browser-previewable output formats (HEIC, JXL, TIFF), a WebP preview is generated alongside the main output. - For non-browser-previewable output formats (HEIC, JXL, TIFF), a WebP preview is generated alongside the main output.
+4 -4
View File
@@ -1,10 +1,10 @@
--- ---
description: Convert videos between MP4, MOV, and WebM. description: Convert videos between MP4, MOV, WebM, AVI, and MKV.
--- ---
# Convert Video # Convert Video
Convert videos between MP4, MOV, and WebM formats with configurable quality presets. Convert videos between MP4, MOV, WebM, AVI, and MKV formats with configurable quality presets.
## API Endpoint ## API Endpoint
@@ -16,7 +16,7 @@ Accepts multipart form data with a video file and a JSON `settings` field. This
| Parameter | Type | Required | Default | Description | | Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------| |-----------|------|----------|---------|-------------|
| format | string | No | `"mp4"` | Output format: `mp4`, `mov`, `webm` | | format | string | No | `"mp4"` | Output format: `mp4`, `mov`, `webm`, `avi`, `mkv` |
| quality | string | No | `"balanced"` | Quality preset: `high`, `balanced`, `small` | | quality | string | No | `"balanced"` | Quality preset: `high`, `balanced`, `small` |
## Example Request ## Example Request
@@ -40,5 +40,5 @@ curl -X POST http://localhost:1349/api/v1/tools/video/convert-video \
## Notes ## Notes
- The `high` quality preset produces the best visual fidelity but larger files. The `small` preset aggressively compresses for minimum file size. - The `high` quality preset produces the best visual fidelity but larger files. The `small` preset aggressively compresses for minimum file size.
- WebM output uses VP9 encoding. MP4 and MOV use H.264. - WebM output uses VP9 encoding. MP4 and MOV use H.264. AVI and MKV are available for legacy or archival workflows.
- Progress updates are available via SSE at `GET /api/v1/jobs/{jobId}/progress` until the job completes. - Progress updates are available via SSE at `GET /api/v1/jobs/{jobId}/progress` until the job completes.
+3 -3
View File
@@ -4,7 +4,7 @@ description: Pull the audio track out of a video.
# Extract Audio # Extract Audio
Extract the audio track from a video file and save it as MP3, WAV, or M4A. Extract the audio track from a video file and save it as MP3, WAV, M4A, or OGG.
## API Endpoint ## API Endpoint
@@ -16,7 +16,7 @@ Accepts multipart form data with a video file and a JSON `settings` field.
| Parameter | Type | Required | Default | Description | | Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------| |-----------|------|----------|---------|-------------|
| format | string | No | `"mp3"` | Output audio format: `mp3`, `wav`, `m4a` | | format | string | No | `"mp3"` | Output audio format: `mp3`, `wav`, `m4a`, `ogg` |
## Example Request ## Example Request
@@ -41,5 +41,5 @@ curl -X POST http://localhost:1349/api/v1/tools/video/extract-audio \
## Notes ## Notes
- If the video has no audio track, the request returns a 400 error. - If the video has no audio track, the request returns a 400 error.
- MP3 is lossy but widely compatible. WAV is lossless but large. M4A (AAC) offers a good balance of quality and size. - MP3 is lossy but widely compatible. WAV is lossless but large. M4A (AAC) offers a good balance of quality and size. OGG is available for open codec workflows.
- When the source audio is already AAC and the output format is M4A, the audio stream is copied without re-encoding. - When the source audio is already AAC and the output format is M4A, the audio stream is copied without re-encoding.
+4 -4
View File
@@ -1,10 +1,10 @@
--- ---
description: Convert an animated GIF into an MP4 or WebM video. description: Convert an animated GIF into an MP4, WebM, or MOV video.
--- ---
# GIF to Video # GIF to Video
Convert an animated GIF into a compact MP4 or WebM video file. Convert an animated GIF into a compact MP4, WebM, or MOV video file.
## API Endpoint ## API Endpoint
@@ -16,7 +16,7 @@ Accepts multipart form data with a GIF file and a JSON `settings` field.
| Parameter | Type | Required | Default | Description | | Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------| |-----------|------|----------|---------|-------------|
| format | string | No | `"mp4"` | Output format: `mp4`, `webm` | | format | string | No | `"mp4"` | Output format: `mp4`, `webm`, `mov` |
## Example Request ## Example Request
@@ -42,4 +42,4 @@ curl -X POST http://localhost:1349/api/v1/tools/video/gif-to-video \
- Converting GIF to video typically reduces file size by 80-90% while maintaining the same visual quality. - Converting GIF to video typically reduces file size by 80-90% while maintaining the same visual quality.
- Only animated GIF files are accepted. Static images should use the image Convert tool. - Only animated GIF files are accepted. Static images should use the image Convert tool.
- MP4 uses H.264 encoding, WebM uses VP9. - MP4 and MOV use H.264 encoding, WebM uses VP9.
+12 -5
View File
@@ -1,6 +1,6 @@
# SnapOtter # SnapOtter
Open-source, self-hostable file processing suite with 200+ tools across image, video, audio, PDF, and files, plus a layer-based image editor and local AI. Runs as a single container with embedded Postgres + Redis for quick start, or a Docker Compose stack for production. No external services. Dual-licensed AGPLv3 and commercial. Open-source, self-hostable file processing suite with 241 catalog tool routes across image, video, audio, PDF, and files, plus a layer-based image editor and local AI. Runs as a single container with embedded Postgres + Redis for quick start, or a Docker Compose stack for production. No external services. Dual-licensed AGPLv3 and commercial.
All processing happens locally. Files never leave your infrastructure. All processing happens locally. Files never leave your infrastructure.
@@ -40,7 +40,7 @@ Then run docker compose up -d, open http://localhost:1349, and log in with admin
## Tools ## Tools
200+ tools across 5 modalities: image, video, audio, PDF, and files. The AI tools run locally on your hardware with no cloud APIs. 241 catalog tool routes across 5 modalities: image, video, audio, PDF, and files. The AI tools run locally on your hardware with no cloud APIs.
### Image ### Image
@@ -83,10 +83,11 @@ API keys support scoped permissions that intersect with user role permissions.
Processing a file: Processing a file:
POST /api/v1/tools/:section/:toolId POST /api/v1/tools/:section/:toolId
Content-Type: multipart/form-data Content-Type: multipart/form-data
Body: file (binary), settings (JSON string) Body: file (binary), settings (JSON string), optional clientJobId, optional fileId
Response: { jobId, downloadUrl, originalSize, processedSize } Fast response: { jobId, downloadUrl, originalSize, processedSize }
Async response: { jobId, async: true }
Long-running tools (video, audio, AI) return 202 Accepted and stream progress via SSE: Fast tools usually return 200, but any queued tool can return 202 if it exceeds the synchronous wait window. Long-running tools and queued jobs stream progress via SSE:
GET /api/v1/jobs/:jobId/progress (EventSource) GET /api/v1/jobs/:jobId/progress (EventSource)
Pipelines (chained tool workflows): Pipelines (chained tool workflows):
@@ -98,6 +99,12 @@ Pipelines (chained tool workflows):
Batch processing: POST /api/v1/tools/:section/:toolId/batch (multiple files in, ZIP archive out). Batch processing: POST /api/v1/tools/:section/:toolId/batch (multiple files in, ZIP archive out).
Surrounding APIs:
- Auth: local login, OIDC, SAML, MFA, users, sessions, API keys
- Files: uploads, library versions, downloads, thumbnails, previews, URL imports
- Admin: health, readiness, metrics, log level, support bundles, usage, backup status, AI bundles
- Enterprise: audit export, config import/export, IP allowlist, legal hold, SCIM, SIEM, webhooks, GDPR lifecycle, upgrade checks
## Tech Stack ## Tech Stack
Frontend: React 19, Vite 6, Tailwind CSS 4, Zustand Frontend: React 19, Vite 6, Tailwind CSS 4, Zustand
+42
View File
@@ -1,3 +1,5 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { apiToolPath, TOOLS } from "@snapotter/shared"; import { apiToolPath, TOOLS } from "@snapotter/shared";
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { buildTestApp, type TestApp } from "../test-server"; import { buildTestApp, type TestApp } from "../test-server";
@@ -87,6 +89,46 @@ describe("API docs", () => {
expect(missing, `OpenAPI missing docs metadata paths: ${missing.join(", ")}`).toEqual([]); expect(missing, `OpenAPI missing docs metadata paths: ${missing.join(", ")}`).toEqual([]);
}); });
it("documents the surrounding non-tool API surface in the spec", async () => {
const res = await testApp.app.inject({
method: "GET",
url: "/api/v1/openapi.yaml",
});
const paths = openApiPathSet(res.body);
const expectedPaths = [
"/api/v1/readyz",
"/api/v1/jobs/{jobId}/cancel",
"/api/v1/preferences",
"/api/auth/mfa/enroll",
"/api/auth/oidc/login",
"/api/auth/saml/metadata",
"/api/v1/files/{id}/preview",
"/api/v1/preview/generate",
"/api/v1/admin/log-level",
"/api/v1/metrics",
"/api/v1/enterprise/scim/token",
"/api/v1/scim/v2/ServiceProviderConfig",
];
const missing = expectedPaths.filter((path) => !paths.has(path));
expect(missing, `OpenAPI missing non-tool API paths: ${missing.join(", ")}`).toEqual([]);
});
it("keeps published docs counts aligned with the live catalog", () => {
const root = process.cwd();
const gettingStarted = readFileSync(join(root, "apps/docs/guide/getting-started.md"), "utf8");
const deployment = readFileSync(join(root, "apps/docs/guide/deployment.md"), "utf8");
const architecture = readFileSync(join(root, "apps/docs/guide/architecture.md"), "utf8");
expect(gettingStarted).toContain("| **Image** | 105 |");
expect(gettingStarted).toContain("| **Video** | 57 |");
expect(gettingStarted).toContain("| **Audio** | 27 |");
expect(gettingStarted).toContain("| **PDF / Document** | 42 |");
expect(gettingStarted).toContain("| **Files** | 10 |");
expect(deployment).not.toContain("All 138 non-AI tools");
expect(architecture).toContain("241 tool routes");
});
it("serves an LLM summary with live catalog tools", async () => { it("serves an LLM summary with live catalog tools", async () => {
const res = await testApp.app.inject({ const res = await testApp.app.inject({
method: "GET", method: "GET",