# Zebra API Reference

Public HTTP API for [Zebra](https://zebra.tg), a free browser-based AI photo editor.

- **OpenAPI 3.1 spec:** <https://zebra.tg/openapi.json>
- **Authentication:** none — see <https://zebra.tg/auth.md>
- **Base URL:** `https://zebra.tg`
- **Support:** <https://zebra.tg/contact/>

## Quick start

```sh
curl https://zebra.tg/api/health
# {"status":"ok","version":"0.1.0"}
```

## Calling from an agent, script, or CLI

Zebra applies an origin check to every state-changing request (`POST`) as a CSRF
speed-bump. It is **not** authentication — there is no key to obtain and nothing
to sign — but a request that sends neither an `Origin` nor a `Referer` header is
rejected with `403 {"error":"forbidden origin"}`.

Non-browser clients must therefore send the header explicitly:

```sh
-H 'Origin: https://zebra.tg'
```

`GET` endpoints need no header.

## What has an endpoint, and what does not

Most Zebra tools — filters, crop, blur, darken, grain, message bubbles, tag
stickers, collage, effects, draw-with-text, image compression, and PDF merge —
run **entirely client-side** in the browser using Canvas and WebAssembly. Your
image never leaves the device, and there is consequently no HTTP endpoint to
call. To use those, open the tool page directly; canonical per-tool URLs are
listed in
[`/.well-known/agent-skills/index.json`](https://zebra.tg/.well-known/agent-skills/index.json).

The endpoints below are the ones that genuinely run on the server.

## Endpoints

### `GET /api/health`

Liveness probe. No rate limit, no side effects.

```sh
curl https://zebra.tg/api/health
```

```json
{ "status": "ok", "version": "0.1.0" }
```

### `GET /api/features`

Returns the catalogue of editor features shown on the homepage — a way to
describe what Zebra does without scraping HTML.

```sh
curl https://zebra.tg/api/features
```

### `GET /api/usage/limits`

Reports the caller's remaining daily quota for metered tools. Identity comes
from the anonymous `zebra_anon` cookie (or a signed-in session if present); a
fresh caller with no cookies gets the anonymous free-tier allowance.

```sh
curl https://zebra.tg/api/usage/limits
```

### `POST /api/compress-pdf`

Compresses a PDF with a Ghostscript distiller preset and returns the compressed
PDF as a binary body.

| Parameter | In | Values | Default | Notes |
| --- | --- | --- | --- | --- |
| `tier` | query | `light`, `medium`, `strong` | `medium` | `light` ≈ 150 dpi (print-safe); `medium`/`strong` ≈ 72 dpi (screen). |
| `file` | multipart form | PDF, max 150 MB | — | Must start with the `%PDF-` magic bytes. |

```sh
curl -X POST 'https://zebra.tg/api/compress-pdf?tier=medium' \
  -H 'Origin: https://zebra.tg' \
  -F 'file=@input.pdf' \
  -D headers.txt \
  -o output.pdf

grep -i '^x-.*-size' headers.txt
# x-original-size: 4812390
# x-compressed-size: 1044120
```

Compare `X-Original-Size` with `X-Compressed-Size` to see what was achieved. If
a tier would not actually shrink the file, the original bytes are returned
unchanged — the response is never larger than the input.

**Errors:** `400` `unknown_tier` / `not_pdf` / `empty_file` · `413` `too_large`
(over 150 MB) · `403` `forbidden origin` · `429` rate-limited.

### `POST /api/remove-bg`

Removes the background from a photo with an AI segmentation model and returns a
transparent PNG at the source resolution.

| Parameter | In | Values | Default | Notes |
| --- | --- | --- | --- | --- |
| `file` | multipart form | JPEG/PNG/WebP, max 20 MB | — | |
| `quality` | multipart form | `standard`, `hd` | `standard` | `standard` is free and anonymous. `hd` requires a signed-in premium account and is not reachable anonymously. |

```sh
curl -X POST https://zebra.tg/api/remove-bg \
  -H 'Origin: https://zebra.tg' \
  -F 'file=@photo.jpg' \
  -F 'quality=standard' \
  -o cutout.png
```

**Errors:** `400` undecodable image · `402` premium required or free allowance
exhausted · `413` over 20 MB · `403` `forbidden origin` · `429` rate-limited.

This endpoint carries the tightest rate limit on the site (roughly 5 requests
per minute per IP at the edge, plus hourly per-identity caps). Budget for it.

### `POST /api/auto-enhance`

Runs several enhancement models over one photo and streams each result back as
it completes, using Server-Sent Events (`text/event-stream`). This lets a client
show the first result without waiting for the slowest model.

```sh
curl -N -X POST https://zebra.tg/api/auto-enhance \
  -H 'Origin: https://zebra.tg' \
  -F 'file=@photo.jpg'
```

```
event: result
data: {"model": "clarity", "image": "data:image/jpeg;base64,..."}

event: result
data: {"model": "vivid", "image": "data:image/jpeg;base64,..."}

event: done
data: {}
```

Each `result` event carries one model's output as a base64 JPEG data URI. A
model that fails yields a `result` event with an `error` field instead of
`image`; a partial failure does not abort the stream. A final `done` event
closes it.

**Errors:** `400` undecodable image · `413` over 20 MB · `403` `forbidden
origin` · `429` rate-limited.

## Rate limits

Limits are enforced per IP and per anonymous cookie, never per authenticated
identity. Exceeding one returns:

```json
{ "error": "rate_limit", "message": "Too many requests, try again later" }
```

with HTTP `429` and a `Retry-After` header. Honour it and back off; retrying
immediately will not succeed.

## Errors

All errors return JSON with a machine-readable `error` code and, where useful, a
human-readable `message`:

```json
{ "error": "not_pdf" }
```

| Status | Meaning |
| --- | --- |
| `400` | Malformed input — wrong file type, empty upload, unknown parameter value. |
| `402` | Premium required, or the free allowance is exhausted. |
| `403` | Missing or wrong `Origin` header. Resend with `Origin: https://zebra.tg`. |
| `413` | Upload exceeds the endpoint's size limit. |
| `429` | Rate limited. Honour `Retry-After`. |
| `5xx` | Upstream model or service failure. Retry with backoff. |

## MCP

Zebra speaks MCP at `https://zebra.tg/api/mcp` over JSON-RPC 2.0. No
authentication.

```sh
curl -X POST https://zebra.tg/api/mcp \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

Supported methods: `initialize`, `tools/list`, `tools/call`, `ping`,
`resources/list`, `prompts/list`. A plain `GET` on the same URL returns a short
descriptor instead of JSON-RPC.

`tools/list` advertises 18 tools in two groups:

- **5 server-backed tools** — `compress_pdf`, `remove_background`,
  `auto_enhance`, `get_usage_limits`, `health_check` — matching the HTTP
  endpoints documented above.
- **13 browser tools** — filters, crop, blur, darken, grain, bubbles, tags,
  image compression, PDF merge, collage, effects, draw-text, AI art — which run
  client-side and therefore resolve to a URL for the user to open.

Because JSON-RPC is a poor envelope for multi-megabyte images, `tools/call`
returns the exact HTTP request to issue (method, URL, headers, form fields)
rather than tunnelling binary through the RPC layer. Issue that request to do
the actual work.

Discovery descriptor:
[MCP server card](https://zebra.tg/.well-known/mcp/server-card.json).

## Further reading

| Resource | URL |
| --- | --- |
| OpenAPI 3.1 spec | <https://zebra.tg/openapi.json> |
| Authentication | <https://zebra.tg/auth.md> |
| Agent resource directory | <https://zebra.tg/.well-known/ai-catalog.json> |
| API catalog (RFC 9727) | <https://zebra.tg/.well-known/api-catalog> |
| A2A agent card | <https://zebra.tg/.well-known/agent-card.json> |
| Agent Skills index | <https://zebra.tg/.well-known/agent-skills/index.json> |
| Site content for agents | <https://zebra.tg/llms-full.txt> |
| Terms of service | <https://zebra.tg/terms/> |
