🎯 What You Can Build

CI/CD Pipeline Gates

Fail a build when a pull request introduces breaking schema changes. Catch dropped columns and NOT_NULL mismatches before they reach production.

Slack Alerts for Schema Drift

Post a formatted diff summary to Slack whenever staging and production schemas diverge. Include risk scores and migration SQL.

Nightly Drift Monitoring

Run a cron job every night that compares your committed schema against your production database. Detect undocumented changes automatically.

🔑 Step 1 — Get a Pro License Key

1

Purchase a Pro license

API access requires a SchemaLens Pro license key. Keys are delivered instantly after purchase.

2

Format

License keys look like SL-XXXX-XXXX-XXXX-XXXX. Pass them in the X-License-Key header or licenseKey body field.

🚀 Step 2 — Make Your First Request

The API has one primary endpoint. Send two SQL schemas and receive a full diff, migration SQL, breaking change analysis, and risk score.

POST https://schemalens.tech/api/diff

Choose your language

curl -X POST https://schemalens.tech/api/diff \
  -H "Content-Type: application/json" \
  -H "X-License-Key: SL-XXXX-XXXX-XXXX-XXXX" \
  -d '{
    "schemaA": "CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255));",
    "schemaB": "CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255));",
    "dialect": "postgres",
    "format": "json"
  }'
const response = await fetch('https://schemalens.tech/api/diff', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-License-Key': 'SL-XXXX-XXXX-XXXX-XXXX'
  },
  body: JSON.stringify({
    schemaA: 'CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255));',
    schemaB: 'CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255));',
    dialect: 'postgres',
    format: 'json'
  })
});

const data = await response.json();
console.log(data.migration);
// ALTER TABLE "users" ADD "email" VARCHAR ( 255 );
import requests

response = requests.post(
    'https://schemalens.tech/api/diff',
    headers={
        'Content-Type': 'application/json',
        'X-License-Key': 'SL-XXXX-XXXX-XXXX-XXXX'
    },
    json={
        'schemaA': 'CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255));',
        'schemaB': 'CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255));',
        'dialect': 'postgres',
        'format': 'json'
    }
)

data = response.json()
print(data['migration'])
# ALTER TABLE "users" ADD "email" VARCHAR ( 255 );
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
)

type DiffRequest struct {
    SchemaA  string `json:"schemaA"`
    SchemaB  string `json:"schemaB"`
    Dialect  string `json:"dialect"`
    Format   string `json:"format"`
}

func main() {
    payload := DiffRequest{
        SchemaA: "CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255));",
        SchemaB: "CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255));",
        Dialect: "postgres",
        Format:  "json",
    }
    body, _ := json.Marshal(payload)

    req, _ := http.NewRequest("POST", "https://schemalens.tech/api/diff", bytes.NewBuffer(body))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("X-License-Key", "SL-XXXX-XXXX-XXXX-XXXX")

    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()

    var result map[string]interface{}
    json.NewDecoder(resp.Body).Decode(&result)
    fmt.Println(result["migration"])
}

📤 Step 3 — Understand the Response

{
  "diff": {
    "tablesAdded": [],
    "tablesRemoved": [],
    "tablesModified": [
      {
        "name": "users",
        "columnsAdded": [{ "name": "email", "type": "VARCHAR(255)", ... }],
        "columnsRemoved": [],
        "columnsModified": []
      }
    ],
    "enumsAdded": [],
    "enumsRemoved": [],
    "triggersAdded": [],
    "triggersRemoved": [],
    "triggersModified": [],
    "viewsAdded": [],
    "viewsRemoved": [],
    "viewsModified": [],
    "functionsAdded": [],
    "functionsRemoved": [],
    "functionsModified": []
  },
  "migration": "ALTER TABLE \"users\" ADD \"email\" VARCHAR ( 255 );",
  "breakingChanges": [],
  "riskScore": {
    "score": 0,
    "label": "Safe",
    "icon": "🟢"
  },
  "summary": {
    "tablesAdded": 0,
    "tablesRemoved": 0,
    "tablesModified": 1,
    "enumsAdded": 0,
    "enumsRemoved": 0,
    "triggersAdded": 0,
    "triggersRemoved": 0,
    "triggersModified": 0,
    "viewsAdded": 0,
    "viewsRemoved": 0,
    "viewsModified": 0,
    "functionsAdded": 0,
    "functionsRemoved": 0,
    "functionsModified": 0,
    "breakingChangeCount": 0
  }
}

Response Fields

diff
Full semantic diff with added, removed, and modified tables, columns, constraints, indexes, enums, triggers, views, and functions.
migration
Generated ALTER TABLE / CREATE TABLE SQL to migrate from schemaA to schemaB.
breakingChanges
Array of dangerous changes (DROP_TABLE, DROP_COLUMN, ADD_NOT_NULL_NO_DEFAULT, etc.). Empty if safe.
riskScore
Object with score (0-100), label (Safe / Low / Medium / High / Critical), and icon.
summary
Counts of all object changes for quick dashboard display.

🛠️ Step 4 — Common Patterns

Pattern A: Fail a CI Build on Breaking Changes

#!/bin/bash
set -e

RESPONSE=$(curl -s -X POST https://schemalens.tech/api/diff \
  -H "Content-Type: application/json" \
  -H "X-License-Key: $SCHEMALENS_KEY" \
  -d "{\"schemaA\":$(cat base.sql | jq -Rs .),\"schemaB\":$(cat head.sql | jq -Rs .),\"dialect\":\"postgres\"}")

COUNT=$(echo "$RESPONSE" | jq '.summary.breakingChangeCount')
if [ "$COUNT" -gt 0 ]; then
  echo "❌ Breaking changes detected:"
  echo "$RESPONSE" | jq '.breakingChanges'
  exit 1
fi

echo "✅ Schema diff passed. No breaking changes."

Pattern B: Post Diff Summary to Slack

# First, get the diff
DIFF=$(curl -s -X POST https://schemalens.tech/api/diff \
  -H "Content-Type: application/json" \
  -H "X-License-Key: $SCHEMALENS_KEY" \
  -d '{"schemaA":"...","schemaB":"...","dialect":"postgres"}')

# Then, send to Slack via SchemaLens webhook proxy
curl -X POST https://schemalens.tech/api/slack \
  -H "Content-Type: application/json" \
  -d "{
    \"webhookUrl\": \"$SLACK_WEBHOOK_URL\",
    \"diff\": $(echo "$DIFF" | jq '.diff'),
    \"migration\": $(echo "$DIFF" | jq '.migration'),
    \"breakingChanges\": $(echo "$DIFF" | jq '.breakingChanges'),
    \"dialect\": \"postgres\"
  }"

Pattern C: Markdown Report for PR Comments

curl -X POST https://schemalens.tech/api/diff \
  -H "Content-Type: application/json" \
  -H "X-License-Key: SL-XXXX-XXXX-XXXX-XXXX" \
  -d '{
    "schemaA": "...",
    "schemaB": "...",
    "dialect": "postgres",
    "format": "markdown"
  }' | jq -r '.markdown' > schema-diff-report.md

# Post the markdown to GitHub PR comment via gh CLI
gh pr comment 42 --body-file schema-diff-report.md

⚠️ Error Handling

StatusMeaningHow to Fix
400Bad RequestCheck that schemaA and schemaB are strings and dialect is valid.
401UnauthorizedProvide a valid Pro licenseKey or X-License-Key header.
429Rate LimitedWait 60 seconds. Maximum 30 requests per minute per IP.
500Server ErrorRetry with exponential backoff. Contact support if persistent.

📋 Parameters Reference

ParameterTypeRequiredDescription
schemaAstringYesThe old (base) schema SQL.
schemaBstringYesThe new (target) schema SQL.
dialectstringNopostgres (default), mysql, sqlite, mssql, oracle
formatstringNojson (default) or markdown
licenseKeystringYesSchemaLens Pro license key. Also accepted as X-License-Key header.

🔒 Privacy & Limits

💻 CLI Alternative

For offline use or air-gapped environments, the SchemaLens CLI provides the same engine without network requests.

node ci/schemalens-diff.js schemaA.sql schemaB.sql --dialect=postgres --format=json

Ready to automate your schema reviews?

Get a SchemaLens Pro license and start diffing programmatically today.

Get Pro — $12/mo Full API Docs →