Skip to main content

Requirements

  • Node.js 20+
  • pnpm
  • PostgreSQL 16+ (Supabase works great)
Refresh-token state lives in the sessions table, so PostgreSQL is the only datastore — no Redis needed.

Environment variables

All configuration is loaded from .env in the API package. Copy apps/api/.env.example to apps/api/.env and fill it in:
Solana chains/RPC/program id are not env. They live in the chain registry (DB), seeded by pnpm db:seed and edited in the workspace admin (GET /v1/chains, POST /v1/admin/chains). RPC API keys, if any, must be public or proxied — the registry is served to clients as-is.

Fiat (DOKU Checkout + internal ledger)

Fiat is one namespace: DOKU Checkout is money-in only (no Sub-Account); every balance lives on the internal fiat ledger. See Fiat & payments. * DOKU endpoints fail at call time until these are set. The global fee policy is versioned in the DB (fiat_fee_policies) and can be published by an admin (POST /v1/fiat/admin/fee-policy); per-app fees are configured per app in the workspace. * Google endpoints return 503 until these are set. Dev and prod run the identical OAuth code path — only the values differ per environment. Create a client per environment at the Google Cloud console: register GOOGLE_CALLBACK_URL (e.g. http://localhost:3301/v1/auth/google/callback) on the dev client and the prod callback URL on the prod client.

Local development with Docker

Deploy to Vercel + Supabase

  1. Supabase — create a project, then run migrations against it (use the session pooler, port 5432, for migrations):
  2. Vercel — API (apps/api/vercel.json is already configured for serverless). Add a project rooted at apps/api and set these env vars: DATABASE_URL (transaction pooler, port 6543, ?pgbouncer=true), JWT_ACCESS_SECRET, JWT_REFRESH_SECRET, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_CALLBACK_URL, COOKIE_SECURE=true, COOKIE_SAMESITE, CLIENT_SUCCESS_URL, CORS_ORIGINS.
  3. Mintlify — docs (canonical; the board user deploys manually). The Mintlify source lives in apps/docs (docs.json + pages + openapi.yaml). In Mintlify: connect the peridot-id repo, set the docs path to apps/docs, and attach custom domains docs.peridot.id (prod) + sandbox.docs.peridot.id (sandbox) via CNAME at Hostinger. The legacy Fumadocs site (apps/docs) keeps serving the old pid.peridotvault.com/docs* paths until cutover; at cutover it lands a redirect old-docs → the Mintlify host.
  4. Attach your custom domains and point DNS at Vercel.

Production build

Legacy-database migration notes

Migrations are additive where possible, but the PID renames (identityId → pid, PidAccount removal (1 identity = 1 wallet) rewrite identity-linked tables. For a fresh database just run db:deploy + seed. For a database with existing rows from older releases:
  1. Email-duplicate audit — a non-null email is unique (one email = one PID). Resolve collisions before migrating (the uniqueness index fails otherwise):
  2. Deploy with pnpm --filter @peridotvault/pid-api db:deploy (same flow as the Supabase steps above).
  3. No backfill. Existing users get no wallet rows — they are created only by explicit user action (POST /v1/account), so nothing is silently created for existing accounts.