@getbitid/core
v0.8.1
Published
The BitID SDK: a verified-identity engine (createBitID) plus 'Sign in with BitID' — wallet status, QR verification sessions, liveness, and scoped agent delegation. Zero runtime dependencies; runs unchanged in React Native, the browser, and Node.
Maintainers
Readme
@getbitid/core
The BitID SDK — add "Sign in with BitID" and verified‑identity features to your app. Zero runtime dependencies; the same code runs in React Native, the browser, and Node.
- Sign in with BitID — a scoped, verified‑identity login via OAuth 2.0 + PKCE. The user signs in on Beem, approves the scopes you asked for, and you get an opaque, scoped token.
- Identity features — wallet status, QR verification sessions, liveness, and scoped agent delegation, each self‑bound to the consenting user.
The BitID backend is baked in — you never configure a URL. Pick an environment and go.
npm install @getbitid/core
# React Native? also install the adapter + button:
npm install @getbitid/react-native
clientIdandredirectUriare issued by the BitID team out‑of‑band (no self‑serve registration yet). Everything below works against the built‑in production backend.
1. Sign in with BitID
React Native — the button
import { SignInWithBitID } from '@getbitid/react-native/signin';
<SignInWithBitID
clientId="your-client-id"
onSuccess={(session) => app.signedIn(session.user)} // { did, verified, tier }
onError={(err) => showError(err)} // err.code: BITID_SIGNIN_*
/>That's the whole config — just clientId. The redirectUri is derived from your app's URL
scheme (yourscheme://bitid-callback), which you register once with your clientId.
expo-web-browser / expo-crypto / expo-linking are used, lazily.
Any platform — one imperative call
import { signIn } from '@getbitid/core/signin';
import * as WebBrowser from 'expo-web-browser';
const session = await signIn({
clientId: 'your-client-id',
redirectUri: 'yourapp://bitid-callback',
scopes: ['bitid.identity.read'], // optional; this is the default
browser: { openAuthSession: WebBrowser.openAuthSessionAsync },
storage, // optional session persistence
});
// session → { accessToken, tokenType, scope, expiresAt, user: { did, verified, tier } }signIn() runs the whole device flow: generate PKCE + state, open Beem in the system
browser (never a WebView), catch the redirect, verify state, exchange the code with
PKCE (no secret), read the user, and persist the session. Denials/errors reject with a
.code of BITID_SIGNIN_* (BITID_SIGNIN_CANCELLED, BITID_SIGNIN_STATE_MISMATCH, …).
Staying signed in
import { loadSession, refresh, signOut } from '@getbitid/core/signin';
const existing = await loadSession({ storage }); // a valid session, or null
await refresh({ accessToken: existing.accessToken, storage }); // rotate + slide expiry
await signOut({ storage }); // clear the local sessionrefresh() rotates the token on the same grant (compare‑and‑swap, so a retried refresh can't
strand you) and slides its expiry up to an absolute cap; past the cap the user re‑consents.
2. Read the signed‑in user's data
A scoped token can read the one consenting user's BitID data — addressed with the literal
self alias, which the server substitutes for the real id (you never learn it).
import { fetchMe, callBitID } from '@getbitid/core/signin';
const me = await fetchMe({ accessToken }); // { did, didStatus, verified, tier }
const balance = await callBitID({ accessToken, path: 'bid-balance/balance/self' });Reads are read‑only, self‑bound, and scope‑gated — the token can't touch any other user or the rest of the Beem API.
3. Scoped writes — liveness, sessions, agents
Beyond reads, a scoped token can drive specific writes, each self‑bound to the user. Each
needs its scope granted at consent, or the call fails bitidPartnerScopeDenied.
import {
registerFace, verifyLiveness, // scope: bitid.liveness
createSession, pollSession, // scope: bitid.session
createAgent, delegateToAgent, // scope: bitid.agent
} from '@getbitid/core/signin';
// Liveness — the extra fields are the liveness API's image/frame payloads:
await verifyLiveness({ accessToken, images_data, use_temporal_analysis: true });
// QR verification session — create, then poll for the holder's approval:
const s = await createSession({ accessToken });
const result = await pollSession({ accessToken, sessionId: s.session_id });
// Agent delegation — an agent bound to the user's DID, then delegate scopes to it:
const { machine_did } = await createAgent({ accessToken, label: 'my-agent' });
await delegateToAgent({ accessToken, agentDid: machine_did, scopeGrant: { action: ['read'] } });- Casing is handled for you — liveness uses
user_id, sessions useuserId; you never pass the id. - Sessions are holder‑approved — the user approves in the Beem app;
approve/deny/verifyare intentionally not in this SDK. - Agents carry no spend — the spend cap is fixed at $0, and delegation requires the
user's human DID to exist on Beem (else
bitidPartnerIdentityUnavailable).
4. The engine — createBitID
For richer, on‑device flows (holder apps, custom verification UI), createBitID gives you the
repositories and state machines directly. You supply platform ports (HTTP, clock, and — for
liveness — a camera); on React Native, @getbitid/react-native supplies them.
import { createBitID } from '@getbitid/core';
const bitid = createBitID({
mode: 'live', // or 'sandbox' — runs against fixtures, no network
environment: 'production', // the backend is baked in; no URLs to configure
http, // your HttpClient port (see @getbitid/react-native)
clock, // your ClockPort
camera, // your CameraPort (only for liveness)
});
// read‑only status (never creates a wallet)
const status = await bitid.wallets.getStatus(userId);
// the single‑writer enrolment machine — only on explicit user intent
const enrollment = bitid.createEnrollment();
await enrollment.enroll(userId);
// a QR verification session — you supply the verifier (your liveness check)
const session = bitid.createSession(async ({ sessionId }) => {
await liveness.run({ userId, sessionId });
return 'liveness';
});
await session.start(userId);bitid exposes wallets, ledger, biometrics, sessions, balances, identity, and
agents, plus createEnrollment(), createSession(), createLiveness(), and
completeVerification(). Every failure is a typed BitIDError.
Sandbox mode
Flip mode: 'sandbox' and the same machines run against fixtures — no network, no ports beyond
a clock. The demo behaves like production because it is the production code over a different
data source.
const demo = createBitID({ mode: 'sandbox', clock });
await demo.wallets.getStatus('u_1'); // fixture-backed5. Advanced · server‑to‑server (confidential client)
If your backend needs to read a user's BitID data with no device in the loop (e.g. a KYC
job), register a confidential client and run the exchange on your server with a
client_secret — same primitives, one extra field:
import { createSigninRequest, exchangeCode, fetchMe } from '@getbitid/core/signin';
const { url, codeVerifier, state } = await createSigninRequest({
clientId, redirectUri, scopes: ['bitid.identity.read'],
});
// …redirect the user, validate `state` on the callback, then:
const token = await exchangeCode({
code, codeVerifier, clientId, redirectUri,
clientSecret: process.env.BITID_CLIENT_SECRET, // confidential clients only
});
const me = await fetchMe({ accessToken: token.accessToken });Run exchangeCode with a secret only on your server; a device SDK never has one.
Errors
Every call rejects with a typed error. Sign‑in errors carry a BITID_SIGNIN_* .code; engine
and network errors are a BitIDError whose .code is one of a small closed union
(not_found, server, network, timeout, unauthorized, …).
import { isBitIDError } from '@getbitid/core';
try {
await bitid.wallets.getStatus(userId);
} catch (e) {
if (isBitIDError(e) && e.code === 'network') retryLater();
}Environments
environment: 'production' (default) or 'staging'. The URLs are baked in — you
never configure them.
API surface
| Import | What |
| --- | --- |
| @getbitid/core | createBitID, the domain types, the error taxonomy, and the platform port interfaces |
| @getbitid/core/signin | signIn, fetchMe, refresh, loadSession, signOut, callBitID, the scoped writes, and the confidential‑client primitives |
| @getbitid/core/testing | fakeClock, mockHttpClient, and friends for your tests |
| @getbitid/react-native | platform ports + holder flows; @getbitid/react-native/signin is the <SignInWithBitID> button |
License
UNLICENSED — © Line Financial PBC. All rights reserved. Use is governed by your agreement with BitID; no other rights are granted.
