scribasedocs

SDK (scribase-js)

SDK — scribase-js

scribase is the typed, fetch-based TypeScript client for the Scribase /v1 control-plane API. Every resource is a property on the client, mutations take an idempotency key (one is minted for you when omitted), and transient failures (429, 503, network errors) are retried with exponential backoff.

It runs anywhere fetch exists: Node.js 22.13+, Deno, Bun, and edge runtimes.

Installation

Not yet published. The scribase package is not on the npm registry yet, so npm install scribase does not work today. Until it is published, build it from the repository (packages/scribase-js) and install it from a local path or tarball:

Terminal
cd packages/scribase-js && npm install && npm run build && npm pack
npm install /path/to/scribase-<version>.tgz   # in your app

Once the package is published, the install is:

Terminal
npm install scribase
# or
pnpm add scribase

Credentials

The client needs two things:

Option Where it comes from
baseUrl https://api.scribase.com for hosted Scribase, or your own gateway in your own cluster
token A personal access token (console Account → Access tokens) or an organization API key (console Organization → API tokens). See Access tokens & API keys.

Keep the token in an environment variable or secret manager, never in source.

Quick start

TypeScript
import { ScribaseClient } from 'scribase';

const scribase = new ScribaseClient({
  baseUrl: process.env.SCRIBASE_API_URL ?? 'https://api.scribase.com',
  token: process.env.SCRIBASE_ACCESS_TOKEN ?? '',
});

// Create an environment — returns an accepted operation
const { operation } = await scribase.environments.create({
  organization_id: 'acme',
  project_id: 'store',
  environment_id: 'production',
  kind: 'production',
  data_mode: 'snapshot',
  region: 'us-east',
  postgres_major: 17,
});

// Wait until it is terminal (throws OperationFailedError on failure)
await scribase.operations.wait(operation.id, 'acme');

Most environment-scoped calls take an EnvironmentRef:

TypeScript
const ref = { organizationId: 'acme', projectId: 'store', environmentId: 'production' };

Project-scoped calls (branches, project exports) take a ProjectRef: { organizationId, projectId }.

Client options

TypeScript
const scribase = new ScribaseClient({
  baseUrl: 'https://api.scribase.com',

  // A string, or a (possibly async) provider called before every request
  token: async () => await readTokenFromVault(),

  timeoutMs: 30_000,        // per-attempt timeout (default 30s)
  maxRetries: 3,            // retries for 429/503/network failures (default 3)
  retryBaseDelayMs: 250,    // backoff base delay (default 250ms)
  retryMaxDelayMs: 20_000,  // ceiling for one backoff wait (default 20s)
  defaultHeaders: { 'X-Request-Source': 'deploy-bot' },
  userAgent: 'my-app/1.0',
});

Organizations, members, and projects

TypeScript
await scribase.organizations.put('acme', { display_name: 'Acme Inc.' });
const org = await scribase.organizations.get('acme');
const orgs = await scribase.organizations.list();

await scribase.organizations.putMembership('acme', { subject: 'user:ada', role: 'developer' });
const members = await scribase.organizations.listMemberships('acme');

await scribase.projects.put('acme', 'store', { region: 'us-east', postgres_major: 17 });
const project = await scribase.projects.get('acme', 'store');
const projects = await scribase.projects.list('acme');

Roles are owner, admin, developer, and viewer.

Environments

TypeScript
const env = await scribase.environments.get(ref);

// List every environment in an org (optionally one project); returns an array
const environments = await scribase.environments.list({ organizationId: 'acme', projectId: 'store' });

// Lifecycle transitions return accepted operations
const { operation } = await scribase.environments.suspend(ref, { idempotencyKey: 'suspend-prod-001' });
await scribase.operations.wait(operation.id, 'acme');
await scribase.environments.resume(ref);
await scribase.environments.delete(ref);

// Server-side only: the Postgres connection URI
const { connection_uri } = await scribase.environments.connection(ref);

// Recent service logs, newest first
const logs = await scribase.environments.logs(ref, { limit: 100, level: 'error' });

kind is production, staging, or preview; data_mode is snapshot, sanitized, or schema_only.

Previews

Previews are TTL-swept environments of kind preview. The resource defaults the data mode to sanitized and always sets a TTL (24 hours unless you pass one):

TypeScript
const { operation } = await scribase.previews.create({
  organizationId: 'acme',
  projectId: 'store',
  environmentId: 'pr-1487',
  region: 'us-east',
  postgres_major: 17,
  ttlHours: 72,
});
await scribase.operations.wait(operation.id, 'acme');

const previews = await scribase.previews.list({ organizationId: 'acme', projectId: 'store' });

const pr = { organizationId: 'acme', projectId: 'store', environmentId: 'pr-1487' };
await scribase.previews.renew(pr, 48);            // extend to 48h from now
const preview = await scribase.previews.get(pr);
scribase.previews.remainingSeconds(preview);       // seconds until the sweep, or null
await scribase.previews.delete(pr);

Backups and verified restore

TypeScript
// Newest first
const backups = await scribase.backups.list(ref, { limit: 20 });

// Record a new backup; its id is the idempotency key (minted when omitted)
const backup = await scribase.backups.create(ref, { backupId: 'nightly-2026-09-23' });

const one = await scribase.backups.get(ref, 'nightly-2026-09-23');
console.log(one.status, one.sha256, one.verified_at_epoch_seconds);

// Restore a backup into a target environment in the same project
const { restore } = await scribase.restores.start(ref, {
  backupId: 'nightly-2026-09-23',
  targetEnvironmentId: 'restore-drill',
});
console.log(restore.status); // 'pending' | 'restored' | 'failed'

Once a restore reaches restored it carries its evidence — the number of relations and RLS-protected relations restored, and the SHA-256 of the evidence record. restoreEvidence(restore) returns it as a typed object, or null if the restore has not completed:

TypeScript
import { restoreEvidence } from 'scribase';

const evidence = restoreEvidence(restore);

Export

Build a runnable, signed bundle of an environment:

TypeScript
const { manifest, bundleRef } = await scribase.export.create(ref, { format: 'compose' });
console.log(manifest.database.sha256, manifest.artifacts.length, bundleRef);

// Project-scoped form: name the source environment in the request
await scribase.export.createForProject(
  { organizationId: 'acme', projectId: 'store' },
  { sourceEnvironmentId: 'production', format: 'kustomize' },
);

const exports = await scribase.export.list({ organizationId: 'acme', projectId: 'store' });
const saved = await scribase.export.getManifest('acme', manifest.bundle_id);

Migrations

TypeScript
const migrations = [
  { version: '1', name: 'init', up: 'create table orders (id bigint primary key);' },
  { version: '2', name: 'add_total', up: 'alter table orders add column total numeric;', down: 'alter table orders drop column total;' },
];

// Plan and lint only — nothing executes
const planned = await scribase.migrations.plan(ref, migrations);
if (planned.lintFindings.some((finding) => finding.severity === 'BLOCK')) {
  throw new Error('migration blocked by lint');
}

// Apply
const result = await scribase.migrations.run(ref, { migrations, apply: true });
console.log(result.applied);

Branches

TypeScript
const project = { organizationId: 'acme', projectId: 'store' };

await scribase.branches.register(project, {
  environment_id: 'pr-1487',
  base_environment_id: 'production',
});

const branches = await scribase.branches.list(project);
const diff = await scribase.branches.diff(project, { base: 'production', head: 'pr-1487' });
const merge = await scribase.branches.merge(project, { base: 'production', head: 'pr-1487', force: false });

await scribase.branches.linkPullRequest(project, 'pr-1487', {
  provider: 'github',
  repository: 'acme/storefront',
  number: 1487,
});

Queues

TypeScript
await scribase.queues.create(ref, 'events');
const { msg_id } = await scribase.queues.send(ref, 'events', { type: 'order.created' }, { delaySeconds: 5 });

// read() returns an array of messages and hides them for the visibility timeout
const messages = await scribase.queues.read(ref, 'events', { quantity: 10, visibilityTimeoutSeconds: 30 });
for (const message of messages) {
  await scribase.queues.archive(ref, 'events', message.msg_id);
}

const queues = await scribase.queues.list(ref);          // with current depth
const waiting = await scribase.queues.peek(ref, 'events'); // look without consuming
await scribase.queues.purge(ref, 'events');

Imports from Supabase

TypeScript
const request = {
  sourceProjectRef: 'abcd1234',
  sourceConnectionString: process.env.SOURCE_PG_URL ?? '',
  sourceServiceKey: process.env.SOURCE_SERVICE_KEY ?? '',
  destination: { organizationId: 'acme', projectId: 'store', environmentId: 'production' },
};

// Plan first — nothing is written
const plan = await scribase.imports.dryRun(request);
console.log(plan.planned.tables, plan.planned.authUsers, plan.findings);

// Then move it
const report = await scribase.imports.create(request);
console.log(report.moved, report.verification?.checks);

// History and a stored report
const history = await scribase.imports.history('acme');

The source credentials are used once and never returned.

Edge functions

TypeScript
import { readFile } from 'node:fs/promises';

const source = await readFile('functions/hello/index.ts', 'utf8');
await scribase.functions.deploy(ref, { name: 'hello', source });

const { functions } = await scribase.functions.list(ref);
const { result } = await scribase.functions.invoke<{ hello: string }>(ref, 'hello', { name: 'world' });

Function names are letters, digits, hyphens, or underscores. See Functions for writing and running functions.

Operations

TypeScript
const op = await scribase.operations.get('op_01J...', 'acme');

await scribase.operations.wait('op_01J...', 'acme', {
  intervalMs: 2000,       // poll interval (default 1000)
  timeoutMs: 600_000,     // give up after (default 300000)
  onUpdate: (snapshot) => console.log(snapshot.state),
  throwOnFailure: true,   // default true
});

Usage, billing, and audit

TypeScript
const usage = await scribase.usage.get(ref);                  // flat-price envelope and caps
const nearing = await scribase.usage.capsNearingLimit(ref, 0.8);
const billing = await scribase.usage.billing('acme');

const events = await scribase.audit.list('acme', { limit: 50 });

// Or iterate every page
for await (const event of scribase.audit.paginate('acme')) {
  console.log(event);
}

Account and credentials

TypeScript
const me = await scribase.account.get();
const tokens = await scribase.account.listTokens();
const created = await scribase.account.createToken({ name: 'ci', expires_in_days: 90 });
console.log(created.token); // shown once — store it now
await scribase.account.revokeToken(created.id);

const key = await scribase.organizations.createApiKey('acme', { name: 'deploy-bot', scopes: ['deploy'] });
await scribase.organizations.revokeApiKey('acme', key.api_key_id);

Read-only views into a live environment

TypeScript
const users = await scribase.auth.listUsers(ref);
const providers = await scribase.auth.listProviders(ref);
const policies = await scribase.auth.listPolicies(ref);

const tables = await scribase.database.tables(ref);

const buckets = await scribase.storage.listBuckets(ref);
const objects = await scribase.storage.listObjects(ref, 'avatars');

const channels = await scribase.realtime.channels(ref);
const metrics = await scribase.metrics.get(ref);
const insights = await scribase.insights.get(ref);

Error handling

Every error extends ScribaseError. HTTP failures are ScribaseApiError subclasses keyed by status, carrying the problem+json code and detail:

TypeScript
import {
  ConflictError,
  NetworkError,
  NotFoundError,
  OperationFailedError,
  QuotaExceededError,
  TimeoutError,
} from 'scribase';

try {
  const { operation } = await scribase.environments.create({ /* ... */ });
  await scribase.operations.wait(operation.id, 'acme');
} catch (error) {
  if (error instanceof QuotaExceededError) {
    console.error('Plan cap reached — add a capacity pack or free a slot');
  } else if (error instanceof OperationFailedError) {
    console.error('Provisioning failed', error);
  } else if (error instanceof NetworkError || error instanceof TimeoutError) {
    console.error('Could not reach the API');
  } else {
    throw error;
  }
}

Idempotency keys

Mutating calls accept idempotencyKey. When omitted the SDK mints one per call; pass your own to make a retried job safe across processes:

TypeScript
import { newIdempotencyKey } from 'scribase';

const key = newIdempotencyKey();
await scribase.environments.suspend(ref, { idempotencyKey: key });
await scribase.environments.suspend(ref, { idempotencyKey: key }); // replay, not a second suspend

Keys are 8–128 printable ASCII characters. A replayed request returns the original response, and the result's idempotencyReplayed flag is true.