{
  "openapi": "3.1.1",
  "info": {
    "title": "Opsdeck Schema Diff & Migration Generator (PostgreSQL, MySQL, SQLite) API",
    "version": "1.0.0",
    "summary": "Compare two DDL versions: ordered migration, rollback script and data-loss warnings.",
    "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://opsdeckschemadiff.usefulweb.site"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "Compare two DDL versions: ordered migration, rollback script and data-loss warnings.",
        "description": "Paste the current and the new CREATE TABLE / CREATE INDEX / ALTER TABLE statements: the tool reads both into a schema model, lists every change per table and writes an ordered migration script for PostgreSQL, MySQL/MariaDB or SQLite plus the reverse (rollback) script. Dropped tables and columns, narrowed types, new NOT NULL, unique, foreign-key and check constraints are flagged. Both scripts are verified by applying them to the model. It is the schema diff of the Opsdeck Database desk.\n\nMethodology:\n- Both DDL texts are tokenised (strings, quoted names, comments, dollar quotes) and read statement by statement into one schema model: tables, columns (normalised type, NULL/NOT NULL, default, identity/auto-increment, generated expression), primary key, unique keys, foreign keys with ON DELETE/UPDATE, checks and indexes. Later ALTER TABLE statements are applied to the model in order.\n- Tables and columns are matched by name (case-insensitive). Rename hints are applied first; without them a renamed column shows up as drop + add, and the tool points that out.\n- The script runs in a safe order: drop changed foreign keys, indexes and constraints; renames; new tables in dependency order; add, change and drop columns; drop tables (children first); then new keys, checks, indexes and foreign keys. MySQL uses MODIFY COLUMN with its position; SQLite rebuilds a table (create new, copy rows, drop, rename) when ALTER TABLE cannot express the change.\n- Type changes are classified: narrower integers, shorter text, fewer decimal digits, float → integer, timestamp → date are destructive; changes between kinds of data are risky. New NOT NULL, unique, primary-key, foreign-key and check constraints are risky because existing rows may break them.\n- Verification: the forward script is applied to the parsed source with the same parser and must give the parsed target; the reverse script must give back the source. PostgreSQL and SQLite add columns at the end, so their column order is compared as a set.\n\nThe script is generated from the DDL text only: it cannot see your data, views, triggers, functions, grants or partitions. Review it, run it on a copy first and keep a backup.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "opsdeck_schema_diff",
        "x-owner-app": "opsdeck",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpsdeckSchemaDiffInput"
              },
              "examples": {
                "example1": {
                  "summary": "Shop schema v1 → v2 (PostgreSQL) with a column rename",
                  "value": {
                    "source": "-- Shop schema, version 1\nCREATE TABLE customers (\n  id integer PRIMARY KEY,\n  email varchar(255) NOT NULL UNIQUE,\n  name text,\n  phone varchar(40),\n  created_at timestamp DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE products (\n  sku varchar(32) PRIMARY KEY,\n  title text NOT NULL,\n  price numeric(10,2) NOT NULL\n);\n\nCREATE TABLE orders (\n  id integer PRIMARY KEY,\n  customer_id integer REFERENCES customers (id),\n  total numeric(12,2) NOT NULL DEFAULT 0,\n  status varchar(20),\n  legacy_code char(4)\n);\nCREATE INDEX orders_status_idx ON orders (status);\n\nCREATE TABLE audit_log (\n  id integer PRIMARY KEY,\n  msg text\n);\n",
                    "target": "-- Shop schema, version 2\nCREATE TABLE customers (\n  id integer PRIMARY KEY,\n  email varchar(120) NOT NULL UNIQUE,\n  full_name text,\n  phone varchar(40),\n  created_at timestamp DEFAULT CURRENT_TIMESTAMP,\n  country char(2) NOT NULL DEFAULT 'DE'\n);\n\nCREATE TABLE products (\n  sku varchar(32) PRIMARY KEY,\n  title text NOT NULL,\n  price numeric(10,2) NOT NULL CHECK (price >= 0)\n);\n\nCREATE TABLE orders (\n  id integer PRIMARY KEY,\n  customer_id integer NOT NULL REFERENCES customers (id) ON DELETE CASCADE,\n  total numeric(10,2) NOT NULL DEFAULT 0,\n  status varchar(20) NOT NULL DEFAULT 'new',\n  shipped_at timestamp\n);\nCREATE INDEX orders_status_idx ON orders (status, shipped_at);\n\nCREATE TABLE order_items (\n  order_id integer NOT NULL REFERENCES orders (id),\n  sku varchar(32) NOT NULL REFERENCES products (sku),\n  qty integer NOT NULL CHECK (qty > 0),\n  PRIMARY KEY (order_id, sku)\n);\n",
                    "dialect": "postgresql",
                    "renames": "customers.name -> full_name"
                  }
                },
                "example2": {
                  "summary": "The same change for SQLite (table rebuilds where ALTER TABLE cannot)",
                  "value": {
                    "source": "-- Shop schema, version 1\nCREATE TABLE customers (\n  id integer PRIMARY KEY,\n  email varchar(255) NOT NULL UNIQUE,\n  name text,\n  phone varchar(40),\n  created_at timestamp DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE products (\n  sku varchar(32) PRIMARY KEY,\n  title text NOT NULL,\n  price numeric(10,2) NOT NULL\n);\n\nCREATE TABLE orders (\n  id integer PRIMARY KEY,\n  customer_id integer REFERENCES customers (id),\n  total numeric(12,2) NOT NULL DEFAULT 0,\n  status varchar(20),\n  legacy_code char(4)\n);\nCREATE INDEX orders_status_idx ON orders (status);\n\nCREATE TABLE audit_log (\n  id integer PRIMARY KEY,\n  msg text\n);\n",
                    "target": "-- Shop schema, version 2\nCREATE TABLE customers (\n  id integer PRIMARY KEY,\n  email varchar(120) NOT NULL UNIQUE,\n  full_name text,\n  phone varchar(40),\n  created_at timestamp DEFAULT CURRENT_TIMESTAMP,\n  country char(2) NOT NULL DEFAULT 'DE'\n);\n\nCREATE TABLE products (\n  sku varchar(32) PRIMARY KEY,\n  title text NOT NULL,\n  price numeric(10,2) NOT NULL CHECK (price >= 0)\n);\n\nCREATE TABLE orders (\n  id integer PRIMARY KEY,\n  customer_id integer NOT NULL REFERENCES customers (id) ON DELETE CASCADE,\n  total numeric(10,2) NOT NULL DEFAULT 0,\n  status varchar(20) NOT NULL DEFAULT 'new',\n  shipped_at timestamp\n);\nCREATE INDEX orders_status_idx ON orders (status, shipped_at);\n\nCREATE TABLE order_items (\n  order_id integer NOT NULL REFERENCES orders (id),\n  sku varchar(32) NOT NULL REFERENCES products (sku),\n  qty integer NOT NULL CHECK (qty > 0),\n  PRIMARY KEY (order_id, sku)\n);\n",
                    "dialect": "sqlite",
                    "renames": "customers.name -> full_name"
                  }
                },
                "example3": {
                  "summary": "MySQL: widen a column, add an index and a foreign key",
                  "value": {
                    "dialect": "mysql",
                    "source": "CREATE TABLE users (id int NOT NULL AUTO_INCREMENT, email varchar(100) NOT NULL, team_id int, PRIMARY KEY (id));\nCREATE TABLE teams (id int NOT NULL, name varchar(50), PRIMARY KEY (id));",
                    "target": "CREATE TABLE users (id int NOT NULL AUTO_INCREMENT, email varchar(255) NOT NULL, team_id int, PRIMARY KEY (id), KEY idx_email (email), CONSTRAINT fk_users_team FOREIGN KEY (team_id) REFERENCES teams (id));\nCREATE TABLE teams (id int NOT NULL, name varchar(50), PRIMARY KEY (id));"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpsdeckSchemaDiffResult"
                }
              }
            }
          },
          "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"
                }
              }
            }
          }
        }
      },
      "OpsdeckSchemaDiffInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "source": {
            "title": "Current schema (DDL)",
            "description": "CREATE TABLE, CREATE INDEX and ALTER TABLE statements of the version that is deployed now.",
            "type": "string",
            "maxLength": 500000,
            "default": "-- Shop schema, version 1\nCREATE TABLE customers (\n  id integer PRIMARY KEY,\n  email varchar(255) NOT NULL UNIQUE,\n  name text,\n  phone varchar(40),\n  created_at timestamp DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE products (\n  sku varchar(32) PRIMARY KEY,\n  title text NOT NULL,\n  price numeric(10,2) NOT NULL\n);\n\nCREATE TABLE orders (\n  id integer PRIMARY KEY,\n  customer_id integer REFERENCES customers (id),\n  total numeric(12,2) NOT NULL DEFAULT 0,\n  status varchar(20),\n  legacy_code char(4)\n);\nCREATE INDEX orders_status_idx ON orders (status);\n\nCREATE TABLE audit_log (\n  id integer PRIMARY KEY,\n  msg text\n);\n"
          },
          "target": {
            "title": "New schema (DDL)",
            "description": "The version you want to migrate to.",
            "type": "string",
            "maxLength": 500000,
            "default": "-- Shop schema, version 2\nCREATE TABLE customers (\n  id integer PRIMARY KEY,\n  email varchar(120) NOT NULL UNIQUE,\n  full_name text,\n  phone varchar(40),\n  created_at timestamp DEFAULT CURRENT_TIMESTAMP,\n  country char(2) NOT NULL DEFAULT 'DE'\n);\n\nCREATE TABLE products (\n  sku varchar(32) PRIMARY KEY,\n  title text NOT NULL,\n  price numeric(10,2) NOT NULL CHECK (price >= 0)\n);\n\nCREATE TABLE orders (\n  id integer PRIMARY KEY,\n  customer_id integer NOT NULL REFERENCES customers (id) ON DELETE CASCADE,\n  total numeric(10,2) NOT NULL DEFAULT 0,\n  status varchar(20) NOT NULL DEFAULT 'new',\n  shipped_at timestamp\n);\nCREATE INDEX orders_status_idx ON orders (status, shipped_at);\n\nCREATE TABLE order_items (\n  order_id integer NOT NULL REFERENCES orders (id),\n  sku varchar(32) NOT NULL REFERENCES products (sku),\n  qty integer NOT NULL CHECK (qty > 0),\n  PRIMARY KEY (order_id, sku)\n);\n"
          },
          "dialect": {
            "title": "SQL dialect",
            "type": "string",
            "enum": [
              "postgresql",
              "mysql",
              "sqlite"
            ],
            "default": "postgresql"
          },
          "renames": {
            "title": "Rename hints (optional)",
            "description": "A diff cannot tell a rename from drop + add. One hint per line keeps the data: table.old -> new for a column, old -> new for a table.",
            "type": "string",
            "maxLength": 4000,
            "default": ""
          }
        },
        "required": []
      },
      "OpsdeckSchemaDiffResult": {
        "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": [
              "dialect",
              "identical",
              "counts",
              "tables",
              "forward",
              "reverse",
              "verifyForward",
              "verifyReverse",
              "sourceIssues",
              "targetIssues",
              "sourceTables",
              "targetTables"
            ],
            "properties": {
              "dialect": {
                "enum": [
                  "postgresql",
                  "mysql",
                  "sqlite"
                ]
              },
              "identical": {
                "type": "boolean"
              },
              "counts": {
                "type": "object",
                "description": "Tables added/dropped/renamed/changed, columns added/dropped/renamed/changed, constraint and index changes."
              },
              "tables": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "table",
                    "change",
                    "details"
                  ],
                  "properties": {
                    "table": {
                      "type": "string"
                    },
                    "change": {
                      "enum": [
                        "added",
                        "dropped",
                        "renamed",
                        "changed"
                      ]
                    },
                    "details": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              "forward": {
                "type": "object",
                "required": [
                  "statements",
                  "sql",
                  "steps",
                  "warnings"
                ],
                "properties": {
                  "statements": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "sql": {
                    "type": "string"
                  },
                  "steps": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "warnings": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "level",
                        "table",
                        "column",
                        "message"
                      ],
                      "properties": {
                        "level": {
                          "enum": [
                            "destructive",
                            "risky",
                            "info"
                          ]
                        },
                        "table": {
                          "type": "string"
                        },
                        "column": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                },
                "description": "Ordered migration source → target: statements, SQL text, steps and warnings (destructive, risky, info)."
              },
              "reverse": {
                "type": "object",
                "required": [
                  "statements",
                  "sql",
                  "steps",
                  "warnings"
                ],
                "properties": {
                  "statements": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "sql": {
                    "type": "string"
                  },
                  "steps": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "warnings": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "level",
                        "table",
                        "column",
                        "message"
                      ],
                      "properties": {
                        "level": {
                          "enum": [
                            "destructive",
                            "risky",
                            "info"
                          ]
                        },
                        "table": {
                          "type": "string"
                        },
                        "column": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                },
                "description": "Rollback script target → source."
              },
              "verifyForward": {
                "type": "object",
                "required": [
                  "matches",
                  "differences",
                  "columnOrderNote"
                ],
                "properties": {
                  "matches": {
                    "type": "boolean"
                  },
                  "differences": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "columnOrderNote": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "The forward script applied to the parsed source equals the parsed target."
              },
              "verifyReverse": {
                "type": "object",
                "required": [
                  "matches",
                  "differences",
                  "columnOrderNote"
                ],
                "properties": {
                  "matches": {
                    "type": "boolean"
                  },
                  "differences": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "columnOrderNote": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "sourceIssues": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "targetIssues": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "sourceTables": {
                "type": "number"
              },
              "targetTables": {
                "type": "number"
              }
            }
          },
          "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"
              }
            }
          }
        }
      }
    }
  }
}