{"openapi":"3.1.0","info":{"title":"SFW API","version":"1.0.0","description":"Nudity detection with four content levels (0 Everyday, 1 Revealing, 2 Implied nudity, 3 Explicit), art and adult-object flags and an optional policy. Media is processed in memory and never stored."},"servers":[{"url":"https://api.sfwapi.dev"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Your API key, e.g. \"Authorization: Bearer sk_live_…\""}},"schemas":{"AnalyzeResponse":{"type":"object","properties":{"id":{"type":"string","example":"an_01JD4Q0K7X3Y9Z8W2V1U0T9S8R"},"media":{"type":"object","properties":{"kind":{"$ref":"#/components/schemas/MediaKind"},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]},"frames_analyzed":{"type":"integer"},"duration_ms":{"type":["integer","null"]}},"required":["kind","width","height","frames_analyzed","duration_ms"]},"nudity":{"type":"object","properties":{"level":{"$ref":"#/components/schemas/Level"},"scores":{"$ref":"#/components/schemas/LevelScores"}},"required":["level","scores"]},"flags":{"$ref":"#/components/schemas/Flags"},"policy":{"$ref":"#/components/schemas/PolicyResult"},"frames":{"type":"array","items":{"$ref":"#/components/schemas/Frame"}},"usage":{"type":"object","properties":{"credits":{"type":"integer","description":"Credits charged for this request"},"balance":{"type":"integer","description":"Credits remaining after this request"}},"required":["credits","balance"]},"timing_ms":{"type":"object","properties":{"upstream":{"type":"integer"},"total":{"type":"integer"}},"required":["upstream","total"]}},"required":["id","media","nudity","flags","usage","timing_ms"]},"MediaKind":{"type":"string","enum":["image","gif","video"]},"Level":{"anyOf":[{"type":"number","enum":[0]},{"type":"number","enum":[1]},{"type":"number","enum":[2]},{"type":"number","enum":[3]}],"description":"0 Everyday · 1 Revealing · 2 Implied nudity · 3 Explicit","example":2},"LevelScores":{"type":"object","properties":{"revealing":{"type":"number","minimum":0,"maximum":100,"description":"Level 1 or higher","example":96.2},"implied_nudity":{"type":"number","minimum":0,"maximum":100,"description":"Level 2 or higher","example":71.5},"explicit":{"type":"number","minimum":0,"maximum":100,"description":"Level 3","example":8.1}},"required":["revealing","implied_nudity","explicit"]},"Flags":{"type":"object","properties":{"art":{"$ref":"#/components/schemas/Flag"},"sex_object":{"allOf":[{"$ref":"#/components/schemas/Flag"},{"description":"Adult toy or object"}]}},"required":["art","sex_object"]},"Flag":{"type":"object","properties":{"score":{"type":"number","minimum":0,"maximum":100,"description":"0-100, calibrated so 50 is the decision threshold"},"detected":{"type":"boolean"}},"required":["score","detected"],"example":{"score":3.4,"detected":false},"description":"Drawing, anime, painting, statue, stylised 3D"},"PolicyResult":{"type":"object","properties":{"blocked":{"type":"boolean"},"reason":{"type":["string","null"],"enum":["level","sex_object","art_level",null],"description":"Why it was blocked: a level at or above block_level, an adult object, or a level under the art policy"},"matched_level":{"allOf":[{"$ref":"#/components/schemas/Level"},{"description":"Highest blocked level seen in any frame"}]}},"required":["blocked","reason","matched_level"]},"Frame":{"type":"object","properties":{"index":{"type":"integer"},"timestamp_ms":{"type":["integer","null"]},"level":{"$ref":"#/components/schemas/Level"},"scores":{"$ref":"#/components/schemas/LevelScores"},"flags":{"$ref":"#/components/schemas/Flags"}},"required":["index","timestamp_ms","level","scores","flags"]},"ErrorResponse":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","permission_error","billing_error","rate_limit_error","media_error","upstream_error","api_error"]},"code":{"type":"string","enum":["invalid_request","url_not_allowed","invalid_api_key","insufficient_credits","key_revoked","account_suspended","email_not_verified","session_required","csrf_rejected","turnstile_failed","quota_exceeded","not_implemented","not_found","media_too_large","unsupported_media","download_failed","decode_failed","rate_limited","concurrency_exceeded","internal_error","upstream_internal","unavailable","overloaded","bandwidth_limited","timeout"]},"message":{"type":"string"},"doc_url":{"type":"string","format":"uri"},"request_id":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["type","code","message","doc_url","request_id"]}},"required":["error"],"example":{"error":{"type":"billing_error","code":"insufficient_credits","message":"Your balance is 0 credits. Buy a pack or wait for the monthly free reset.","doc_url":"https://sfwapi.dev/docs/errors#insufficient_credits","request_id":"01JD4Q0K7X3Y9Z8W2V1U0T9S8R"}}},"AnalyzeJsonRequest":{"type":"object","properties":{"url":{"type":"string","maxLength":2048,"format":"uri","description":"Public http(s) URL of an image, GIF or video. Fetched by our servers; never stored.","example":"https://example.com/photo.jpg"},"base64":{"type":"string","minLength":1,"description":"Raw media bytes, base64 encoded (prefer multipart upload: base64 costs 33% more bandwidth)"},"max_frames":{"type":"integer","minimum":1,"maximum":32,"description":"Frames sampled evenly over a GIF or video (first and last always included). Default 5 for GIF, 10 for video. One credit per analysed frame.","example":10},"include_frames":{"type":"boolean","default":false,"description":"Return per-frame scores for GIF/video"},"policy":{"$ref":"#/components/schemas/Policy"}},"additionalProperties":false},"Policy":{"type":"object","properties":{"block_level":{"anyOf":[{"type":"number","enum":[1]},{"type":"number","enum":[2]},{"type":"number","enum":[3]}],"description":"Block media at this level or above (1 Revealing, 2 Implied nudity, 3 Explicit)","example":2},"block_sex_objects":{"type":"boolean","default":false,"description":"Also block adult toys and objects"},"art":{"$ref":"#/components/schemas/ArtPolicy"}},"required":["block_level"],"additionalProperties":false,"description":"The same policy engine the SfwBot Discord bot applies per server. Optional.","example":{"block_level":2,"block_sex_objects":true,"art":"block_from_3"}},"ArtPolicy":{"type":"string","enum":["same_as_photos","allow","block_from_1","block_from_2","block_from_3"],"default":"same_as_photos","description":"How drawn/anime/stylised content is treated: like photos, never blocked, or blocked from a different level"},"AnalyzeMultipartRequest":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"The image, GIF or video"},"max_frames":{"type":"string","description":"See max_frames","example":"10"},"include_frames":{"type":"string","example":"false"},"policy":{"type":"string","description":"JSON-encoded Policy object"}},"required":["file"]},"MeResponse":{"type":"object","properties":{"account_id":{"type":"string"},"key_prefix":{"type":"string","example":"sk_live_a1B2c3D4"},"tier":{"$ref":"#/components/schemas/Tier"},"limits":{"$ref":"#/components/schemas/Limits"},"balance":{"$ref":"#/components/schemas/Balance"}},"required":["account_id","key_prefix","tier","limits","balance"]},"Tier":{"type":"string","enum":["free","paid","pro","enterprise"]},"Limits":{"type":"object","properties":{"requests_per_second":{"type":"number"},"burst":{"type":"integer"},"max_inflight":{"type":"integer"},"max_media_bytes":{"type":"integer","description":"Largest accepted upload / base64 / URL image"},"video_allowed":{"type":"boolean"},"bytes_per_day":{"type":"integer"}},"required":["requests_per_second","burst","max_inflight","max_media_bytes","video_allowed","bytes_per_day"]},"Balance":{"type":"object","properties":{"free":{"type":"integer"},"paid":{"type":"integer"},"total":{"type":"integer"},"free_resets_at":{"type":"string","format":"date-time"}},"required":["free","paid","total","free_resets_at"]},"UsageResponse":{"type":"object","properties":{"month":{"type":"object","properties":{"from":{"type":"string","format":"date-time"},"requests":{"type":"integer"},"credits":{"type":"integer"},"bytes_in":{"type":"integer"}},"required":["from","requests","credits","bytes_in"]},"balance":{"$ref":"#/components/schemas/Balance"}},"required":["month","balance"]},"StatusResponse":{"type":"object","properties":{"status":{"type":"string","enum":["ok","degraded","down"]},"replicas_ready":{"type":"integer"},"replicas_total":{"type":"integer"}},"required":["status","replicas_ready","replicas_total"]}},"parameters":{}},"paths":{"/v1/analyze":{"post":{"tags":["Analyze"],"summary":"Analyze an image, GIF or video","description":"Send media by URL, base64 or multipart upload. Returns the nudity level (0-3), the three level scores, the art and adult-object flags and, when a policy is given, whether that policy blocks the media. Media is processed in memory and never stored.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzeJsonRequest"}},"multipart/form-data":{"schema":{"$ref":"#/components/schemas/AnalyzeMultipartRequest"}}}},"responses":{"200":{"description":"Scores","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzeResponse"}}}},"400":{"description":"Invalid request or URL not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Insufficient credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Key revoked or account suspended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Media too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"Unsupported media type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Media could not be downloaded or decoded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Temporarily unavailable or overloaded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"504":{"description":"Timed out","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/me":{"get":{"tags":["Account"],"summary":"The key, tier, limits and balance behind this request","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/usage":{"get":{"tags":["Account"],"summary":"This month's usage and the current balance","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/status":{"get":{"tags":["Status"],"summary":"Public service status","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusResponse"}}}}}}}},"webhooks":{}}