{
  "openapi": "3.1.0",
  "info": {
    "title": "PlainGrade API",
    "version": "0.3.1",
    "description": "English readability scores and text statistics. Text is scored in memory and never stored or logged. Anonymous: about 10/min and 50/day per IP; free key: 100/day, 30/min. Also a remote MCP server at /mcp (see /llms.txt).",
    "contact": {
      "email": "digitalpromohub.support@gmail.com"
    }
  },
  "servers": [
    {
      "url": "https://plaingrade-api.mike-tusa.workers.dev"
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Free key from POST /v1/keys. Optional on /v1/score and /mcp."
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Same key as X-Api-Key; accepted on /mcp only."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "ok",
          "error"
        ],
        "properties": {
          "ok": {
            "const": false
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "ScoreRequest": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "maxLength": 20480,
            "description": "English text, at most 20,480 bytes UTF-8 (413 text_too_large otherwise)."
          },
          "lang": {
            "type": "string",
            "maxLength": 20,
            "pattern": "^[Ee][Nn]([-_][A-Za-z0-9]{2,8})*$",
            "default": "en"
          },
          "long_sentence_words": {
            "type": "integer",
            "minimum": 5,
            "maximum": 200,
            "default": 25
          }
        }
      },
      "ScoreResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "const": true
          },
          "lang": {
            "type": "string"
          },
          "scores": {
            "type": "object"
          },
          "interpretation": {
            "type": "object"
          },
          "stats": {
            "type": "object"
          },
          "long_sentences": {
            "type": "object"
          },
          "notes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "text_bytes": {
            "type": "integer"
          },
          "engine": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        }
      }
    }
  },
  "paths": {
    "/v1/score": {
      "post": {
        "operationId": "scoreText",
        "summary": "Score English text",
        "security": [
          {},
          {
            "apiKeyHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScoreRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Scores",
            "headers": {
              "X-Api-Plan": {
                "description": "anonymous or free_key",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "Daily request quota",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left today (approximate)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix time of the next 00:00 UTC reset",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Policy": {
                "description": "Daily and per-minute policy, e.g. \"100;w=86400, 30;w=60\"",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScoreResponse"
                }
              }
            }
          },
          "400": {
            "description": "invalid_json, missing_text, invalid_lang, invalid_long_sentence_words",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "body_too_large, text_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "unsupported_language",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited, ip_daily_quota, key_quota_exceeded, issuer_quota_exceeded, daily_cap_reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "not_configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/keys": {
      "post": {
        "operationId": "createKey",
        "summary": "Issue a free API key (shown once)",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Key issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "api_key": {
                      "type": "string"
                    },
                    "key_id": {
                      "type": "string"
                    },
                    "plan": {
                      "type": "string"
                    },
                    "daily_quota": {
                      "type": "integer"
                    },
                    "per_minute": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "cross_origin_forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "body_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited, key_issue_limit, daily_cap_reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable, not_configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "operationId": "getUsage",
        "summary": "Today's usage for a key",
        "security": [
          {
            "apiKeyHeader": []
          }
        ],
        "responses": {
          "200": {
            "description": "Usage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "key_id": {
                      "type": "string"
                    },
                    "plan": {
                      "type": "string"
                    },
                    "daily_quota": {
                      "type": "integer"
                    },
                    "used_today": {
                      "type": "integer"
                    },
                    "remaining_today": {
                      "type": "integer"
                    },
                    "per_minute": {
                      "type": "integer"
                    },
                    "resets_at": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "missing_api_key, invalid_api_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "not_configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "health",
        "summary": "Status and version",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "version": {
                      "type": "string"
                    },
                    "build": {
                      "type": "string"
                    },
                    "environment": {
                      "type": "string"
                    },
                    "configured": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "operationId": "mcp",
        "summary": "MCP JSON-RPC 2.0 (Streamable HTTP, stateless, JSON responses)",
        "security": [
          {},
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "description": "Protocol versions 2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05. Tool score_readability. Max 65,536 bytes, batches up to 10 with at most one tools/call. Non-check messages: about 120/min per IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "JSON-RPC 2.0 request, notification or batch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response (tool errors are results with isError: true)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "202": {
            "description": "Notification or client response accepted (empty body)"
          },
          "400": {
            "description": "Parse error, invalid request, protocol-version or header mismatch (JSON-RPC error)"
          },
          "401": {
            "description": "Invalid API key (JSON-RPC error, data.code invalid_api_key; no WWW-Authenticate)"
          },
          "403": {
            "description": "Origin not allowed"
          },
          "404": {
            "description": "Method not found (protocol 2026-07-28)"
          },
          "413": {
            "description": "Body too large"
          },
          "429": {
            "description": "Too many MCP requests",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Internal error"
          },
          "503": {
            "description": "Not configured"
          }
        }
      },
      "get": {
        "operationId": "mcpGet",
        "summary": "Not supported (no SSE stream)",
        "responses": {
          "405": {
            "description": "Method not allowed",
            "headers": {
              "Allow": {
                "description": "POST, OPTIONS",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "mcpDelete",
        "summary": "Not supported (no sessions)",
        "responses": {
          "405": {
            "description": "Method not allowed",
            "headers": {
              "Allow": {
                "description": "POST, OPTIONS",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "llmsTxt",
        "summary": "llms.txt",
        "responses": {
          "200": {
            "description": "text/plain",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "openApi",
        "summary": "This document",
        "responses": {
          "200": {
            "description": "OpenAPI 3.1",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  }
}