{
  "openapi": "3.1.1",
  "info": {
    "title": "Query Plan Visualizer API",
    "version": "1.0.0",
    "summary": "PostgreSQL EXPLAIN (ANALYZE) or MySQL EXPLAIN JSON → plan tree, hot nodes and index suggestions.",
    "description": "Every endpoint performs the same calculation as the human page and returns inputs, result, units, assumptions, sources, warnings and computedAt. MCP endpoint: /mcp."
  },
  "servers": [
    {
      "url": "https://queryplanvisualizer.usefulweb.site"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "PostgreSQL EXPLAIN (ANALYZE) or MySQL EXPLAIN JSON → plan tree, hot nodes and index suggestions.",
        "description": "Paste PostgreSQL EXPLAIN / EXPLAIN ANALYZE output (text or FORMAT JSON) or MySQL EXPLAIN FORMAT=JSON. The plan is drawn as a node tree with cost, estimated vs actual rows, inclusive and exclusive time per node, and checked for the classic problems: row misestimates, sequential scans that discard most rows, sorts and hashes spilling to disk, nested loops over large inputs — each with a concrete fix such as a specific CREATE INDEX statement or work_mem setting.\n\nMethodology:\n- PostgreSQL text plans are parsed by the \"->\" indentation; node headers give cost=startup..total rows width and (actual time=startup..total rows loops); detail lines (Filter, Rows Removed by Filter, Sort Method, Buckets/Batches, Buffers…) are mapped to the same keys as FORMAT JSON.\n- Actual time and rows are per-loop averages (PostgreSQL docs), so inclusive time = actual total time × loops; exclusive time = inclusive − children's inclusive time. Under Gather, time is divided by the number of participating processes.\n- Misestimate = max(estimated, actual)/min(…) rows per loop at or above the threshold (and ≥100 rows absolute difference).\n- Rules: Seq Scan with Filter removing ≥90% of ≥1000 rows; Index Scan still filtering; Sort Method external/Disk; Hash Batches > 1; Nested Loop with ≥1000 outer rows and a sequential inner side or ≥10k inner loops; lossy bitmaps; workers not launched; heap fetches in index-only scans.\n- Index candidates take equality-compared filter columns first, then the first range column (B-tree column-order rule); functions in filters call for expression indexes and leading-wildcard LIKE for pg_trgm.\n- MySQL: access_type ALL / index, filtered %, attached_condition columns, using_join_buffer, using_filesort and using_temporary_table (EXPLAIN FORMAT=JSON has estimates only, no timings).",
        "tags": [
          "devcloud"
        ],
        "x-mcp-tool": "query_plan_visualizer",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QueryPlanVisualizerInput"
              },
              "examples": {
                "example1": {
                  "summary": "PostgreSQL EXPLAIN ANALYZE: seq scan, disk sort, batched hash",
                  "value": {
                    "text": "                                                              QUERY PLAN\n-------------------------------------------------------------------------------------------------------------------------------------------\n Sort  (cost=152340.21..153372.39 rows=412873 width=72) (actual time=2211.041..2389.770 rows=412873 loops=1)\n   Sort Key: o.created_at DESC\n   Sort Method: external merge  Disk: 48960kB\n   Buffers: shared hit=1204 read=58210, temp read=12240 written=12268\n   ->  Hash Join  (cost=31250.00..102149.11 rows=5000 width=72) (actual time=402.118..1712.904 rows=412873 loops=1)\n         Hash Cond: (o.customer_id = c.id)\n         ->  Seq Scan on orders o  (cost=0.00..68340.00 rows=5000 width=40) (actual time=0.031..1105.226 rows=412873 loops=1)\n               Filter: ((status = 'shipped'::text) AND (created_at >= '2026-01-01 00:00:00'::timestamp without time zone))\n               Rows Removed by Filter: 4587127\n         ->  Hash  (cost=18750.00..18750.00 rows=1000000 width=36) (actual time=398.402..398.403 rows=1000000 loops=1)\n               Buckets: 262144  Batches: 8  Memory Usage: 9201kB\n               ->  Seq Scan on customers c  (cost=0.00..18750.00 rows=1000000 width=36) (actual time=0.012..141.882 rows=1000000 loops=1)\n Planning Time: 0.412 ms\n Execution Time: 2412.330 ms\n(14 rows)"
                  }
                },
                "example2": {
                  "summary": "PostgreSQL JSON: nested loop with a misestimate",
                  "value": {
                    "text": "[{\"Plan\":{\"Node Type\":\"Aggregate\",\"Strategy\":\"Plain\",\"Partial Mode\":\"Simple\",\"Parallel Aware\":false,\"Startup Cost\":2412.3,\"Total Cost\":2412.31,\"Plan Rows\":1,\"Plan Width\":32,\"Actual Startup Time\":4380.2,\"Actual Total Time\":4380.21,\"Actual Rows\":1,\"Actual Loops\":1,\"Plans\":[{\"Node Type\":\"Nested Loop\",\"Parent Relationship\":\"Outer\",\"Parallel Aware\":false,\"Join Type\":\"Inner\",\"Startup Cost\":0.43,\"Total Cost\":2412.25,\"Plan Rows\":10,\"Plan Width\":10,\"Actual Startup Time\":0.09,\"Actual Total Time\":4362,\"Actual Rows\":20000,\"Actual Loops\":1,\"Inner Unique\":false,\"Plans\":[{\"Node Type\":\"Index Scan\",\"Parent Relationship\":\"Outer\",\"Parallel Aware\":false,\"Scan Direction\":\"Forward\",\"Index Name\":\"line_items_order_id_idx\",\"Relation Name\":\"line_items\",\"Alias\":\"li\",\"Startup Cost\":0.43,\"Total Cost\":12.5,\"Plan Rows\":10,\"Plan Width\":14,\"Actual Startup Time\":0.03,\"Actual Total Time\":35.1,\"Actual Rows\":20000,\"Actual Loops\":1,\"Index Cond\":\"(order_id = ANY ('{5001,5002,5003}'::integer[]))\",\"Rows Removed by Index Recheck\":0},{\"Node Type\":\"Seq Scan\",\"Parent Relationship\":\"Inner\",\"Parallel Aware\":false,\"Relation Name\":\"products\",\"Alias\":\"p\",\"Startup Cost\":0,\"Total Cost\":240,\"Plan Rows\":1,\"Plan Width\":14,\"Actual Startup Time\":0.1,\"Actual Total Time\":0.215,\"Actual Rows\":1,\"Actual Loops\":20000,\"Filter\":\"(sku = li.sku)\",\"Rows Removed by Filter\":1999}]}]},\"Planning Time\":0.3,\"Triggers\":[],\"Execution Time\":4381}]"
                  }
                },
                "example3": {
                  "summary": "MySQL EXPLAIN FORMAT=JSON: full scan + filesort",
                  "value": {
                    "text": "{\"query_block\":{\"select_id\":1,\"cost_info\":{\"query_cost\":\"10431.25\"},\"ordering_operation\":{\"using_filesort\":true,\"nested_loop\":[{\"table\":{\"table_name\":\"o\",\"access_type\":\"ALL\",\"possible_keys\":null,\"rows_examined_per_scan\":98500,\"rows_produced_per_join\":985,\"filtered\":\"1.00\",\"cost_info\":{\"read_cost\":\"9947.75\",\"eval_cost\":\"98.50\",\"prefix_cost\":\"10046.25\",\"data_read_per_join\":\"30K\"},\"used_columns\":[\"id\",\"customer_id\",\"status\",\"created_at\"],\"attached_condition\":\"((`shop`.`o`.`status` = 'shipped') and (`shop`.`o`.`created_at` >= DATE'2026-01-01'))\"}},{\"table\":{\"table_name\":\"c\",\"access_type\":\"eq_ref\",\"possible_keys\":[\"PRIMARY\"],\"key\":\"PRIMARY\",\"used_key_parts\":[\"id\"],\"key_length\":\"4\",\"ref\":[\"shop.o.customer_id\"],\"rows_examined_per_scan\":1,\"rows_produced_per_join\":985,\"filtered\":\"100.00\",\"cost_info\":{\"read_cost\":\"246.25\",\"eval_cost\":\"98.50\",\"prefix_cost\":\"10391.00\",\"data_read_per_join\":\"61K\"},\"used_columns\":[\"id\",\"name\"]}}]}}}"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QueryPlanVisualizerResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tool": {
      "get": {
        "operationId": "getToolDefinition",
        "summary": "Inputs, output schema and examples for this tool",
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "QueryPlanVisualizerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "text": {
            "title": "EXPLAIN output",
            "description": "PostgreSQL: EXPLAIN (ANALYZE, BUFFERS) … as text, or EXPLAIN (ANALYZE, FORMAT JSON). MySQL: EXPLAIN FORMAT=JSON …. psql headers, \"+\" line continuations and \"(N rows)\" footers are ignored.",
            "type": "string",
            "maxLength": 3000000
          },
          "file": {
            "title": "Or upload the file",
            "description": "Read in memory as UTF-8 text and not stored. Max 5 MiB. Upload as {filename, contentType?, contentBase64}. Max 5120 KiB. Accepts .txt,.json,.sql,.log.",
            "type": "object",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "misestimateFactor": {
            "title": "Flag row misestimates from",
            "description": "Ratio between estimated and actual rows per loop.",
            "type": "number",
            "minimum": 2,
            "maximum": 1000,
            "default": 10,
            "x-unit": "×"
          }
        },
        "required": []
      },
      "QueryPlanVisualizerResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "engine",
              "format",
              "analyzed",
              "planningMs",
              "executionMs",
              "totalCost",
              "nodeCount",
              "nodes",
              "findings",
              "indexCandidates"
            ],
            "properties": {
              "engine": {
                "type": "string",
                "enum": [
                  "postgresql",
                  "mysql"
                ]
              },
              "format": {
                "type": "string",
                "enum": [
                  "json",
                  "text"
                ]
              },
              "analyzed": {
                "type": "boolean"
              },
              "planningMs": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "executionMs": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "totalCost": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "nodeCount": {
                "type": "integer"
              },
              "nodes": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "parentId",
                    "depth",
                    "type",
                    "label",
                    "relation",
                    "index",
                    "startupCost",
                    "totalCost",
                    "exclusiveCost",
                    "planRows",
                    "actualRows",
                    "loops",
                    "inclusiveMs",
                    "exclusiveMs",
                    "rowsRatio",
                    "details"
                  ],
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "parentId": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "depth": {
                      "type": "integer"
                    },
                    "type": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "relation": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "index": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "startupCost": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "totalCost": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "exclusiveCost": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "planRows": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "actualRows": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "loops": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "inclusiveMs": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "exclusiveMs": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "rowsRatio": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": {
                        "type": [
                          "string",
                          "number",
                          "boolean"
                        ]
                      }
                    }
                  }
                }
              },
              "findings": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "nodeId",
                    "severity",
                    "issue",
                    "detail",
                    "suggestion"
                  ],
                  "properties": {
                    "nodeId": {
                      "type": "integer"
                    },
                    "severity": {
                      "type": "string",
                      "enum": [
                        "high",
                        "medium",
                        "low"
                      ]
                    },
                    "issue": {
                      "type": "string"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "suggestion": {
                      "type": "string"
                    }
                  }
                }
              },
              "indexCandidates": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "relation",
                    "columns",
                    "statement",
                    "reason"
                  ],
                  "properties": {
                    "relation": {
                      "type": "string"
                    },
                    "columns": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "statement": {
                      "type": "string"
                    },
                    "reason": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}