Skip to Content
SFW API

MCP server

Reference for the SFW API MCP server. The hosted endpoint and the local package, the analyze_url, analyze_file and get_account tools, their inputs, results, errors and cost.

Updated on · View as Markdown

The MCP server gives an AI agent the API as tools: it passes a URL or a file and gets the content level back. This page is the reference. For copy-and-paste setup per client (Claude Code, Cursor, VS Code, Codex, Claude Desktop) see the MCP server page.

Use it when the agent itself should check media, for example while triaging reports or reviewing a folder of assets. To have an agent write the API into your own application, use the skill and the Markdown docs instead.

Two forms

Hosted endpointLocal package
Addresshttps://api.sfwapi.dev/mcpnpx -y @sfwapi/mcp
TransportStreamable HTTP, statelessstdio
KeyAuthorization: Bearer <key> headerSFWAPI_API_KEY environment variable
Toolsanalyze_url, get_accountanalyze_url, analyze_file, get_account
NeedsA client that can send a headerNode.js 20 or newer

Both use the same key and the same credits as the HTTP API. analyze_file exists only locally, because a hosted server cannot read your disk.

Hosted

claude mcp add --transport http sfwapi https://api.sfwapi.dev/mcp --header "Authorization: Bearer $SFWAPI_API_KEY"

Any other client: point it at the URL and send the header. Each request is independent; there is no session to keep alive. The endpoint has no OAuth sign-in, so clients that can only connect through OAuth cannot use it.

Local

claude mcp add sfwapi -e SFWAPI_API_KEY=$SFWAPI_API_KEY -- npx -y @sfwapi/mcp

Clients with a JSON config use the same command:

mcpServers entry
{
  "mcpServers": {
    "sfwapi": {
      "command": "npx",
      "args": ["-y", "@sfwapi/mcp"],
      "env": { "SFWAPI_API_KEY": "sk_live_..." }
    }
  }
}

Tools

analyze_url

Analyse an image, GIF or video at a public http(s) URL. Our servers download it; nothing is stored.

InputType
urlstring, requiredPublic URL of the media, up to 2048 characters
block_level1, 2 or 3Apply a policy: block at this level or above. The result then has policy.blocked
block_sex_objectsbooleanWith block_level: also block adult toys and objects
artstringWith block_level: how drawn content is treated. same_as_photos (default), allow, block_from_1, block_from_2, block_from_3
max_frames1 to 32Frames sampled from a GIF or video. Default 5 for a GIF, 10 for a video
include_framesbooleanReturn the scores of each frame

analyze_file

Local package only. Analyse a file on the machine the server runs on. The file is uploaded to the API without its name, analysed in memory and discarded.

InputType
pathstring, requiredPath of the file, absolute or relative to the working directory
block_level, block_sex_objects, art, max_frames, include_framesAs for analyze_url

get_account

No input. Returns the plan, its limits, the credit balance and this month's usage. Free.

Results

Every tool answers with one readable line and the full result as structured content.

Text
Level 2 (Implied Nudity). Scores 0-100: revealing 96.2, implied nudity 71.5, explicit 8.1. Blocked by the policy (level). 1 credit used, 499 left.

The structured content of analyze_url and analyze_file is the response of POST /v1/analyze, field for field:

Structured content
{
  "id": "an_01JD4Q0K7X3Y9Z8W2V1U0T9S8R",
  "media": { "kind": "image", "width": 1200, "height": 800, "frames_analyzed": 1, "duration_ms": null },
  "nudity": { "level": 2, "scores": { "revealing": 96.2, "implied_nudity": 71.5, "explicit": 8.1 } },
  "flags": { "art": { "score": 3.4, "detected": false }, "sex_object": { "score": 0.9, "detected": false } },
  "policy": { "blocked": true, "reason": "level", "matched_level": 2 },
  "usage": { "credits": 1, "balance": 499 },
  "timing_ms": { "upstream": 41, "total": 58 }
}

Levels are 0 Everyday, 1 Revealing, 2 Implied nudity and 3 Explicit. Scores run from 0 to 100 and count above 50. policy is present only when block_level was passed; see Policies.

get_account returns tier, limits, balance (free, paid, total, free_resets_at) and month (from, requests, credits, bytes_in).

Errors

A failed call comes back as a tool error (isError: true) whose text is the API's error code, its message, a link to the docs and the request id:

download_failed: The URL returned 404
Docs: https://sfwapi.dev/docs/errors#download_failed
Request id: 4f1c2a9e7b5d4c3fa1e0b9d8c7f6e5a4

A missing or wrong key is different on the hosted endpoint: the HTTP request itself is answered with 401 and the usual error body, so the client reports that it could not connect. The local package refuses to start without SFWAPI_API_KEY.

Cost and limits

  • One credit per image, or one per analysed frame of a GIF or video, plus the size surcharge above 10 MB. The same as the HTTP API.
  • Connecting, listing the tools, get_account and every failed call are free.
  • The rate limits of your plan apply to the analyses. An agent that fans out too widely gets rate_limited with the time to wait.
  • Video needs a paid account.

The key

Keep the key in SFWAPI_API_KEY or in your client's secret prompt, not in a file you commit. Anyone with the key can spend your credits; rotate or revoke it in the console. The tool result never contains the key or the media.

Environment variables of the local package

Variable
SFWAPI_API_KEYRequired. Your API key
SFWAPI_BASE_URLOptional. API origin, default https://api.sfwapi.dev

On this page