HelipodDocs

SQL

Browse tables and run SQL against a database or one of its branches.

Every call here takes an optional environmentId (query or body) to run against that environment's branch. Without it, the primary (production) branch is used.

Tables

GET/databases/{databaseId}/tables
Tables and views in the public schema.
{ "success": true, "data": [ { "name": "todos", "type": "BASE TABLE", "columnCount": 2 } ] }
GET/databases/{databaseId}/tables/{table}/columns
{ "success": true, "data": [ { "name": "id", "type": "integer", "nullable": false, "default": "nextval('todos_id_seq'::regclass)" }, { "name": "title", "type": "text", "nullable": false, "default": null } ] }
GET/databases/{databaseId}/tables/{table}/rows
Rows of a table, limit 1 to 200 (default 50) and offset.

Run a statement

POST/databases/{databaseId}/query

Prop

Type

curl -X POST https://api.helipod.io/api/databases/$DB/query \
  -H "Authorization: Bearer hel_your_key" -H "Content-Type: application/json" \
  -d '{ "sql": "select id, title from todos where title ilike $1", "params": ["%milk%"] }'
{
  "success": true,
  "data": { "readOnly": true, "command": "select", "fields": ["id", "title"], "rows": [ { "id": 1, "title": "Buy milk" } ], "rowCount": 1, "truncated": false }
}

At most 200 rows come back; truncated: true means there were more. Use limit and offset in your SQL to page.

Read-only by default

  • SELECT, WITH, EXPLAIN, SHOW, TABLE and VALUES run in a read-only transaction.
  • Anything that changes data or schema (INSERT, UPDATE, DELETE, CREATE, ALTER, DROP, TRUNCATE, GRANT, ...) answers 428 WRITE_NOT_ALLOWED unless you send "allowWrite": true.
  • SET, RESET, LISTEN, NOTIFY and LOCK are always refused, because they change the session rather than your data.
curl -X POST https://api.helipod.io/api/databases/$DB/query \
  -H "Authorization: Bearer hel_your_key" -H "Content-Type: application/json" \
  -d '{ "sql": "insert into todos (title) values ($1) returning id", "params": ["Buy milk"], "allowWrite": true }'
{ "success": true, "data": { "readOnly": false, "command": "insert", "fields": ["id"], "rows": [ { "id": 1 } ], "rowCount": 1, "truncated": false } }

Check a statement first

POST/databases/{databaseId}/query/classify
Says what a statement is and whether it writes, without running it.
curl -X POST https://api.helipod.io/api/databases/$DB/query/classify \
  -H "Authorization: Bearer hel_your_key" -H "Content-Type: application/json" \
  -d '{ "sql": "delete from todos where done" }'
{
  "success": true,
  "data": {
    "command": "delete",
    "readOnly": false,
    "writes": true,
    "statements": [ { "command": "delete", "readOnly": false } ],
    "requiresAllowWrite": true
  }
}

Use it to decide whether to ask a person before running a statement. requiresAllowWrite: true means /query needs "allowWrite": true. Several statements, or SET, LISTEN and similar session statements, answer 400 SQL_ERROR here as they would on /query.

Errors from Postgres

An error in your statement comes back as 400 SQL_ERROR with Postgres's own message, for example relation "todo" does not exist.

Migrations

This endpoint is for quick queries, admin fixes and agents. For your app's schema, keep migrations in your repository (Prisma, Drizzle, Alembic, ...) and run them with the connection string. Try them on a staging branch first.

On this page