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-js

Initialize

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 set

Identity

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 unlinked

Unlinking 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.
  • create returns 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, and transferConfirm are trust-critical: in popup mode they run on the PeridotID origin (see Popup flow).
  • Fiat methods throw Error on failure (not the T | ApiError union that reads return).
  • Pass the initiating app's clientId as 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 issued

Errors

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.

On this page