{
  "openapi": "3.1.0",
  "info": {
    "title": "SmuggleCheck",
    "version": "0.1.2",
    "description": "Find hidden prompt-injection text and deceptive Unicode: Unicode tag characters (\"ASCII smuggling\", hidden text decoded), zero-width characters (incl. binary stego decode), variation-selector stego (\"emoji smuggling\", payload decoded), bidi controls (Trojan Source), homoglyphs (UTS #39 confusables: mixed-script, whole-script and full-width look-alikes), chat-template special tokens (ChatML, Llama, Mistral, Gemma, Phi, harmony, DeepSeek; also when obfuscated) and markdown / HTML image links built to exfiltrate data. Pure text analysis; your text is never stored; no network calls. Anonymous: about 20/min per IP, shared daily pool about 5000. Free key (POST /api/keys): about 500 checks/month. SmuggleCheck is not affiliated with, endorsed or sponsored by Meta, OpenAI, Anthropic, Google, Mistral AI, DeepSeek, Alibaba (Qwen), the Unicode Consortium or any other AI or model vendor. Model and token names are used only to describe formats.",
    "contact": {
      "email": "digitalpromohub.support@gmail.com"
    }
  },
  "servers": [
    {
      "url": "https://smugglecheck.mike-tusa.workers.dev"
    }
  ],
  "security": [
    {},
    {
      "bearerAuth": []
    },
    {
      "apiKeyHeader": []
    }
  ],
  "paths": {
    "/api/scan": {
      "post": {
        "operationId": "scanText",
        "summary": "Scan text for smuggled instructions, invisible characters, homoglyphs, chat-template tokens and exfiltration links",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 32768,
                    "description": "Up to 32768 UTF-8 bytes (JSON Schema maxLength counts characters; the enforced limit is 32768 UTF-8 bytes, so non-ASCII text hits it sooner)"
                  },
                  "include_cleaned": {
                    "type": "boolean",
                    "default": false
                  }
                },
                "required": [
                  "text"
                ]
              }
            },
            "text/plain": {
              "schema": {
                "type": "string",
                "maxLength": 32768,
                "description": "The raw text to scan, up to 32768 UTF-8 bytes"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Scan report",
            "headers": {
              "X-SmuggleCheck-Quota-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Approximate checks left (shared daily pool when anonymous, monthly quota with a key)"
              },
              "X-SmuggleCheck-Tier": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "anonymous",
                    "free"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScanReport"
                }
              }
            }
          },
          "400": {
            "description": "invalid_input or invalid_json (rejected before rate limits or the daily pool are charged)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited, daily_limit (anonymous), monthly_limit or pool_exhausted (key; counts are approximate, batched per instance)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable: key check temporarily failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "scanGetNotAllowed",
        "summary": "Not supported (POST the text)",
        "responses": {
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/compare": {
      "post": {
        "operationId": "compareStrings",
        "summary": "Are two strings visually confusable? (UTS #39 skeletons)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "a": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1024
                  },
                  "b": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1024
                  }
                },
                "required": [
                  "a",
                  "b"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Comparison",
            "headers": {
              "X-SmuggleCheck-Quota-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Approximate checks left (shared daily pool when anonymous, monthly quota with a key)"
              },
              "X-SmuggleCheck-Tier": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "anonymous",
                    "free"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompareReport"
                }
              }
            }
          },
          "400": {
            "description": "invalid_input or invalid_json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited, daily_limit (anonymous), monthly_limit or pool_exhausted (key; counts are approximate, batched per instance)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable: key check temporarily failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "compareGetNotAllowed",
        "summary": "Not supported (POST JSON)",
        "responses": {
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/keys": {
      "post": {
        "operationId": "issueKey",
        "summary": "Issue a free API key (no sign-up, no email). The key is shown once.",
        "security": [
          {}
        ],
        "responses": {
          "201": {
            "description": "New key",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string",
                      "pattern": "^sc_live_[0-9a-f]{32}$"
                    },
                    "plan": {
                      "type": "string",
                      "const": "free"
                    },
                    "monthlyLimit": {
                      "type": "integer"
                    },
                    "note": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "key",
                    "plan",
                    "monthlyLimit"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (about 3 new keys per hour per IP, best-effort) or pool_exhausted (global cap of about 200 new keys per day)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "unavailable: key issuance paused until the daily storage write budget resets",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "issueKeyGetNotAllowed",
        "summary": "Not supported",
        "responses": {
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/mcp/server-card.json": {
      "get": {
        "operationId": "serverCard",
        "summary": "Static MCP server card (same tools as tools/list)",
        "responses": {
          "200": {
            "description": "Server card",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "health",
        "summary": "Health and version",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "version": {
                      "type": "string"
                    },
                    "day": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "operationId": "mcp",
        "summary": "Remote MCP server (JSON-RPC 2.0 over Streamable HTTP, stateless)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "JSON-RPC 2.0 request, notification or batch (max 10, at most one tools/call). Max 270336 bytes."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "JSON-RPC 2.0 response (or batch array)"
                }
              }
            }
          },
          "202": {
            "description": "Notification or client response accepted (empty body)"
          },
          "400": {
            "description": "Parse error, invalid request or protocol/header mismatch",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "JSON-RPC 2.0 response (or batch array)"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key (data.code invalid_key; no WWW-Authenticate)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "JSON-RPC 2.0 response (or batch array)"
                }
              }
            }
          },
          "403": {
            "description": "Origin not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "JSON-RPC 2.0 response (or batch array)"
                }
              }
            }
          },
          "404": {
            "description": "Method not found (2026-07-28 requests)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "JSON-RPC 2.0 response (or batch array)"
                }
              }
            }
          },
          "413": {
            "description": "Body too large",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "JSON-RPC 2.0 response (or batch array)"
                }
              }
            }
          },
          "429": {
            "description": "Too many MCP requests (Retry-After)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "JSON-RPC 2.0 response (or batch array)"
                }
              }
            }
          },
          "500": {
            "description": "Internal error"
          },
          "503": {
            "description": "Temporarily unavailable"
          }
        }
      },
      "get": {
        "operationId": "mcpGet",
        "summary": "Not supported (no SSE stream)",
        "responses": {
          "405": {
            "description": "Method not allowed",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string",
                  "const": "POST, OPTIONS"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "mcpDelete",
        "summary": "Not supported (no sessions)",
        "responses": {
          "405": {
            "description": "Method not allowed",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string",
                  "const": "POST, OPTIONS"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Free API key from POST /api/keys"
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_input",
                  "invalid_json",
                  "invalid_key",
                  "too_large",
                  "rate_limited",
                  "daily_limit",
                  "monthly_limit",
                  "pool_exhausted",
                  "unavailable",
                  "method_not_allowed",
                  "not_found",
                  "internal_error"
                ]
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      },
      "Finding": {
        "type": "object",
        "properties": {
          "severity": {
            "type": "string",
            "enum": [
              "critical",
              "warning",
              "info"
            ]
          },
          "category": {
            "type": "string",
            "enum": [
              "tag_chars",
              "zero_width",
              "variation_selectors",
              "bidi",
              "confusables",
              "special_tokens",
              "markdown_exfil",
              "control_chars",
              "invisible",
              "other"
            ]
          },
          "code": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "index": {
            "type": "integer",
            "description": "0-based code point offset of the first occurrence"
          },
          "positions": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "codepoints": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "decoded": {
            "type": "string",
            "description": "Hidden text decoded from the input (untrusted)"
          },
          "decodedHex": {
            "type": "string"
          },
          "scheme": {
            "type": "string"
          },
          "token": {
            "type": "string"
          },
          "family": {
            "type": "string"
          },
          "word": {
            "type": "string"
          },
          "scripts": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "looksLike": {
            "type": "string"
          },
          "skeleton": {
            "type": "string"
          },
          "host": {
            "type": "string"
          },
          "hosts": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "url": {
            "type": "string"
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "excerpt": {
            "type": "string",
            "description": "Context with invisible characters shown as ⟦U+XXXX⟧ (untrusted)"
          },
          "message": {
            "type": "string"
          },
          "fix": {
            "type": "string"
          }
        },
        "required": [
          "severity",
          "category",
          "code",
          "count",
          "index",
          "message",
          "fix"
        ]
      },
      "ScanReport": {
        "type": "object",
        "properties": {
          "verdict": {
            "type": "string",
            "enum": [
              "clean",
              "suspicious",
              "dangerous"
            ]
          },
          "riskScore": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "summary": {
            "type": "object",
            "properties": {
              "critical": {
                "type": "integer"
              },
              "warning": {
                "type": "integer"
              },
              "info": {
                "type": "integer"
              },
              "codePoints": {
                "type": "integer"
              },
              "bytes": {
                "type": "integer"
              },
              "lines": {
                "type": "integer"
              },
              "categories": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                }
              },
              "verdictMeaning": {
                "type": "string"
              }
            }
          },
          "hidden": {
            "type": "object",
            "properties": {
              "tagCharacters": {
                "type": "string"
              },
              "variationSelectors": {
                "type": "string"
              },
              "zeroWidth": {
                "type": "string"
              }
            }
          },
          "allowed": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "findings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Finding"
            }
          },
          "findingsOmitted": {
            "type": "integer"
          },
          "cleaned": {
            "type": "string",
            "description": "Only with include_cleaned"
          },
          "removedCodePoints": {
            "type": "integer"
          },
          "meta": {
            "type": "object",
            "properties": {
              "version": {
                "type": "string"
              },
              "stored": {
                "type": "boolean",
                "const": false
              },
              "networkLookups": {
                "type": "boolean",
                "const": false
              },
              "unicodeConfusables": {
                "type": "string"
              },
              "positions": {
                "type": "string"
              },
              "untrusted": {
                "type": "string"
              }
            }
          }
        },
        "required": [
          "verdict",
          "riskScore",
          "summary",
          "hidden",
          "allowed",
          "findings",
          "meta"
        ]
      },
      "Side": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string"
          },
          "skeleton": {
            "type": "string"
          },
          "scripts": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "mixedScript": {
            "type": "boolean"
          },
          "nonAscii": {
            "type": "boolean"
          },
          "invisibleChars": {
            "type": "integer"
          }
        }
      },
      "CompareReport": {
        "type": "object",
        "properties": {
          "verdict": {
            "type": "string",
            "enum": [
              "identical",
              "confusable",
              "confusable_ignoring_case",
              "distinct"
            ]
          },
          "confusable": {
            "type": "boolean"
          },
          "confusableIgnoringCase": {
            "type": "boolean"
          },
          "identical": {
            "type": "boolean"
          },
          "explanation": {
            "type": "string"
          },
          "a": {
            "$ref": "#/components/schemas/Side"
          },
          "b": {
            "$ref": "#/components/schemas/Side"
          },
          "meta": {
            "type": "object"
          }
        },
        "required": [
          "verdict",
          "confusable",
          "identical",
          "a",
          "b",
          "meta"
        ]
      }
    }
  }
}