Referencia HTTP/JSON de gabysql-server: endpoints, autenticación, payloads y respuestas.


🧭 Resumen

gabysql-server expone una API simple para:


🔐 Autenticación

Si el server se arranca con -token <valor>, cada request debe incluir uno de estos headers:

Sin token válido, el server responde 401.

Para sesiones cross-request (M13) hay un header adicional sin relación con autenticación:


🗂️ Modos de operación

Modo Comando Implicación
Single DB gabysql-server -db demo.db -addr :8080 no necesitas enviar db en cada request
Multi DB gabysql-server -dir ./dbs -addr :8080 debes enviar db en los endpoints que operan sobre una base

🚏 Endpoints

Método Ruta Qué hace
GET /health health check del server
GET /metrics métricas operacionales (JSON: contadores + latencias p50/p95)
GET /dbs lista bases disponibles
POST /dbs crea una DB en modo -dir
GET /tables lista tablas de una DB
GET /schema devuelve schema de una tabla
GET /rows devuelve filas con paginación
POST /exec ejecuta una o más sentencias SQL (auto-commit por request, o dentro de sesión con X-Gabysql-Session)
POST /tx/begin M13 (2026-06-15) — abre una sesión cross-request con tx activa, devuelve session_id
POST /tx/commit?session=<id> M13 — commit + cierra la sesión
POST /tx/rollback?session=<id> M13 — rollback + cierra la sesión

GET /health

Ejemplo de respuesta:

{
  "ok": true,
  "name": "gabysql-server",
  "single": false,
  "dir": "/data",
  "timeUnix": 1773970519
}

GET /metrics

Devuelve un snapshot de las métricas operacionales acumuladas desde el arranque del server. Pensado para scraping periódico (cron + curl, Vector, Telegraf) o un dashboard mínimo sin desplegar Prometheus. Ver ADR-0014.

Ejemplo de respuesta:

{
  "ok": true,
  "started_unix": 1773970519,
  "uptime_s": 4827,
  "requests_total": 1284,
  "requests_by_status": {
    "200": 1199,
    "400": 42,
    "401": 11,
    "404": 18,
    "500": 14
  },
  "errors_total": 85,
  "latency_ms": {
    "p50": 2,
    "p95": 38,
    "samples": 1024,
    "count": 1284
  }
}

Notas:


GET /dbs

Respuesta en modo single DB

{
  "ok": true,
  "mode": "single-db",
  "dbs": ["demo.db"],
  "single": "demo.db"
}

Respuesta en modo multi DB

{
  "ok": true,
  "mode": "multi-db",
  "dbs": ["demo.db", "test.db"]
}

POST /dbs

Crea una base en modo -dir.

Request:

{ "db": "demo" }

El server normaliza el nombre y agrega .db si falta.

Posibles respuestas:


GET /tables?db=demo.db

Lista todas las tablas con su schema completo (mismo shape que /schema, pero embebido en un array). Útil para reverse-engineering one-shot.

{
  "ok": true,
  "tables": [
    {
      "name": "users",
      "primaryKey": "id",
      "rootPage": 2,
      "columns": [
        { "name": "id",    "type": "INT",  "pk": true,  "notNull": true,  "unique": false, "hasDefault": false, "default": null,      "references": null },
        { "name": "email", "type": "TEXT", "pk": false, "notNull": true,  "unique": true,  "hasDefault": false, "default": null,      "references": null },
        { "name": "status","type": "TEXT", "pk": false, "notNull": true,  "unique": false, "hasDefault": true,  "default": "pending", "references": null }
      ],
      "indexes": [
        { "name": "uq_users_email", "column": "email", "rootPage": 4, "unique": true }
      ]
    }
  ]
}

GET /schema?db=demo.db&table=users

Retorna el schema completo de una tabla, con la información necesaria para reconstruir el CREATE TABLE original (modo reverse-engineering del modeler).

Ejemplo:

{
  "ok": true,
  "table": {
    "name": "users",
    "primaryKey": "id",
    "rootPage": 2,
    "columns": [
      { "name": "id",    "type": "INT",  "pk": true,  "notNull": true,  "unique": false, "hasDefault": false, "default": null },
      { "name": "email", "type": "TEXT", "pk": false, "notNull": true,  "unique": true,  "hasDefault": false, "default": null },
      { "name": "status","type": "TEXT", "pk": false, "notNull": true,  "unique": false, "hasDefault": true,  "default": "pending" },
      { "name": "score", "type": "FLOAT","pk": false, "notNull": false, "unique": false, "hasDefault": true,  "default": 0.0,  "references": null },
      { "name": "active","type": "BOOL", "pk": false, "notNull": false, "unique": false, "hasDefault": true,  "default": true, "references": null },
      { "name": "manager_id", "type": "INT", "pk": false, "notNull": false, "unique": false, "hasDefault": false, "default": null,
        "references": { "table": "users", "column": "id", "onDelete": "RESTRICT" } }
    ],
    "indexes": [
      { "name": "uq_users_email", "column": "email", "rootPage": 4, "unique": true }
    ]
  }
}

Reglas del campo default:

Campo references:

Status posibles: 200 con ok: true, 404 con ok: false, error: "tabla no existe".


GET /rows?db=demo.db&table=users&limit=25&offset=0

Reglas:

Ejemplo:

{
  "ok": true,
  "db": "demo.db",
  "table": "users",
  "total": 2,
  "limit": 25,
  "offset": 0,
  "columns": ["id", "name"],
  "rows": [[1, "Ana"], [2, "Beto"]]
}

POST /exec

Ejecuta una o más sentencias SQL dentro de una transacción. Acepta:

UPDATE y DELETE son single-table (sin JOIN ni UPDATE ... FROM) pero aceptan el WHERE completo y operan multi-fila (response message trae la cuenta). SELECT con JOINs admite WHERE cualificado (tabla.col = val) como post-filter.

Las sentencias DATABASE-level (CREATE/DROP/SHOW DATABASE) no abren un Pager — el server las despacha contra el directorio configurado con -dir. No se admite mezclarlas con sentencias de tabla en el mismo /exec: el server retorna 400 si lo intentas. En modo single-DB (-db) responden 405.

Request:

{
  "db": "demo.db",
  "sql": "CREATE TABLE users (id INT PRIMARY KEY, name TEXT); INSERT INTO users (id,name) VALUES (1,'Ana'); UPDATE users SET name = 'Ana M' WHERE id = 1; SELECT * FROM users;"
}

Ejemplo de respuesta:

{
  "ok": true,
  "results": [
    { "columns": [], "rows": [], "message": "OK" },
    { "columns": [], "rows": [], "message": "OK" },
    { "columns": ["id", "name"], "rows": [[1, "Ana"]] }
  ]
}

POST /tx/begin · POST /tx/commit · POST /tx/rollback (M13)

Cross-request transactions vía sesión single-slot global. Habilitado por ADR-0090. Permite que un cliente externo (ORM, batch loader, script) abra una transacción en un request, ejecute N sentencias en requests subsiguientes, y commitee/rolbackee al final.

Flujo

  1. Abrir sesiónPOST /tx/begin (opcionalmente {"db":"<name>"} en modo -dir).
    • 200 → {"ok":true,"session":"<hex16>","db":"<name>"}. Guardar el session para usarlo en cada /exec y para cerrar la tx.
    • 409 → ya hay una sesión activa. El server es single-slot: el cliente debe esperar a que la sesión existente cierre, o forzar cierre con /tx/rollback si conoce el ID.
  2. Ejecutar SQL en la sesiónPOST /exec con header X-Gabysql-Session: <hex16> o query param ?session=<hex16>. El server NO auto-commit; el Pager de la sesión persiste sus dirty pages entre requests.
    • 200 → {"ok":true,"session":"<hex16>","results":[...]}.
    • 404 → el session no existe o expiró (idle timeout 300s).
  3. Cerrar sesiónPOST /tx/commit?session=<hex16> o POST /tx/rollback?session=<hex16>. Devuelve {"ok":true,"message":"COMMIT","db":"<name>"} (o ROLLBACK).

Ejemplo curl

# 1) Abrir sesión.
SESSION=$(curl -sX POST http://127.0.0.1:8080/tx/begin -d '{}' | jq -r '.session')

# 2) Operar varios requests dentro de la misma tx.
curl -sX POST http://127.0.0.1:8080/exec \
    -H "X-Gabysql-Session: $SESSION" \
    -d '{"sql":"INSERT INTO users (id, name) VALUES (1, \"Ana\")"}'

curl -sX POST http://127.0.0.1:8080/exec \
    -H "X-Gabysql-Session: $SESSION" \
    -d '{"sql":"INSERT INTO users (id, name) VALUES (2, \"Beto\")"}'

# 3) Decidir commit o rollback al final.
curl -sX POST "http://127.0.0.1:8080/tx/commit?session=$SESSION"

Headers

Errores específicos

Notas


🚨 Errores frecuentes

Código Motivo típico
400 SQL inválido, tabla inexistente, request incompleto
401 token faltante o incorrecto
404 endpoint o tabla inexistente
405 operación no permitida en ese modo
409 DB ya existe
500 error interno inesperado
503 techo de conexiones simultáneas alcanzado (default 64)

🧠 Notas operacionales