BorderCash Usage Guide
This guide covers installing, configuring, and operating a BorderCash deployment. It is written for developers and enterprises evaluating or running the platform.
For what the product is and how it is architected, see BorderCash Overview.
1. Prerequisites
| Requirement | Notes |
|---|---|
| Docker and Docker Compose | Recommended and supported path |
| Python 3.10+ | Only for non-container installs |
| PostgreSQL 15 | Containerised by default |
| Redis 7 | Containerised by default |
| A valid Pluggedspace licence | Required. Without it the node runs read-only. |
| Provider API credentials | Stellar, payments, and utility providers as applicable |
Outbound internet access is required on first run to pull images and Python packages. After installation, licence verification is offline.
2. Installation
2.1 Quick start
git clone https://github.com/pluggedspace/BorderCash.git
cd BorderCash
cp .env.example .env
Edit .env before starting anything. At minimum you must set the licence block and
replace every generate_a_... placeholder. See
Configuration.
docker compose up --build -d
docker compose exec web python manage.py migrate
docker compose exec web python manage.py createsuperuser
The API is then served on the canonical port:
curl http://localhost:8000/healthz
2.2 Verifying the installation
docker compose ps # all services should be healthy
docker compose exec web python manage.py check
manage.py check is the important one. It fails loudly on a missing or placeholder
licence:
app.E001- licence token empty or a placeholderapp.E002- public key missing or still the bundled default
2.3 Non-container installation
Manual setup (advanced)
- Create a virtual environment and install dependencies.
- Configure a PostgreSQL database and Redis instance.
- Export the environment variables from
.env.example. - Run
python manage.py migrate. - Serve with Gunicorn and run Celery worker and beat separately.
3. Configuration
All configuration is environment driven. .env.example is the authoritative template.
3.1 Core
| Variable | Purpose |
|---|---|
APP_NAME, APP_VERSION | Reported in headers and diagnostics |
DEBUG | Must be False in production |
DJANGO_SECRET_KEY | Django secret. Generate a unique 64-character value. |
SERVER_JWT_KEY | Signing key for access tokens. Must be unique per deployment. |
ALLOWED_HOSTS | Comma-separated hostnames |
CSRF_TRUSTED_ORIGINS | Origins permitted to submit state-changing requests |
CORS_ALLOWED_ORIGINS | Client origins allowed by CORS |
FRONTEND_URL | Public URL of the client |
PORT | 8000 |
3.2 Datastores
| Variable | Purpose |
|---|---|
POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD | Database credentials |
DATABASE_URL | Full connection string |
REDIS_PASSWORD | Redis auth |
CELERY_BROKER_URL, CELERY_RESULT_BACKEND | Broker and result backend |
3.3 Licensing
PLUGGEDSPACE_LICENSE_ENGINE_URL=https://registry.pluggedspace.org
PLUGGEDSPACE_API_KEY=
PLUGGEDSPACE_DEPLOYMENT_ID=
PLUGGEDSPACE_LICENSE_TOKEN=
PLUGGEDSPACE_PUBLIC_KEY=
Never commit
.env. It is gitignored and is also excluded from release artifacts by the packaging script.
3.4 CORS
The Flutter client sends these headers on every request:
Authorization, Content-Type, Accept, X-Request-ID, X-Request-Timestamp,
X-Client-Platform, X-Client-Version
CORS_ALLOW_HEADERS must list all of them or browser preflights fail and the
client receives ERR_FAILED on every call. Note the singular ALLOW:
CORS_ALLOWED_HEADERS is silently ignored and the library falls back to a default
list that omits the custom headers.
4. The admin console
The console is served at /admin/. Sign in with the superuser created during
installation.
4.1 Home versus app pages
Two different page types appear in the admin, and they are easy to confuse:
| Page | What it is |
|---|---|
/admin/ - Home | The executive dashboard: total users, today's transaction volume, pending KYC queue, disputes and anomalies, platform status, and a quick-action launcher. |
/admin/<app>/ - an app page | A plain list of the models registered under that Django app (for example Users, Products, Transactions). Not a dashboard. |
Every app page carries a "← Back to Dashboard" link.
4.2 Enabling and disabling modules
Admin → App → Platform Feature & Module Toggles is a single record that controls
what the product exposes. Changes take effect immediately - the client fetches its
feature flags from /api/v1/config/features/ at start-up and on refresh.
| Toggle | Controls |
|---|---|
| Enable Shop & Utilities | Products, cart, airtime, gift cards, bill payment |
| Enable Investments & Stocks | Tokenized stock trading and portfolios |
| Enable Virtual IBAN Accounts (EUR/GBP) | Virtual IBAN generation |
| Enable African NUBAN Accounts | African virtual accounts and transfers |
| Enable Mobile Money (M-Pesa / MoMo) | Mobile money deposits and withdrawals |
| Enable KYC Requirements | Identity verification enforcement |
| Enable Dispute & Support System (Monica) | User-raised disputes |
| Enable Crypto Deposits & Swaps | Changelly-backed crypto flows |
| Enable Fiat On/Off-Ramps | Bank, Transak, Railsr transfers |
| Enable Points & Rewards System | Loyalty and referrals |
| Enforce 2FA on Login | OTP required at sign-in |
| Enforce Enhanced KYC Verification | External biometric/ID verification |
| Global Maintenance Mode | Puts non-admin API routes into maintenance |
| Maintenance Notice Message | Banner shown while maintenance is active |
The same record selects the active brokerage provider (Stellar DEX, Alpaca Markets, or Alpha Vantage) and whether fractional share trading is permitted.
4.3 Brokerage providers
The active brokerage is a single setting on the same Platform Feature & Module Toggles record: Active Stock Brokerage Provider.
| Provider | Instruments | Trading | Notes |
|---|---|---|---|
| Stellar DEX | Curated registry file | Yes | Tokenized assets, 24/7. Needs an issuer address per instrument. |
| Alpaca Markets | Synced from the provider | Yes | Real US equities, fractional shares, paper-trading by default. |
| Alpha Vantage | — | No | Market data only. Buy/Sell fail by design. |
Switching is admin-only — there is no API endpoint for it. Edit the record in Django Admin; the change applies immediately, because the client reads its feature flags and the brokerage engine resolves the provider per request.
Populating the instrument catalogue
Nothing is typed in by hand any more. Run:
docker compose exec web python manage.py sync_instruments
or pin a provider explicitly:
docker compose exec web python manage.py sync_instruments --provider alpaca
docker compose exec web python manage.py sync_instruments --provider stellar
docker compose exec web python manage.py sync_instruments --provider alpaca --limit 100
docker compose exec web python manage.py sync_instruments --provider alpaca --deactivate-missing
Alpaca populates itself from GET /v2/assets, so it is safe to schedule
(e.g. nightly via celery beat). Add credentials under
Provider & API Secret Configurations with provider alpaca, or set
ALPACA_API_KEY_ID / ALPACA_API_SECRET_KEY.
Stellar cannot be discovered live — the DEX has no asset-list endpoint and a
price lookup needs a (code, issuer) pair. Instruments come from
invest/data/stellar_instruments.json, which ships empty. Add the real issuer
public keys for the equities you intend to list, then re-run the command. Entries
without an issuer_address are skipped with a warning, because a price lookup
without one raises AssetIssuerInvalidError.
The same symbol can exist on both rails at once: instruments are keyed on
(provider, symbol), and the API only ever returns the active provider's rows.
Price refresh
docker compose exec web python manage.py update_stock_prices --verbose
Instruments with no cached price yet are populated on the first successful fetch. For Alpaca, order sizing uses a live quote at submit time rather than the cached price, so a stale cache cannot cause a spurious "insufficient balance" rejection.
If you do not want to curate a Stellar list, switch the active provider to Alpaca — it populates itself.
4.4 Branding
Admin → App → Organization & Branding Configurations sets the organisation name, legal entity, logo, favicon, three colour tokens, support contacts, and the Terms/Privacy URLs. These are database records, so a rebuild is never required.
Admin → App → Provider & API Secret Configurations stores third-party API credentials. Values are encrypted at rest and displayed masked.
5. Running the client
The Flutter client targets web, desktop, and mobile from one codebase.
cd FRONTEND/border
flutter run -d chrome --dart-define=BASE_URL=http://localhost:8000
Brand colours can be overridden at build time:
flutter run -d chrome \
--dart-define=BASE_URL=http://localhost:8000 \
--dart-define=PRIMARY_COLOR=0xFF1E3A8A \
--dart-define=ACCENT_COLOR=0xFF10B981
5.1 Navigation
The bottom navigation bar is assembled dynamically from the active module flags:
| Tab | Always present | Notes |
|---|---|---|
| Home | Yes | Balance, quick actions, recent activity |
| Banking | Yes | Cards, virtual accounts, IBAN details |
| Invest | Only when Investments is enabled | Portfolio and stock market |
| Shop | Only when Shop & Utilities is enabled | Catalogue, cart, orders, gift cards |
| Profile | Yes | KYC status, settings, security |
5.2 Screen walkthrough
- Home - total and local balance, Deposit / Withdraw / Send P2P / Banking quick actions, recent transactions, and a notification bell.
- Banking & Cards - virtual account and card presentation showing IBAN/SWIFT, bank name, and auto-convert settings.
- Invest - portfolio value, holdings, profit/loss, and the tokenized stock market with buy/sell.
- Shop - product catalogue with categories and search, cart, shipping and fees, checkout, and order history.
- Utilities - airtime, data bundles, combo bundles, gift cards, and bill payment, each scoped by country and provider.
- Profile - KYC status and submission, personal details, security settings, transaction PIN, and points/referrals.
Navigation uses the platform default back behaviour. On a detail page the platform shows a back affordance; on a root tab it does not, because there is nothing to pop.
6. API reference
Interactive documentation is generated on every node:
- Swagger UI -
http://localhost:8000/swagger/ - ReDoc -
http://localhost:8000/redoc/ - OpenAPI schema -
http://localhost:8000/swagger.json
6.1 Authentication
Obtain a token, then send it as a bearer credential:
curl -X POST http://localhost:8000/api/login/ \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"<password>"}'
curl http://localhost:8000/api/balance/ \
-H "Authorization: Bearer <access_token>"
6.2 Public configuration endpoint
GET /api/v1/config/ requires no authentication and intentionally returns no
secrets - only branding, enabled modules, and active banking rails. This is what the
client uses to configure itself.
6.3 Health endpoints
| Endpoint | Purpose |
|---|---|
/healthz | Basic liveness |
/livez | Liveness probe |
/readyz | Readiness probe |
/api/v1/node/ | Node identity and licence status |
7. Operating a deployment
7.1 Routine commands
docker compose ps # service health
docker compose logs -f web # web logs
docker compose logs -f celery_worker # background task logs
docker compose exec web python manage.py migrate # apply migrations
docker compose exec web python manage.py test # run the test suite
docker compose restart web # restart after config change
7.2 Backups
Admin → Backup schedules and records jobs; uploads are tracked alongside each job. Confirm that your storage credentials are valid before relying on the schedule, and restore into a scratch environment periodically to prove the backups are usable.
7.3 Scheduled tasks
celery_beat dispatches scheduled work from the database schedule, including periodic
account monitoring. Both Celery services run a broker ping health check rather than the
image's default HTTP probe, so docker compose ps reporting healthy reflects real
broker connectivity.
7.4 Before going to production
-
DEBUG=False -
DJANGO_SECRET_KEYandSERVER_JWT_KEYregenerated and unique - Database, Redis, and Celery passwords rotated away from defaults
- Real licence token and public key installed (
manage.py checkclean) - Superuser password rotated away from the initial value
-
ALLOWED_HOSTS,CSRF_TRUSTED_ORIGINS, andCORS_ALLOWED_ORIGINSset to real origins - All four custom client headers present in
CORS_ALLOW_HEADERS - Static assets collected at build time
- Backups scheduled and a restore rehearsed
- Provider credentials populated in Provider & API Secret Configurations
- Module toggles set to match what you intend to offer
8. Known gaps and beta features
These are known and deliberate. Plan around them rather than discovering them in production.
| Area | Current behaviour | Impact |
|---|---|---|
| Utility top-ups - airtime, data, bills | Requests validate and return pending_integration | No dispatch to live provider APIs. Treat as beta. |
| Gift cards | Catalogue endpoints work; fulfilment pending | Requires provider credentials |
| Shop checkout | Creates a pending order and empties the cart | Does not yet charge a wallet or reserve stock |
| Monica AI assistant | Requires a real GROQ_API_KEY | Unset, it returns "something went wrong" |
| Stellar instrument list | Ships empty in invest/data/stellar_instruments.json | Needs real Stellar issuer addresses, or switch to Alpaca which self-populates |
| Brokerage switching | Admin only, no API | Edit Platform Feature & Module Toggles |
Empty state is expected on a fresh install. The Shop catalogue, utility providers, and gift cards ship with no seed data, so those pages are blank until you create records in the admin.
9. Troubleshooting
9.1 Every write returns 403
The node is in read-only maintenance because the licence is missing or invalid.
Reads, /admin/, and health checks still work so you can diagnose it.
docker compose exec web python manage.py check
app.E001- the token is empty or still a placeholder such aspaste_your_...app.E002- the public key is missing or still the bundled default
A placeholder is the most common cause, because it looks like a credentials problem rather than a licensing one. There is no switch to bypass enforcement; install a valid licence.
9.2 Client calls fail with ERR_FAILED in the browser
A CORS preflight is being rejected. Check that CORS_ALLOW_HEADERS includes
x-client-platform and x-client-version in addition to x-request-id and
x-request-timestamp. See CORS.
9.3 Celery shows unhealthy
Should not occur on a stock deployment. The services use a broker ping rather than the image's HTTP probe. If it does, check Redis connectivity and that the broker URL matches the configured Redis password.
9.4 The invest or notifications screen reports a type error
A stale client build. Stop the running process with q and start it again - a hot
reload does not recompile the Dart kernel, so code changes will not take effect.
9.5 Background tasks are not running
docker compose logs --tail 50 celery_beat celery_worker
Confirm Redis is healthy and that CELERY_BROKER_URL carries the correct password.
10. Support and security
- Security vulnerabilities - report privately to
support@pluggedspace.org. Please do not open a public issue. SeeSECURITY.md. - Licensing and releases - the Pluggedspace registry at
https://registry.pluggedspace.org. - In-product support - the support email and phone configured under Organization & Branding Configurations are what your users see.
Related documentation
- BorderCash Overview - what the product is
- BorderCash Architecture & Subsystems - architecture and subsystem internals
README.md- repository quick start/swagger/on a running node - generated API reference