{
  "openapi": "3.1.0",
  "info": {
    "title": "mate.tools API",
    "version": "1.2.0",
    "description": "Programmatic access to mate.tools utilities — text analysis and transformation, encoding, hashing, password generation and analysis, JSON validation and CSV conversion, regex testing, date math and timestamp parsing, slug generation, case conversion and sort. Seventeen endpoints in v1.\n\nNo authentication required. Bodies are capped at 1 MB. CORS open. Each endpoint returns standard X-RateLimit-* headers (60 req/min and 600 req/hour per IP). The /health endpoint is excluded from rate limiting.\n\nTo use from an AI agent, prefer the MCP server at https://mate.tools/api/mcp/ over this raw OpenAPI surface — it wraps these endpoints with friendlier names and richer responses.",
    "contact": { "name": "mate.tools", "url": "https://mate.tools/contact" },
    "license": { "name": "Free for personal and commercial use", "url": "https://mate.tools/terms" }
  },
  "servers": [{ "url": "https://mate.tools/api/v1", "description": "Production" }],
  "tags": [
    { "name": "text",     "description": "Text analysis, transformation and manipulation" },
    { "name": "encoding", "description": "Encode and decode between text and binary representations" },
    { "name": "crypto",   "description": "Hashing, password generation and password analysis" },
    { "name": "data",     "description": "Validate and transform structured data (JSON, CSV)" },
    { "name": "datetime", "description": "Date math, timestamp parsing and timezone conversion" },
    { "name": "meta",     "description": "API metadata and health" }
  ],
  "paths": {
    "/health.php": {
      "get": {
        "operationId": "health",
        "summary": "API health probe",
        "description": "Returns liveness, current API version, and the list of available endpoints. Excluded from rate limits — safe to poll from an uptime monitor.",
        "tags": ["meta"],
        "x-openai-isConsequential": false,
        "responses": {
          "200": { "description": "API is live", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HealthResponse" } } } }
        }
      }
    },

    "/count-lines.php": {
      "post": {
        "operationId": "countLines",
        "summary": "Count lines, words, sentences, paragraphs, characters and more",
        "description": "Returns sixteen metrics about a piece of text — total/empty/non-empty/duplicate lines, character count with and without spaces, word count, unique words, longest word, paragraph and sentence counts, line-length statistics, reading and speaking time estimates, and line-ending detection (LF, CRLF, CR).",
        "tags": ["text"],
        "x-openai-isConsequential": false,
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["text"], "properties": { "text": { "type": "string", "description": "UTF-8 text to analyse. Max 1 MB." } } } } } },
        "responses": { "200": { "description": "Success", "content": { "application/json": {} } }, "400": { "$ref": "#/components/responses/BadRequest" }, "413": { "$ref": "#/components/responses/PayloadTooLarge" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },

    "/base64-encode.php": {
      "post": {
        "operationId": "base64Encode",
        "summary": "Base64-encode text",
        "description": "Encodes the supplied text as Base64. When `url_safe=true`, uses the RFC 4648 §5 URL-safe alphabet (`-` and `_` instead of `+` and `/`) with padding stripped.",
        "tags": ["encoding"],
        "x-openai-isConsequential": false,
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["text"], "properties": { "text": {"type":"string"}, "url_safe": {"type":"boolean", "default": false} } } } } },
        "responses": { "200": { "description": "Success", "content": { "application/json": {} } }, "400": { "$ref": "#/components/responses/BadRequest" }, "413": { "$ref": "#/components/responses/PayloadTooLarge" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },

    "/base64-decode.php": {
      "post": {
        "operationId": "base64Decode",
        "summary": "Base64-decode a string",
        "description": "Decodes Base64. Accepts both standard and URL-safe alphabets; missing padding is auto-fixed. If the decoded bytes are valid UTF-8, returns them as text; otherwise returns hex.",
        "tags": ["encoding"],
        "x-openai-isConsequential": false,
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["encoded"], "properties": { "encoded": {"type":"string"} } } } } },
        "responses": { "200": { "description": "Success", "content": { "application/json": {} } }, "400": { "$ref": "#/components/responses/BadRequest" }, "413": { "$ref": "#/components/responses/PayloadTooLarge" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },

    "/url-encode.php": {
      "post": {
        "operationId": "urlEncode",
        "summary": "Percent-encode a string for URL use",
        "description": "Encodes a string for use in URLs. `mode=component` (default, RFC 3986 strict — every reserved character escaped). `mode=form` produces application/x-www-form-urlencoded (space → `+`). `mode=path` keeps `/` characters intact for use as a URL path segment.",
        "tags": ["encoding"],
        "x-openai-isConsequential": false,
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["text"], "properties": { "text": {"type":"string"}, "mode": {"type":"string","enum":["component","form","path"], "default": "component"} } } } } },
        "responses": { "200": { "description": "Success", "content": { "application/json": {} } }, "400": { "$ref": "#/components/responses/BadRequest" }, "413": { "$ref": "#/components/responses/PayloadTooLarge" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },

    "/url-decode.php": {
      "post": {
        "operationId": "urlDecode",
        "summary": "Decode a percent-encoded URL string",
        "description": "Decodes a percent-encoded URL string back to its original text. `mode=component` (default, rawurldecode). `mode=form` interprets `+` as space.",
        "tags": ["encoding"],
        "x-openai-isConsequential": false,
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["encoded"], "properties": { "encoded": {"type":"string"}, "mode": {"type":"string","enum":["component","form"], "default": "component"} } } } } },
        "responses": { "200": { "description": "Success", "content": { "application/json": {} } }, "400": { "$ref": "#/components/responses/BadRequest" }, "413": { "$ref": "#/components/responses/PayloadTooLarge" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },

    "/hash.php": {
      "post": {
        "operationId": "hash",
        "summary": "Compute cryptographic hashes of a text input",
        "description": "Computes one or more hashes of the supplied text. Defaults to MD5, SHA-1, SHA-256, SHA-384, SHA-512, CRC32, SHA3-256 and SHA3-512. Provide `algorithms` to compute a different subset.",
        "tags": ["crypto"],
        "x-openai-isConsequential": false,
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["text"], "properties": { "text": {"type":"string"}, "algorithms": {"type":"array", "items": {"type":"string"}} } } } } },
        "responses": { "200": { "description": "Success", "content": { "application/json": {} } }, "400": { "$ref": "#/components/responses/BadRequest" }, "413": { "$ref": "#/components/responses/PayloadTooLarge" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },

    "/password.php": {
      "post": {
        "operationId": "generatePassword",
        "summary": "Generate cryptographically random passwords",
        "description": "Generates one or more random passwords using PHP's `random_int` (CSPRNG). At least one character from each enabled class is guaranteed. `exclude_ambiguous` strips characters like `0/O/1/l/I/|`. Reports `entropy_bits` and a coarse strength label.",
        "tags": ["crypto"],
        "x-openai-isConsequential": false,
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "length": {"type":"integer","minimum":8,"maximum":128,"default":16}, "count": {"type":"integer","minimum":1,"maximum":50,"default":1}, "uppercase": {"type":"boolean","default":true}, "lowercase": {"type":"boolean","default":true}, "digits": {"type":"boolean","default":true}, "symbols": {"type":"boolean","default":true}, "exclude_ambiguous": {"type":"boolean","default":false} } } } } },
        "responses": { "200": { "description": "Success", "content": { "application/json": {} } }, "400": { "$ref": "#/components/responses/BadRequest" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },

    "/password-strength.php": {
      "post": {
        "operationId": "passwordStrength",
        "summary": "Score and analyse a password",
        "description": "Computes entropy in bits, returns a 0-4 score and a strength label, identifies common-password fingerprints and keyboard sequences, and estimates time-to-crack against an offline GPU at 10^11 guesses/second. The password itself is never logged — only its metrics are returned.",
        "tags": ["crypto"],
        "x-openai-isConsequential": false,
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["password"], "properties": { "password": {"type":"string"} } } } } },
        "responses": { "200": { "description": "Success", "content": { "application/json": {} } }, "400": { "$ref": "#/components/responses/BadRequest" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },

    "/json-format.php": {
      "post": {
        "operationId": "jsonFormat",
        "summary": "Validate, pretty-print, minify or analyse a JSON document",
        "description": "Parses the supplied JSON. Returns a structural analysis (node counts by type, max nesting depth, top distinct keys). `mode=pretty|minify|validate|analyze`.",
        "tags": ["data"],
        "x-openai-isConsequential": false,
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["json"], "properties": { "json": {"type":"string"}, "mode": {"type":"string","enum":["pretty","minify","validate","analyze"],"default":"pretty"}, "indent": {"type":"integer","minimum":1,"maximum":8,"default":2} } } } } },
        "responses": { "200": { "description": "Success", "content": { "application/json": {} } }, "400": { "$ref": "#/components/responses/BadRequest" }, "413": { "$ref": "#/components/responses/PayloadTooLarge" }, "422": { "description": "JSON parse error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },

    "/json-to-csv.php": {
      "post": {
        "operationId": "jsonToCsv",
        "summary": "Convert a JSON array of objects to CSV",
        "description": "Converts a top-level JSON array of objects to CSV. Optional explicit `columns` (subset/ordering), configurable `delimiter` and `newline`. With `flatten=true`, nested objects expand into dotted column names (`meta.city`, `meta.zip`). Max 50,000 rows per request.",
        "tags": ["data"],
        "x-openai-isConsequential": false,
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["json"], "properties": { "json": {"type":"string"}, "delimiter": {"type":"string","default":","}, "include_headers": {"type":"boolean","default":true}, "columns": {"type":"array","items":{"type":"string"}}, "flatten": {"type":"boolean","default":false}, "newline": {"type":"string","enum":["\n","\r\n","\r"],"default":"\n"} } } } } },
        "responses": { "200": { "description": "Success", "content": { "application/json": {} } }, "400": { "$ref": "#/components/responses/BadRequest" }, "413": { "$ref": "#/components/responses/PayloadTooLarge" }, "422": { "description": "JSON parse error or wrong shape", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },

    "/case-convert.php": {
      "post": {
        "operationId": "caseConvert",
        "summary": "Convert text into multiple case styles",
        "description": "Returns the input converted into 8 case styles: upper, lower, title, sentence, snake, kebab, camel, pascal, constant. Pass `cases` to limit the response.",
        "tags": ["text"],
        "x-openai-isConsequential": false,
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["text"], "properties": { "text": {"type":"string"}, "cases": {"type":"array","items":{"type":"string"}} } } } } },
        "responses": { "200": { "description": "Success", "content": { "application/json": {} } }, "400": { "$ref": "#/components/responses/BadRequest" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },

    "/slugify.php": {
      "post": {
        "operationId": "slugify",
        "summary": "Convert any text to a URL-safe slug",
        "description": "Produces a URL-safe slug. Unicode-aware: by default, accented and non-Latin characters are transliterated to ASCII (set `transliterate=false` to keep them). Configurable separator (`-`, `_`, `.`, `~`) and max length.",
        "tags": ["text"],
        "x-openai-isConsequential": false,
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["text"], "properties": { "text": {"type":"string"}, "separator": {"type":"string","default":"-"}, "max_length": {"type":"integer","default":80,"minimum":1,"maximum":200}, "lowercase": {"type":"boolean","default":true}, "transliterate": {"type":"boolean","default":true} } } } } },
        "responses": { "200": { "description": "Success", "content": { "application/json": {} } }, "400": { "$ref": "#/components/responses/BadRequest" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },

    "/sort-lines.php": {
      "post": {
        "operationId": "sortLines",
        "summary": "Sort lines of text",
        "description": "Sorts lines: `order=asc|desc`. Optional case-insensitive comparison, locale-aware collation, natural-sort (where line2 sorts before line10), dedupe, line trimming and empty-line stripping.",
        "tags": ["text"],
        "x-openai-isConsequential": false,
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["text"], "properties": { "text": {"type":"string"}, "order": {"type":"string","enum":["asc","desc"],"default":"asc"}, "case_sensitive": {"type":"boolean","default":true}, "natural": {"type":"boolean","default":false}, "locale": {"type":"string"}, "dedupe": {"type":"boolean","default":false}, "trim_lines": {"type":"boolean","default":false}, "drop_empty": {"type":"boolean","default":false} } } } } },
        "responses": { "200": { "description": "Success", "content": { "application/json": {} } }, "400": { "$ref": "#/components/responses/BadRequest" }, "413": { "$ref": "#/components/responses/PayloadTooLarge" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },

    "/lorem-ipsum.php": {
      "post": {
        "operationId": "loremIpsum",
        "summary": "Generate placeholder text",
        "description": "Generates `count` paragraphs, sentences or words of lorem-ipsum. Default starts with the canonical 'Lorem ipsum dolor sit amet…' opener — set `start_with_lorem=false` to skip it.",
        "tags": ["text"],
        "x-openai-isConsequential": false,
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "kind": {"type":"string","enum":["paragraphs","sentences","words"],"default":"paragraphs"}, "count": {"type":"integer","minimum":1,"maximum":50,"default":3}, "start_with_lorem": {"type":"boolean","default":true} } } } } },
        "responses": { "200": { "description": "Success", "content": { "application/json": {} } }, "400": { "$ref": "#/components/responses/BadRequest" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },

    "/timestamp.php": {
      "post": {
        "operationId": "timestamp",
        "summary": "Convert any moment in time between formats",
        "description": "Bidirectional conversion: Unix epoch ↔ ISO 8601 / RFC 3339 / RFC 2822 / human-readable. Auto-detects epoch resolution (seconds, ms, µs, ns) by magnitude. Accepts strtotime-style natural language ('next monday', '+2 weeks', 'tomorrow 3pm'). Returns the moment in 6 formats plus components in any timezone.",
        "tags": ["datetime"],
        "x-openai-isConsequential": false,
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "input": {"type":"string","description":"Date/time string or Unix epoch. Omit for current time."}, "timezone": {"type":"string","default":"UTC","description":"IANA timezone name e.g. 'Europe/Madrid'."} } } } } },
        "responses": { "200": { "description": "Success", "content": { "application/json": {} } }, "400": { "$ref": "#/components/responses/BadRequest" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },

    "/date-math.php": {
      "post": {
        "operationId": "dateMath",
        "summary": "Add/subtract a duration from a date, or compute the diff between two dates",
        "description": "Two modes: (1) provide `start` + `add` (e.g. '+90 days', '-2 weeks', 'next friday') to compute the resulting date, OR (2) provide `start` + `end` to compute the diff. Diff mode optionally counts business days only (Mon-Fri). Times are in UTC.",
        "tags": ["datetime"],
        "x-openai-isConsequential": false,
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["start"], "properties": { "start": {"type":"string"}, "add": {"type":"string"}, "end": {"type":"string"}, "business_days": {"type":"boolean","default":false} } } } } },
        "responses": { "200": { "description": "Success", "content": { "application/json": {} } }, "400": { "$ref": "#/components/responses/BadRequest" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },

    "/regex-test.php": {
      "post": {
        "operationId": "regexTest",
        "summary": "Match, replace or split with a regular expression",
        "description": "Tests a PCRE pattern against text. `operation=match` returns the first match; `match_all` returns all matches with offsets and capture groups; `replace` performs substitution; `split` divides the text on matches. ReDoS-protected: catastrophic patterns return 422 `pcre_error: backtrack limit exhausted`.",
        "tags": ["text"],
        "x-openai-isConsequential": false,
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["pattern","text"], "properties": { "pattern": {"type":"string"}, "text": {"type":"string"}, "flags": {"type":"string","description":"any of i, m, s, u, x"}, "operation": {"type":"string","enum":["match","match_all","replace","split"],"default":"match_all"}, "replacement": {"type":"string"}, "split_limit": {"type":"integer","minimum":0,"maximum":100000} } } } } },
        "responses": { "200": { "description": "Success", "content": { "application/json": {} } }, "400": { "$ref": "#/components/responses/BadRequest" }, "413": { "$ref": "#/components/responses/PayloadTooLarge" }, "422": { "description": "PCRE failure (catastrophic pattern, JIT limit, etc.)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    }
  },

  "components": {
    "schemas": {
      "HealthResponse": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "version": { "type": "string" },
          "docs": { "type": "string", "format": "uri" },
          "openapi": { "type": "string", "format": "uri" },
          "mcp": { "type": "string", "format": "uri" },
          "time": { "type": "string", "format": "date-time" },
          "endpoints": { "type": "array", "items": { "type": "object" } }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code":    { "type": "integer" },
              "message": { "type": "string" },
              "field":   { "type": ["string", "null"] }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest":       { "description": "Malformed request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "MethodNotAllowed": { "description": "Method not allowed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "PayloadTooLarge":  { "description": "Request body or text exceeds 1 MB", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "RateLimited":      { "description": "Rate limit exceeded (60/min, 600/hour per IP). Includes Retry-After header.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    }
  }
}
