tursodatabase/turso · error · DatabaseError

HTTP error! status

Error message

HTTP error! status: ${response.status}

What it means

The POST to <baseUrl>/v3/cursor returned a non-2xx status. This is the transport path used by execute(), executeRaw(), and batch(); the handler tries to read the response body as JSON and surface its message field, and only falls back to the literal 'HTTP error! status: N' when the body is not parseable JSON with a message. Authentication, URL, and request-shape problems all land here.

Solutions

  1. Verify credentials and endpoint: `turso db show <name> --url` and recreate the token if expired
  2. Reproduce outside the app to see the raw body: curl -X POST <url>/v3/cursor -H 'Authorization: Bearer <token>' -d '{}'
  3. Confirm the URL points at a Turso database endpoint (scheme libsql://, turso://, or https://) and the database still exists
  4. Check whether a proxy/gateway sits in front of the database and inspect its error response

Example fix

// before
const session = new Session({ url: 'https://db.example.com', authToken: OLD_TOKEN });
await session.execute('SELECT 1'); // HTTP error! status: 401

// after
const session = new Session({ url: process.env.TURSO_DATABASE_URL!, authToken: process.env.TURSO_AUTH_TOKEN! });
await session.execute('SELECT 1');
Defensive patterns

Strategy: try-catch

Validate before calling

// fail fast on obviously bad config before first query
function assertConfig(cfg: { url: string; authToken?: string }) {
  if (!/^(https|libsql|turso):\/\//.test(cfg.url)) throw new Error(`bad url: ${cfg.url}`);
  if (process.env.TURSO_AUTH_TOKEN === undefined) throw new Error('TURSO_AUTH_TOKEN not set');
}

Type guard

import { DatabaseError } from '@tursodatabase/serverless';

function isHttpError(e: unknown): e is DatabaseError {
  return e instanceof DatabaseError && /^HTTP error! status: (\d+)$/.test(e.message);
}

Try / catch

try {
  await session.execute(sql);
} catch (e) {
  if (isHttpError(e)) {
    const status = Number(e.message.match(/status: (\d+)/)?.[1]);
    if (status === 401 || status === 403) await refreshToken(); // then rebuild Session and retry once
  }
  throw e;
}

Prevention

When it happens

Trigger: 401/403 from a missing, malformed, or expired authToken; 404 from a wrong URL or a deleted database; 400 from a stale baton after the server restarted the stream; an intermediary (proxy, gateway) returning an HTML error page so the body parse fails and you see only the status code.

Common situations: Database token expired in a long-running deployment; URL typo or pointing at libsql:// when https:// was expected (normalizeUrl handles libsql:// and turso:// but not arbitrary hosts); local `turso dev` not running; a corporate proxy intercepting the request.

Related errors


AI-assisted analysis of tursodatabase/turso@c1e5928725 (2026-08-20). Data as JSON: /api/errors/abe499069c5e51cf. Report an issue: GitHub.

Appendix: source

Thrown at serverless/javascript/src/protocol.ts:317

  let response: Response;
  try {
    response = await fetch(`${ctx.url}/v3/cursor`, buildFetchOptions(ctx, JSON.stringify(request), signal));
  } catch (error) {
    wrapAbortError(error);
  }

  if (!response.ok) {
    let errorMessage = `HTTP error! status: ${response.status}`;
    try {
      const errorBody = await response.text();
      const errorData = JSON.parse(errorBody);
      if (errorData.message) {
        errorMessage = errorData.message;
      }
    } catch {
      // If we can't parse the error body, use the default HTTP error message
    }
    throw new DatabaseError(errorMessage);
  }

  const reader = response.body?.getReader();
  if (!reader) {
    throw new DatabaseError('No response body');
  }

  const decoder = new TextDecoder();
  let buffer = '';
  let cursorResponse: CursorResponse | undefined;

  // First, read until we get the cursor response (first line)
  try {
    while (!cursorResponse) {
      const { done, value } = await reader.read();
      if (done) break;

      buffer += decoder.decode(value, { stream: true });

View on GitHub (pinned to c1e5928725)