{
  "openapi": "3.0.3",
  "info": {
    "title": "TWDxCleanLinks API",
    "version": "1.0.0",
    "description": "Strip tracking parameters from URLs. Completely free and open — no API key or registration required.",
    "contact": { "url": "https://cleanlinks.thewebdexter.com/Developers/" }
  },
  "servers": [
    { "url": "https://cleanlinks.thewebdexter.com" }
  ],
  "paths": {
    "/api/v1/clean": {
      "get": {
        "summary": "Clean tracking parameters from a URL (stable versioned endpoint)",
        "description": "Strips tracking parameters from a URL. No authentication required. Open CORS — can be called from browser JavaScript. Rate limiting is enforced at the network edge.",
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "schema": { "type": "string" },
            "description": "Percent-encoded absolute URL to clean (max 4096 chars, http or https only)"
          }
        ],
        "responses": {
          "200": {
            "description": "Cleaned URL and details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean" },
                    "cleaned": { "type": "string", "description": "The cleaned URL" },
                    "original": { "type": "string", "description": "The original URL as submitted" },
                    "removed": { "type": "integer", "description": "Number of tracking parameters removed" },
                    "bytesSaved": { "type": "integer", "description": "Bytes saved by removing parameters" },
                    "isHttps": { "type": "boolean" },
                    "wasUnwrapped": { "type": "boolean", "description": "True if an affiliate/redirect wrapper was unwrapped" },
                    "unwrappedVia": { "type": "string", "nullable": true, "description": "Hostname of the wrapper, if unwrapped" },
                    "hopCount": { "type": "integer", "description": "Number of redirect hops followed" },
                    "rulesVersion": { "type": "string", "nullable": true, "description": "Version of the cleaning ruleset used" },
                    "categories": {
                      "type": "object",
                      "description": "Removed parameters grouped by category",
                      "additionalProperties": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "name": { "type": "string" },
                            "value": { "type": "string" },
                            "reason": { "type": "string" }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/Error" },
          "414": { "$ref": "#/components/responses/Error" },
          "429": {
            "description": "Rate limit exceeded — wait and retry",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } }
          },
          "500": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/api/clean": {
      "get": {
        "summary": "Clean tracking parameters from a URL (legacy endpoint)",
        "description": "Same cleaning engine as /api/v1/clean. Browser cross-origin calls require an X-Turnstile-Token header. Non-browser callers (curl, server-to-server) are unaffected. Prefer /api/v1/clean for new integrations.",
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "schema": { "type": "string" },
            "description": "Percent-encoded absolute URL to clean (max 4096 chars)"
          }
        ],
        "responses": {
          "200": { "description": "Cleaned URL", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CleanResult" } } } },
          "400": { "$ref": "#/components/responses/SimpleError" },
          "403": { "$ref": "#/components/responses/SimpleError" },
          "500": { "$ref": "#/components/responses/SimpleError" }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CleanResult": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean" },
          "cleaned": { "type": "string" },
          "original": { "type": "string" },
          "removed": { "type": "integer" },
          "bytesSaved": { "type": "integer" },
          "isHttps": { "type": "boolean" },
          "wasUnwrapped": { "type": "boolean" },
          "unwrappedVia": { "type": "string", "nullable": true },
          "hopCount": { "type": "integer" },
          "rulesVersion": { "type": "string", "nullable": true },
          "categories": { "type": "object" }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "string" },
              "message": { "type": "string" }
            }
          }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Error response (structured envelope)",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } }
      },
      "SimpleError": {
        "description": "Error response",
        "content": { "application/json": { "schema": { "type": "object", "properties": { "error": { "type": "string" } } } } }
      }
    }
  }
}
