JavaScript SDK
The official PeridotID SDK for the browser
The SDK (@peridotvault/pid-sdk-js) wraps the API for browsers. It sends credentials with every
request, so the API's cookies are handled automatically.
Install
npm install @peridotvault/pid-sdk-jsInitialize
import { Peridot } from '@peridotvault/pid-sdk-js';
const peridot = Peridot({
baseUrl: 'https://api.pid.peridotvault.com',
popupBaseUrl: 'https://app.pid.peridotvault.com', // trust-critical actions open here
onUnauthorized: async () => {
// Access token expired — try to refresh silently before redirecting to login
const ok = await peridot.auth.refresh();
if (!ok) {
const url = await peridot.auth.login(); // Google OAuth URL — navigate to it
if (url) window.location.assign(url);
}
},
});Sign in
auth.login() resolves the Google OAuth URL — navigate to it. The user signs in and is sent back to
CLIENT_SUCCESS_URL with the session cookies already set — or, for a new credential,
to the PID picker (?claim=1), where they choose the permanent <handle>@pid
(check auth.pidAvailable(handle) first; the handle can never be changed, reused,
or reassigned):
const url = await peridot.auth.login();
if (url) window.location.assign(url);
const { available, pid } = await peridot.auth.pidAvailable("ifal");
// { available: true, pid: "ifal@pid" }
const status = await peridot.auth.claimStatus();
// { pending: true, email, displayName, avatarUrl } when a claim is waiting
const claimed = await peridot.auth.claim("ifal");
// { ok: true, pid: "ifal@pid", pidCode? } — session cookies setIdentity
const me = await peridot.identity.me();
// { id: "ifal@pid", status: "active", createdAt: "2026-08-03T13:30:00.000Z" }Login credentials
const credentials = await peridot.identity.credentials();
// [{ id: "3f9c...", provider: "google", email: "a@b.com", linkedAt, lastLoginAt }]
await peridot.identity.unlinkCredential(credentials[0].id); // true if unlinkedUnlinking the identity's last credential fails (returns false) — every identity must
keep at least one way to log in.
Profile
const profile = await peridot.profile.me();
// { pid, displayName, avatarUrl, locale, createdAt, updatedAt }
const updated = await peridot.profile.update({
displayName: 'PeridotPlayer',
locale: 'en-US',
});The PID (pid, e.g. ifal@pid) is permanent and immutable — only the mutable
labels above can be updated. There is no endpoint that changes, reuses, or reassigns it.
Wallet
Each identity owns one personal wallet — its chain rows (Solana smart account
- EVM counterfactuals), all derived from the PID. Custody is record-only: Peridot never generates, holds, or returns any key material.
const rows = await peridot.wallet.me();
// [{ id, chainNamespace, chainReference, address, accountType, status, ... }]
// or ApiError { statusCode: 404 } when the PID has no wallet yet
const created = await peridot.wallet.createAccount();
// idempotent ensure — same rows back on repeat calls- Creating again returns the existing wallet — a PID can never have more than one.
- The wallet belongs to the PID, not to Google (or any other provider). Unlinking a credential leaves it untouched, and logging in from another device does not change it.
createreturns the API error object on validation failure (400).
Money (fiat)
peridot.fiat covers top-up, balance, statement, and sends on the internal IDR
ledger. Full guide: Fiat & payments.
const bal = await peridot.fiat.balance(); // { balanceIdr, source: 'fiat-ledger', ... }
const view = await peridot.fiat.ledger(); // statement rows
const deposit = await peridot.fiat.checkoutDeposit('100000'); // → { paymentUrl, grossIdr, feeIdr, netIdr }
await peridot.fiat.syncTransaction(deposit.id); // corroborate payment → credits the ledger
await peridot.fiat.transferViaPopup({ amountIdr: '100000', beneficiaryPid: 'live2dev@pid' });checkoutDeposit,transferViaPopup, andtransferConfirmare trust-critical: in popup mode they run on the PeridotID origin (see Popup flow).- Fiat methods throw
Erroron failure (not theT | ApiErrorunion that reads return). - Pass the initiating app's
clientIdas the second argument to apply that app's own fee — it stacks on the global 0.1% (min Rp100, no cap) and credits the app.
Apps, fees & raw calls
PeridotClient also exposes get / post / patch / put / delete for
endpoints without a dedicated wrapper — app management, per-app fees, and machine
auth use these:
await peridot.get('/v1/apps');
await peridot.patch(`/v1/apps/${id}`, { allowedOrigins: ['https://mygame.dev'] });
await peridot.put(`/v1/apps/${id}/fees/topup`, {
percentBps: 200, minIdr: '0', maxIdr: '10000', enabled: true,
});See Apps & workspace for the machine token and the full fee model.
Log out
await peridot.auth.logout();Refreshing
Usually you never call refresh() directly — the onUnauthorized callback does it. If
you need to force a refresh:
const ok = await peridot.auth.refresh(); // true if a new session was issuedErrors
Methods return the API error object instead of throwing for failed requests:
const result = await peridot.profile.me();
if ('statusCode' in result && result.statusCode === 401) {
// handle unauthenticated
}See Errors for the full error contract.