HTTP API
Every route is under https://api.persql.com/api/v1, takes and answers JSON, and needs a developer key as a bearer token: Authorization: Bearer persql_... (X-Api-Key: persql_... works too). These are the routes the console itself uses; there is no second API.
Answers and failures
Section titled “Answers and failures”A failure answers a status and { "error": "<sentence>" }:
| Status | Meaning |
|---|---|
400 bad request |
The body did not fit the route. |
401 sign in first |
No key, or a revoked one. |
402 |
The free week or the subscription has ended; the sentence says which. Reading stays open; writing does not. |
404 not found |
Not yours, or gone. |
409 |
A limit: too many databases, too many saved queries, too many notes. |
422 the statement failed |
SQLite refused the SQL; rejection.message carries its own words. |
429 |
The assistant’s hourly cap; Retry-After says when. |
Times are milliseconds since the epoch. A cell is a string, a number, a boolean, null, or bytes as a base64 string.
Databases
Section titled “Databases”GET /databases — your databases: id, name, tableCount, sizeBytes, createdAt, updatedAt.
POST /databases { name } — makes one; answers 201 with the database. A name is up to 80 characters.
GET /databases/:id — the database with its tables, each { name, rowCount }.
PATCH /databases/:id { name } — renames it.
DELETE /databases/:id — deletes it with its backups, jobs and notes; 204.
GET /databases/:id/schema — tables (with columns: name, type, pk, notNull, defaultValue, and the CREATE sql), indexes, foreignKeys, views.
Rows and SQL
Section titled “Rows and SQL”GET /databases/:id/tables/:table?offset=0&limit=100 — a page of a table’s rows as { columns, rows }; limit is at most 500. For millions of rows, page by rowid in your own SQL instead.
POST /databases/:id/query { sql, params? } — runs one statement, up to 200000 characters, with ? placeholders bound from params in order. Answers { columns, rows, rowsRead, rowsWritten, changes, lastInsertRowid, truncated, wrote, ms }. A result is capped at 1000 rows and says truncated: true past that; page with LIMIT/OFFSET or a WHERE rowid > ?. A statement that writes counts as a change for the plan’s door.
Uploads and import
Section titled “Uploads and import”A file goes to storage in parts, then an import job reads it. Any size.
POST /databases/:id/uploads { name, type } — begins an upload; answers 201 with { id, name, type, bytes, partBytes }. Send the file in parts of exactly partBytes bytes, the last one smaller.
PUT /databases/:id/uploads/:uploadId/parts/:n — one part, raw bytes as the body with Content-Length set, n from 1; answers { partNumber, etag }.
POST /databases/:id/uploads/:uploadId/complete { parts: [{ partNumber, etag }] } — closes the upload; answers the upload with its bytes.
POST /databases/:id/import { uploads: [id] } — starts an import job over up to 200 uploads: CSV, TSV, Excel, a SQL dump, a SQLite file, or a zip of any. Answers 202 with the job. The job’s result lists tables made (name, rows, from) and files skipped with a reason.
Export and jobs
Section titled “Export and jobs”POST /databases/:id/export { kind, table? } — kind is sql, sqlite or csv; csv takes a table and the others take none. Answers 202 with the job; when it is done, result has fileName and bytes.
GET /databases/:id/jobs — the database’s jobs, newest first.
GET /jobs/:jobId — one job: kind (import, export, snapshot, restore), status (queued, running, done, failed), progress, result, error. Poll it every second or so.
GET /jobs/:jobId/download — the finished export’s bytes, with Content-Disposition.
Backups
Section titled “Backups”GET /databases/:id/snapshots — backups, newest first: id, label, auto, kind (bookmark, a point-in-time mark SQLite keeps for thirty days; dump, a SQL file), status, bytes, expiresAt, createdAt.
POST /databases/:id/snapshots { label? } — takes a dump; answers 201 with { snapshot, job }. The dump is ready when the job is done.
POST /databases/:id/snapshots/:snapshotId/restore — restores it, after taking a backup of now; answers { before, job }, where job is null when the restore was a bookmark and happened at once.
GET /databases/:id/snapshots/:snapshotId/download — a ready dump’s SQL.
DELETE /databases/:id/snapshots/:snapshotId — 204.
Saved queries
Section titled “Saved queries”GET /databases/:id/queries, POST /databases/:id/queries { name, sql }, PATCH /databases/:id/queries/:queryId { name?, sql? }, DELETE /databases/:id/queries/:queryId — the SQL view’s saved queries.
Notes are what the person and the assistant know about a table or a column: about is a table name or table.column, text says what it means.
GET /databases/:id/notes, POST /databases/:id/notes { about, text }, PATCH /notes/:noteId { about?, text? }, DELETE /notes/:noteId.
The assistant
Section titled “The assistant”The assistant is the console’s conversation; these routes drive it and are listed for completeness.
GET /databases/:id/runs and DELETE /databases/:id/runs — the thread and its clearing. GET /databases/:id/proposals — every change the assistant proposed. POST /databases/:id/run/stream { instruction?, after?, runId?, attach? } — starts or rejoins a run as a stream of events. POST /databases/:id/run/cancel — stops it. POST /proposals/:proposalId/apply and POST /proposals/:proposalId/discard — the person’s decision on a proposal.
Account
Section titled “Account”GET /usage — this month’s runs, queries and tokens. GET /plan — the trial, the subscription, and what is on offer. GET /agents — the agents you allowed in through the MCP door for hosted clients: client id, name, when. DELETE /agents/:clientId — removes one: its tokens stop at once and it has to ask again. The subscription and store routes — POST /subscription/checkout, POST /subscription/confirm, POST /subscription/cancel, POST /subscription/resume, POST /subscription/portal, POST /purchases/apple, POST /purchases/google — are the console’s and the phone’s; a key does not need them.