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 Hide 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: Hide
description: '**Conceal content inside a cover image.** The output is a stego image visually identical to the original cover that carries an encrypted hidden payload. All endpoints require `X-Api-Key`.
**Workflow — 7 steps:**
**Step 1 — Create a session**
`POST /api/session` · body: `{"type":"hide"}`
Returns `upload_uid` — include it in every upload for this session.
**Step 2 — Upload the content to hide (the secret)**
`POST /api/upload` · headers `X-Upload-Uid`, `X-Filename`, `Content-Type`, `X-Upload-Step: 1`, file as the body.
This is the image or video you want to conceal.
**Step 3 — Upload the cover image**
`POST /api/upload` · same headers but `X-Upload-Step: 2`. This is the carrier image the payload will be hidden inside.
A hide job needs both steps — `ready_for_job_start` stays false until the cover arrives.
Large files or unreliable connections: use TUS instead (`Upload-Metadata: upload_uid ,step MQ==` / `Mg==`), `POST` to create then `PATCH` the raw binary until all bytes are sent.
**Step 4 — Verify both uploads are complete**
`GET /api/session/{uid}/files`
Both files must show `"status":"completed"` before proceeding. If any show `"pending"`, the upload is still in progress.
**Step 5 — Start the processing job**
`POST /api/job` · Required: `upload_uid`, `password`, `expiry`
Optional encoding: `output_format`, `packing_strategy`, `scale_secret_to_fit`, `strip_cover_metadata`, `strip_secret_metadata`, `video_output_format`, `secret_video_crf`, `secret_video_codec`
Returns `{"status":"queued", ...}`.
**Step 6 — Poll until done**
`GET /api/job/{uid}` — repeat every few seconds.
Watch `status`: `queued` → `processing` → `completed` (or `partial` / `failed`).
`progress_message` shows per-cover progress while processing.
**Step 7 — Download the stego output**
`GET /api/job/{uid}/results`
Returns `result_files` with a `download_url` for each stego output image (or video).'
paths:
/api/session:
post:
tags:
- Hide
summary: Create an upload session
description: Creates a new upload session for a **hide** (conceal) or **reveal** (extract) 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_stego_session_create
requestBody:
required: true
content:
application/json:
schema:
required:
- type
properties:
type:
description: '`hide` to conceal content inside a cover image; `reveal` to extract hidden content from a stego image.'
type: string
enum:
- hide
- reveal
example: hide
type: object
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
type:
type: string
enum:
- hide
- reveal
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: Step-by-step upload instructions. Each key is a step name; value describes what to upload and which TUS metadata to set.
type: object
type: object
'400':
description: Invalid `type` value
'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/session/{uid}/files:
get:
tags:
- Hide
summary: List uploaded files for a session
description: Returns all files currently attached to a session. Use this to verify that all uploads have completed (status `complete`) and that each file is on the correct step before calling `POST /api/job`. **Authentication:** `X-Api-Key` header required.
operationId: get_api_stego_session_files
parameters:
- name: uid
in: path
description: The `upload_uid` returned by `POST /api/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
type:
type: string
enum:
- hide
- reveal
session_status:
type: string
example: uploading
files:
type: array
items:
properties:
step:
description: 1 = secret/content to hide; 2 = cover image (hide sessions only)
type: integer
example: 1
name:
type: string
example: photo.jpg
mime_type:
type: string
example: image/jpeg
file_size:
description: File size in bytes
type: integer
status:
description: 'pending: upload in progress; complete: ready for job; staged: job already started'
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/job:
post:
tags:
- Hide
summary: Start a processing job
description: 'Submits options and dispatches the steganography job to the worker queue. All files must be fully uploaded via TUS before calling this endpoint — verify with `GET /api/session/{uid}/files`.
**Hide jobs** accept the full set of encoding options below. **Reveal jobs** only use `upload_uid`, `password`, and `expiry` — all encoding keys are ignored.
The call is **idempotent**: submitting options 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_stego_job_start
requestBody:
required: true
content:
application/json:
schema:
required:
- upload_uid
- password
- expiry
properties:
upload_uid:
description: The `upload_uid` returned by `POST /api/session`
type: string
format: uuid
example: 550e8400-e29b-41d4-a716-446655440000
password:
description: Encryption password. Required for both hide and reveal. Must be at least 8 characters with mixed case, a digit, and a symbol.
type: string
example: Str0ng!Pass
expiry:
description: 'How long the result download link remains 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
output_format:
description: '**Hide only.** Image format for stego output files. All formats are lossless. Default: `avif`.'
type: string
enum:
- avif
- heic
- jp2
- jxl
- png
- webp
example: avif
packing_strategy:
description: '**Hide only.** How secrets are distributed across covers. `spread`: each cover altered as little as possible (default). `tight`: pack multiple secrets per cover to minimise cover count. `quality`: preserve visual quality above all else.'
type: string
enum:
- spread
- tight
- quality
example: spread
scale_secret_to_fit:
description: '**Hide only.** When a secret is too large to hide in its cover, automatically reduce it (resize images, increase video compression) until it fits. Set to `false` to fail instead of degrading quality. Default: `true`.'
type: boolean
example: true
strip_cover_metadata:
description: '**Hide only.** Remove EXIF, GPS, and all other embedded metadata from the stego output cover. Recommended when privacy matters. Default: `false`.'
type: boolean
example: false
strip_secret_metadata:
description: '**Hide only.** Remove EXIF, GPS, and embedded metadata from secret files before they are hidden. Reduces payload size. Default: `false`.'
type: boolean
example: false
video_output_format:
description: '**Hide only, video covers.** Lossless codec and container for stego video output. Default: `ffv1_mkv`.'
type: string
enum:
- ffv1_mkv
- huffyuv_avi
- lagarith_avi
- magicyuv_avi
- prores4444_mov
- prores4444xq_mov
- av1lossless_mkv
example: ffv1_mkv
secret_video_crf:
description: '**Hide only, video secrets.** Compression quality for secret videos before hiding (lower = better quality, larger payload). Default: `28`.'
type: integer
maximum: 40
minimum: 18
example: 28
secret_video_codec:
description: '**Hide only, video secrets.** Codec used to compress secret videos. `h265` gives ~40% smaller payloads. Default: `h265`.'
type: string
enum:
- h264
- h265
example: h265
vault_profile_id:
description: '**Pro accounts only, hide jobs only.** ID of a Vault Profile to use as an additional key factor in the steganographic key derivation. Vault profiles are created and managed at `/vault` in the web UI. Providing this on a free account returns HTTP 402.'
type: integer
example: 1
type: object
responses:
'201':
description: Job queued
content:
application/json:
schema:
properties:
upload_uid:
description: Session UUID — use with `GET /api/job/{uid}` to poll status
type: string
format: uuid
example: 550e8400-e29b-41d4-a716-446655440000
job_uid:
description: Job UUID — identical to `upload_uid` for stego (1:1 session:job). Provided for SDK consistency across all three pipelines.
type: string
format: uuid
example: 550e8400-e29b-41d4-a716-446655440000
reference:
description: Short human-readable reference (REF- prefix + first 8 chars)
type: string
example: REF-550E8400
job_type:
type: string
enum:
- hide
- reveal
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` — weak password, 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`.'
'402':
description: Pro account required — `vault_profile_id` is only available on Pro plans
'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/job/{uid}:
get:
tags:
- Hide
summary: Get job status
description: 'Returns the current status and progress of a steganography processing job. Poll this endpoint until `status` is terminal: `completed`, `partial`, `failed` or `terminated` (there is no `done` status). Once it is, call `GET /api/job/{uid}/results` with the same `X-Api-Key` to retrieve per-file asset IDs. **Authentication:** `X-Api-Key` header required.'
operationId: get_api_stego_job_status
parameters:
- name: uid
in: path
description: The `upload_uid` returned by `POST /api/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: Job status
content:
application/json:
schema:
properties:
upload_uid:
type: string
format: uuid
job_uid:
description: Job UUID — identical to `upload_uid` for stego (1:1 session:job). Provided for SDK consistency across all three pipelines.
type: string
format: uuid
reference:
type: string
example: REF-550E8400
job_type:
type: string
enum:
- hide
- reveal
status:
type: string
enum:
- queued
- processing
- completed
- partial
- failed
- terminated
progress_message:
description: Human-readable description of the current worker stage, e.g. "Encoding cover 2/5"
type:
- string
- 'null'
queued_at:
type:
- string
- 'null'
format: date-time
started_at:
type:
- string
- 'null'
format: date-time
completed_at:
type:
- string
- 'null'
format: date-time
error:
description: Error description when `status` is `failed`
type:
- string
- 'null'
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/job/{uid}/results:
get:
tags:
- Hide
summary: Get job result files
description: Returns download URLs for all result files once a job is complete. If the job is still processing, `result_files` will be an empty array — keep polling `GET /api/job/{uid}` until `status` is terminal (`completed`, `partial`, `failed` or `terminated`). **Authentication:** `X-Api-Key` header required.
operationId: get_api_stego_job_results
parameters:
- name: uid
in: path
description: The `upload_uid` returned by `POST /api/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: Job results
content:
application/json:
schema:
properties:
upload_uid:
type: string
format: uuid
job_uid:
description: Job UUID — identical to `upload_uid` for stego (1:1 session:job). Provided for SDK consistency across all three pipelines.
type: string
format: uuid
reference:
type: string
example: REF-550E8400
job_type:
type: string
enum:
- hide
- reveal
status:
type: string
enum:
- queued
- processing
- completed
- partial
- failed
- terminated
result_files:
description: Empty while job is still processing; populated once `status` is `complete`
type: array
items:
properties:
asset_id:
description: Pass to `GET /api/job/download/{assetId}` with the same `X-Api-Key` to download the file.
type: string
format: uuid
name:
type: string
example: stego-output-1.avif
mime_type:
type: string
example: image/avif
file_size:
description: File size in bytes
type: integer
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/job/download/{assetId}:
get:
tags:
- Hide
summary: Download a hide/reveal result file
description: Downloads a single result file from a hide or reveal job. The file must belong to a session created with the same `X-Api-Key`. Returns the raw binary file. **Authentication:** `X-Api-Key` header required.
operationId: get_api_stego_file_download
parameters:
- name: assetId
in: path
description: The `asset_id` from a file in `result_files` returned by `GET /api/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 — raw binary. `Content-Disposition: attachment` or `inline` depending on MIME type.'
'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