API reference

Every database is an HTTP endpoint

The whole platform is plain HTTPS with JSON in and JSON out. No SDK, no connection string, no client library to keep in sync. Base URL https://api-repogrid.vercel.app.

Quick start

Three calls: create a database, mint a key, then run a statement. Create the database and the key from the dashboard, or with the session-authenticated endpoints.

  1. Create a database in the dashboard, or POST /api/dbs with a dashboard session. You get an id like db_c1c00d9e8fcb9c15.
  2. Create an API key under API Keys. The full value is returned once; store it somewhere safe.
  3. Send a request with Authorization: Bearer db_….
curl "https://api-repogrid.vercel.app/api/dbs/DATABASE_ID/sql" \
  -H "Authorization: Bearer db_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"sql":"SELECT id, email FROM users LIMIT 20"}'
const res = await fetch("https://api-repogrid.vercel.app/api/dbs/" + dbId + "/sql", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.REPOGRID_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    sql: "SELECT id, email FROM users WHERE active = ?",
    params: [true],
  }),
});

const { result } = await res.json(); // /sql wraps everything in "result"
const { kind, fields, rows, changes, durationMs } = result;
console.log(kind, fields.map((f) => f.name), rows, changes, durationMs + "ms");

Authentication

One header, three levels of access. The `any` label below means a dashboard session or an API key both work.

Authorization: Bearer db_your_key_here

  • any

    Reads accept a session cookie or an API key, so the dashboard and your app can share endpoints.

  • apikey

    Every write needs a real API key. A session is not enough, so a stray tab cannot mutate your data.

  • session

    Management: create, rename and delete databases, and manage keys. Session only.

Discovering a database

Everything the platform knows about your account and your databases.

GETnone

/api/health

Liveness probe. Returns ok, service name, time and supported types.

GETsession

/api/dbs

List the databases in the signed-in account.

GETany

/api/dbs/:id/schema

Tables, columns and indexes for a relational database.

GETany

/api/dbs/:id/collections

List a JSON database's collections and their metadata (names, indexes, timestamps).

GETany

/api/dbs/:id/versions

Commit history, newest first, for any database.

GETany

/api/dbs/:id/stats

Versions, record counts, repository size, cache and rate-limit usage.

GETsession

/api/keys

API keys for the account. Full key values are only returned once, at creation.

POSTsession

/api/dbs

Create a relational or JSON database and its backing repository.

Relational data

SQLite through POST /sql, plus paginated reads for table browsers. Statements accept bound params so you never concatenate user input.

GETany

/api/dbs/:id/tables

List tables with row counts.

GETany

/api/dbs/:id/tables/:table/rows

Read rows with limit, offset and optional version.

POSTany

/api/dbs/:id/sql

Run one statement. DDL, DML and reads, with bound params. Accepts a session or an API key.

curl "https://api-repogrid.vercel.app/api/dbs/DATABASE_ID/sql" \
  -H "Authorization: Bearer db_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"sql":"SELECT id, email FROM users LIMIT 20"}'
curl -G "https://api-repogrid.vercel.app/api/dbs/DATABASE_ID/tables/users/rows" \
  -H "Authorization: Bearer db_your_key_here" \
  --data-urlencode "limit=20" \
  --data-urlencode "offset=0"

Response shape

{
  "result": {
    "kind": "select",          // select | dml | ddl | other
    "fields": [ { "name": "id", "declaredType": "INTEGER" } ],
    "rows": [ { "id": 1, "email": "ada@example.com" } ],
    "changes": 0,              // rows modified (dml/ddl only)
    "ddlChanged": false,
    "lastInsertRowid": null,
    "durationMs": 3
  }
}

// POST /api/dbs/:id/tables/:name/rows returns positional arrays, not objects:
//   fields: ["id", "email"], rows: [[1, "ada@example.com"]]

result.fields is an array of { name, declaredType } objects; result.rows are objects keyed by column. The structured /rows endpoint returns columns as strings and each row as a value array.

JSON documents

Collections hold documents with stable ids and revisions. Reads work like the table browser; writes are commits.

GETany

/api/dbs/:id/collections/:name/docs

Query documents with filter, sort, fields, limit, offset or page.

GETany

/api/dbs/:id/collections/:name/docs/:docId

Read a single document by id.

POSTapikey

/api/dbs/:id/collections/:name/docs

Insert one document or a batch of up to 1000.

PATCHapikey

/api/dbs/:id/collections/:name/docs/:docId

Update, upsert or unset fields on a document.

DELETEapikey

/api/dbs/:id/collections/:name/docs/:docId

Delete a document by id.

curl -G "https://api-repogrid.vercel.app/api/dbs/DATABASE_ID/collections/customers/docs" \
  -H "Authorization: Bearer db_your_key_here" \
  --data-urlencode 'filter={"plan":"pro","country":{"$in":["DE","AT"]}}' \
  --data-urlencode 'sort={"createdAt":-1}' \
  --data-urlencode 'fields=name,plan,country' \
  --data-urlencode "limit=20"
curl -X PATCH \
  "https://api-repogrid.vercel.app/api/dbs/DATABASE_ID/collections/customers/docs/ada@example.com" \
  -H "Authorization: Bearer db_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"$set":{"plan":"enterprise"},"$unset":["legacy_tag"]}'

Filtering, sorting and paging

Pass filter and sort as URL-encoded JSON. Missing fields never match a condition, so a filter is also an existence check.

filter

URL-encoded JSON object of conditions, combined with $and by default.

sort

URL-encoded JSON, e.g. {"createdAt":-1}. No dash-prefix shorthand.

fields

Projection, comma separated or a JSON array of dot-paths.

limit / offset

Classic paging, limit up to 1000. Default 50 relational / 100 JSON.

page

1-based page index, as an alternative to offset.

version

Read a past commit: full sha or rev-N. Omit for the newest.

OperatorUse
$eq / $neequality and inequality
$gt / $gte / $lt / $lterange comparisons
$in / $ninmembership in a list
$regexpattern matching on strings
$existsfield presence
$and / $or / $nor / $notcombine conditions

Reading a past version

Every write is a commit, so any read can be pinned to a past state without restoring or branching anything.

curl "https://api-repogrid.vercel.app/api/dbs/DATABASE_ID/versions" \
  -H "Authorization: Bearer db_your_key_here"

curl "https://api-repogrid.vercel.app/api/dbs/DATABASE_ID/tables/users/rows?version=COMMIT_SHA" \
  -H "Authorization: Bearer db_your_key_here"

Use the full 40-character sha returned by /versions. Short shas and the string latest are rejected ("400 VALIDATION_ERROR"); omit version to read the newest commit. Pinned versions are read-only — writes always land on the latest commit.

Errors and retries

Errors are JSON with a stable code, so you can branch on the code instead of the message.

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The sql field is required."
  }
}
400

VALIDATION_ERROR / QUERY_ERROR / SCHEMA_ERROR / DOCUMENT_ERROR

Malformed body, filter or parameter; or a rejected statement.

401

UNAUTHORIZED / INVALID_API_KEY

Missing, expired, revoked or mis-scoped credential.

403

DATABASE_BLOCKED

The database's API kill-switch is on (dashboard only to unblock).

404

NOT_FOUND / DATABASE_VERSION_NOT_FOUND

Unknown database, collection, document, or commit sha.

409

CONCURRENT_MODIFICATION

Another write landed first. Re-read and retry.

413

PAYLOAD_TOO_LARGE

Request body over 4 MB. Batch or shrink.

429

RATE_LIMITED / GITHUB_RATE_LIMIT

GitHub API budget exhausted. Back off and retry.

502

GITHUB_API_ERROR / GITHUB_UPSTREAM_UNAVAILABLE

GitHub hiccup. Retry via exponential backoff.

Retry 429 and 502 with exponential backoff. Both mean GitHub was busy, not that your request was wrong.

Limits worth knowing

No surprises, just ceilings.

  • Relational reads default to 50 rows per page, JSON reads to 100; both accept up to 1000 per request.
  • Batches: document inserts accept up to 1000 documents per request, all in one commit.
  • Request bodies and single documents are capped at roughly 4 MB.
  • SQL is one statement per request. Use bound params instead of string interpolation.
  • Every write is one commit, so a write request is not a bulk import and consumes GitHub rate-limit budget.
  • Reads are cached for about a minute, namespaced per database, and invalidated on every write or restore.