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
- Verify credentials and endpoint: `turso db show <name> --url` and recreate the token if expired
- Reproduce outside the app to see the raw body: curl -X POST <url>/v3/cursor -H 'Authorization: Bearer <token>' -d '{}'
- Confirm the URL points at a Turso database endpoint (scheme libsql://, turso://, or https://) and the database still exists
- 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
- Load url/authToken from environment at startup and validate presence, so failures surface at deploy not first query
- Rotate tokens before expiry and recreate the Session afterwards — the token is captured per Session
- Health-check with a cheap `SELECT 1` after building the Session to fail fast on 401/404
- Note this path parses the server's JSON message; when you only see the status code, the body was not JSON — inspect it with curl
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
- GenericFailure
- HTTP error! status
- HTTP request failed: . URL: , Method: , Body size: bytes
- Invalid request line
- Query timed out
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)