Aurabase Logo
aurabasedocs
docs›Guides›Client options and errors

Client options, errors, retries and timeouts

The behaviour shared by every service of the SDK, and the options that change it.

9 min read·Level intermediate·Revised on Oct 12, 2026
This English text was generated automatically from the French original and has not been reviewed yet.
#
Create

1. Create the client

src/lib/aurabase.tstypescript
import { createClient } from '@aurabase/aurabase-js'

export const aura = createClient(
  process.env.AURA_URL!,       // https://my-app.aurabase.cloud
  process.env.AURA_ANON_KEY!,  // the public key
)

createClient(url, key, options?) takes the project URL and one key. The project is identified by the host of the URL, so there is no project id to pass. In a local stack, use a URL with the project subdomain, such as http://my-app-ab12.localhost:8080; with a bare http://localhost:8080 the client prints a warning, because the gateway cannot tell which project you mean.

Use the public key (aura_anon_…) in browsers and apps. A service_role key (aura_sk_…) belongs on your server only, and the client enforces it:

server/admin.tstypescript
import { createAdminClient } from '@aurabase/aurabase-js'

// Server only: bypasses row-level security.
export const admin = createAdminClient(process.env.AURA_URL!, process.env.AURA_SERVICE_ROLE_KEY!)

In a browser, createClient throws when it receives a service_role key, so a leaked key cannot ship by accident. Set refuseServiceRoleInBrowser: false to downgrade the error to a warning; do not do this in production.

Every service hangs off the client: aura.auth, aura.db, aura.storage, aura.realtime, aura.functions, aura.ai, aura.notifications, and aura.from('table') as a shortcut for aura.db.from('table'). See the client reference.

#
Options

2. Options

OptionDefaultEffect
auth.persistSessiontrue in a browserKeep the session between page loads
auth.storagelocalStorage in a browser, memory elsewhereWhere the session lives: pass your own store (cookies for SSR)
auth.autoRefreshtrueRefresh the access token before it expires, and keep tabs in sync
auth.detectSessionInUrltrue in a browserRead a session from the URL after an OAuth redirect, then clean the URL
fetchglobalThis.fetchYour own fetch, for tests or proxies
retrytrueRetry reads on transient errors. false disables, or { enabled, maxRetries }
requestTimeoutMs30000Time limit of a request. 0 disables it.
onEventnoneReceives observability events, see below
debugfalseAlso print each event with console.debug('[aurabase]', event)
refuseServiceRoleInBrowsertrueThrow when a service_role key reaches a browser

For server-side rendering, use the cookie-based clients described in Next.js App Router and SSR.

#
Errors

3. The error object

Every call returns { data, error } and never throws. When it fails, error is an AuraError:

errors.tstypescript
import { isAuraError } from '@aurabase/aurabase-js'

const { data, error } = await aura.db.from('posts').select('*')
if (error) {
  console.log(error.status)  // 500, or 0 when the failure is on the client
  console.log(error.code)    // 'INTERNAL', 'timeout', 'network_error', …
  console.log(error.message) // human-readable
  console.log(error.details) // the raw body, including error.request_id from the server
  isAuraError(error)         // true
}

A real failing call returned:

error.detailsjson
{ "error": { "code": "INTERNAL", "message": "boom", "request_id": "req-123" } }
FieldMeaning
statusThe HTTP status. 0 means the failure happened before any response: network, timeout, or a refusal by the client.
codeA stable identifier. Server codes keep their value; the client adds the codes below.
messageA readable explanation
detailsThe raw response body
retryAfterSeconds to wait, from a Retry-After header, on a 429
isNotFound, isUnauthorized, isForbidden, isRateLimitedShortcuts on status

Codes produced by the client itself (status 0): timeout, network_error, invalid_response (a success whose body is not JSON), no_session, refresh_discarded, invalid_path, invalid_body. Authentication adds codes such as invalid_credentials, weak_password, email_already_in_use, user_not_found, invalid_otp, session_expired, session_revoked and rate_limit_exceeded. A 429 always carries rate_limit_exceeded.

Test data rather than error when you need the value. Some calls succeed without content (204), and then both data and error are null.

#
Resilience

4. Retries and timeouts

Retries. The client retries a request automatically only when all of these hold:

  • the method is GET or HEAD (a POST, PUT, PATCH or DELETE is never replayed, so nothing runs twice);
  • the response status is 408, 429, 502, 503 or 504, or the network failed;
  • fewer than 3 retries have happened.

The delay is random between 0 and min(500 ms × 2^attempt, 10 s). A Retry-After header wins over it, capped at 60 seconds. Retried requests carry an X-Retry-Count header. A timeout is not retried.

Measured against a server that answered 503 twice, then 200:

text
GET  /flaky   590 ms  { data: [...] }          events: retry#1:503, retry#2:503
POST /flaky     5 ms  { error: SERVICE_UNAVAILABLE }   (not retried)
GET  /flaky     1 ms  { error: SERVICE_UNAVAILABLE }   with retry: false

Timeouts. A request that gets no answer within requestTimeoutMs (30 s by default) ends with status: 0 and code: 'timeout'. Reading the body is bounded by the same limit, so a server that sends headers and then stalls cannot hang the call. A request to a server that never answered, with a 400 ms limit, returned the error after 405 ms.

Two cases do not use this limit because they manage their own: a call where you pass an AbortSignal, and uploads. Functions use their own timeout option, see Errors, timeouts and retries.

#
Observability

5. Observe the client

onEvent receives one event per notable thing the client does. It never makes a request fail: an exception thrown by your hook is ignored.

kindFieldsWhen
retryattempt, status?, urlBefore a retry
timeouturlA request hit requestTimeoutMs
refresh-successnoneA session refresh succeeded
refresh-discardedreasonA refresh was dropped because the session changed meanwhile
jwks-fetch-failedbaseUrlThe signing keys could not be fetched (local token checks)
storage-blockedopThe browser refused storage access
observe.tstypescript
const aura = createClient(url, key, {
  onEvent: (e) => metrics.increment(`aurabase.${e.kind}`),
})

URLs in events have their query string and fragment removed, because they can carry secrets (?apikey=, signed links).

#
Sessions

6. Sessions and tokens

  • The client sends apikey on every request, and Authorization: Bearer <access token> once a user is signed in.
  • It refreshes the access token when fewer than 30 seconds remain. Concurrent refreshes are merged into one, so a rotating refresh token is used once.
  • A 401 triggers one refresh and one replay of the request, then stops.
  • In a browser, refreshes are coordinated across tabs.
  • aura.auth.onAuthStateChange(callback) tells you about SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED, USER_UPDATED and MFA_CHALLENGE. See the auth guide.
Last updated · Oct 12, 2026