snapwonders-com Convert API
**Convert images, videos, and PDFs to different formats.** Supports format conversion, quality control, image resize, video resolution cap, metadata stripping, and PDF-to-image rendering. All endpoints require `X-Api-Key`. **Workflow — 6 steps:** **Step 1 — Create a session** `POST /api/convert/session` Returns `upload_uid` — include it in every upload for this session. **Step 2 — Upload files to convert** `POST /api/upload` · headers `X-Upload-Uid`, `X-Filename`, `Content-Type`, file as the body. One request per file. Large files or unreliable connections: TUS `POST /api/tus` with `Upload-Metadata: upload_uid ,step MQ==` instead. Supported types: images (JPEG, PNG, WebP, AVIF, HEIC, JXL), videos (MP4, MKV, WebM, MOV), PDFs. **Step 3 — Verify all uploads are complete** `GET /api/convert/session/{uid}/files` All files must show `"status":"completed"` before proceeding. **Step 4 — Start the conversion job** `POST /api/convert/job` · Required: `upload_uid` Optional: `image_format`, `video_format`, `image_quality`, `video_crf`, `video_max_dimension`, `strip_metadata`, `image_resize_width`, `image_resize_height`, `pdf_dpi`, `pdf_page_from`, `pdf_page_to`, `expiry` Returns `{"status":"queued", "job_uid": "..."}`. **Step 5 — Poll until done** `GET /api/convert/job/{uid}` — repeat every few seconds. Watch `status`: `queued` → `processing` → `completed` (or `partial` / `failed`). **Step 6 — Download converted files** `GET /api/convert/job/{uid}/results` Returns `result_items` with a `download_url` for each completed item. `GET /api/convert/download/{itemUid}` — streams the output file (requires same `X-Api-Key`).
POST
/api/convert/session
Create a media convert session
#
GET
/api/convert/session/{uid}/files
List uploaded files for a convert session
#
POST
/api/convert/job
Start a media conversion job
#
GET
/api/convert/job/{uid}
Get convert job status
#
GET
/api/convert/job/{uid}/results
Get convert job results
#
GET
/api/convert/download/{assetId}
Download a converted output file
#
Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: snapWONDERS API / MCP Convert API
description: 'The snapWONDERS API is available over **Clearnet**, **Tor** and **I2P** (dark web). All endpoints and this Swagger UI work identically on all three networks. ---
## Authentication
All endpoints **except `GET /api/status`, `OPTIONS /api/tus` and the no-auth MCP methods (`initialize`, `tools/list`, `status`, `*_get_started`)** require an API key, sent in the `X-Api-Key` request header (or as `Authorization: Bearer `):
```
X-Api-Key:
```
Obtain an API key from your **snapWONDERS account profile** once you have a registered account. Keys begin with `sw_` and are hashed server-side — store the raw key immediately after generation as it cannot be retrieved again. ---
## SDKs — skip the choreography
Everything below this section is the **raw HTTP contract**. You do not have to write it yourself. A full run means: create a session → upload each file → start a job → poll until it finishes → fetch the result URLs → download. Uploading is one `POST /api/upload` per file, so the tedious part is the polling and the plumbing rather than the transfer — unless your files are large enough to need the resumable protocol, which is genuinely fiddly. The **snapWONDERS SDKs** collapse all of it into one call. The first two examples do the **same thing** in each language — hide `secret.pdf` inside `cover.jpg` (Vaultify stego) — and the third shows forensic analysis. Each block is self-contained, so it runs as-is. ### Python — hide a file, then download the result
```python
from snapwonders import Client
client = Client(api_key="sw_...")
# The last path is the cover; everything before it is a secret. job = client.stego.hide(["secret.pdf", "cover.jpg"], password="Str0ng!Pass")
for result in job.results():
result.download("out/")
```
### JavaScript / TypeScript — the same hide, then download
```javascript
import { Client } from "@snapwonders/sdk";
const client = new Client("sw_...");
// The last path is the cover; everything before it is a secret. const job = await client.stego.hide(["secret.pdf", "cover.jpg"], { password: "Str0ng!Pass" });
for (const result of await job.results()) {
await result.download("out/");
}
```
### Python — forensic analysis, graded A–F
```python
from snapwonders import Client
client = Client(api_key="sw_...")
# Grade each file A–F and collect the overlays the analysis produced. job = client.analyse.run(["photo.jpg"], face_detection=True)
for item in job.results():
print(item.filename, item.grade, item.face_count)
for asset in item.assets: # ELA map, face overlay, …
asset.download("out/")
```
In JavaScript that is the same call — `await client.analyse.run(["photo.jpg"], { face_detection: true })` — with the result fields in camelCase (`item.faceCount`). Sessions, TUS chunking and resume, polling until the job finishes, retries on transient failures, and typed errors are all handled for you. `client.convert` (media conversion) follows the same shape: one call in, results out. | Language | Package | Install | Source |
|----------|---------|---------|--------|
| Python (3.10+) | `snapwonders` | `pip install snapwonders` | GitHub |
| JavaScript / TypeScript (Node 18+) | `@snapwonders/sdk` | `npm install @snapwonders/sdk` | GitHub |
| PHP (8.2+) | `snapwonders/sdk` | `composer require snapwonders/sdk` | GitHub |
| Go (1.21+) | — | `go get github.com/snapWONDERS/snapWONDERS-SDK-Go` | GitHub |
| Ruby, Java, C#, … | generated from this spec | see the Developers hub | — |
**Status:** All four are **live**. Python and JavaScript via `pip install snapwonders` / `npm install @snapwonders/sdk`; PHP via `composer require snapwonders/sdk` on Packagist; Go straight from GitHub with `go get`. All four clients are **open source on GitHub** (Python, JavaScript / TypeScript, PHP, Go). See the **Developers hub** for full quickstarts and the generation recipe for other languages. Prefer the raw API — or working in a language without a client? Everything the SDKs do is documented below; nothing is hidden from you.'
version: 1.0.0
servers:
- url: https://snapwonders.com
description: Clearnet / Web
- url: http://swonderstzr43aczpcwdoyc25vwxngyromja7pyb5sf26ap3v535sxqd.onion
description: Tor / Dark Web
- url: http://6ymp5jqysizmejdwaqiehcsgjoyb4s7sbgssquishk66drujomka.b32.i2p
description: I2P / Dark Web
security:
- ApiKey: []
tags:
- name: Convert
description: '**Convert images, videos, and PDFs to different formats.** Supports format conversion, quality control, image resize, video resolution cap, metadata stripping, and PDF-to-image rendering. All endpoints require `X-Api-Key`.
**Workflow — 6 steps:**
**Step 1 — Create a session**
`POST /api/convert/session`
Returns `upload_uid` — include it in every upload for this session.
**Step 2 — Upload files to convert**
`POST /api/upload` · headers `X-Upload-Uid`, `X-Filename`, `Content-Type`, file as the body.
One request per file. Large files or unreliable connections: TUS `POST /api/tus` with `Upload-Metadata: upload_uid ,step MQ==` instead. Supported types: images (JPEG, PNG, WebP, AVIF, HEIC, JXL), videos (MP4, MKV, WebM, MOV), PDFs.
**Step 3 — Verify all uploads are complete**
`GET /api/convert/session/{uid}/files`
All files must show `"status":"completed"` before proceeding.
**Step 4 — Start the conversion job**
`POST /api/convert/job` · Required: `upload_uid`
Optional: `image_format`, `video_format`, `image_quality`, `video_crf`, `video_max_dimension`, `strip_metadata`, `image_resize_width`, `image_resize_height`, `pdf_dpi`, `pdf_page_from`, `pdf_page_to`, `expiry`
Returns `{"status":"queued", "job_uid": "..."}`.
**Step 5 — Poll until done**
`GET /api/convert/job/{uid}` — repeat every few seconds.
Watch `status`: `queued` → `processing` → `completed` (or `partial` / `failed`).
**Step 6 — Download converted files**
`GET /api/convert/job/{uid}/results`
Returns `result_items` with a `download_url` for each completed item.
`GET /api/convert/download/{itemUid}` — streams the output file (requires same `X-Api-Key`).'
paths:
/api/convert/session:
post:
tags:
- Convert
summary: Create a media convert session
description: Creates a new upload session for a media **convert** job. Returns the `upload_uid` you must include in every TUS upload request for this session. **Authentication:** `X-Api-Key` header required.
operationId: post_api_convert_session_create
requestBody:
required: false
content:
application/json:
schema:
properties: {}
responses:
'201':
description: Session created
content:
application/json:
schema:
properties:
upload_uid:
description: UUID to include in every TUS upload for this session
type: string
format: uuid
example: 550e8400-e29b-41d4-a716-446655440000
status:
type: string
example: pending
created_at:
type: string
format: date-time
tus_endpoint:
description: TUS root endpoint — append the upload ID for PATCH/HEAD requests
type: string
example: /api/tus
instructions:
description: Human-readable upload instructions
type: string
type: object
'401':
description: 'Not authenticated. `error` names the exact cause: `missing_api_key`, `malformed_api_key`, `invalid_api_key` (key unknown/revoked/expired), or — when the key itself is valid — `account_not_verified` / `account_inactive`.'
'429':
description: Too many upload sessions created for this API key inside the sliding window (`api.session_create_hard_limit` per `api.session_create_window_seconds`, default 120 per hour). Body is the same `throttled` shape as job start, with `sessions_created`, `limit` and `window_seconds` in place of `active_jobs`. Wait for the window to pass, then retry.
/api/convert/session/{uid}/files:
get:
tags:
- Convert
summary: List uploaded files for a convert session
description: Returns all files currently attached to a convert session. Use this to verify that all uploads show `"status":"completed"` before calling `POST /api/convert/job`. **Authentication:** `X-Api-Key` header required.
operationId: get_api_convert_session_files
parameters:
- name: uid
in: path
description: The `upload_uid` returned by `POST /api/convert/session`
required: true
schema:
type: string
format: uuid
pattern: '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'
example: 550e8400-e29b-41d4-a716-446655440000
responses:
'200':
description: File list
content:
application/json:
schema:
properties:
upload_uid:
type: string
format: uuid
session_status:
type: string
example: pending
files:
type: array
items:
properties:
name:
type: string
example: photo.jpg
mime_type:
type: string
example: image/jpeg
file_size:
description: File size in bytes
type: integer
status:
description: Upload status — must be `completed` before starting job
type: string
enum:
- pending
- completed
- failed
- staged
type: object
type: object
'401':
description: 'Not authenticated. `error` names the exact cause: `missing_api_key`, `malformed_api_key`, `invalid_api_key` (key unknown/revoked/expired), or — when the key itself is valid — `account_not_verified` / `account_inactive`.'
'404':
description: '`not_found` — What you''re looking for is not here.'
/api/convert/job:
post:
tags:
- Convert
summary: Start a media conversion job
description: 'Submits conversion options and dispatches the job to the worker queue. All files must be fully uploaded via TUS and show `"status":"completed"` (verify with `GET /api/convert/session/{uid}/files`) before calling this endpoint.
The call is **idempotent**: calling it again for a session that already has a job returns the existing job without creating a duplicate.
**Authentication:** `X-Api-Key` header required.'
operationId: post_api_convert_job_start
requestBody:
required: true
content:
application/json:
schema:
required:
- upload_uid
properties:
upload_uid:
description: The `upload_uid` returned by `POST /api/convert/session`
type: string
format: uuid
example: 550e8400-e29b-41d4-a716-446655440000
image_format:
description: 'Output format for image files. Default: `jpeg`.'
type: string
enum:
- jpeg
- png
- webp
- avif
- heic
- jxl
example: webp
video_format:
description: 'Output format for video files. Default: `mp4h264`.'
type: string
enum:
- mp4h264
- mp4h265
- webm
- mkv
- mov
example: mp4h264
image_quality:
description: 'JPEG/WebP output quality 1–100. Default: 85.'
type: integer
maximum: 100
minimum: 1
example: 85
video_crf:
description: 'Video CRF value 0–51 (lower = better quality). Default: 23.'
type: integer
maximum: 51
minimum: 0
example: 23
video_max_dimension:
description: Cap video resolution to this height in pixels. Omit or set to 0 to keep original resolution.
type: integer
enum:
- 480
- 720
- 1080
example: 1080
strip_metadata:
description: 'Strip EXIF/GPS/encoder metadata from output files. Default: `false`.'
type: boolean
example: false
image_resize_width:
description: Resize images to fit within this bounding-box width in pixels. Longest-edge fit, aspect ratio preserved, no upscaling. Omit for no resize.
type: integer
maximum: 10000
minimum: 1
example: 1920
image_resize_height:
description: Resize images to fit within this bounding-box height in pixels. Longest-edge fit, aspect ratio preserved, no upscaling. Omit for no resize.
type: integer
maximum: 10000
minimum: 1
example: 1080
pdf_dpi:
description: '**PDF only.** Render resolution in DPI. Default: 150.'
type: integer
maximum: 300
minimum: 72
example: 150
pdf_page_from:
description: '**PDF only.** First page to render, 1-based. Default: 1.'
type: integer
minimum: 1
example: 1
pdf_page_to:
description: '**PDF only.** Last page to render, 1-based. `0` means all pages. Default: 0.'
type: integer
minimum: 0
example: 0
expiry:
description: 'How long the result download links remain accessible after the job completes. `1h` = 1 hour, `12h` = 12 hours, `1d` = 1 day (default), `2d` = 2 days, `1w` = 1 week, `1m` = 1 month. **Free-tier keys are silently clamped** to `upload.free_max_expiry` (default `1d`): the job still succeeds, but anything longer is reduced. Pro accounts get the requested value. No response currently returns the granted expiry, so assume the free maximum unless the owning account is Pro.'
type: string
enum:
- 1h
- 12h
- 1d
- 2d
- 1w
- 1m
example: 1d
type: object
responses:
'201':
description: Job queued
content:
application/json:
schema:
properties:
upload_uid:
type: string
format: uuid
job_uid:
description: Job UUID — use with `GET /api/convert/job/{uid}` to poll status
type: string
format: uuid
status:
type: string
example: queued
queued_at:
type: string
format: date-time
warning:
description: Advisory only, and PRESENT ONLY at or above `api.concurrent_job_soft_limit`. The job has already been queued; this is the one warning you get before the hard ceiling starts returning 429. Absent (not null) below the limit, so test for presence.
type: string
type: object
'200':
description: Job already exists (idempotent repeat call) — returns existing job
'400':
description: 'Validation error, named by `error`: `invalid_json`, `missing_upload_uid`, `invalid_option` (a non-scalar option value, with `option` naming the field; enum values are case-insensitive, and an unknown scalar falls back to its default with an entry in the 201 response `warnings` array), `no_files`, `no_acceptable_files` — no files uploaded, invalid option value, etc.'
'403':
description: '`account_blocked` — this account cannot submit jobs. Contact support; do not retry.'
'401':
description: 'Not authenticated. `error` names the exact cause: `missing_api_key`, `malformed_api_key`, `invalid_api_key` (key unknown/revoked/expired), or — when the key itself is valid — `account_not_verified` / `account_inactive`.'
'404':
description: '`not_found` — What you''re looking for is not here.'
'429':
description: Too many active jobs for this API key. Wait for queued/processing jobs to finish, then retry. Response includes `active_jobs` and `limit`.
/api/convert/job/{uid}:
get:
tags:
- Convert
summary: Get convert job status
description: 'Returns the current status and per-item progress of a conversion job. Poll this endpoint until `status` is terminal: `completed`, `partial`, `failed` or `terminated` (there is no `done` status). Once it is, call `GET /api/convert/job/{uid}/results` with the same `X-Api-Key` to retrieve per-item asset IDs. **Authentication:** `X-Api-Key` header required.'
operationId: get_api_convert_job_status
parameters:
- name: uid
in: path
description: Either the `upload_uid` returned by `POST /api/convert/session` or the `job_uid` returned by `POST /api/convert/job`. Both identify the job.
required: true
schema:
type: string
format: uuid
pattern: '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'
example: 550e8400-e29b-41d4-a716-446655440000
responses:
'200':
description: Job status
content:
application/json:
schema:
properties:
upload_uid:
type: string
format: uuid
job_uid:
description: Pass to `GET /api/convert/job/{uid}/results` with the same `X-Api-Key` to retrieve result files once complete.
type: string
format: uuid
status:
type: string
enum:
- queued
- processing
- completed
- partial
- failed
- terminated
total_items:
description: Total number of files in this job
type: integer
completed:
description: Number of items successfully converted
type: integer
failed:
description: Number of items that failed
type: integer
queued_at:
type:
- string
- 'null'
format: date-time
started_at:
type:
- string
- 'null'
format: date-time
completed_at:
type:
- string
- 'null'
format: date-time
items:
type: array
items:
properties:
uid:
type: string
format: uuid
name:
type: string
example: photo.jpg
status:
type: string
enum:
- queued
- processing
- completed
- failed
- deferred
progress_pct:
type: integer
maximum: 100
minimum: 0
error:
type:
- string
- 'null'
type: object
type: object
'401':
description: 'Not authenticated. `error` names the exact cause: `missing_api_key`, `malformed_api_key`, `invalid_api_key` (key unknown/revoked/expired), or — when the key itself is valid — `account_not_verified` / `account_inactive`.'
'404':
description: '`not_found` — What you''re looking for is not here.'
/api/convert/job/{uid}/results:
get:
tags:
- Convert
summary: Get convert job results
description: Returns per-item conversion results with asset IDs once a job has finished (`completed` or `partial`). If the job is still processing, `result_files` will be present but items will have `"status":"queued"` or `"status":"processing"` — keep polling `GET /api/convert/job/{uid}` until `status` is terminal (`completed`, `partial`, `failed` or `terminated`) or `partial`. **Authentication:** `X-Api-Key` header required.
operationId: get_api_convert_job_results
parameters:
- name: uid
in: path
description: Either the `upload_uid` returned by `POST /api/convert/session` or the `job_uid` returned by `POST /api/convert/job`. Both identify the job.
required: true
schema:
type: string
format: uuid
pattern: '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'
example: 550e8400-e29b-41d4-a716-446655440000
responses:
'200':
description: Job results
content:
application/json:
schema:
properties:
upload_uid:
type: string
format: uuid
job_uid:
type: string
format: uuid
status:
type: string
enum:
- queued
- processing
- completed
- partial
- failed
- terminated
result_files:
description: One entry per input file. `asset_id` is present only when `status` is `complete` for that item.
type: array
items:
properties:
asset_id:
description: Pass to `GET /api/convert/download/{assetId}` with the same `X-Api-Key` to download the file.
type: string
format: uuid
name:
description: Original input filename
type: string
example: photo.jpg
output_name:
description: Output filename — null until conversion completes
type:
- string
- 'null'
example: photo.webp
status:
type: string
enum:
- queued
- processing
- completed
- failed
- deferred
mime_type:
type:
- string
- 'null'
example: image/webp
file_size:
description: Output file size in bytes
type:
- integer
- 'null'
error:
type:
- string
- 'null'
type: object
type: object
'401':
description: 'Not authenticated. `error` names the exact cause: `missing_api_key`, `malformed_api_key`, `invalid_api_key` (key unknown/revoked/expired), or — when the key itself is valid — `account_not_verified` / `account_inactive`.'
'404':
description: '`not_found` — What you''re looking for is not here.'
/api/convert/download/{assetId}:
get:
tags:
- Convert
summary: Download a converted output file
description: Downloads a single converted output file. The item must belong to a session created with the same `X-Api-Key`. Available only once the item `status` is `complete`. **Authentication:** `X-Api-Key` header required.
operationId: get_api_convert_download
parameters:
- name: assetId
in: path
description: The `asset_id` from `GET /api/convert/job/{uid}/results`
required: true
schema:
type: string
format: uuid
pattern: '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'
example: 550e8400-e29b-41d4-a716-446655440000
responses:
'200':
description: 'File download — `Content-Disposition: attachment`'
'401':
description: 'Not authenticated. `error` names the exact cause: `missing_api_key`, `malformed_api_key`, `invalid_api_key` (key unknown/revoked/expired), or — when the key itself is valid — `account_not_verified` / `account_inactive`.'
'404':
description: '`not_found` — What you''re looking for is not here.'
components:
securitySchemes:
ApiKey:
type: apiKey
description: 'API key generated on your snapWONDERS account profile page. Required everywhere except `GET /api/status`, `OPTIONS /api/tus` and the no-auth MCP methods. `Authorization: Bearer <key>` is also accepted.'
name: X-Api-Key
in: header