Skip to main content
ZangTable exposes a read-only SQL query endpoint for reporting, lookups, joins, and debugging data relationships. Index endpoints help speed up common filters and sorts, especially when a table starts getting larger. Use the query endpoint when you want to answer questions like:
  • “Which members have overdue library books?”
  • “Which customers have open invoices?”
  • “Which orders should be merged with shipment rows by order_id?”
  • “What query shape should I turn into a dedicated app endpoint later?”
The query endpoint is read-only. Use record and schema endpoints for writes.

Read-only query

curl -X POST "$BASE_URL/projects/$PROJECT/query" \
  -H "Authorization: Bearer $ZANGTABLE_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"sql":"SELECT id, title FROM books WHERE status = ?","params":["late"]}'
Example response:
{
  "ok": true,
  "data": {
    "rows": [
      {"id": 101, "title": "The Pragmatic Programmer"},
      {"id": 102, "title": "Clean Architecture"}
    ],
    "row_count": 2,
    "limit": 500,
    "duration_ms": 4
  },
  "error": null
}

Relational example: find members with late library books

First, fetch late loans:
curl -X POST "$BASE_URL/projects/$PROJECT/query" \
  -H "Authorization: Bearer $ZANGTABLE_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"sql":"SELECT member_id, book_id, due_date FROM loans WHERE returned_at IS NULL AND due_date < ?","params":["2026-06-01"]}'
{
  "ok": true,
  "data": {
    "rows": [
      {"member_id": 7, "book_id": 101, "due_date": "2026-05-20"},
      {"member_id": 9, "book_id": 102, "due_date": "2026-05-22"}
    ],
    "row_count": 2
  },
  "error": null
}
Then fetch the matching members:
curl -X POST "$BASE_URL/projects/$PROJECT/query" \
  -H "Authorization: Bearer $ZANGTABLE_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"sql":"SELECT id, email, name FROM members WHERE id IN (?, ?)","params":[7,9]}'
{
  "ok": true,
  "data": {
    "rows": [
      {"id": 7, "email": "ada@example.com", "name": "Ada"},
      {"id": 9, "email": "grace@example.com", "name": "Grace"}
    ],
    "row_count": 2
  },
  "error": null
}
Your backend can merge those results by member_id / id and send reminder emails to members with late books. Once the pattern is stable, you can also run it as one SQL query with a join:
SELECT members.id, members.email, members.name, loans.book_id, loans.due_date
FROM members
JOIN loans ON loans.member_id = members.id
WHERE loans.returned_at IS NULL AND loans.due_date < ?

Indexes

Use indexes when the same columns power frequent filters, sorts, joins, or dashboard views. For example, if you often query late loans by returned_at and due_date, create an index on those columns so SQLite can find matching rows faster.

Run query

List indexes

Create index

Delete index