The developer dashboard for Cosmos Pay: a self-service console where developers sign in, manage their organization, mint API keys, and operate against the Cosmos Pay Payments API (Stellar SEP-7 payment intents, webhooks, products, customers and analytics).
It is an Astro SSR app (Node adapter) that authenticates users through Authentik (OAuth2 via Better Auth), provisions per-user credentials in the APISIX gateway, and proxies the Cosmos Pay Payments API on the developer's behalf.
Looking for the JS/TS client that calls the public API? See the CosmosJS SDK. Hitting deploy/runtime issues? See COMMON_ISSUES.md.
- Auth — OAuth2 login through Authentik (Better Auth
genericOAuth), sessions in Postgres. - Organizations & teams — orgs, members, role/permission scoping, email invitations (magic links) with per-plan seat limits.
- API keys — create/rotate/revoke keys backed by APISIX consumers & credentials, with per-key scopes (read/write), role and environment (testnet/mainnet) forwarded downstream.
- Payments — create and manage SEP-7 payment intents (
pay/tx), validate transactions. - Webhooks — register endpoints, inspect deliveries, redeliver, rotate signing secrets.
- Products & customers — catalog items / price links and merchant-managed customers.
- Analytics — summary, balances, API & webhook logs.
- Client activity — what the dashboard and the Cosmos Pay Wallet report about themselves (errors, timings, transactions), including everything that never became an API call; browsable under Build → Activity log.
- Plans & onboarding — feature-flagged onboarding wizard and plan selection.
- Support — in-dashboard tickets.
- i18n — multi-language UI (en, es, pt, fr, de).
Browser ──▶ Cloudflare ──▶ nginx ──▶ Astro SSR app (this repo, :4321)
│
┌──────────────────────┼───────────────────────────┐
▼ ▼ ▼
Authentik (OAuth2) Postgres (Prisma) APISIX gateway
user login accounts, orgs, ├─ admin API: keys/consumers
invitations, tickets └─ data plane: proxies the
Cosmos Pay Payments API
│
▼
Cosmos Pay Payments API
(NestJS community server, :3000)
- The dashboard talks to the Payments API server-to-server (
src/lib/cosmos.ts), presenting the gateway secret + the signed-in user's consumer identity — exactly as a real API key would after passing through APISIX. - It (re)syncs the APISIX routes to the current
COSMOS_API_URLon startup (src/lib/apisix-route.ts): the key-auth route external API-key traffic is proxied through, plus keyless routes for the OAuth callbacks and for SEP-1/10/30 (stellar.toml, web auth and account recovery, all served by the community server).COSMOS_API_URLmay list severalhost:portreplicas, balanced round-robin.
Stack: Astro 6 (SSR, @astrojs/node standalone) · React 19 islands · Better Auth ·
Prisma + PostgreSQL · APISIX · Resend/SMTP (email) · Zod + zod-to-openapi · GSAP.
src/
├── pages/
│ ├── api/ # SSR API routes (account, api-keys, payment-intents,
│ │ # webhooks, products, customers, organizations, support,
│ │ # notifications, cosmos proxy, auth catch-all, openapi.json)
│ ├── dashboard.astro # main app shell
│ ├── onboarding.astro # onboarding wizard
│ ├── pricing.astro · docs/ · invite/[token].astro · index.astro
├── components/ # UI (cosmos/dashboard views, widgets, modals) + React islands
├── layouts/ # CosmosLayout etc.
├── lib/ # auth, cosmos (Payments API client), activity (telemetry
│ # buffer), rate-limit, invitations, mailer, plans, profile,
│ # prisma, apisix-route, notifications, i18n…
├── utils/ # apisix.ts (admin API: routes/consumers/credentials)
├── schemas/ # Zod schemas (OpenAPI source of truth)
├── emails/ # transactional email templates
├── middleware.ts # session resolution, CORS, one-time APISIX route sync, and
│ # the activity record of this app's own /api/* traffic
└── styles/
prisma/schema.prisma # User, Session, Account, Profile, Organization(+Member,
# Invitation), Notification, SupportTicket/Message …
ecosystem.config.cjs # PM2 process config (prod build + dev), loads .env for Prisma
COMMON_ISSUES.md # production troubleshooting runbook
- Node.js ≥ 22.12
- PostgreSQL (for the dashboard's own accounts/profile data)
- Authentik OAuth2 application (the login provider)
- APISIX gateway with admin API access
- The Cosmos Pay Payments API (NestJS community server) reachable from this app
- An email sender: Resend API key (preferred) or any SMTP server
# 1. Install
npm install
# 2. Configure — copy the example and fill it in
cp .env.example .env
# 3. Create the schema in your database
npm run db:push
# 4. Run
npm run dev # http://localhost:4321All variables are validated via Astro's typed env (astro.config.mjs). See
.env.example for the full, commented list. The essentials:
| Variable | Purpose |
|---|---|
DATABASE_URL |
PostgreSQL connection (Prisma) |
BETTER_AUTH_SECRET |
Session signing secret |
BETTER_AUTH_URL / PUBLIC_BETTER_AUTH_URL |
Public app URL (server + browser). Must match the deployed domain |
AUTHENTIK_CLIENT_ID / _SECRET / _DISCOVERY_URL |
Authentik OAuth2 provider |
APISIX_URL / APISIX_ADMIN_KEY / APISSIX_ROUTE_ID |
APISIX admin API + the synced route id |
COSMOS_API_URL |
Upstream Payments API the routes proxy to (comma-separated replicas allowed) |
COSMOS_RECOVERY_{A,B}_HOST / _UPSTREAM |
Optional: a host-bound SEP route per recovery server deployment |
COSMOS_API_ENTRY / COSMOS_API_REWRITE |
Public route path + rewrite (/cosmos-api/* → /$1) |
COSMOS_GATEWAY_SECRET |
X-Gateway-Secret for direct server-to-server calls |
WALLET_AUTH_CONSOLE_SECRET / WALLET_RECOVERY_CONSOLE_SECRETS |
Admit the community server's console legs (sign-in codes + provisioning / recovery codes) |
RESEND_API_KEY |
Resend HTTP email (preferred). Falls back to SMTP_* if unset |
SMTP_HOST/PORT/USER/PASS/SECURE / SMTP_FROM |
SMTP transport + verified sender |
ONBOARDING_ENABLED / PLANS_ENABLED / ALLOW_USER_PLAN_CHANGES / ENABLED_PLANS |
Feature flags |
⚠️ PUBLIC_BETTER_AUTH_URLis inlined at build time — change it beforenpm run build, not just before restarting. See COMMON_ISSUES.md for the why.
- Authentik: register a Redirect URI of
https://<your-domain>/api/auth/callback/ak. (better-auth 1.7 rebuilt generic OAuth as a social provider and moved the callback off/api/auth/oauth2/callback/:id; an instance upgraded from 1.6 must register the new URI before the deploy — Authentik accepts both, so keep the old one until the cutover lands.) - Email: verify your
SMTP_FROMdomain in Resend (SPF/DKIM). - APISIX: point
APISIX_URLat the local/internal admin API (never expose it publicly).
| Command | Action |
|---|---|
npm run dev |
Dev server with HMR at localhost:4321 (--host) |
npm run build |
prisma generate + production build → dist/ |
npm run start |
Run the built server (node ./dist/server/entry.mjs) |
npm run preview |
Preview the build locally |
npm run db:generate |
Regenerate the Prisma client |
npm run db:push |
Push the Prisma schema to the database |
npm run sync:route |
Manually re-point the APISIX routes' upstreams |
npm run export:wallet-data |
Export the retired wallet backup / recovery tables to JSON for the community server |
The repo ships an ecosystem.config.cjs that loads .env into the
process (so Prisma sees DATABASE_URL) and defines two apps that share it:
# Install from the lockfile, build, then run the compiled server.
# `npm ci` and not `npm install`: install re-resolves the ranges in package.json, which can
# put a newer minor in node_modules while dist/ is still the build made against the old one.
# Dependencies are external in that bundle, so the mismatch only shows when the server boots
# ("does not provide an export named ...") and PM2 restart-loops. Rebuild after every install.
npm ci
npm run build
pm2 start ecosystem.config.cjs --only devplat # production
pm2 save
# (dev variant — astro dev, do NOT run alongside devplat: same port 4321)
pm2 start ecosystem.config.cjs --only devplat-devnginx terminates TLS and proxies the domain to 127.0.0.1:4321. For the full production
runbook — Cloudflare and OAuth gotchas, the APISIX/Docker upstream, email/SMTP, database init —
read COMMON_ISSUES.md.
Every route under src/pages/api/** is a typed SSR endpoint. A generated OpenAPI document is
served at /api/openapi.json (schemas live in src/schemas/). Highlights:
POST /api/auth/[...all]— Better Auth (OAuth2 sign-in/callback, session, sign-out)GET|POST /api/api-keys·…/[id]— manage APISIX-backed API keys…/api/payment-intents(+/[id],/[id]/validate) — SEP-7 intents…/api/webhooks(+ deliveries, ping, rotate-secret) — webhook endpoints…/api/products·…/api/customers— catalog & customers…/api/organizations/[id]/…— orgs, members, invitationsGET /api/cosmos/[metric]— analytics proxyGET|POST /api/activity·GET /api/activity/summary— the client-activity feed (POST is the dashboard reporting its own page views, actions and errors)POST /api/telemetry— public: activity from a wallet that has no Cosmos Pay account yet, forwarded upstream under one shared consumer so it stays anonymous rather than being attributed to a guess. Rate-limited per address and globally…/api/support/…·…/api/notifications/…— tickets & notifications
Part of the Cosmos Pay ecosystem:
- CosmosPay Community Server — the NestJS Payments API (Stellar SEP-7 intents, webhooks, products, customers, analytics). This is the upstream that the dashboard proxies through APISIX.
- CosmosJS SDK — the object-oriented JS/TS client developers use to call the public API with their API keys.
Built on top of:
- Authentik (source) — the OAuth2 / OpenID Connect identity provider that handles user login.
- Apache APISIX (source) — the API gateway where per-user consumers/credentials (API keys) live and through which external API traffic is authenticated and proxied to the Payments API.
Proprietary — © Cosmos Pay. All rights reserved.