API Tiers & Documentation Visibility
Axiom Exchange publishes progressively more documentation the deeper you
go, rather than putting the entire reference behind a login. The moat is
the service, the data, the models, and the contractual access — not
whether a developer can read what GET /plans returns.
The four things kept separate
Authentication alone never determines what a request can do. Four concerns, evaluated in this order for every request:
Identity → Role → OAuth Scope → API Gateway → Usage Metering → API
A caller can be fully authenticated and still lack the scope to reach a
given endpoint — see AUTH_ARCHITECTURE.md.
Classification matrix
| API family | Documentation | API access | Real endpoints today |
|---|---|---|---|
| Product/catalog | Public | Free/key | GET /utilities, GET /plans |
| Tariffs/pricing | Public | Key/metered | GET /plans, GET /rates/{utility_id}/history |
| Address/serviceability | Public | Metered | GET /utilities/lookup, POST /geo/utilities/status |
| Forecasting | Public description | Metered | GET /usage/{id}/forecast |
| Customer/account | Authenticated docs | OAuth | /resellers, /customers, /cost-keys, /assets, /geo/* |
| Customer enrollment | Authenticated docs | OAuth | /enrollments |
| Meter/usage | Authenticated docs | OAuth | GET /usage/{id} |
| Trading/order execution | Private docs | Contract + OAuth | /exchange/transactions |
| Settlement | Private docs | Contract | (scope reserved: settlement:* — no dedicated endpoint yet) |
| DER/device control | Private docs | Partner OAuth | (scope reserved: der:* — no dedicated endpoint yet; der:read currently gates nothing) |
| Grid operations | Private docs | Contract | (scope reserved: grid:* — not yet implemented) |
| Market data | Public summary | Metered | (scope reserved: market:* — belongs to the Axiom Market/ETRM module, not Exchange) |
| Internal orchestration | Never public | Internal only | database schema, algorithm internals, infrastructure config |
Rows marked "reserved" are real scopes in the code today (see
app/core/scopes.py) with no route wired to them yet — declared now,
deliberately, so that when DER, grid, settlement, and market endpoints
ship, they slot into an existing authorization model instead of
triggering a second wave of retrofitting.
OpenAPI document split (target)
/openapi/public.json → catalog, tariffs, serviceability, forecast description
/openapi/developer.json → public + sandbox-only operations
/openapi/customer.json → developer + accounts, enrollment, meter/usage
/openapi/partner.json → customer + DER read, advanced market data
/openapi/trading.json → partner + trading, settlement
/internal/openapi.json → administration, orchestration, infrastructure (never public)
Current state: /openapi.json (and Swagger UI at /docs) is now
gated behind a real portal login (standard username/password, Twilio-
Verify-ready 2FA — see the Developer Portal) rather than served openly;
every operation's security block is correctly populated with its real
required scope (see main.py's custom_openapi()). That single gate is
coarser than the tiered split above — it's "logged in or not," not
"which tier does this operation belong to" — so splitting into the
actual /openapi/<tier>.json documents, each independently gated by
role, remains the next concrete step, tracked in TESTING.md.
Never document publicly
Regardless of tier: internal orchestration logic, pricing/forecasting
model internals, database schemas, infrastructure/deployment
configuration, and algorithm implementations. docs/DEPLOYMENT.md
has already moved off the public static docs site and is served only
through the login-gated GET /portal/docs/deployment endpoint — the
same treatment docs/AUTH_ARCHITECTURE.md received. docs/DATA_MODEL.md
remains public for now; it describes schema shape and rationale for
engineering onboarding, not deployment secrets or infrastructure
topology, which is the distinction worth re-checking if it grows to
include either.