Veritris Energy — AXIOM Exchange API Platform

An energy marketplace/exchange API, domain-modeled after the plan/utility/ usage/enrollment structure exposed by PowerHQ's public developer docs (powerhq.co, built on EnergyBot's backend), extended with:

This isn't just a design doc. Every endpoint has been run end-to-end against a live PostgreSQL/PostGIS instance, with five real bugs found and fixed along the way. See the Verification Log for the full details.

↗ Try the Territory Explorer — an interactive map demo driven by a snapshot of real seeded output: scope to a reseller, drag a box over any area, and see the customers and utility status inside it.

Documentation

Doc Covers
API Reference Every endpoint, with real captured request/response examples
ArcGIS Integration Wiring AXIOM into the ArcGIS JS API, Esri JSON conversion, publishing to ArcGIS Online
Data Model The tenancy model, why one generic assets table, why Postgres+PostGIS+JSONB
Deployment Local, on-prem, AWS, Azure, and Kong deployment, plus the environment variable reference
Verification Log What was actually verified, and the five bugs found + fixed while doing it

The tenancy model in one paragraph

A reseller is the account holder and the tenant boundary. A customer is deliberately not owned by a single reseller — it attaches to many, one row per relationship in customer_resellers, and the cost key lives on that relationship rather than on either side. The key defaults to the reseller's UUID so it always resolves back to a reseller row, and can be overridden with a reseller's own billing code. A property is an asset carrying reseller_id, customer_id, state, and the derived cost_key. One customer account can therefore hold many properties spread across many states, each attributable to whichever reseller services it. Reseller-scoped callers see only their own data — including through spatial queries, and including seeing only their own side of a customer shared with a competitor.

Why this stack

Requirement Choice Why
Structured + unstructured data PostgreSQL 16 + PostGIS + JSONB One database instead of two. Relational tables sit next to a JSONB attributes column on every table for whatever unstructured fields a record needs — no schema migration per new field. Same flexibility Cosmos DB/DynamoDB give you, without giving up joins, transactions, and geospatial indexing.
Geospatial polygons for ArcGIS PostGIS + GeoAlchemy2 + Shapely Native ST_Intersects/ST_DWithin/ST_Contains; output as RFC 7946 GeoJSON (consumed directly by the ArcGIS JS API's GeoJSONLayer) or Esri JSON for ArcGIS REST/Runtime clients.
Portable to Aurora / managed cloud DB Aurora PostgreSQL or Azure Database for PostgreSQL Both wire-compatible with vanilla PostgreSQL and both support PostGIS as a managed extension — AXIOM_DATABASE_URL is the only thing that changes.
Load balancing nginx In front of N stateless API replicas locally, or inside the cluster/VPC in the cloud.
Pluggable into an API Gateway Stateless JWT/API-key auth + OpenAPI 3 spec No session state, so any number of instances can sit behind AWS API Gateway, Azure APIM, or Kong. Reseller scope travels in the JWT claim or the X-Axiom-Reseller-Id header.

Architecture

ArcGIS Online / ArcGIS JS API (GeoJSONLayer) ─┐
Web / mobile clients ─────────────────────────┼──▶ API Gateway (optional)
Service-to-service callers ───────────────────┘        (TLS, auth, quota, WAF)
                                                                │
                                                                ▼
                                                        nginx (load balancer)
                                                          │              │
                                                          ▼              ▼
                                                     api1 (FastAPI)  api2 (FastAPI)
                                                          │              │
                                                          └──────┬───────┘
                                                                 ▼
                                                PostgreSQL + PostGIS (+ JSONB)
                                                Redis (cache / rate-limit state)

Run it locally

cp .env.example .env        # edit AXIOM_JWT_SECRET at minimum
docker compose up --build

Try the reseller + spatial endpoints

K='X-API-Key: axiom-local-dev-key'

# Every cost key, the reseller it bills to, and what it covers:
curl http://localhost:8080/api/v1/cost-keys -H "$K"

# One customer's full reseller attachment list (the one-to-many view):
curl http://localhost:8080/api/v1/customers/<customer_id> -H "$K"

# Customers holding properties inside an area:
curl -X POST http://localhost:8080/api/v1/geo/customers/search -H "$K" \
  -H "Content-Type: application/json" \
  -d '{"geometry":{"type":"Polygon","coordinates":[[[-98.2,29.9],[-96.4,29.9],[-96.4,33.3],[-98.2,33.3],[-98.2,29.9]]]}}'

# Utility service status across that same area:
curl -X POST http://localhost:8080/api/v1/geo/utilities/status -H "$K" \
  -H "Content-Type: application/json" \
  -d '{"geometry":{"type":"Polygon","coordinates":[[[-98.2,29.9],[-96.4,29.9],[-96.4,33.3],[-98.2,33.3],[-98.2,29.9]]]}}'

# A reseller-scoped token — every call above then filters to that reseller:
curl -X POST http://localhost:8080/api/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{"api_key":"axiom-local-dev-key","reseller_id":"<reseller_uuid>"}'

Every geo endpoint accepts ?format=geojson (default) or ?format=esri.

Scaling out

Notes on the domain model