"Direct Access"

Connect directly to your environment database using `@libsql/client` for advanced control over tables, data, and schema beyond what the CRUD API provides.

Overview

Tether's Database API and Functions handle most use cases, but sometimes you need lower-level access — managing tables, running migrations, bulk operations, or querying data in ways the CRUD API doesn't support. Direct Access lets you run raw SQL against your environment database using the standard @libsql/client library.

Each connection is scoped to a single environment and authenticated with a dedicated database token that has granular permissions.

Creating a database token

Database tokens are managed per environment from the Tether dashboard or via the API. Each token has a name, a set of permissions, and an optional expiry date.

Available permissions

Permission Description
read SELECT queries and reading data
write INSERT and UPDATE statements
delete DELETE statements
schema CREATE TABLE, ALTER TABLE, DROP TABLE, and access to internal Tether tables
pragma PRAGMA statements for database configuration

Via the API

// Create a token with specific permissions
const response = await fetch(
  'https://tether-api.strands.gg/api/v1/projects/{projectId}/env/{envName}/db-tokens',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer YOUR_API_KEY',
    },
    body: JSON.stringify({
      name: 'Admin token',
      permissions: ['read', 'write', 'delete', 'schema'],
      expires_at: '2026-12-31T23:59:59Z', // optional
    }),
  }
);

const { raw_token, connection_url } = await response.json();
// raw_token: "tthr_db_..." — store this securely, it's only shown once
// connection_url: "https://tether-api.strands.gg/api/v1/projects/{id}/env/{env}"

Important: The raw token is only returned once at creation time. Store it securely — Tether only keeps a hash of the token and cannot retrieve it later.

Connecting with @libsql/client

Install the libSQL client and connect using the token and URL from the previous step.

npm install @libsql/client
import { createClient } from '@libsql/client';

const db = createClient({
  url: 'https://tether-api.strands.gg/api/v1/projects/{projectId}/env/{envName}',
  authToken: 'tthr_db_...',
});

// Run queries
const result = await db.execute('SELECT * FROM users');
console.log(result.rows);

// Insert data
await db.execute({
  sql: 'INSERT INTO users (name, email) VALUES (?, ?)',
  args: ['Alice', '[email protected]'],
});

// Run migrations
await db.execute(`
  CREATE TABLE IF NOT EXISTS audit_log (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    action TEXT NOT NULL,
    user_id TEXT,
    created_at TEXT DEFAULT (datetime('now'))
  )
`);

// Batch operations
await db.batch([
  { sql: 'INSERT INTO tags (name) VALUES (?)', args: ['typescript'] },
  { sql: 'INSERT INTO tags (name) VALUES (?)', args: ['sqlite'] },
  { sql: 'INSERT INTO tags (name) VALUES (?)', args: ['realtime'] },
]);

Permission enforcement

Every SQL statement is classified and checked against the token's permissions before execution. If a statement requires a permission the token doesn't have, the request is rejected with a PERMISSION_DENIED error.

// Token with only "read" permission
const db = createClient({ url, authToken: readOnlyToken });

// This works
await db.execute('SELECT * FROM users');

// This fails with PERMISSION_DENIED
await db.execute('INSERT INTO users (name) VALUES (?)', ['Bob']);
// Error: Permission denied: 'write' permission required for this statement

Internal table protection

Tables prefixed with _tether_ are internal to the Tether system and are protected by default. Only tokens with the schema permission can access them.

Managing tokens

List tokens

GET /api/v1/projects/{projectId}/env/{envName}/db-tokens

Response:

{
  "tokens": [
    {
      "id": "token-uuid",
      "name": "Admin token",
      "permissions": ["read", "write", "delete", "schema"],
      "created_by": "user-id",
      "created_at": "2026-01-15T10:30:00Z",
      "expires_at": null,
      "last_used_at": "2026-03-07T14:22:00Z",
      "revoked": false
    }
  ]
}

Update a token

PATCH /api/v1/projects/{projectId}/env/{envName}/db-tokens/{tokenId}
{
  "name": "Read-only token",
  "permissions": ["read"]
}

Revoke a token

DELETE /api/v1/projects/{projectId}/env/{envName}/db-tokens/{tokenId}

Response:

{ "revoked": true }

Revoked tokens are immediately invalidated. Any connections using a revoked token will be rejected on the next request.

Realtime compatibility

Changes made through Direct Access automatically trigger realtime subscription updates. If you modify a table that has active WebSocket subscribers, they'll receive invalidation signals and re-fetch their data — just as if the change came through the CRUD API.

Use cases

Use case Description
Migrations Create, alter, or drop tables beyond what the schema file supports
Bulk operations Import or transform large datasets with raw SQL
Complex queries JOINs, subqueries, CTEs, and aggregations not available via the CRUD API
Admin tooling Build custom admin panels or scripts with full database access
Debugging Inspect and fix data directly during development