Documentation Index
Fetch the complete documentation index at: https://docs.dr.green/llms.txt
Use this file to discover all available pages before exploring further.
Store Architecture Guide
Audience: Engineers and architects designing a Dr Green-backed storefront. This document explains the complete moving parts so you can decide where each piece of your store lives.
The big picture
┌─────────────────────────────────────────────────────────────────────────┐
│ CUSTOMER-FACING │
│ │
│ ┌──────────────────┐ ┌────────────────────┐ ┌──────────────┐ │
│ │ Your store UI │ │ Email from │ │ FirstAML │ │
│ │ (web, mobile) │ │ Dr Green │ │ hosted KYC │ │
│ │ │ │ (KYC link) │ │ portal │ │
│ └────────┬─────────┘ └──────────┬─────────┘ └──────┬───────┘ │
│ │ │ │ │
└────────────┼──────────────────────────┼──────────────────────┼───────────┘
│ │ │
│ HTTPS │ │ KYC docs
▼ │ │ + liveness
┌─────────────────────┐ │ │
│ Your store │ │ ▼
│ backend │ │ ┌─────────────────┐
│ │ │ │ FirstAML │
│ - Customer DB │ │ │ (KYC/AML │
│ - Cart/order │ │ │ provider) │
│ state │ │ └────────┬────────┘
│ - Polling jobs │ │ │
└──────────┬──────────┘ │ │ POST /kyc/webhook
│ │ │ (verification result)
│ x-auth-apikey + │ │
│ x-auth-signature │ │
▼ │ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ Dr Green backend (api.drgreennft.com) │
│ │
│ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │ DualAuthGuard│ │ Global │ │ Inbound │ │
│ │ (JWT or APIK)│ │ exception │ │ webhooks │ │
│ └──────┬───────┘ │ filter │ │ /kyc/webhook │ │
│ │ │ + envelope │ │ /payments/* │ │
│ ▼ └─────────────┘ └──────────────┘ │
│ ┌──────────────┐ │
│ │ NestJS │ │
│ │ controllers │ ◀──── Outbound webhooks ────▶ FirstAML, RPC, S3│
│ │ (147 paths) │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ Prisma ORM │───▶│ PostgreSQL │ │
│ └──────────────┘ └──────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
│
│ ethers.js (event listener sidecar)
▼
┌──────────────────┐
│ Ethereum │
│ smart │
│ contracts │
└──────────────────┘
Components and ownership
Owned by you (the store builder)
- Store UI — the storefront the customer sees. Web, mobile, both. Yours.
- Store backend — your service that calls Dr Green’s API on behalf of the customer. Keeps your own customer DB, cart/order state, polling jobs.
- Customer DB — typically you mirror Dr Green’s
clients/orders records to your own DB so you can render fast (don’t hit Dr Green for every page load). Keep it lean: store IDs, status, last-known shape; don’t duplicate PII unless you have a clear retention reason.
- Polling infrastructure — background jobs that periodically check order/KYC status and notify customers of changes.
Owned by Dr Green
- API backend (
api.drgreennft.com) — NestJS, Postgres, behind Envoy proxy
- Auth — both the DAPP UI’s JWT auth flow and your API-key signature flow
- Catalogue & inventory — the strain catalogue, country-availability, pricing
- Order fulfilment — admin approval, payment processor integration, dispatch
- KYC integration — the FirstAML relationship and case management
- Smart-contract integration — NFT ownership, on-chain commission settlements
- Email — transactional emails to customers (KYC, order updates)
Owned by third parties
- FirstAML — KYC/AML verification (customer interacts directly with their portal)
- CoinRemitter — crypto payment gateway
- Payinn / PGPay — fiat payment gateways
- Sumsub ❌ — not used. (Older versions of these docs incorrectly listed Sumsub as the KYC provider; that was based on a mis-read of the codebase. The provider is FirstAML.)
Data flows in detail
Read flow (browsing the catalogue)
[Customer] → [Your store UI] → [Your backend] → GET /dapp/strains?countryCode=ZAF
↓
[Dr Green]
↓
Postgres query
↓
[Customer] ← [Your store UI] ← [Your backend] ← {strains, pageMetaDto}
Caching: Catalogue changes infrequently. Cache strain lists in your backend for 5 minutes per (countryCode, page, limit) cache key. Cache strain detail for 10 minutes per strainId. Drop on 404.
Write flow (customer onboarding)
[Customer] [Dr Green] [FirstAML]
│ │ │
│ submits form │ │
▼ │ │
[Your backend] │ │
│ POST /dapp/clients │ │
├─────────────────────────────────────────────▶│ │
│ │ writes to Postgres │
│ │ sends email to customer│
│ │ POST to FirstAML ──────▶│
│ ◀────────── 201 {id} ────────────────────────│ │ opens case
│ │ │
│ (your DB: client created, kyc_pending=true) │ │
▼ │ │
[Customer] ← (waits) ← email │
│ │
│ clicks email, completes KYC │
├────────────────────────────────────────────────────────────────────────▶│
│ │ verifies
│ ┌─── webhook ────────────│
│ ▼ POST /kyc/webhook │
│ updates client.isKYCVerified │
│ │
[Your backend] (polling every 5 min) │ │
│ GET /dapp/clients/{id} │ │
├─────────────────────────────────────────────▶│ │
│ ◀─── {isKYCVerified: true} ──────────────────│ │
│ │
│ (your DB: kyc_pending=false, ready=true) │
▼ │
[Customer] ← email/notification "ready to order" │
Order lifecycle flow
See order-lifecycle.md for the detailed version.
Where to put which logic
| Concern | Your backend | Dr Green | Why |
|---|
| Customer-facing UI | ✅ | — | You own the brand experience |
| Customer login (your store) | ✅ | — | Decoupled from Dr Green wallet auth |
| Cart state during shopping | ✅ | (also stored, but defer to yours) | Fast UI; Dr Green’s /dapp/carts is the audit copy |
| KYC verification | — | ✅ | Compliance ownership; FirstAML relationship |
| Order pricing in customer currency | — | ✅ | Dr Green sets pricing; your store displays |
| Payment collection | — | ✅ | Dr Green owns the processor relationships |
| Inventory & stock-outs | — | ✅ | Strain catalogue is Dr Green’s source-of-truth |
| Order shipping | — | ✅ | Dispatch is Dr Green’s responsibility |
| Customer support (general) | ✅ | — | First-line is your team |
| Customer support (KYC issues) | (escalate) | ✅ | Dr Green ↔ FirstAML; you can’t resolve directly |
| Customer support (delivery issues) | (escalate) | ✅ | Same — Dr Green dispatches |
| Marketing & customer comms | ✅ | (transactional only) | Dr Green sends KYC + order emails; you do everything else |
Auth boundaries
Customer Holder Store dev (you)
│ │ │
│ wallet sign │ DAPP UI │ API key + sig
▼ ▼ ▼
┌─────────────┐ ┌──────────────┐ ┌─────────────────────────┐
│ No auth │ │ JWT │ │ ECDSA signature on │
│ (browses, │ │ (manages │ │ every request │
│ KYC docs) │ │ keys, NFTs)│ │ (long-lived, scoped │
│ │ │ │ │ to one primary NFT) │
└─────────────┘ └──────────────┘ └─────────────────────────┘
│
▼
/dapp/* (33 endpoints)
Three auth layers, each with their own use cases:
- No auth — public endpoints (
/auth/nonce, /public/healthStatus, customer-facing KYC/payment redirects)
- JWT — used by the DAPP UI when the holder is logged in. Generated via wallet sign-in. Required for
/keys/*, /user/me, /dapp/users/nfts. Store builders do not have a JWT — they have the API key pair the holder generated using their JWT.
- API-key + signature — used by stores. Long-lived (no expiry, no refresh), per-NFT scope, signed per-request with ECDSA secp256k1.
The DualAuthGuard on /dapp/* routes accepts either JWT or API-key+signature — meaning the same endpoints serve the DAPP UI and external stores.
Scoping: per-NFT, per-key
Every API key pair is scoped to one primary NFT. If a holder owns multiple NFTs and runs separate businesses for each:
- Generate one key pair per NFT
- Treat them as separate stores in your code (or as separate accounts in your multi-tenant setup)
- Don’t try to “swap NFTs” mid-session — switching primary NFT is a DAPP-UI-only operation, and doing it from your store would corrupt your in-flight state
Your nft.tokenId from any authenticated response tells you which NFT the current key is scoped to. Cache it on first call; verify on startup if you’ve encoded NFT-specific config.
Network topology
Production
- API base:
https://api.drgreennft.com/api/v1
- TLS enforced
- Reverse proxy: Envoy (production deployment)
- The OpenAPI spec is publicly accessible at
/api-json
Staging
- API base:
https://stage-api.drgreennft.com/api/v1
- ⚠️ Currently HTTP 503 (Envoy upstream connect error) — has been since at least 8 May 2026. If you need staging access, contact Dr Green.
Your topology (recommendations)
- Outbound from your backend → Dr Green: stable IPs are not currently required by Dr Green, but expect this to change. If your hosting rotates IPs, talk to Dr Green about IP allow-listing if/when they enforce it.
- No customer-browser → Dr Green calls. Your backend is the proxy. CORS-allowed origins are limited; call from server-side only.
- Outbound to FirstAML, payment processors: none. You do not call these directly. All third-party integrations are Dr Green’s domain.
Operational concerns
Rate limits
Not enforced as of 10 May 2026, but engineer politely:
- ≤ 1 req/sec per holder for background polling
- Cache the catalogue aggressively (5–60 min TTLs)
- Use
/dapp/orders/recent as a heartbeat across all customers rather than polling each one
Failure modes you must handle
| Mode | Detection | Mitigation |
|---|
| Dr Green API 5xx | Retryable HTTP error | Exponential backoff, max 3 attempts. Surface as “temporarily unavailable” |
| API key revoked by holder | 401 "User is not authorized" after previously working | Alert ops; degrade to read-only or stop accepting orders |
| FirstAML KYC stuck for hours | isKYCVerified stays false for >24h | Surface to customer (“verification in progress, contact support if it persists”); escalate to Dr Green |
| Payment processor down | Order stuck in paymentStatus: PENDING | Wait — can take hours for crypto. If >24h, escalate |
| Idempotency on order POST | Network error during placement | GET /dapp/orders to check before retrying. See 04-errors.md § Idempotency |
| Strain soft-deleted between catalogue and order | 404 on order line | Re-fetch catalogue, drop stale items, re-render |
Observability
For every Dr Green API call, log:
- HTTP method, path, status code
- Response time
- Your generated correlation ID (for cross-service tracing)
- Truncated
x-auth-apikey (first 16 chars) for key-rotation diagnostics
Don’t log:
- Full
secretKey ever
- Customer KYC fields (DOB, ID numbers, medical record content)
- Full
x-auth-signature headers (verbose; useful only for very specific debugging)
Multi-tenant store builders
If you operate stores for multiple holders (e.g. you sell a SaaS that wraps Dr Green):
- Store each holder’s
(apiKey, secretKey) pair encrypted-at-rest, indexed by your tenant ID
- Cache the resolved
nft.tokenId per tenant for routing/scoping
- Use a separate DAPP-Green API client instance per tenant (don’t share the long-lived signing key across requests)
- Implement bulk operations carefully — a 1000-tenant batch poll without jitter will create thundering herds
Anti-patterns to avoid
| Anti-pattern | Why bad | Do instead |
|---|
Storing secretKey in code or git | Catastrophic if leaked | Secrets manager + env var injection at deploy |
| Calling Dr Green from customer browsers | CORS, exposes API keys, fragile | Server-side only, your backend proxies |
| Polling every customer every minute | Rate-limit risk + infrastructure cost | Active-set polling with jitter, see 06-webhooks.md |
| Using customer email as primary key | Email is not unique across holders | Always use the UUID id |
Auto-correcting profitRecieved to profitReceived | Returns undefined from API | Match the typo until Dr Green ships v2 |
Trusting data.totalProfit from /revenue/summary | Equals totalSales in observed data — likely buggy | Use dashboard.totalProfit as canonical |
| Re-implementing FirstAML’s KYC UI | Not your job | Customer goes to FirstAML’s hosted portal via Dr Green’s email |
Where to next