Client SDK

@authoric/js: sign-in, data, realtime, files, teams and billing for your app's own users, from a browser, React Native or your server.

@authoric/js is the library your app's frontend calls Authoric with. It speaks for one of your app's signed-in users (or nobody, for signing in), and wraps the same REST API documented in the API reference: sign-in and the user's own account, your tables, live changes, custom actions, files, organizations, roles and billing.

npm install @authoric/js
import { createClient } from "@authoric/js";

const authoric = createClient({
  url: "https://api.authoric.ai", // your API's address
  appUid: "0199a3c2-…", // your app's uid, from its page in the console
});

const result = await authoric.auth.signInWithPassword({ email, password });
if (result.mfa) {
  await authoric.auth.mfa.verify({ challengeId: result.mfa.challengeId, code });
}

const { items } = await authoric.from("invoices").list({ where: { status: "approved" }, limit: 25 });

It runs anywhere fetch does: browsers, Node 18+, Deno, Bun, edge runtimes and React Native. It has no dependencies of its own.

What it holds

createClient takes two things, the API's address and your app's uid, and neither is a secret. The browser holds nothing else but the signed-in user's own session. Everything the user may do is decided on the server: which rows they read and write, which organization a row belongs to, which columns are redacted, which tables are locked. The client sends requests; the server decides.

Agent API keys (plak_…) belong on a server. The client refuses one outright, before any request is made.

The session

Where it lives
Access tokenMemory only (on your server, the session cookie). Lasts 15 minutes; the client refreshes it before it expires.
Refresh tokenThe storage you choose. In a browser the default is localStorage, so a reload keeps the user signed in. On your server, a cookie on your own domain.
createClient({ url, appUid, storage: "local" }); // the browser default
createClient({ url, appUid, storage: "session" }); // one tab, gone when it closes
createClient({ url, appUid, storage: "memory" }); // nothing kept; the default outside a browser
createClient({ url, appUid, storage: mySecureStore }); // { getItem, setItem, removeItem }, sync or async

localStorage is readable by any script on your page, as all browser storage is. Refresh tokens are single-use: when one is presented twice, the session ends for every holder of it, so a copied token stops working the moment the real one is used.

Tabs share one session. Only one refreshes at a time and the others pick up its result, so any number of open tabs stays signed in. Signing out in one tab signs out all of them.

auth.onChange tells you when the session starts, refreshes, switches organization or ends, in this tab or another:

authoric.auth.onChange(({ event, session, reason }) => {
  if (event === "signedOut") showSignIn(reason); // "signed_out", or why the server ended it
});

Errors

Every failure is an AuthoricError with the API's status, code, message, details and requestId. Branch on code; the error codes page lists them, and the package types them.

import { isAuthoricError } from "@authoric/js";

try {
  await authoric.from("invoices").update(uid, { total: 0 });
} catch (error) {
  if (isAuthoricError(error) && error.code === "TABLE_LOCKED") showReadOnly();
  else throw error;
}

An error is safe to log: it carries the server's envelope and never a token or a request body.

The client waits out what the server asks it to: an app waking from a pause (APP_WAKING), a restore in progress or a rate limit, each for the time in Retry-After and once. It sends a write again only when the write carries an Idempotency-Key, which every table write does by default, so a retry never writes twice.

In this section

  • Signing in: every sign-in method, two-factor, OAuth, organizations and the user's own account.
  • Server-side sign-in: the session in a cookie on your own domain, for pages your server renders.
  • Rows and realtime: queries, counts, batch writes, live changes and custom actions.
  • Files: uploads, downloads and links.
  • Teams and roles: organizations, members, invitations and roles.
  • Billing: a pricing page, payments and entitlements.
  • Typed tables: TypeScript types generated from your app's schema.

On this page