{
  "openapi": "3.1.0",
  "info": {
    "title": "ContrastKit",
    "version": "0.1.0",
    "description": "WCAG 2.x color contrast checker, nearest accessible color, CSS color conversion and palette checks. Pure computation; nothing stored; no API key. About 60 checks per minute per IP. Contrast ratio = (L1 + 0.05) / (L2 + 0.05) with WCAG 2.x relative luminance (sRGB). Levels: aa 4.5:1, aa-large 3:1, aaa 7:1, aaa-large 4.5:1, ui 3:1 (aa = SC 1.4.3 normal text, aa-large = 18pt / 14pt bold text, aaa = SC 1.4.6, ui = SC 1.4.11 non-text). Ratios are floored to 2 decimals and pass/fail uses the exact value, so 4.499:1 fails AA. A translucent foreground is blended over the background; a translucent background is blended over white. ContrastKit is not affiliated with, endorsed by or sponsored by the W3C, WebAIM, Meta, Anthropic, OpenAI or Anysphere. WCAG is a W3C standard; a passing ratio is one part of accessibility, not a full audit.",
    "contact": {
      "email": "digitalpromohub.support@gmail.com"
    }
  },
  "servers": [
    {
      "url": "https://contrastkit.mike-tusa.workers.dev"
    }
  ],
  "paths": {
    "/api/contrast": {
      "get": {
        "operationId": "checkContrast",
        "summary": "WCAG contrast ratio and pass/fail",
        "parameters": [
          {
            "name": "foreground",
            "in": "query",
            "required": true,
            "description": "CSS color (URL-encode # as %23, or omit it). Alias: fg.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            },
            "example": "#777777"
          },
          {
            "name": "background",
            "in": "query",
            "required": true,
            "description": "CSS color (URL-encode # as %23, or omit it). Alias: bg.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            },
            "example": "#ffffff"
          }
        ],
        "responses": {
          "200": {
            "description": "Contrast result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contrast"
                }
              }
            }
          },
          "400": {
            "description": "invalid_input or invalid_color (missing/oversized input is rejected before the rate limiter)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited",
            "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"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "checkContrastPostNotAllowed",
        "summary": "Not supported (use GET)",
        "responses": {
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/suggest": {
      "get": {
        "operationId": "suggestColor",
        "summary": "Nearest color that meets a WCAG level",
        "parameters": [
          {
            "name": "foreground",
            "in": "query",
            "required": true,
            "description": "CSS color (URL-encode # as %23, or omit it). Alias: fg.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            },
            "example": "#777777"
          },
          {
            "name": "background",
            "in": "query",
            "required": true,
            "description": "CSS color (URL-encode # as %23, or omit it). Alias: bg.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            },
            "example": "#ffffff"
          },
          {
            "name": "level",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "aa",
                "aa-large",
                "aaa",
                "aaa-large",
                "ui"
              ],
              "default": "aa"
            }
          },
          {
            "name": "adjust",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "foreground",
                "background",
                "both"
              ],
              "default": "foreground"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Suggestions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Suggest"
                }
              }
            }
          },
          "400": {
            "description": "invalid_input or invalid_color (missing/oversized input is rejected before the rate limiter)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited",
            "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"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "suggestColorPostNotAllowed",
        "summary": "Not supported (use GET)",
        "responses": {
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/convert": {
      "get": {
        "operationId": "convertColor",
        "summary": "Convert a CSS color (hex, rgb, hsl, luminance, best text color)",
        "parameters": [
          {
            "name": "color",
            "in": "query",
            "required": true,
            "description": "CSS color (URL-encode # as %23, or omit it).",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            },
            "example": "rebeccapurple"
          }
        ],
        "responses": {
          "200": {
            "description": "Converted color",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Convert"
                }
              }
            }
          },
          "400": {
            "description": "invalid_input or invalid_color (missing/oversized input is rejected before the rate limiter)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited",
            "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"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "convertColorPostNotAllowed",
        "summary": "Not supported (use GET)",
        "responses": {
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/palette": {
      "get": {
        "operationId": "checkPalette",
        "summary": "Contrast of every pair of 2-12 colors",
        "parameters": [
          {
            "name": "colors",
            "in": "query",
            "required": false,
            "description": "2-12 CSS colors separated by | (alternatively repeat color=).",
            "schema": {
              "type": "string",
              "maxLength": 780
            },
            "example": "#ffffff|#111827|#1a73e8"
          },
          {
            "name": "level",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "aa",
                "aa-large",
                "aaa",
                "aaa-large",
                "ui"
              ],
              "default": "aa"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pairs",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Palette"
                }
              }
            }
          },
          "400": {
            "description": "invalid_input or invalid_color (missing/oversized input is rejected before the rate limiter)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited",
            "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"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "checkPalettePostNotAllowed",
        "summary": "Not supported (use GET)",
        "responses": {
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/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"
                    }
                  },
                  "required": [
                    "ok",
                    "version"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/.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"
                }
              }
            }
          }
        }
      }
    },
    "/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)"
              }
            }
          }
        },
        "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)"
                }
              }
            }
          },
          "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": {
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_input",
                  "invalid_color",
                  "rate_limited",
                  "method_not_allowed",
                  "not_found",
                  "internal_error"
                ]
              },
              "message": {
                "type": "string"
              },
              "field": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      },
      "Color": {
        "type": "object",
        "properties": {
          "hex": {
            "type": "string"
          },
          "rgb": {
            "type": "string"
          },
          "hsl": {
            "type": "string"
          },
          "alpha": {
            "type": "number"
          },
          "luminance": {
            "type": "number"
          }
        },
        "required": [
          "hex",
          "rgb",
          "hsl",
          "alpha",
          "luminance"
        ]
      },
      "Contrast": {
        "type": "object",
        "properties": {
          "foreground": {
            "type": "object",
            "properties": {
              "hex": {
                "type": "string"
              },
              "rgb": {
                "type": "string"
              },
              "hsl": {
                "type": "string"
              },
              "alpha": {
                "type": "number"
              },
              "luminance": {
                "type": "number"
              }
            },
            "required": [
              "hex",
              "rgb",
              "hsl",
              "alpha",
              "luminance"
            ]
          },
          "background": {
            "type": "object",
            "properties": {
              "hex": {
                "type": "string"
              },
              "rgb": {
                "type": "string"
              },
              "hsl": {
                "type": "string"
              },
              "alpha": {
                "type": "number"
              },
              "luminance": {
                "type": "number"
              }
            },
            "required": [
              "hex",
              "rgb",
              "hsl",
              "alpha",
              "luminance"
            ]
          },
          "effectiveForeground": {
            "type": "string"
          },
          "effectiveBackground": {
            "type": "string"
          },
          "ratio": {
            "type": "number"
          },
          "ratioExact": {
            "type": "number"
          },
          "pass": {
            "type": "object",
            "properties": {
              "aa": {
                "type": "boolean"
              },
              "aa_large": {
                "type": "boolean"
              },
              "aaa": {
                "type": "boolean"
              },
              "aaa_large": {
                "type": "boolean"
              },
              "ui": {
                "type": "boolean"
              }
            }
          },
          "notes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "ratio",
          "pass",
          "notes",
          "version"
        ]
      },
      "Suggest": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "foreground": {
                "type": "object",
                "properties": {
                  "hex": {
                    "type": "string"
                  },
                  "rgb": {
                    "type": "string"
                  },
                  "hsl": {
                    "type": "string"
                  },
                  "alpha": {
                    "type": "number"
                  },
                  "luminance": {
                    "type": "number"
                  }
                },
                "required": [
                  "hex",
                  "rgb",
                  "hsl",
                  "alpha",
                  "luminance"
                ]
              },
              "background": {
                "type": "object",
                "properties": {
                  "hex": {
                    "type": "string"
                  },
                  "rgb": {
                    "type": "string"
                  },
                  "hsl": {
                    "type": "string"
                  },
                  "alpha": {
                    "type": "number"
                  },
                  "luminance": {
                    "type": "number"
                  }
                },
                "required": [
                  "hex",
                  "rgb",
                  "hsl",
                  "alpha",
                  "luminance"
                ]
              },
              "effectiveForeground": {
                "type": "string"
              },
              "effectiveBackground": {
                "type": "string"
              },
              "ratio": {
                "type": "number"
              },
              "ratioExact": {
                "type": "number"
              },
              "pass": {
                "type": "object",
                "properties": {
                  "aa": {
                    "type": "boolean"
                  },
                  "aa_large": {
                    "type": "boolean"
                  },
                  "aaa": {
                    "type": "boolean"
                  },
                  "aaa_large": {
                    "type": "boolean"
                  },
                  "ui": {
                    "type": "boolean"
                  }
                }
              },
              "notes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "version": {
                "type": "string"
              }
            },
            "required": [
              "ratio",
              "pass",
              "notes",
              "version"
            ]
          },
          {
            "type": "object",
            "properties": {
              "level": {
                "type": "string"
              },
              "target": {
                "type": "number"
              },
              "adjust": {
                "type": "string"
              },
              "alreadyPasses": {
                "type": "boolean"
              },
              "suggestions": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "adjust": {
                      "type": "string"
                    },
                    "color": {
                      "type": "object",
                      "properties": {
                        "hex": {
                          "type": "string"
                        },
                        "rgb": {
                          "type": "string"
                        },
                        "hsl": {
                          "type": "string"
                        },
                        "alpha": {
                          "type": "number"
                        },
                        "luminance": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "hex",
                        "rgb",
                        "hsl",
                        "alpha",
                        "luminance"
                      ]
                    },
                    "ratio": {
                      "type": "number"
                    },
                    "lightnessChange": {
                      "type": "number"
                    }
                  }
                }
              }
            },
            "required": [
              "level",
              "target",
              "alreadyPasses",
              "suggestions"
            ]
          }
        ]
      },
      "Convert": {
        "type": "object",
        "properties": {
          "input": {
            "type": "string"
          },
          "hex": {
            "type": "string"
          },
          "rgb": {
            "type": "string"
          },
          "hsl": {
            "type": "string"
          },
          "alpha": {
            "type": "number"
          },
          "luminance": {
            "type": "number"
          },
          "contrastOnWhite": {
            "type": "number"
          },
          "contrastOnBlack": {
            "type": "number"
          },
          "bestTextColor": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "hex",
          "luminance",
          "bestTextColor",
          "version"
        ]
      },
      "Palette": {
        "type": "object",
        "properties": {
          "level": {
            "type": "string"
          },
          "target": {
            "type": "number"
          },
          "colors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "count": {
            "type": "integer"
          },
          "passing": {
            "type": "integer"
          },
          "pairs": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "a": {
                  "type": "string"
                },
                "b": {
                  "type": "string"
                },
                "ratio": {
                  "type": "number"
                },
                "passes": {
                  "type": "boolean"
                }
              }
            }
          },
          "notes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "pairs",
          "count",
          "passing",
          "version"
        ]
      }
    }
  }
}