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
- **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
- **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`
- **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)
- **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
+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 {
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("");
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("Base URL: `/api/v1`");
lines.push("");
lines.push("## Docs");
lines.push("- [Interactive API Reference](/api/docs): Full interactive API documentation");
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);
lines.push(`- ${section.name} (${tools.length} 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})`);
}
}
@@ -74,6 +81,41 @@ function generateLlmsTxt(spec: OpenAPISpec): string {
lines.push("## Authentication");
lines.push("- Session token via `POST /api/auth/login` -> `Authorization: Bearer <token>`");
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");
}
+1 -1
View File
@@ -238,7 +238,7 @@ export async function registerScimRoutes(app: FastifyInstance): Promise<void> {
async (_request: FastifyRequest, reply: FastifyReply) => {
return reply.send({
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 },
bulk: { supported: false, maxOperations: 0, maxPayloadSize: 0 },
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 |
| `face-detection` | 200-300 MB | blur-faces, red-eye-removal, smart-crop |
| `object-eraser-colorize` | 1-2 GB | erase-object, colorize, ai-canvas-expand |
| `upscale-enhance` | 4-5 GB | upscale, enhance-faces, noise-removal |
| `photo-restoration` | 800 MB - 1 GB | restore-photo |
| `ocr` | 3-4 GB | ocr, ocr-pdf |
| `upscale-enhance` | 5-6 GB | upscale, enhance-faces, noise-removal |
| `photo-restoration` | 4-5 GB | restore-photo |
| `ocr` | 5-6 GB | ocr, ocr-pdf |
| `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 |
| `DELETE` | `/api/auth/users/:id` | Admin | Delete a user |
| `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
@@ -79,6 +91,7 @@ Keys are prefixed `si_` and stored as scrypt hashes - the raw key is shown once
| 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/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. |
## Using Tools
@@ -104,9 +117,11 @@ curl -X POST http://localhost:1349/api/v1/tools/<section>/<toolId>/batch \
- Upload is `multipart/form-data`.
- `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`.
- **Long-running tools** (202) return JSON: `{"jobId":"...","async":true}`. Connect to SSE for progress, then download when complete (see [Progress Tracking](#progress-tracking)).
- **Batch** returns a ZIP archive streamed directly (with `X-Job-Id` header).
- `clientJobId` is an optional form field for caller-supplied progress correlation.
- `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`.
- **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
@@ -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` |
| `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 |
| `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) |
### Video Tools
| 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) |
| `trim-video` | Trim Video | `startS`, `endS`, `precise` (bool, frame-accurate cut) |
| `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) |
| `watermark-video` | Watermark Video | `text`, `position`, `fontSize`, `opacity`, `color` |
| `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-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) |
@@ -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 |
| `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) |
| `extract-audio` | Extract Audio | `format` (mp3/wav/m4a) |
| `extract-audio` | Extract Audio | `format` (mp3/wav/m4a/ogg) |
### 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) |
| `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` |
| `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
@@ -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) |
| `yaml-json` | YAML / JSON | - (bidirectional) |
| `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) |
| `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/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-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/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. |
## 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
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}}]}'
```
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
@@ -446,20 +471,22 @@ Each step's output is the next step's input. Unlimited steps per pipeline by def
## 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
# 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 \
-H "Authorization: Bearer <token>"
curl -N http://localhost:1349/api/v1/jobs/<jobId>/progress
```
Event format:
```
data: {"progress":42,"status":"processing","message":"Upscaling frame 2/5"}
data: {"progress":100,"status":"completed"}
data: {"jobId":"...","type":"single","phase":"processing","stage":"Upscaling","percent":42}
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
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: [...] }`) |
| `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) |
| `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 |
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
@@ -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).
## 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
Custom role management with granular permissions.
@@ -516,9 +554,9 @@ Custom role management with granular permissions.
| Method | Path | Access | Description |
|--------|------|--------|-------------|
| `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`) |
| `PUT` | `/api/v1/roles/:id` | Admin (`users: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) |
| `POST` | `/api/v1/roles` | Admin (`security:manage`) | Create a custom role (`name`, `description`, `permissions`) |
| `PUT` | `/api/v1/roles/:id` | Admin (`security:manage`) | Update a custom role (cannot modify built-in roles) |
| `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`.
@@ -537,6 +575,7 @@ Query parameters:
| `page` | Page number (default: 1) |
| `limit` | Entries per page (default: 50, max: 100) |
| `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 |
| `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/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 |
| `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
@@ -588,5 +692,7 @@ All errors return JSON:
| 403 | Insufficient permissions |
| 404 | Resource not found |
| 413 | File too large (see `MAX_UPLOAD_SIZE_MB`) |
| 422 | Processing failed after validation |
| 429 | Rate limited (see `RATE_LIMIT_PER_MIN`) |
| 501 | Required AI feature bundle is not installed (`FEATURE_NOT_INSTALLED`) |
| 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`)
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
- 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)
@@ -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
- 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
- Custom branding - logo upload endpoint; the uploaded image is stored at `data/branding/logo.png` and served to the frontend
- Swagger/OpenAPI documentation at `/api/docs`
- Custom branding and runtime preferences through database-backed settings
- Scalar/OpenAPI documentation at `/api/docs`
- Serving the built frontend as a SPA in production
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_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. |
| `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_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. |
+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) |
| 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
deploy:
@@ -242,7 +242,7 @@ deploy:
|---|---|
| CPU | 4 cores |
| 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) |
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 |
|---|---|
| Background removal | 3-4 GB |
| Upscale + Face enhance + Noise removal | 4-5 GB |
| Background removal | 4-5 GB |
| Upscale + Face enhance + Noise removal | 5-6 GB |
| Face detection | 200-300 MB |
| Object eraser + Colorize | 1-2 GB |
| OCR | 3-4 GB |
| Photo restoration | 800 MB - 1 GB |
| **All bundles** | **~14 GB** |
| OCR | 5-6 GB |
| Photo restoration | 4-5 GB |
| **All bundles** | **~24 GB** |
```yaml
deploy:
+6 -6
View File
@@ -111,14 +111,14 @@ pnpm dev
## What You Can Do
### File Processing (200+ Tools)
### File Processing (241 Tools)
| Modality | Count | Example Tools |
|----------|-------|---------------|
| **Image** | 64 | Resize, Crop, Compress, Convert, Remove Background, Upscale, OCR, Watermark, Collage, Colorize, GIF Tools |
| **Video** | 29 | Trim, Crop, Compress, Convert, Merge, Extract Audio, Auto Subtitles, Video to GIF, Resize, Stabilize |
| **Audio** | 17 | Trim, Merge, Convert, Normalize, Noise Reduction, Transcribe, Pitch Shift, Fade, Ringtone Maker |
| **PDF / Document** | 37 | Merge, Split, Compress, OCR, Watermark, Redact, Word to PDF, Excel to PDF, Rotate, Protect, Repair |
| **Image** | 105 | Resize, Crop, Compress, Convert, Remove Background, Upscale, OCR, Watermark, Collage, Colorize, GIF Tools, format presets |
| **Video** | 57 | Trim, Crop, Compress, Convert, Merge, Extract Audio, Auto Subtitles, Video to GIF, Resize, Stabilize, format presets |
| **Audio** | 27 | Trim, Merge, Convert, Normalize, Noise Reduction, Transcribe, Pitch Shift, Fade, Ringtone Maker, format presets |
| **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 |
### 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.
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
+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
- 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
## 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 .
```
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
+4 -4
View File
@@ -14,7 +14,7 @@ SnapOtter ships three built-in roles, 17 granular permissions, and support for c
### 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
@@ -90,12 +90,12 @@ All 17 permissions. Full control over the instance.
## 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
```bash
curl -X POST http://localhost:13490/api/v1/roles \
curl -X POST http://localhost:1349/api/v1/roles \
-H "Authorization: Bearer si_..." \
-H "Content-Type: application/json" \
-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.
```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 "Content-Type: application/json" \
-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.
- `mono-to-stereo` duplicates the mono channel to both left and right.
- `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).
- 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.
- 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.
- 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.
- Ideal for podcasts, audiobooks, and broadcast content where consistent loudness is important.
- 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.
- A shift of 12 semitones equals one octave up; -12 equals one octave down.
- 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
- 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.
- Increase `minSilenceS` to only strip longer pauses while keeping short natural gaps.
- 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
- 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.
- `srt` and `vtt` formats include timestamps for each segment, suitable for subtitles.
- `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`).
- The `endS` value must be greater than `startS`.
- 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.
- 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>`
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
@@ -51,7 +51,7 @@ curl -X POST http://localhost:1349/api/v1/tools/image/content-aware-resize \
## 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.
- 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.
+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)
**Model bundle:** `upscale-enhance` (4-5 GB)
**Model bundles:** `upscale-enhance` (5-6 GB) and `face-detection` (200-300 MB)
## Parameters
@@ -74,7 +74,7 @@ data: {"phase":"processing","stage":"Enhancing faces...","percent":60}
## 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.
- Output is always PNG format for maximum quality.
- 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)
**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
+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)
**Model bundle:** `upscale-enhance` (4-5 GB)
**Model bundle:** `upscale-enhance` (5-6 GB)
## Parameters
@@ -69,7 +69,7 @@ data: {"phase":"processing","stage":"Denoising...","percent":65}
## 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.
- 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.
+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`
**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
@@ -45,7 +45,7 @@ curl -X POST http://localhost:1349/api/v1/tools/image/ocr \
### 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
@@ -54,8 +54,8 @@ data: {"phase":"processing","stage":"Recognizing text...","percent":50}
## Notes
- Requires the `ocr` model bundle to be installed (3-4 GB).
- Unlike most AI tools, OCR returns a synchronous JSON response with extracted text (not an image download URL).
- Requires the `ocr` model bundle to be installed (5-6 GB).
- 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.
- 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.
+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)
**Model bundle:** `photo-restoration` (800 MB - 1 GB)
**Model bundle:** `photo-restoration` (4-5 GB)
## Parameters
@@ -83,7 +83,7 @@ data: {"phase":"processing","stage":"Enhancing faces...","percent":60}
## 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 `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.
+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)
**Model bundle:** `upscale-enhance` (4-5 GB)
**Model bundle:** `upscale-enhance` (5-6 GB)
## Parameters
@@ -73,7 +73,7 @@ data: {"phase":"processing","stage":"Upscaling...","percent":60}
## 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.
- 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.
+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 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
@@ -16,7 +16,7 @@ Accepts multipart form data with a video file and a JSON `settings` field. This
| 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` |
## Example Request
@@ -40,5 +40,5 @@ curl -X POST http://localhost:1349/api/v1/tools/video/convert-video \
## Notes
- 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.
+3 -3
View File
@@ -4,7 +4,7 @@ description: Pull the audio track out of a video.
# 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
@@ -16,7 +16,7 @@ Accepts multipart form data with a video file and a JSON `settings` field.
| 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
@@ -41,5 +41,5 @@ curl -X POST http://localhost:1349/api/v1/tools/video/extract-audio \
## Notes
- 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.
+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
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
@@ -16,7 +16,7 @@ Accepts multipart form data with a GIF file and a JSON `settings` field.
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| format | string | No | `"mp4"` | Output format: `mp4`, `webm` |
| format | string | No | `"mp4"` | Output format: `mp4`, `webm`, `mov` |
## 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.
- 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
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.
@@ -40,7 +40,7 @@ Then run docker compose up -d, open http://localhost:1349, and log in with admin
## 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
@@ -83,10 +83,11 @@ API keys support scoped permissions that intersect with user role permissions.
Processing a file:
POST /api/v1/tools/:section/:toolId
Content-Type: multipart/form-data
Body: file (binary), settings (JSON string)
Response: { jobId, downloadUrl, originalSize, processedSize }
Body: file (binary), settings (JSON string), optional clientJobId, optional fileId
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)
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).
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
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 { afterAll, beforeAll, describe, expect, it } from "vitest";
import { buildTestApp, type TestApp } from "../test-server";
@@ -87,6 +89,46 @@ describe("API docs", () => {
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 () => {
const res = await testApp.app.inject({
method: "GET",