docs: sync API and documentation coverage (#379)

* docs: sync API and docs coverage

* ci: pin pandoc for sandboxed conversions
This commit is contained in:
SnapOtter
2026-06-29 22:35:05 +08:00
committed by GitHub
parent fd6ebe77b5
commit 37dc0098ba
13 changed files with 5261 additions and 68 deletions
+8 -2
View File
@@ -89,8 +89,14 @@ jobs:
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install system dependencies (image formats + doc-engine qpdf/pandoc/LibreOffice)
run: sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends libheif-examples libheif-plugin-x265 libheif-plugin-libde265 libimage-exiftool-perl libraw-bin imagemagick ghostscript libjxl-tools libopenjp2-tools qpdf pandoc libreoffice-calc libreoffice-impress libreoffice-writer
- name: Install system dependencies (image formats + doc-engine qpdf/LibreOffice)
run: sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends libheif-examples libheif-plugin-x265 libheif-plugin-libde265 libimage-exiftool-perl libraw-bin imagemagick ghostscript libjxl-tools libopenjp2-tools qpdf libreoffice-calc libreoffice-impress libreoffice-writer
- name: Install Pandoc 3.10 (sandboxed DOCX/EPUB)
run: |
curl -fsSL https://github.com/jgm/pandoc/releases/download/3.10/pandoc-3.10-1-amd64.deb -o /tmp/pandoc.deb
sudo apt-get install -y --no-install-recommends /tmp/pandoc.deb
pandoc --version
- name: Install pdfcpu (doc-engine PDF layout binary; matches docker/Dockerfile v0.13.0)
run: |
+2 -2
View File
@@ -3,7 +3,7 @@
</p>
> [!NOTE]
> **SnapOtter v2.0.0 is coming soon.** The current Docker image (`latest`) is v1.x and includes image tools only. v2.0 adds 200+ tools across image, video, audio, documents, and files. We're fixing a last-minute issue with local AI installs before publishing the new image. Stay tuned!
> **SnapOtter v2.0.0** is the current monorepo version, with 200+ tools across image, video, audio, PDF, and files. For published image channels and GPU variants, see the Docker Tags guide.
<p align="center">
<a href="https://hub.docker.com/r/snapotter/snapotter"><img src="https://img.shields.io/docker/v/snapotter/snapotter?label=Docker%20Hub&logo=docker" alt="Docker Hub"></a>
@@ -33,7 +33,7 @@ Stirling-PDF stops at PDFs. ConvertX stops at conversions. SnapOtter runs all fi
- **Image (105):** resize, crop, compress, convert, watermark, color adjust, beautify screenshots, generate memes, vectorize, GIF tools, find duplicates, passport photos, plus dedicated format converters (JPG to PNG, HEIC to JPG, WebP to PNG, image to PDF, and more). Supports 55+ input formats (including 23 camera RAW formats) and 14 output formats
- **Video (57):** convert, compress, trim, resize, crop, merge, video-to-GIF, extract audio, stabilize, change FPS, burn/extract subtitles, plus dedicated converters (MOV to MP4, MKV to MP4, MP4 to MP3, and more)
- **Audio (27):** convert, trim, normalize, volume, fade, pitch shift, silence removal, noise reduction, merge/split, waveform, plus dedicated converters (M4A to MP3, AAC to MP3, OGG to WAV, and more)
- **Documents / PDF (28):** merge, split, compress, convert (Word/Excel/PowerPoint/EPUB), protect/unlock, redact, watermark, page numbers, OCR, plus PDF to JPG/PNG/TIFF
- **PDF (29):** merge, split, compress, convert, protect/unlock, redact, sign, watermark, page numbers, OCR, plus PDF to JPG/PNG/TIFF
- **Files (23):** CSV/JSON/XML/YAML conversion, CSV merge/split, Excel to CSV, chart maker, ZIP create/extract
- **Image editor:** Layer-based editor with brushes, shapes, adjustments, filters, curves, and keyboard shortcuts. Runs in your browser, processes on your hardware
- **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
+4960 -45
View File
File diff suppressed because it is too large Load Diff
+14 -2
View File
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import scalarPlugin from "@scalar/fastify-api-reference";
import { SECTIONS, TOOLS, toolSection } from "@snapotter/shared";
import type { FastifyInstance } from "fastify";
import yaml from "js-yaml";
@@ -58,10 +59,21 @@ function generateLlmsTxt(spec: OpenAPISpec): string {
lines.push(`- ${tag.name} (${count} endpoints): ${tag.description || ""}`);
}
lines.push("");
lines.push("## Tools");
for (const section of SECTIONS) {
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";
lines.push(` - ${tool.name} - ${tool.description} (${tool.id}, ${mode})`);
}
}
lines.push("");
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("- Session token via `POST /api/auth/login` -> `Authorization: Bearer <token>`");
lines.push("- API key (prefixed `si_`) -> `Authorization: Bearer si_...`");
return lines.join("\n");
}
+2
View File
@@ -135,6 +135,7 @@ export default defineConfig({
{
text: "Tools",
items: [
{ text: "Conversion Presets", link: "/tools/conversion-presets" },
{
text: "Image",
collapsed: false,
@@ -325,6 +326,7 @@ export default defineConfig({
{ text: "PDF Page Numbers", link: "/tools/pdf/pdf-page-numbers" },
{ text: "Flatten PDF", link: "/tools/pdf/flatten-pdf" },
{ text: "Redact PDF", link: "/tools/pdf/redact-pdf" },
{ text: "Sign PDF", link: "/tools/pdf/sign-pdf" },
{ text: "PDF to Text", link: "/tools/pdf/pdf-to-text" },
{ text: "PDF to Word", link: "/tools/pdf/pdf-to-word" },
{ text: "PDF Metadata", link: "/tools/pdf/pdf-metadata" },
+12 -1
View File
@@ -110,6 +110,14 @@ curl -X POST http://localhost:1349/api/v1/tools/<section>/<toolId>/batch \
## Tools Reference
### Conversion Presets
The shared catalog includes 83 dedicated conversion preset endpoints such as `jpg-to-png`, `mov-to-mp4`, `m4a-to-mp3`, `pdf-to-jpg`, and `excel-to-csv`. Presets are first-class tool routes:
`POST /api/v1/tools/<section>/<presetId>`
Each preset locks the output format and delegates to a base tool such as `convert`, `convert-video`, `extract-audio`, `convert-audio`, `image-to-pdf`, `pdf-to-image`, `svg-to-raster`, or `convert-spreadsheet`. See [Conversion Presets](/tools/conversion-presets) for the complete route table and optional settings.
### Essentials
| Tool ID | Name | Key settings |
@@ -161,7 +169,7 @@ All AI tools run on your hardware: CPU by default, or NVIDIA CUDA when a support
| `noise-removal` | Noise Removal | Tiered denoising | `tier` (quick/balanced/quality/maximum), `strength`, `detailPreservation`, `colorNoise`, `format`, `quality` |
| `red-eye-removal` | Red Eye Removal | Face landmark + color analysis | `sensitivity`, `strength` |
| `restore-photo` | Photo Restoration | Multi-step pipeline | `mode` (auto/light/heavy), `scratchRemoval`, `faceEnhancement`, `fidelity`, `denoise`, `denoiseStrength`, `colorize` |
| `passport-photo` | Passport Photo | MediaPipe landmarks | `country` (37 countries), `printLayout` (4x6/A4/none), `backgroundColor` |
| `passport-photo` | Passport Photo | MediaPipe landmarks | Two-phase flow. Analyze uses multipart `file`; generate uses JSON with `countryCode`, `bgColor`, `printLayout` (none/4x6/a4), landmarks, image dimensions |
| `content-aware-resize` | Content-Aware Resize | Seam carving (caire) | `width`, `height`, `protectFaces`, `blurRadius`, `sobelThreshold`, `square` |
| `transparency-fixer` | PNG Transparency Fixer | BiRefNet HR-matting | `defringe` (0-100), `outputFormat` (png/webp) |
| `background-replace` | Background Replace | rembg (BiRefNet) | `backgroundType` (color/gradient), `color` (hex), `gradientColor1`, `gradientColor2`, `gradientAngle`, `feather` (0-20), `format` (png/webp) |
@@ -297,6 +305,7 @@ All AI tools run on your hardware: CPU by default, or NVIDIA CUDA when a support
| `pdf-page-numbers` | PDF Page Numbers | `position` (bl/bc/br/tl/tc/tr), `fontSize` |
| `flatten-pdf` | Flatten PDF | - (bakes forms and annotations) |
| `redact-pdf` | Redact PDF | `terms` (string[]), `caseSensitive` (bool) |
| `sign-pdf` | Sign PDF | Custom multipart route with PDF `file`, signature files `sig0`, `sig1`, and `placements` JSON array |
| `pdf-to-text` | PDF to Text | - |
| `pdf-to-word` | PDF to Word | - |
| `pdf-metadata` | PDF Metadata | `title`, `author`, `subject`, `keywords` |
@@ -373,6 +382,7 @@ Some tools expose additional endpoints beyond the standard `POST /api/v1/tools/<
| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/api/v1/tools/popular` | Return popular tool IDs, falling back to a curated default list when usage data is sparse |
| `POST` | `/api/v1/tools/image/remove-background/effects` | Apply background effects (color/gradient/blur/shadow) without re-running AI. Uses cached mask from initial removal. |
| `POST` | `/api/v1/tools/image/edit-metadata/inspect` | Read existing EXIF/IPTC/XMP metadata from an image |
| `POST` | `/api/v1/tools/image/strip-metadata/inspect` | Inspect metadata fields before stripping |
@@ -464,6 +474,7 @@ Persistent file storage with version history.
| `GET` | `/api/v1/files/:id/download` | Download file |
| `GET` | `/api/v1/files/:id/thumbnail` | Get 300px JPEG thumbnail |
| `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/download/:jobId/:filename` | Download a processed file from a workspace |
+2
View File
@@ -208,6 +208,8 @@
/tools/red-eye-removal.html /tools/image/red-eye-removal 301
/tools/redact-pdf /tools/pdf/redact-pdf 301
/tools/redact-pdf.html /tools/pdf/redact-pdf 301
/tools/sign-pdf /tools/pdf/sign-pdf 301
/tools/sign-pdf.html /tools/pdf/sign-pdf 301
/tools/remove-background /tools/image/remove-background 301
/tools/remove-background.html /tools/image/remove-background 301
/tools/remove-pages /tools/pdf/remove-pages 301
+127
View File
@@ -0,0 +1,127 @@
---
description: Dedicated conversion preset endpoints generated from the SnapOtter tool catalog.
---
# Conversion Presets
SnapOtter exposes 83 dedicated conversion preset endpoints in addition to the base converter tools. Each preset locks the output format and delegates to its base processing pipeline, so the behavior, validation, and output contract match the base tool listed below.
## API Endpoint Pattern
`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`.
## Image Presets
| Preset ID | Converts | Route | Base tool | Accepted inputs | Optional settings |
|-----------|----------|-------|-----------|-----------------|-------------------|
| `jpg-to-png` | JPG to PNG | `/api/v1/tools/image/jpg-to-png` | `convert` | `.jpg`, `.jpeg` | quality |
| `png-to-jpg` | PNG to JPG | `/api/v1/tools/image/png-to-jpg` | `convert` | `.png` | quality |
| `jpg-to-webp` | JPG to WebP | `/api/v1/tools/image/jpg-to-webp` | `convert` | `.jpg`, `.jpeg` | quality |
| `png-to-webp` | PNG to WebP | `/api/v1/tools/image/png-to-webp` | `convert` | `.png` | quality |
| `webp-to-jpg` | WebP to JPG | `/api/v1/tools/image/webp-to-jpg` | `convert` | `.webp` | quality |
| `webp-to-png` | WebP to PNG | `/api/v1/tools/image/webp-to-png` | `convert` | `.webp` | quality |
| `jpg-to-avif` | JPG to AVIF | `/api/v1/tools/image/jpg-to-avif` | `convert` | `.jpg`, `.jpeg` | quality |
| `png-to-avif` | PNG to AVIF | `/api/v1/tools/image/png-to-avif` | `convert` | `.png` | quality |
| `webp-to-avif` | WebP to AVIF | `/api/v1/tools/image/webp-to-avif` | `convert` | `.webp` | quality |
| `heic-to-jpg` | HEIC to JPG | `/api/v1/tools/image/heic-to-jpg` | `convert` | `.heic`, `.heif` | quality |
| `heic-to-png` | HEIC to PNG | `/api/v1/tools/image/heic-to-png` | `convert` | `.heic`, `.heif` | quality |
| `heic-to-avif` | HEIC to AVIF | `/api/v1/tools/image/heic-to-avif` | `convert` | `.heic`, `.heif` | quality |
| `jpg-to-gif` | JPG to GIF | `/api/v1/tools/image/jpg-to-gif` | `convert` | `.jpg`, `.jpeg` | quality |
| `png-to-gif` | PNG to GIF | `/api/v1/tools/image/png-to-gif` | `convert` | `.png` | quality |
| `gif-to-jpg` | GIF to JPG | `/api/v1/tools/image/gif-to-jpg` | `convert` | `.gif` | quality |
| `gif-to-png` | GIF to PNG | `/api/v1/tools/image/gif-to-png` | `convert` | `.gif` | quality |
| `webp-to-gif` | WebP to GIF | `/api/v1/tools/image/webp-to-gif` | `convert` | `.webp` | quality |
| `jpg-to-tiff` | JPG to TIFF | `/api/v1/tools/image/jpg-to-tiff` | `convert` | `.jpg`, `.jpeg` | quality |
| `png-to-tiff` | PNG to TIFF | `/api/v1/tools/image/png-to-tiff` | `convert` | `.png` | quality |
| `tiff-to-jpg` | TIFF to JPG | `/api/v1/tools/image/tiff-to-jpg` | `convert` | `.tiff`, `.tif` | quality |
| `tiff-to-png` | TIFF to PNG | `/api/v1/tools/image/tiff-to-png` | `convert` | `.tiff`, `.tif` | quality |
| `psd-to-jpg` | PSD to JPG | `/api/v1/tools/image/psd-to-jpg` | `convert` | `.psd` | quality |
| `psd-to-png` | PSD to PNG | `/api/v1/tools/image/psd-to-png` | `convert` | `.psd` | quality |
| `png-to-eps` | PNG to EPS | `/api/v1/tools/image/png-to-eps` | `convert` | `.png` | quality |
| `jpg-to-eps` | JPG to EPS | `/api/v1/tools/image/jpg-to-eps` | `convert` | `.jpg`, `.jpeg` | quality |
| `eps-to-png` | EPS to PNG | `/api/v1/tools/image/eps-to-png` | `convert` | `.eps` | quality |
| `eps-to-jpg` | EPS to JPG | `/api/v1/tools/image/eps-to-jpg` | `convert` | `.eps` | quality |
| `png-to-svg` | PNG to SVG | `/api/v1/tools/image/png-to-svg` | `vectorize` | `.png` | none |
| `jpg-to-svg` | JPG to SVG | `/api/v1/tools/image/jpg-to-svg` | `vectorize` | `.jpg`, `.jpeg` | none |
| `tiff-to-svg` | TIFF to SVG | `/api/v1/tools/image/tiff-to-svg` | `vectorize` | `.tiff`, `.tif` | none |
| `psd-to-svg` | PSD to SVG | `/api/v1/tools/image/psd-to-svg` | `vectorize` | `.psd` | none |
| `eps-to-svg` | EPS to SVG | `/api/v1/tools/image/eps-to-svg` | `vectorize` | `.eps` | none |
| `svg-to-png` | SVG to PNG | `/api/v1/tools/image/svg-to-png` | `svg-to-raster` | `.svg`, `.svgz` | quality, width, height, dpi, backgroundColor |
| `svg-to-jpg` | SVG to JPG | `/api/v1/tools/image/svg-to-jpg` | `svg-to-raster` | `.svg`, `.svgz` | quality, width, height, dpi, backgroundColor |
| `jpg-to-pdf` | JPG to PDF | `/api/v1/tools/image/jpg-to-pdf` | `image-to-pdf` | `.jpg`, `.jpeg` | pageSize, orientation, margin, targetSize, collate |
| `png-to-pdf` | PNG to PDF | `/api/v1/tools/image/png-to-pdf` | `image-to-pdf` | `.png` | pageSize, orientation, margin, targetSize, collate |
| `heic-to-pdf` | HEIC to PDF | `/api/v1/tools/image/heic-to-pdf` | `image-to-pdf` | `.heic`, `.heif` | pageSize, orientation, margin, targetSize, collate |
| `tiff-to-pdf` | TIFF to PDF | `/api/v1/tools/image/tiff-to-pdf` | `image-to-pdf` | `.tiff`, `.tif` | pageSize, orientation, margin, targetSize, collate |
| `webp-to-pdf` | WebP to PDF | `/api/v1/tools/image/webp-to-pdf` | `image-to-pdf` | `.webp` | pageSize, orientation, margin, targetSize, collate |
| `gif-to-pdf` | GIF to PDF | `/api/v1/tools/image/gif-to-pdf` | `image-to-pdf` | `.gif` | pageSize, orientation, margin, targetSize, collate |
| `eps-to-pdf` | EPS to PDF | `/api/v1/tools/image/eps-to-pdf` | `image-to-pdf` | `.eps` | pageSize, orientation, margin, targetSize, collate |
## Video Presets
| Preset ID | Converts | Route | Base tool | Accepted inputs | Optional settings |
|-----------|----------|-------|-----------|-----------------|-------------------|
| `mov-to-mp4` | MOV to MP4 | `/api/v1/tools/video/mov-to-mp4` | `convert-video` | `.mov` | quality |
| `webm-to-mp4` | WEBM to MP4 | `/api/v1/tools/video/webm-to-mp4` | `convert-video` | `.webm` | quality |
| `mkv-to-mp4` | MKV to MP4 | `/api/v1/tools/video/mkv-to-mp4` | `convert-video` | `.mkv` | quality |
| `avi-to-mp4` | AVI to MP4 | `/api/v1/tools/video/avi-to-mp4` | `convert-video` | `.avi` | quality |
| `mp4-to-mov` | MP4 to MOV | `/api/v1/tools/video/mp4-to-mov` | `convert-video` | `.mp4` | quality |
| `mp4-to-webm` | MP4 to WEBM | `/api/v1/tools/video/mp4-to-webm` | `convert-video` | `.mp4` | quality |
| `webm-to-mov` | WEBM to MOV | `/api/v1/tools/video/webm-to-mov` | `convert-video` | `.webm` | quality |
| `mkv-to-mov` | MKV to MOV | `/api/v1/tools/video/mkv-to-mov` | `convert-video` | `.mkv` | quality |
| `avi-to-mov` | AVI to MOV | `/api/v1/tools/video/avi-to-mov` | `convert-video` | `.avi` | quality |
| `mp4-to-avi` | MP4 to AVI | `/api/v1/tools/video/mp4-to-avi` | `convert-video` | `.mp4` | quality |
| `mov-to-avi` | MOV to AVI | `/api/v1/tools/video/mov-to-avi` | `convert-video` | `.mov` | quality |
| `mkv-to-avi` | MKV to AVI | `/api/v1/tools/video/mkv-to-avi` | `convert-video` | `.mkv` | quality |
| `avi-to-mkv` | AVI to MKV | `/api/v1/tools/video/avi-to-mkv` | `convert-video` | `.avi` | quality |
| `mp4-to-gif` | MP4 to GIF | `/api/v1/tools/video/mp4-to-gif` | `video-to-gif` | `.mp4` | fps, width, startS, durationS |
| `mov-to-gif` | MOV to GIF | `/api/v1/tools/video/mov-to-gif` | `video-to-gif` | `.mov` | fps, width, startS, durationS |
| `mkv-to-gif` | MKV to GIF | `/api/v1/tools/video/mkv-to-gif` | `video-to-gif` | `.mkv` | fps, width, startS, durationS |
| `avi-to-gif` | AVI to GIF | `/api/v1/tools/video/avi-to-gif` | `video-to-gif` | `.avi` | fps, width, startS, durationS |
| `gif-to-mp4` | GIF to MP4 | `/api/v1/tools/video/gif-to-mp4` | `gif-to-video` | `.gif` | none |
| `gif-to-webm` | GIF to WEBM | `/api/v1/tools/video/gif-to-webm` | `gif-to-video` | `.gif` | none |
| `gif-to-mov` | GIF to MOV | `/api/v1/tools/video/gif-to-mov` | `gif-to-video` | `.gif` | none |
| `mp4-to-mp3` | MP4 to MP3 | `/api/v1/tools/video/mp4-to-mp3` | `extract-audio` | `.mp4` | none |
| `mov-to-mp3` | MOV to MP3 | `/api/v1/tools/video/mov-to-mp3` | `extract-audio` | `.mov` | none |
| `mkv-to-mp3` | MKV to MP3 | `/api/v1/tools/video/mkv-to-mp3` | `extract-audio` | `.mkv` | none |
| `webm-to-mp3` | WEBM to MP3 | `/api/v1/tools/video/webm-to-mp3` | `extract-audio` | `.webm` | none |
| `avi-to-mp3` | AVI to MP3 | `/api/v1/tools/video/avi-to-mp3` | `extract-audio` | `.avi` | none |
| `mp4-to-wav` | MP4 to WAV | `/api/v1/tools/video/mp4-to-wav` | `extract-audio` | `.mp4` | none |
| `mov-to-wav` | MOV to WAV | `/api/v1/tools/video/mov-to-wav` | `extract-audio` | `.mov` | none |
| `mp4-to-ogg` | MP4 to OGG | `/api/v1/tools/video/mp4-to-ogg` | `extract-audio` | `.mp4` | none |
## Audio Presets
| Preset ID | Converts | Route | Base tool | Accepted inputs | Optional settings |
|-----------|----------|-------|-----------|-----------------|-------------------|
| `m4a-to-mp3` | M4A to MP3 | `/api/v1/tools/audio/m4a-to-mp3` | `convert-audio` | `.m4a` | none |
| `m4a-to-wav` | M4A to WAV | `/api/v1/tools/audio/m4a-to-wav` | `convert-audio` | `.m4a` | none |
| `aac-to-mp3` | AAC to MP3 | `/api/v1/tools/audio/aac-to-mp3` | `convert-audio` | `.aac` | none |
| `aac-to-wav` | AAC to WAV | `/api/v1/tools/audio/aac-to-wav` | `convert-audio` | `.aac` | none |
| `aac-to-flac` | AAC to FLAC | `/api/v1/tools/audio/aac-to-flac` | `convert-audio` | `.aac` | none |
| `ogg-to-mp3` | OGG to MP3 | `/api/v1/tools/audio/ogg-to-mp3` | `convert-audio` | `.ogg` | none |
| `ogg-to-wav` | OGG to WAV | `/api/v1/tools/audio/ogg-to-wav` | `convert-audio` | `.ogg` | none |
| `wav-to-mp3` | WAV to MP3 | `/api/v1/tools/audio/wav-to-mp3` | `convert-audio` | `.wav` | none |
| `mp3-to-wav` | MP3 to WAV | `/api/v1/tools/audio/mp3-to-wav` | `convert-audio` | `.mp3` | none |
| `flac-to-mp3` | FLAC to MP3 | `/api/v1/tools/audio/flac-to-mp3` | `convert-audio` | `.flac` | none |
## PDF Presets
| Preset ID | Converts | Route | Base tool | Accepted inputs | Optional settings |
|-----------|----------|-------|-----------|-----------------|-------------------|
| `pdf-to-jpg` | PDF to JPG | `/api/v1/tools/pdf/pdf-to-jpg` | `pdf-to-image` | `.pdf` | dpi, quality, colorMode, pages |
| `pdf-to-png` | PDF to PNG | `/api/v1/tools/pdf/pdf-to-png` | `pdf-to-image` | `.pdf` | dpi, quality, colorMode, pages |
| `pdf-to-tiff` | PDF to TIFF | `/api/v1/tools/pdf/pdf-to-tiff` | `pdf-to-image` | `.pdf` | dpi, quality, colorMode, pages |
## Files Presets
| Preset ID | Converts | Route | Base tool | Accepted inputs | Optional settings |
|-----------|----------|-------|-----------|-----------------|-------------------|
| `excel-to-csv` | Excel to CSV | `/api/v1/tools/files/excel-to-csv` | `convert-spreadsheet` | `.xlsx`, `.xls` | none |
## Notes
- Presets are first-class API endpoints and are also valid in batch requests where their base route supports batch processing.
- Presets that use video conversion can return `202 Accepted`; connect to the job progress SSE endpoint before downloading the result.
- For advanced options not exposed by a preset, call the base converter tool directly and set the output format in `settings`.
+4 -4
View File
@@ -10,7 +10,7 @@ AI-powered passport and ID photo generator. Two-phase workflow: analyze (face de
This tool uses a two-phase flow with separate endpoints for analysis and generation.
**Model bundle:** `background-removal` (4-5 GB)
**Model bundles:** `background-removal` and `face-detection`
---
@@ -88,7 +88,7 @@ Crops, resizes, and optionally tiles the photo onto a print sheet. Uses cached i
| countryCode | string | Yes | - | Country code for passport spec (e.g., `US`, `GB`, `IN`) |
| documentType | string | No | `"passport"` | Document type (from country spec) |
| bgColor | string | No | `"#FFFFFF"` | Background color hex |
| printLayout | string | No | `"none"` | Print paper layout: `none`, `4x6`, `a4`, `letter` |
| printLayout | string | No | `"none"` | Print paper layout: `none`, `4x6`, `a4` |
| maxFileSizeKb | number | No | `0` | Max file size constraint in KB (0 = no limit) |
| dpi | number | No | `300` | Output DPI (72-1200) |
| customWidthMm | number | No | - | Custom photo width in mm (overrides country spec) |
@@ -161,10 +161,10 @@ Returns guidance to use the correct sub-endpoint.
## Notes
- Requires the `background-removal` model bundle to be installed (4-5 GB).
- Requires the `background-removal` and `face-detection` model bundles to be installed.
- Phase 1 runs AI (face landmarks + background removal) and caches results. Phase 2 is pure Sharp image manipulation (fast, no AI needed).
- Landmarks are returned as normalized coordinates (0-1 range relative to image dimensions).
- The `preview` field in the analyze response is a base64-encoded PNG (max 800px wide) for fast display.
- Country specs include document dimensions, head height ratios, and eye-line positioning based on official passport photo requirements.
- The `printLayout` option generates a tiled sheet on standard paper sizes (4x6", A4, Letter) with 2mm gutters between photos.
- The `printLayout` option generates a tiled sheet on 4x6" or A4 paper with 2mm gutters between photos.
- When `maxFileSizeKb` is set, the output is iteratively compressed to fit within the size limit.
+76
View File
@@ -0,0 +1,76 @@
---
description: Stamp uploaded signature images onto a PDF using normalized page placements.
---
# Sign PDF
Stamp one or more uploaded signature PNG images onto any page of a PDF. This route uses a custom multipart contract because it needs the PDF, one or more signature images, and placement coordinates.
## API Endpoint
`POST /api/v1/tools/pdf/sign-pdf`
Accepts multipart form data. The PDF is sent as `file`; signatures are sent as `sig0`, `sig1`, and so on; placements are sent in a `placements` JSON field.
## Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| file | file | Yes | - | PDF file to sign |
| sig0 | file | Yes | - | First signature image. Additional images use `sig1`, `sig2`, and so on |
| placements | JSON string | Yes | - | Array of placement objects: `{ "sig": 0, "page": 0, "x": 0.2, "y": 0.7, "w": 0.25, "h": 0.08 }` |
| clientJobId | string | No | - | Optional UUID for progress tracking via SSE |
| fileId | string | No | - | Optional file library ID to save the signed result as a new version |
## Placement Coordinates
| Field | Type | Description |
|-------|------|-------------|
| sig | integer | Signature image index. `0` maps to `sig0` |
| page | integer | Zero-based PDF page index |
| x | number | Left position as a page fraction |
| y | number | Top position as a page fraction |
| w | number | Signature width as a page fraction |
| h | number | Signature height as a page fraction |
Coordinates use a top-left origin. Values may bleed slightly beyond the page edge; the PDF renderer clips the final stamp to the page.
## Example Request
```bash
curl -X POST http://localhost:1349/api/v1/tools/pdf/sign-pdf \
-H "Authorization: Bearer si_your-api-key" \
-F "file=@contract.pdf" \
-F "sig0=@signature.png" \
-F 'placements=[{"sig":0,"page":0,"x":0.64,"y":0.82,"w":0.22,"h":0.08}]'
```
## Example Response
```json
{
"jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"downloadUrl": "/api/v1/download/a1b2c3d4-e5f6-7890-abcd-ef1234567890/contract_signed.pdf",
"previewUrl": "/api/v1/download/a1b2c3d4-e5f6-7890-abcd-ef1234567890/preview.png",
"originalSize": 245000,
"processedSize": 249000
}
```
If the request cannot finish inside the synchronous wait window, the API returns:
```json
{
"jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"async": true
}
```
Connect to `/api/v1/jobs/<jobId>/progress` and download the result when the job completes.
## Notes
- Accepted PDF input format: `.pdf`.
- Signature images must be valid image files, typically PNG with transparency.
- Up to 100 signature images and 100 placements are accepted.
- `sign-pdf` is a custom route and does not use the standard tool `settings` JSON field.
+12 -2
View File
@@ -204,9 +204,11 @@ FROM node:22-bookworm@sha256:c601a46abb4d2ab80a9dc3da208d50d1122642d53f17a101926
# ============================================
ARG TARGETOS
ARG TARGETARCH
ARG PANDOC_VERSION=3.10
FROM base-${TARGETOS}-${TARGETARCH} AS production
ARG TARGETARCH
ARG PANDOC_VERSION
# Pin corepack's cache to a system-wide path so all users share the same pnpm
# binary without downloading it on each container start.
@@ -248,9 +250,8 @@ RUN for i in 1 2 3; do apt-get -o Acquire::Retries=3 update && break || sleep $(
python3 python3-pip python3-venv python3-dev \
tesseract-ocr tesseract-ocr-eng tesseract-ocr-deu tesseract-ocr-fra tesseract-ocr-spa \
tesseract-ocr-chi-sim tesseract-ocr-jpn tesseract-ocr-kor \
# Document engine: qpdf + LibreOffice headless + pandoc + WeasyPrint runtime deps
# Document engine: qpdf + LibreOffice headless + WeasyPrint runtime deps
# calibre deferred (5.2 GB ruling; pandoc covers epub/markdown families)
pandoc \
qpdf \
libpango-1.0-0 libpangocairo-1.0-0 libcairo2 libgdk-pixbuf-2.0-0 \
fonts-dejavu-core \
@@ -272,6 +273,15 @@ RUN for i in 1 2 3; do apt-get -o Acquire::Retries=3 update && break || sleep $(
&& if apt-cache show libcublas-12-6 >/dev/null 2>&1; then \
apt-get install -y --no-install-recommends libcublas-12-6; \
fi \
&& case "$TARGETARCH" in \
amd64) PANDOC_ARCH=amd64 ;; \
arm64) PANDOC_ARCH=arm64 ;; \
*) echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 1 ;; \
esac \
&& curl -fsSL "https://github.com/jgm/pandoc/releases/download/${PANDOC_VERSION}/pandoc-${PANDOC_VERSION}-1-${PANDOC_ARCH}.deb" -o /tmp/pandoc.deb \
&& apt-get install -y --no-install-recommends /tmp/pandoc.deb \
&& rm -f /tmp/pandoc.deb \
&& pandoc --version \
&& rm -rf /var/lib/apt/lists/*
# Embedded-mode databases: PostgreSQL 17 (PGDG, to match the Compose postgres:17
+3 -4
View File
@@ -56,7 +56,7 @@ Transcribe Audio, Convert Audio, Trim Audio, Volume Adjust, Normalize Audio, Fad
### PDF
PDF OCR, PDF to Image, Merge PDFs, Split PDF, Compress PDF, Rotate PDF, Extract Pages, Remove Pages, Organize PDF, Protect PDF, Unlock PDF, Repair PDF, Web-Optimize PDF, Grayscale PDF, PDF/A Convert, Crop PDF, N-up PDF, Booklet PDF, Watermark PDF, PDF Page Numbers, Flatten PDF, Redact PDF, PDF to Text, PDF to Word, PDF Metadata, PDF to JPG, PDF to PNG, PDF to TIFF.
PDF OCR, PDF to Image, Merge PDFs, Split PDF, Compress PDF, Rotate PDF, Extract Pages, Remove Pages, Organize PDF, Protect PDF, Unlock PDF, Repair PDF, Web-Optimize PDF, Grayscale PDF, PDF/A Convert, Crop PDF, N-up PDF, Booklet PDF, Watermark PDF, PDF Page Numbers, Flatten PDF, Redact PDF, Sign PDF, PDF to Text, PDF to Word, PDF Metadata, PDF to JPG, PDF to PNG, PDF to TIFF.
### Files
@@ -69,10 +69,9 @@ Background removal (rembg), image upscaling 2x/4x (RealESRGAN), object eraser (L
## REST API
Base URL: http://localhost:1349/api/v1
Interactive docs: http://localhost:1349/api/docs (Swagger UI)
Machine-readable spec: http://localhost:1349/api/docs/json (OpenAPI 3.1)
Interactive docs: http://localhost:1349/api/docs (Scalar UI)
OpenAPI YAML: http://localhost:1349/api/v1/openapi.yaml
LLM-optimized spec: http://localhost:1349/llms.txt (generated from OpenAPI at runtime)
Runtime LLM summary: http://localhost:1349/llms.txt
Full plain-text API docs: http://localhost:1349/llms-full.txt
Authentication methods:
+39 -6
View File
@@ -1,3 +1,4 @@
import { apiToolPath, TOOLS } from "@snapotter/shared";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { buildTestApp, type TestApp } from "../test-server";
@@ -12,6 +13,10 @@ describe("API docs", () => {
await testApp.cleanup();
});
function openApiPathSet(body: string): Set<string> {
return new Set([...body.matchAll(/^ {2}(\/[^:]+):/gm)].map((match) => match[1]));
}
it("serves the OpenAPI spec as YAML", async () => {
const res = await testApp.app.inject({
method: "GET",
@@ -54,15 +59,43 @@ describe("API docs", () => {
expect(res.headers["content-type"]).toContain("text/html");
});
it("includes all tool endpoints in the spec", async () => {
it("includes every catalog tool endpoint in the spec", async () => {
const res = await testApp.app.inject({
method: "GET",
url: "/api/v1/openapi.yaml",
});
const body = res.body;
expect(body).toContain("/api/v1/tools/image/resize");
expect(body).toContain("/api/v1/tools/image/compress");
expect(body).toContain("/api/v1/tools/image/remove-background");
expect(body).toContain("/api/v1/tools/image/ocr");
const paths = openApiPathSet(res.body);
const missing = TOOLS.map((tool) => apiToolPath(tool.id)).filter((path) => !paths.has(path));
expect(missing, `OpenAPI missing tool paths: ${missing.join(", ")}`).toEqual([]);
});
it("documents public docs metadata routes", async () => {
const res = await testApp.app.inject({
method: "GET",
url: "/api/v1/openapi.yaml",
});
const paths = openApiPathSet(res.body);
const expectedPaths = [
"/llms.txt",
"/llms-full.txt",
"/api/v1/openapi.yaml",
"/api/v1/tools/popular",
];
const missing = expectedPaths.filter((path) => !paths.has(path));
expect(missing, `OpenAPI missing docs metadata paths: ${missing.join(", ")}`).toEqual([]);
});
it("serves an LLM summary with live catalog tools", async () => {
const res = await testApp.app.inject({
method: "GET",
url: "/llms.txt",
});
expect(res.statusCode).toBe(200);
expect(res.body).toContain("## Tools");
expect(res.body).toContain("- Image (105 tools)");
expect(res.body).toContain("Resize - Resize by pixels");
expect(res.body).toContain("Sign PDF -");
});
});