API Reference
Two HTTP APIs, both NestJS (Fastify), both under the global prefix /v1, both
with interactive Swagger UI at /docs.
| API |
Base URL |
Auth |
Source |
| Customer API |
https://api.aimgold.org/v1 |
Bearer JWT (user); some routes public |
aim-digigold-real/apps/api |
| Admin API |
https://admin-api.aimgold.org/v1 |
Bearer JWT (admin) + role |
aim-digigold-real/apps/admin-api |
The parked Go backend (aim-gold-backend) exposes a separate /api/v1 surface
documented in its own OpenAPI spec (GET /openapi.yaml) — it is not deployed
and not covered here.
Conventions
- Auth: a global
JwtAuthGuard protects every route unless marked
@Public() (libs/common/src/guards/jwt-auth.guard.ts). Send
Authorization: Bearer <accessToken>. Payload is {sub, phone, deviceId};
the admin API additionally runs RolesGuard and per-route @Roles(...).
- Admin roles:
SUPER_ADMIN > ADMIN > SUPPORT. A route lists the
roles allowed.
- Pagination: list endpoints take
?page (default 1) and ?limit
(default 20, max 100) and return {items, total, page, limit}
(libs/common/src/dto/pagination.dto.ts).
- Webhooks are
@Public() but verify an HMAC signature over the raw
request body (captured via Fastify rawBody); a bad signature is 401.
- Rate limiting: the customer API enforces 100 requests / 60 s per client
IP globally (
ThrottlerGuard); exceeding it returns 429. Provider webhooks,
/health, and /metrics are exempt (@SkipThrottle) so callbacks and
scrapes are never dropped. The client IP is resolved via one trusted proxy
hop (Caddy), so it can't be spoofed with X-Forwarded-For.
501 Not Implemented marks endpoints that exist as contracts but are
not built yet — they are called out below.
Customer API (api.aimgold.org/v1)
Auth (modules/auth)
| Method |
Path |
Auth |
Summary |
| POST |
/auth/send-otp |
public |
Send login OTP. Body {phone} → {sent, expiresInSec:600}. |
| POST |
/auth/verify-otp |
public |
Verify OTP; creates user on first login. Body {phone, otp, deviceId?, deviceName?} → {accessToken, refreshToken, user, isNewUser, mpinSet}. 401 invalid/deleted/blocked. |
| POST |
/auth/set-mpin |
JWT |
Set/replace MPIN. Body {mpin(4-6)} → {updated:true}. |
| POST |
/auth/login-mpin |
public |
Phone+MPIN login. 429 after 5 fails in 15 min (30-min lockout). |
| POST |
/auth/forgot-mpin |
public |
Send MPIN-reset OTP (uniform response, anti-enumeration). |
| POST |
/auth/reset-mpin |
public |
Reset MPIN with OTP. Body {phone, otp, newMpin}. |
| POST |
/auth/biometric-token |
JWT |
Issue device-bound biometric token. 400 if no active session for the device. |
| POST |
/auth/login-biometric |
public |
Login with a biometric token. Body {phone, deviceId, biometricToken}. |
| POST |
/auth/refresh |
public |
Rotate refresh token (old one revoked). Body {refreshToken}. |
| POST |
/auth/logout |
JWT |
Revoke the session for a refresh token. 204. |
| GET |
/auth/sessions |
JWT |
List active sessions. |
| DELETE |
/auth/sessions/:id |
JWT |
Revoke one session + its biometric token. |
Users (modules/users)
| Method |
Path |
Auth |
Summary |
| GET |
/users/me |
JWT |
Caller profile (credential hashes stripped). |
| PATCH |
/users/me |
JWT |
Update {full_name?, email?}. |
| POST |
/users/me/fcm-tokens |
JWT |
Register an FCM push token. |
| DELETE |
/users/me/fcm-tokens/:token |
JWT |
Remove an FCM token. |
| POST |
/users/me/deletion/request |
JWT |
Send deletion OTP to own phone. 429 if >3 per 15 min. |
| POST |
/users/me/deletion/confirm |
JWT |
Confirm deletion: {otp, confirmation:"DELETE"} → purges sessions/notifications, anonymizes PII, tombstones user. 409 if active orders/payouts/SIP. |
| GET |
/users/me/deletion/status |
JWT |
{status:'none'} or {status:'deleted', deletedAt}. |
Gold orders & SIP (modules/orders)
| Method |
Path |
Auth |
Summary |
| POST |
/gold/buy |
JWT |
Create a buy: SafeGold verify + Razorpay order; allocation on payment webhook. Body {unit:'INR'\|'GRAMS', amount, idempotency_key} → {order, payment, sg_tx_id, expires_at}. 409 duplicate. |
| POST |
/gold/sell |
JWT |
Sell gold: balance-locked SafeGold confirm, TDS, holding debit, payout queued. Body {unit, amount, bank_account_id, idempotency_key}. 400 insufficient balance. |
| POST |
/gold/sip |
JWT |
Create a SIP plan. Body {amount_inr>=10, frequency, duration_installments(1-240), start_date?, idempotency_key}. |
| GET |
/gold/sip |
JWT |
List caller's SIP plans. |
| GET |
/gold/sip/:id |
JWT |
Get one SIP plan. |
| POST |
/gold/sip/:id/cancel |
JWT |
Cancel a SIP plan. |
| GET |
/gold/sip/:id/installments |
JWT |
List a plan's installment orders. |
| GET |
/gold/orders |
JWT |
List gold orders (?status, ?type filters). |
| GET |
/gold/orders/:id |
JWT |
Get one gold order. |
Portfolio (modules/portfolio)
| Method |
Path |
Auth |
Summary |
| GET |
/portfolio |
JWT |
Summary at live sell price (30 s cache): balance, invested, current value, P&L. |
| GET |
/portfolio/history |
JWT |
Paginated gold-order history. |
| GET |
/portfolio/passbook |
JWT |
501 — not implemented. |
| GET |
/portfolio/statement |
JWT |
501 — not implemented. |
Price (modules/price)
| Method |
Path |
Auth |
Summary |
| GET |
/price/current |
public |
Live SafeGold buy/sell/spot price. Upstream failure → 5xx. |
| GET |
/price/history |
public |
Snapshots for ?range=1d\|1w\|1m\|1y (max 5000 rows). |
There is also a socket.io gateway on namespace /price (no auth) that
broadcasts price_update every 10 s — not an HTTP endpoint, and unused by the
shipped clients.
Products & product orders (modules/products, modules/product-orders)
| Method |
Path |
Auth |
Summary |
| GET |
/products |
public |
Active catalogue (?q, ?category). |
| GET |
/products/:id |
public |
One product. |
| GET |
/products/:id/image |
public |
Image proxy from MinIO (1-day cache). |
| POST |
/product-orders |
JWT |
Place a COD order. Body {items:[{productId, quantity}], shippingAddress, idempotencyKey?}; prices recomputed server-side. 400 product unavailable. |
| GET |
/product-orders |
JWT |
List caller's product orders. |
| GET |
/product-orders/:id |
JWT |
One product order. |
KYC (modules/kyc)
| Method |
Path |
Auth |
Summary |
| POST |
/kyc/submit |
JWT |
Submit PAN/Aadhaar + doc key; PII encrypted at rest. |
| POST |
/kyc/aadhaar-otp |
JWT |
501 — not implemented. |
| POST |
/kyc/aadhaar-otp/verify |
JWT |
501 — not implemented. |
| GET |
/kyc/status |
JWT |
Status with masked last4. |
| POST |
/webhooks/kyc |
HMAC x-signature |
KYC provider webhook (event parsing is still a TODO). |
Payouts & bank accounts (modules/payouts)
| Method |
Path |
Auth |
Summary |
| GET |
/payouts |
JWT |
List caller's payouts. |
| GET |
/payouts/:id |
JWT |
One payout. |
| POST |
/webhooks/payout |
HMAC x-razorpay-signature |
RazorpayX payout + fund-account-validation events. |
| GET |
/bank-accounts |
JWT |
List (masked last4). |
| POST |
/bank-accounts |
JWT |
Add (encrypted); fires penny-drop validation. |
| PATCH |
/bank-accounts/:id/primary |
JWT |
Set primary. |
| DELETE |
/bank-accounts/:id |
JWT |
Delete. |
| PATCH |
/bank-accounts/:id/verify |
JWT |
Re-trigger penny-drop (scoped to the caller's own account). |
Payments webhook (modules/payments)
| Method |
Path |
Auth |
Summary |
| POST |
/webhooks/payment |
HMAC x-razorpay-signature |
payment.captured/order.paid → mark order paid (triggers SafeGold allocation); payment.failed → mark failed. |
Transactions, notifications, referrals
| Method |
Path |
Auth |
Summary |
| GET |
/transactions |
JWT |
Unified feed (gold + product orders + payouts, merged in memory). |
| GET |
/notifications |
JWT |
Paginated notifications. |
| PATCH |
/notifications/:id/read |
JWT |
Mark one read. |
| GET/POST |
/notifications/preferences |
JWT |
501 — not implemented. |
| GET |
/referrals/me |
JWT |
Caller's referral code. |
| POST |
/referrals/apply |
JWT |
Apply a code once. |
| GET |
/referrals/rewards |
JWT |
Referrals where caller is referrer. |
Ops
| Method |
Path |
Auth |
Summary |
| GET |
/health |
public |
Liveness. |
| GET |
/metrics |
internal only (Caddy returns 404 publicly; scraped container-to-container on aim_edge) |
Prometheus metrics. |
Admin API (admin-api.aimgold.org/v1)
Every route below is JWT + role-guarded unless noted. Seed the first admin
with npm run seed:admin (writes admin_users with a bcrypt password).
Auth & ops
| Method |
Path |
Roles |
Summary |
| POST |
/auth/login |
public |
Email/password → admin JWT. |
| POST |
/auth/logout |
any admin |
Stateless no-op (client discards token). |
| GET |
/health |
public |
Liveness. |
| GET |
/metrics |
public (internal) |
Prometheus metrics. |
| Method |
Path |
Roles |
Summary |
| GET |
/devops/runs |
SUPER_ADMIN, ADMIN |
Recent GitHub Actions runs per target (?target=backend\|website\|admin-portal\|monitoring, ?per_page). 503 if GITHUB_PAT unset. |
| POST |
/devops/deploy |
SUPER_ADMIN |
Trigger workflow_dispatch on main for a target. Body {target}. 429 if fired < 60 s ago (Redis lock). Audited devops.deploy.trigger. |
Dashboard, users, KYC
| Method |
Path |
Roles |
Summary |
| GET |
/dashboard/overview |
all |
Headline KPIs. |
| GET |
/dashboard/gmv |
all |
Daily GMV series (?days). |
| GET |
/dashboard/user-growth |
all |
Daily signups. |
| GET |
/users |
all |
List/search customers. |
| GET |
/users/:id |
all |
One user + holding/kyc/banks. |
| PATCH |
/users/:id/block |
SUPER_ADMIN, ADMIN |
Block/unblock. |
| PATCH |
/users/:id/force-kyc-reverify |
SUPER_ADMIN, ADMIN |
Force KYC back to PENDING. |
| GET |
/kyc |
all |
Review queue (default SUBMITTED). |
| GET |
/kyc/:id |
all |
One KYC record. |
| POST |
/kyc/:id/approve |
SUPER_ADMIN, ADMIN |
Approve; registers user with SafeGold on first approval. |
| POST |
/kyc/:id/reject |
SUPER_ADMIN, ADMIN |
Reject with reason. |
| POST |
/kyc/:id/request-resubmit |
SUPER_ADMIN, ADMIN |
Send back to PENDING. |
Orders, payouts, products
| Method |
Path |
Roles |
Summary |
| GET |
/orders / /orders/:id |
all |
List/get gold orders. |
| POST |
/orders/:id/reconcile |
SUPER_ADMIN, ADMIN |
501 stub (audits first). |
| POST |
/orders/:id/refund |
SUPER_ADMIN, ADMIN |
501 stub (Razorpay refund pending). |
| GET |
/payouts |
all |
List (default PENDING+PROCESSING). |
| POST |
/payouts/:id/approve |
SUPER_ADMIN, ADMIN |
Dispatch IMPS payout via RazorpayX. ADMIN capped at ₹100,000. |
| POST |
/payouts/:id/retry |
SUPER_ADMIN, ADMIN |
Reset FAILED→PENDING and re-approve as the retrying admin's role (an ADMIN retry is still subject to the ₹1L cap). |
| GET |
/product-orders |
all |
List physical orders. |
| PATCH |
/product-orders/:id/status |
SUPER_ADMIN, ADMIN |
Set status (no audit log written). |
| GET |
/products / /products/:id |
all |
List/get. |
| POST |
/products |
SUPER_ADMIN, ADMIN |
Create. |
| PATCH |
/products/:id |
SUPER_ADMIN, ADMIN |
Update. |
| POST |
/products/:id/image |
SUPER_ADMIN, ADMIN |
Upload image (raw base64). |
| DELETE |
/products/:id |
SUPER_ADMIN, ADMIN |
Soft delete (is_active=false). |
Price, config, reports, audit
| Method |
Path |
Roles |
Summary |
| GET |
/price/config |
SUPER_ADMIN, ADMIN |
Read spread config (Redis). |
| POST |
/price/config |
SUPER_ADMIN |
Set spread config. |
| POST |
/price/override |
SUPER_ADMIN |
Partial stub: saves a MANUAL price row then 501 (cache push + broadcast pending). |
| GET |
/price/live |
all |
SafeGold live price passthrough. |
| GET |
/config/feature-flags |
SUPER_ADMIN, ADMIN |
Read flags (Redis). |
| PUT |
/config/feature-flags |
SUPER_ADMIN |
Upsert a flag. |
| GET |
/config/limits |
SUPER_ADMIN, ADMIN |
Read limits. |
| PUT |
/config/limits |
SUPER_ADMIN |
Replace limits. |
| GET |
/config/banners |
SUPER_ADMIN, ADMIN |
Read CMS banners. |
| PUT |
/config/banners/:id |
SUPER_ADMIN, ADMIN |
Upsert a banner. |
| GET |
/reports/aum |
all |
AUM snapshot from gold_holdings. |
| GET |
/reports/daily-transactions |
SUPER_ADMIN, ADMIN |
Stub. |
| GET |
/reports/gst |
SUPER_ADMIN, ADMIN |
Stub. |
| GET |
/reports/tds |
SUPER_ADMIN, ADMIN |
Stub. |
| GET |
/reports/safegold-reconciliation |
SUPER_ADMIN, ADMIN |
Stub. |
| GET |
/transactions |
all |
Unified admin-wide feed. |
| GET |
/audit |
SUPER_ADMIN, ADMIN |
Audit log, newest first, with filters. |