1. Register your app
CallPOST /v1/apps with your PeridotID session (cookie) to get a public client_id.
List every URL PeridotID may return users to — codes are only issued to these:
https://… origins, no paths).
Pass the exact return URL in code — its origin must be listed:
Your redirect URI origins are automatically allowed for browser API calls (within
~60s of registering, no redeploy needed). Local development needs nothing at all:
http://localhost, http://127.0.0.1 and http://[::1] (any port, any path) are
always allowed — arbitrary production hosts are never accepted without registration
(an open redirect would let anyone steal users’ login codes).
2. Open the login popup
pid_code via window.opener) or denies/cancels. If the popup is
blocked, the SDK falls back to a new tab automatically; peridot.auth.loginTab()
is available if you prefer a tab outright. You never handle a password and
nothing runs in your DOM. Passkeys run on the PeridotID origin (browsers bind
WebAuthn to our domain); Google uses the redirect round-trip. No API calls happen
before login, so there is nothing CORS-related to configure.
First sign-in creates a PID. If the visitor has no PeridotID yet, the
window shows the emission-free Create your PID step (the handle is permanent
and user-chosen). Login only resolves once a PID exists and the user allows.
popupBaseUrl is reused as the auth host; approval popups (a smaller window
on the PeridotID origin) are used for confirmations like transfers and payments.
In React, wrap the same two calls in your own button + hook — there is no
provider component to install.
Who owns what: auth vs session
We authenticate the human, you sessionize your user. PeridotID proves “this browser israni@pid” and hands you proof (pid_code → exchange →
{ pid, profile, accessToken }). Everything session-shaped is yours: your
cookie, your expiry, your logout route, your users table keyed by pid.
Our session cookies (pid_access/pid_refresh) never leave our origin — no
third-party app can or should read them.
One rule covers most mistakes: keep the accessToken server-side. It is a
read-only bearer (identity, profile, balance, ledger — never writes), and
anything in browser JS or localStorage is stealable via XSS. Exchange in the
browser while prototyping if you like; production backends exchange
server-side (step 3) and hand the browser only their own httpOnly cookie.
3. Handle the login
After login the tab returns a one-timepid_code (posted to your opener; a
full-page redirect_uri fallback is also supported). Already signed in? A
consent screen appears first — who you’re continuing as, with Allow, a
different account, or Deny — and only Allow issues the code. Denying returns
with ?error=access_denied.
For production, exchange server-side instead of calling
peridot.auth.exchange in the browser: verify the code against PeridotID,
store the bearer server-side, then mint your app’s own session:
- The bearer is read-only (
scope: "read"): balance/ledger/identity/profile are reachable, transfers and writes always return 403. - It lasts 30 days, non-refreshable — afterwards the user re-clicks login (one tap if their PeridotID session is alive). Never log the bearer.
- There is no single-token revoke: a leaked bearer stays valid until expiry unless the whole identity is deactivated. The read-only scope is the containment — the reason this pattern is blessed for reads only.
- Verify-or-cache is your call: live-verify every request (fail-closed when we’re down, deactivation honored immediately) or cache identity up to ~15 min per route — never longer.
- Logout doesn’t propagate: our sign-out ends our session, not yours — delete your own cookie server-side on your logout route.
- Don’t confuse it with the machine token: the user bearer reads their
data;
POST /v1/auth/tokenreads your app’s data (escrow balance).
Notes
- Passkey login always runs on a PeridotID-hosted page (browsers bind WebAuthn to our
domain) and returns with
pid_codelike Google does. - Codes are single-use, expire in ~5 minutes, and are bound to your
client_id. - No framework? The snippet in step 2 is already framework-free:
loginPopup(popup, tab fallback) oropenLoginTab/openLoginPopup, plusloginWithPasskey({ clientId, returnTo })andexchange(code, clientId).
Verified partners
Admins can mark your app as a verified partner (in the dashboard). Verified apps show a ✓ badge on the login consent screen — and your users skip the PeridotID fiat fee on top-ups, sends, and withdrawals (0%). Your own app fee, DOKU provider fees, and on-chain fees still apply. Interested? Talk to us — verification is granted, never self-issued.Next: money
Once users can sign in, you can fund their balance, hold escrow on your app’s account, and charge your own fee:- Fiat & payments — top-up, balance, sends, escrow.
- Apps & workspace — manage your app, set per-app fees, read the escrow balance with a machine token.