"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 |