# Lite NSFW Detector API Docs

Lite NSFW Detector (轻湖NSFW检测) is a free, no-login image-moderation service by Lite Lake (轻湖): upload one image, get back the NSFW (pornographic/exposing content) probability in [0,1] plus a suggested decision. You decide the actual policy — this service never enforces anything and never stores your images.

- Web home: `https://nsfwdetector.litelake.com/`
- Skill for AI agents: `https://nsfwdetector.litelake.com/docs/skill.md`

## Base URL

**`https://nsfwdetector.litelake.com`**

This is the single public entry of the current deployment. All example calls below use it as-is.

## General rules

- **No auth, no API key (anonymous tier): 10 requests/minute and 30 requests/day per IP.** Need more? Register a free account for **100 requests/day**, or subscribe to Pro for **10,000 requests/day** — then call with an API key (see "Detect with an API key" below). 429 responses carry a `Retry-After` header — respect it.
- **Privacy**: images are analyzed in memory and discarded. Nothing is written to disk and nothing is stored.
- Max file size: **50 MB**. Allowed formats (detected from content, not extension): JPEG / PNG / WebP / BMP / TIFF / GIF. Animated formats are scored on the **first frame** (`image.frames` tells you the frame count).
- Max dimensions are per-format; oversized files are rejected outright:
  - **JPEG**: longest edge ≤ **30,000 px** and total ≤ **200,000,000 px** — very large camera originals are handled automatically;
  - **non-JPEG** (PNG / WebP / BMP / TIFF / GIF): longest edge ≤ **8192 px** and total ≤ **36,000,000 px**.
- **Fail direction**: if the service is temporarily unavailable the API returns `503` with code `5001`. Treat that as "unknown, needs review" — **never** as "safe".

## Endpoint: detect one image

```
POST https://nsfwdetector.litelake.com/api/public/detect
```

Two mutually exclusive ways to send the image:

**Option A — multipart form** (any language / curl -F):

```bash
curl -F file=@/path/to/image.jpg https://nsfwdetector.litelake.com/api/public/detect
```

**Option B — raw bytes** (request body IS the image):

```bash
curl --data-binary @/path/to/image.jpg -H "Content-Type: image/jpeg" \
  https://nsfwdetector.litelake.com/api/public/detect
```

Optional request header: `X-Device-Id: <uuid>` — a valid UUID (v4) that enables per-device abuse tracking. Invalid or missing values are ignored.

### Success response (HTTP 200)

```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "decision": "review",
    "scores": { "nsfw": 0.883104, "sfw": 0.116896 },
    "thresholds": { "allow": 0.10, "block": 0.90 },
    "image": {
      "sha256": "9f2c...",
      "format": "jpeg",
      "width": 800,
      "height": 600,
      "bytes": 123456,
      "frames": 1
    },
    "timing_ms": 61.3
  }
}
```

### Field notes

| Field | Meaning |
| --- | --- |
| `scores.nsfw` / `scores.sfw` | Probabilities in [0,1], sum ≈ 1. **Named by label — never index the raw array**: label order is the #1 historical pitfall of NSFW models. |
| `decision` | SUGGESTION from the current thresholds: `allow` (< 0.10), `review` (gray zone 0.10–0.90), `block` (≥ 0.90). The caller owns the policy. |
| `scores.nsfw` comparability | Scores are not comparable across recalibrations — always store `thresholds` alongside any score you keep. |
| `thresholds` | Thresholds in effect for THIS response. They may be recalibrated over time — store them alongside any score you keep. |
| `image.sha256` | Content hash — use it as YOUR cache key (repeat images are common; the service does not cache). |
| `image.frames` | Frame count of the input; `> 1` means animation and only frame 0 was scored — reject or handle as you see fit. |
| `timing_ms` | Whole-inference wall time in milliseconds (server side). |

### Errors (HTTP status carries the failure class)

| HTTP | code | Meaning | Notes |
| --- | --- | --- | --- |
| 400 | 4001 | Bad request | missing/empty `file`, multiple files, wrong Content-Type |
| 400 | 4002 | Unsupported format | not in the format whitelist |
| 400 | 4003 | File too large | > 50 MB |
| 400 | 4004 | Dimensions exceeded | per-format limits (JPEG 30000px/200MP; non-JPEG 8192px/36MP) |
| 400 | 4005 | Undecodable image | corrupt file, disguised type |
| 401 | 4011 | Invalid API key | missing/malformed/revoked key on `/api/open/detect`; never falls back to anonymous |
| 429 | 4290 | Rate limited | per-minute; `Retry-After` present |
| 429 | 4291 | Daily quota used | per-day; `Retry-After` present |
| 503 | 5001 | Service temporarily unavailable | do NOT treat as "safe" (see Fail direction) |

```json
{ "code": 4004, "message": "image edge exceeds limit" }
```

## Endpoint: detect with an API key

```
POST https://nsfwdetector.litelake.com/api/open/detect
Authorization: Bearer nsk_live_<32hex>
```

Same image formats, same request body options (multipart `file=` or raw `image/*` bytes), same response shape as the anonymous endpoint — only authentication and quota accounting differ:

- **Quota is per ACCOUNT, not per key.** Multiple keys of one account share one daily quota. Free accounts: 100/day; Pro: 10,000/day (and 30/minute).
- Response headers report your account quota: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (Unix seconds of the next UTC midnight) and `X-Quota-Tier` (`free` / `pro`).
- **A missing, malformed, revoked or unknown key is always `401` with code `4011` — it never falls back to the anonymous quota.** Keys are created in the product console (login → Console → API keys); the full secret is shown only once at creation.

```bash
curl -H "Authorization: Bearer nsk_live_xxxx" -F file=@/path/to/image.jpg \
  https://nsfwdetector.litelake.com/api/open/detect
```

## Endpoint: health

```
GET https://nsfwdetector.litelake.com/api/public/health
```

Returns service identity and versions (no upstream probe; rate-limit free):

```json
{ "code": 0, "message": "ok", "data": { "status": "ok", "service": "lite-nsfw-detector", "version": "1.3.0", "skill_version": "1.3.0" } }
```

## Skill for AI agents

Install guide: [`https://nsfwdetector.litelake.com/docs/skill.md`](/docs/skill.md) — a stdlib-only CLI (`detect`, `detect --url`, `--json`, `--version`, plus auto-compression with `--raw`/`--compress` overrides) your agent can self-install from `https://nsfwdetector.litelake.com/skill/SKILL.md` and `https://nsfwdetector.litelake.com/skill/nsfwdetector.py`. The CLI accepts `--api-key` (or the `NSFWDETECTOR_API_KEY` env var) to call the authenticated endpoint with per-account quota.
