Veritris Axiom — Exchange API Reference

Base URL (local, via nginx): http://localhost:8080/api/v1 Base URL (local, direct to a single replica, for interactive docs): http://localhost:8000 → Swagger UI at /docs, ReDoc at /redoc, raw spec at /openapi.json

Every endpoint below was exercised against a live PostgreSQL/PostGIS instance while writing this document; request/response bodies shown are real captured output, not hand-written examples.

Table of contents

  1. Authentication & authorization
  2. Where Exchange fits
  3. Conventions
  4. Health
  5. Resellers & customers (tenancy)
  6. Assets / properties
  7. Geo queries
  8. Utilities
  9. Plans
  10. Rate history
  11. Usage
  12. Enrollments
  13. Exchange transactions
  14. Query registry
  15. SDKs
  16. Error format
  17. Contact

See also: API_TIERS.md (documentation/access classification), AUTH_ARCHITECTURE.md (full scope model and metering), PRICING.md, LICENSING.md.

Where Exchange fits

Axiom Exchange is one module of the Veritris Axiom energy platform — the connected layer for energy data, transactions, partners, developers, and API-first infrastructure. It is not the whole platform.

Veritris  (master brand)
├── VTT ......................... communications / network services
├── Veritris Technologies ....... IP ownership and licensing
├── StrataLink + SecureWave ..... defense / critical-infrastructure resilience
└── Veritris Energy ............. energy operating brand
    ├── OEPC .................... Oklahoma Electric Power Company (utility-facing vehicle)
    └── Veritris Axiom .......... modular energy platform
        ├── Grid, Market/ETRM, Forecast
        ├── Retail/CRM, eMobility
        ├── DER, Resilience
        └── Exchange  ← this API

Sibling Axiom modules (Grid, Market/ETRM, Forecast, DER, Retail/CRM, Resilience, eMobility, and the AI-assisted Orchestrate workflow layer) are out of scope for this API. Exchange is deliberately the integration and transaction surface the others — and third parties — call into.

Positioning constraints

These are hard constraints on how this API and its docs describe the business, not stylistic preferences:


Authentication & authorization

Every route except /healthz, /readyz, /, /auth/token, and /contact requires one of:

Method Header Notes
API key X-API-Key: <key> sk_test_... = sandbox, sk_live_... = production. Configured via AXIOM_API_KEYS. Simplest for service-to-service and sandbox use.
Bearer JWT Authorization: Bearer <token> Obtained from POST /auth/token (or a real OAuth2/OIDC provider once federated — see AUTH_ARCHITECTURE.md). Expires after AXIOM_JWT_EXPIRE_MINUTES (default 60).
Gateway identity X-Axiom-Gateway-Identity + X-Axiom-Gateway-Secret Set by a trusted API Gateway that has already authenticated the caller — see DEPLOYMENT.md. Bypasses scope checks entirely; only for internal/trusted service traffic.

Authentication alone is not authorization. Every operation additionally requires a specific OAuth-style scope — visible per-operation in the security block of /openapi.json, and enforced identically whether the caller used a JWT, an API key, or (not yet, but by design) an OIDC token. Full scope list, role bundles, and the metering that fires on every authorized call: AUTH_ARCHITECTURE.md. Which operations are public vs. gated behind partner/trading access: API_TIERS.md.

No credentials:

GET /api/v1/assets

401 Unauthorized
{"detail":"Missing credentials. Provide Authorization: Bearer <jwt> or X-API-Key."}

Get a token, resolving scopes from a role's default bundle:

POST /api/v1/auth/token
Content-Type: application/json

{"api_key": "sk_test_local_dev_0001", "role": "developer"}
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in_minutes": 60,
  "role": "developer",
  "environment": "sandbox",
  "scopes": ["catalog:read", "tariffs:read", "market:read", "serviceability:read", "forecast:read", "quote:read"]
}

Explicit scopes override the role's bundle when you need a narrower grant than the role's ceiling:

POST /api/v1/auth/token
{"api_key": "sk_test_local_dev_0001", "role": "partner", "scopes": ["customer:read", "meter:read"]}

Reseller-scoped token — pass reseller_id and every tenant-aware endpoint automatically filters to that reseller. Omit for a platform-level token that sees across all resellers:

POST /api/v1/auth/token
{"api_key": "sk_test_local_dev_0001", "role": "customer", "reseller_id": "e45961fe-f23c-4366-8330-543c2bd6ab42"}

Privileged scopes require production, verified in this session: a trading_partner token issued with "environment": "sandbox" requesting POST /exchange/transactions (which needs trading:execute) is rejected —

{"detail": "trading:execute is a privileged scope and requires a production credential (this request authenticated as environment=sandbox)."}

— regardless of the role's default bundle including that scope. Set "environment": "production" to actually exercise a privileged scope. Privileged scopes: trading:execute, der:control, settlement:write, grid:control, admin.


Conventions


Health

GET /healthz

Liveness probe. No auth required. Always returns {"status": "ok"} if the process is running — used by nginx/orchestrators to know the container is alive.

GET /readyz

Readiness probe. Runs SELECT 1 against the database. Returns 503-equivalent failure (connection error) if the DB is unreachable, so a load balancer can pull the instance out of rotation.


Resellers & customers (tenancy)

The account model: a reseller is the tenant and the account holder; a customer belongs to many resellers; a property is an asset owned by a customer and serviced by one of those resellers.

The customer↔reseller attachment is a first-class record because the cost key lives on the relationship, not on either side.

POST /resellers

{"code": "LONESTAR-RETAIL", "name": "Lone Star Retail Energy", "operating_states": ["TX", "NM"]}

201 Createdoperating_states is enforced: attaching a customer in a state the reseller doesn't operate in is rejected with 422.

GET /resellers

Optional state filter. A reseller-scoped caller sees only itself.

GET /resellers/{id} · PUT /resellers/{id}

GET /resellers/{id}/customers

Every customer attached to this reseller. Optional state.

GET /resellers/{id}/properties

Every property this reseller services, across all states. Optional state.

POST /customers

{"name": "Brazos Property Holdings", "email": "ap@brazos.example", "external_ref": "CUST-4471"}

A customer is deliberately not owned by a single reseller.

GET /customers

Optional reseller_id, state. A reseller-scoped caller sees only customers attached to it.

GET /customers/{id} — the one-to-many view

Returns the customer plus every reseller attached to it:

{
  "customer": {"id": "...", "name": "Brazos Property Holdings", "external_ref": "CUST-4471", "status": "active"},
  "resellers": [
    {"reseller_id": "e45961fe-...", "cost_key": "e45961fe-f23c-4366-8330-543c2bd6ab42", "state": "TX", "is_primary": true},
    {"reseller_id": "8c609aef-...", "cost_key": "SOONER-COST-882", "state": "OK", "is_primary": false},
    {"reseller_id": "e45961fe-...", "cost_key": "e45961fe-f23c-4366-8330-543c2bd6ab42", "state": "NM", "is_primary": false}
  ]
}

A reseller-scoped caller gets the same customer but only its own attachment rows — it never learns which other resellers share the customer.

POST /customers/{id}/resellers — attach (creates the cost key)

{"reseller_id": "8c609aef-...", "state": "OK", "cost_key": "SOONER-COST-882", "is_primary": false}

GET /customers/{id}/resellers

The attachment list. Reseller-scoped callers see only their own row.

POST /customers/{id}/resellers/{link_id}/primary

Promotes one attachment to primary and demotes the incumbent in the same transaction. At most one primary per customer is enforced twice: the service layer demotes before promoting, and a partial unique index (uq_customer_one_primary_reseller ON customer_resellers (customer_id) WHERE is_primary) makes two primaries impossible even under concurrent writes.

204. Detaching does not delete the customer.

GET /cost-keys — cost attribution rollup

The reverse lookup billing needs: given a cost key, which reseller does it bill to and what is it currently carrying.

[
  {
    "cost_key": "SOONER-COST-882",
    "reseller_id": "8c609aef-...", "reseller_code": "SOONER-POWER", "reseller_name": "Sooner Power Partners",
    "customer_count": 1, "property_count": 1, "states": ["OK"]
  },
  {
    "cost_key": "e45961fe-f23c-4366-8330-543c2bd6ab42",
    "reseller_id": "e45961fe-...", "reseller_code": "LONESTAR-RETAIL", "reseller_name": "Lone Star Retail Energy",
    "customer_count": 1, "property_count": 2, "states": ["NM", "TX"]
  }
]

Reseller-scoped callers see only their own keys.


Assets / properties

The generic geospatial registry. One asset type, discriminated by asset_type, backs every kind of spatial thing in the platform — polygons, lines, or points.

POST /assets — create any type of asset

POST /api/v1/assets
X-API-Key: axiom-local-dev-key
Content-Type: application/json

{
  "organization_id": "veritris-energy",
  "asset_type": "well_pad",
  "name": "Permian Well Pad 12",
  "geometry": {
    "type": "Polygon",
    "coordinates": [[[-102.1,31.9],[-102.09,31.9],[-102.09,31.91],[-102.1,31.91],[-102.1,31.9]]]
  },
  "attributes": {"api_number": "42-135-12345", "operator": "Veritris Energy"}
}

201 Created:

{
  "id": "eb491174-4e09-410b-bf6b-545f6321ad36",
  "organization_id": "veritris-energy",
  "asset_type": "well_pad",
  "name": "Permian Well Pad 12",
  "status": "active",
  "external_ref": null,
  "description": null,
  "attributes": {"operator": "Veritris Energy", "api_number": "42-135-12345"},
  "created_at": "2026-08-21T21:18:55.521612Z",
  "updated_at": "2026-08-21T21:18:55.521612Z"
}

Tenancy fields. reseller_id, customer_id, and state make an asset a property attributable to a reseller relationship. The cost_key is derived, not trusted — the API resolves it from the customer's attachment to that reseller for that state, so a property can never be stamped with a key that doesn't correspond to a real relationship. If no attachment covers the property's state, the request fails with 422 naming the states that are covered. A reseller-scoped token supplies reseller_id implicitly.

GET /assets additionally filters on reseller_id, customer_id, state, and cost_key.

geometry accepts any standard GeoJSON geometry type — Point, LineString, Polygon, MultiPolygon, etc. Use a Point for a meter/premise, a LineString for a pipeline or feeder segment, a Polygon/MultiPolygon for a lease, farm, or territory.

Note: this endpoint (and PUT) does not return the geometry itself — fetch it separately via GET /assets/{id}/geometry, which is the endpoint built for map rendering.

GET /assets — list / filter

Query params: organization_id, asset_type, status, limit (default 100, max 1000), offset.

GET /api/v1/assets?asset_type=solar_farm
[{
  "id": "5d8f9269-4878-4dea-a252-f56725708536",
  "organization_id": "veritris-energy",
  "asset_type": "solar_farm",
  "name": "Lone Star Solar Array 1",
  "status": "active",
  "external_ref": "LSSA-001",
  "description": "Demo utility-scale solar generation asset",
  "attributes": {"operator": "Veritris Energy", "capacity_mw": 85.5, "commissioned": "2024-06-01"},
  "created_at": "2026-08-21T21:16:12.498872Z",
  "updated_at": "2026-08-21T21:16:12.498872Z"
}]

GET /assets/{id} — single asset, non-spatial fields

Returns the same shape as above for one asset. 404 if not found.

GET /assets/{id}/geometrymap-ready geometry (the ArcGIS-facing endpoint)

Query param: format=geojson (default) | format=esri.

GET /api/v1/assets/5d8f9269-4878-4dea-a252-f56725708536/geometry
{
  "type": "Feature",
  "id": "5d8f9269-4878-4dea-a252-f56725708536",
  "geometry": {
    "type": "Polygon",
    "coordinates": [[[-97.8,30.2],[-97.78,30.2],[-97.78,30.22],[-97.8,30.22],[-97.8,30.2]]]
  },
  "properties": {
    "id": "5d8f9269-4878-4dea-a252-f56725708536",
    "organization_id": "veritris-energy",
    "asset_type": "solar_farm",
    "name": "Lone Star Solar Array 1",
    "status": "active",
    "external_ref": "LSSA-001",
    "description": "Demo utility-scale solar generation asset",
    "operator": "Veritris Energy",
    "capacity_mw": 85.5,
    "commissioned": "2024-06-01"
  }
}
GET /api/v1/assets/5d8f9269-4878-4dea-a252-f56725708536/geometry?format=esri
{
  "geometry": {
    "rings": [[[-97.8,30.2],[-97.78,30.2],[-97.78,30.22],[-97.8,30.22],[-97.8,30.2]]],
    "spatialReference": {"wkid": 4326}
  },
  "attributes": {
    "id": "5d8f9269-4878-4dea-a252-f56725708536",
    "organization_id": "veritris-energy",
    "asset_type": "solar_farm",
    "name": "Lone Star Solar Array 1",
    "status": "active",
    "external_ref": "LSSA-001",
    "operator": "Veritris Energy",
    "capacity_mw": 85.5,
    "commissioned": "2024-06-01"
  }
}

See ARCGIS_INTEGRATION.md for how to point an ArcGIS JS API map layer directly at this endpoint.

PUT /assets/{id} — partial update

Any subset of name, status, description, geometry, attributes. Only fields present in the body are changed.

PUT /api/v1/assets/eb491174-4e09-410b-bf6b-545f6321ad36
{"status": "under_review"}
{"id":"eb491174-...","status":"under_review", "...": "..."}

DELETE /assets/{id}

204 No Content on success, 404 if the asset doesn't exist.


Geo queries

The spatial-join layer — "what's here" and "what's near this."

POST /geo/query — arbitrary polygon intersection

Body: a GeoJSON geometry (typically a polygon drawn or exported from ArcGIS), plus optional asset_types, organization_id, limit (default 500, max 5000). Query param format=geojson|esri.

POST /api/v1/geo/query
{
  "geometry": {"type": "Polygon", "coordinates": [[[-97.9,30.15],[-97.6,30.15],[-97.6,30.45],[-97.9,30.45],[-97.9,30.15]]]}
}

Returns a FeatureCollection of every asset — regardless of its own geometry type — that intersects the supplied polygon. In testing, a query polygon around a demo solar farm and a demo premise correctly returned both the Polygon asset and the Point asset in one response.

POST /geo/query/radius — proximity search

Body: lon, lat, radius_meters (max 200,000), optional asset_types, limit.

POST /api/v1/geo/query/radius
{"lon": -97.7431, "lat": 30.2672, "radius_meters": 5000}

Uses a true geodesic distance calculation (geography cast in PostGIS), not a flat-earth approximation, so it stays accurate at any latitude.

GET /geo/bbox — map viewport load

Query params: min_lon, min_lat, max_lon, max_lat, optional asset_type, organization_id, limit (max 5000), format.

GET /api/v1/geo/bbox?min_lon=-98&min_lat=30&max_lon=-97.4&max_lat=30.6

The standard "load whatever's currently on screen" call — pass the map's current extent (as most JS map SDKs, including the ArcGIS JS API's view.extent, expose it).

POST /geo/customers/search — spatial customer search

Given any GeoJSON area, returns the customers holding properties inside it, each with the properties found, the servicing reseller, and the cost key they bill to. This is the reseller-facing "who do I have in this territory" query.

{
  "property_count": 4,
  "customers": [
    {
      "customer_id": "81b1a684-...", "customer_name": "Brazos Property Holdings",
      "external_ref": "CUST-4471", "states": ["TX"], "property_count": 2,
      "properties": [
        {"id": "...", "name": "Austin Distribution Center", "asset_type": "commercial_property",
         "state": "TX", "cost_key": "e45961fe-...", "reseller_code": "LONESTAR-RETAIL"}
      ]
    }
  ]
}

POST /geo/utilities/status — utility status across an area

Every utility territory intersecting the area, with its current service status and how many in-scope properties sit inside it.

{
  "utilities": [
    {"code": "CTX-DEMO", "name": "Central Texas Demo Utility", "state": "TX",
     "service_status": "degraded", "affected_property_count": 3,
     "affected_properties": [{"id": "...", "name": "Austin Distribution Center"}]},
    {"code": "NORTHTX-DEMO", "name": "North Texas Demo Delivery", "state": "TX",
     "service_status": "operational", "affected_property_count": 1, "affected_properties": []}
  ]
}

Both endpoints are tenant-scoped: a reseller drawing a polygon over three states gets back only its own customers and only its own affected properties.


Utilities

GET /utilities

Optional state filter (2-letter code).

GET /utilities/lookup — point-in-polygon utility lookup

GET /api/v1/utilities/lookup?lon=-97.74&lat=30.27
{
  "utility": {
    "id": "30d0c50b-d60d-43db-9261-b0020094a8e1",
    "code": "DEMO-UTIL-TX",
    "name": "Central Texas Demo Utility",
    "state": "TX",
    "attributes": {"deregulated": true, "regulatory_body": "PUCT"}
  },
  "matched_by": "point_in_territory"
}

If no territory contains the point: {"utility": null, "matched_by": "not_found"}.

This is the geospatial equivalent of PowerHQ's "find the utility for this zip/address" lookup — instead of a zip-code table, it's a true polygon boundary, so it works at any resolution/shape a real utility territory actually has.

GET /utilities/{id}

Single utility by ID. 404 if not found.


Plans

GET /plans

Optional utility_id, is_business filters.

[{
  "id": "690c27f5-c734-49c3-8c18-02f5ce96ba08",
  "utility_id": "30d0c50b-d60d-43db-9261-b0020094a8e1",
  "name": "AXIOM Fixed 12",
  "rate_type": "fixed",
  "rate_cents_per_kwh": 11.4,
  "term_months": 12,
  "is_business": false,
  "signup_url": "https://example.com/signup/axiom-fixed-12",
  "effective_date": "2026-08-21",
  "expiration_date": null
}]

GET /plans/{id}


Rate history

GET /rates/{utility_id}/history

Optional area_code, since (date), limit.

[{"area_code": "78701", "rate_cents_per_kwh": 12.1, "recorded_at": "2026-07-22"}]

Usage

GET /usage/me — the calling credential's own usage

Powers the developer portal dashboard. Requires only a valid credential, not a specific scope — a usage-introspection call isn't itself a billable product call. Aggregates real usage_events by billing code:

{
  "subject": "apikey:sk_test_loca",
  "role": "developer",
  "environment": "sandbox",
  "since_hours": 720,
  "total_calls": 7,
  "estimated_total_cost_usd": 0.015,
  "by_billing_code": [
    {"billing_code": "BASIC_CALL", "calls": 5, "units": 5.0, "reference_price_usd": 0.001, "estimated_cost_usd": 0.005},
    {"billing_code": "ADDRESS_SERVICEABILITY", "calls": 2, "units": 2.0, "reference_price_usd": 0.005, "estimated_cost_usd": 0.01}
  ],
  "recent_calls": [
    {"endpoint": "/api/v1/plans", "method": "GET", "scope": "tariffs:read", "billing_code": "BASIC_CALL", "status_code": 200, "occurred_at": "2026-08-22T20:39:51.350297+00:00"}
  ]
}

Optional since_hours (default 720 = 30 days, max 8760 = 1 year).

GET /usage/{premise_asset_id}

premise_asset_id is the id of any asset with asset_type='premise'. Returns period/kWh history, most recent first.

[
  {"period_start": "2026-07-21", "period_end": "2026-08-21", "kwh": 815.0},
  {"period_start": "2026-06-21", "period_end": "2026-07-21", "kwh": 830.0}
]

GET /usage/{premise_asset_id}/forecast

Query param periods_ahead (default 3, max 24). Returns a naive trailing-average projection — replace usage_forecast() in app/api/v1/routers/usage.py with a real forecasting model as volume grows; the response contract (period_start, period_end, projected_kwh) is deliberately model-agnostic.

[
  {"period_start": "2026-08-21", "period_end": "2026-09-21", "projected_kwh": 837.5}
]

Enrollments

POST /enrollments

{
  "plan_id": "690c27f5-c734-49c3-8c18-02f5ce96ba08",
  "premise_asset_id": "b833da92-f92b-4fdb-a1f3-0f82fab6d536",
  "customer_name": "Jordan Rivera",
  "customer_email": "jordan@example.com"
}

201 Created, status: "pending", enrolled_at set to submission time.

GET /enrollments/{id}


Exchange transactions

Nominations, trades, and settlements — optionally tied back to a physical asset via asset_id, so a trade can be reconciled against the generation asset or delivery point it actually corresponds to.

POST /exchange/transactions

{
  "counterparty_id": "4facce94-386a-48f7-bd98-1a873869f711",
  "asset_id": "5d8f9269-4878-4dea-a252-f56725708536",
  "transaction_type": "trade",
  "volume_mwh": 250.5,
  "price_per_mwh": 34.20,
  "delivery_start": "2026-09-01T00:00:00Z",
  "delivery_end": "2026-09-02T00:00:00Z"
}

transaction_type must be one of nomination, trade, settlement. 201 Created, status: "pending".

GET /exchange/transactions

Filters: counterparty_id, transaction_type, status, asset_id. Filtering by asset_id returns every transaction reconciled against a given physical asset — e.g. every trade settled against a specific solar farm.

GET /exchange/transactions/{id}


SDKs

Thin, unopinionated clients — no caching, no business logic, no duplicated authorization. Every call still hits the authenticated, metered API; possessing or redistributing an SDK confers no API access on its own. MIT-licensed, source in sdks/ in this repository.

Language Package Import
Python sdks/python (pyproject.toml) from axiom_exchange import AxiomExchangeClient
JavaScript / TypeScript sdks/javascript (package.json) import { AxiomExchangeClient } from "@veritris/axiom-exchange"
PHP sdks/php (composer.json) use Veritris\AxiomExchange\AxiomExchangeClient;
from axiom_exchange import AxiomExchangeClient

client = AxiomExchangeClient(api_key="sk_test_...")
plans = client.plans.list()
lookup = client.utilities.lookup(lon=-97.74, lat=30.27)
import { AxiomExchangeClient } from "@veritris/axiom-exchange";

const client = new AxiomExchangeClient({ apiKey: "sk_test_..." });
const plans = await client.plans.list();
$client = new Veritris\AxiomExchange\AxiomExchangeClient(apiKey: 'sk_test_...');
$plans = $client->plans()->list();

Each client accepts either api_key/apiKey (sandbox/production static key) or access_token/accessToken (a Bearer JWT — from POST /auth/token today, from OAuth2 Authorization Code + PKCE once OIDC is live, with no client-code change required either way).


Error format

Validation errors (422) follow FastAPI/Pydantic's standard shape:

{
  "detail": [
    {
      "type": "uuid_parsing",
      "loc": ["body", "counterparty_id"],
      "msg": "Input should be a valid UUID, invalid length: expected length 32 for simple format, found 0",
      "input": ""
    }
  ]
}

Auth failures (401/403) and not-found (404) return {"detail": "<message>"}.


Query registry

Saved queries live in a separate database from the parcel/asset inventory — its own engine, pool, and connection string (AXIOM_QUERY_DATABASE_URL). Different retention, different write profile, independent blast radius. No foreign keys cross the boundary; reseller_id and asset ids are stored as opaque values.

POST /queries — instantiate and get a query_id

{
  "endpoint": "/geo/customers/search",
  "label": "TX territory sweep",
  "request_params": {"geometry": {"type": "Polygon", "coordinates": [[[-98.2,29.9],[-96.4,29.9],[-96.4,33.3],[-98.2,33.3],[-98.2,29.9]]]}},
  "is_stateful": false,
  "ttl_days": 90
}

201 Created returns a short, quotable handle:

{"query_id": "qry_cd0bd1b079fd", "label": "TX territory sweep", "endpoint": "/geo/customers/search",
 "is_stateful": false, "replay_count": 0, "created_by": "apikey:axiom-lo"}

Stateful vs stateless, chosen per request:

Supplying a response_snapshot without is_stateful returns 422.

endpoint must be one of an allowlist (/geo/query, /geo/query/radius, /geo/bbox, /geo/customers/search, /geo/utilities/status, /assets, /cost-keys). Anything else returns 422 — a saved query must never become a generic server-side request forwarder.

GET /queries · GET /queries/{query_id}

List (filters: endpoint, is_stateful, include_expired) or fetch one. Fetching a stateful query returns its frozen response_snapshot. An expired query returns 410 with its expiry timestamp. Reseller-scoped callers only see their own queries; another reseller's returns 403.

POST /queries/{query_id}/replay

Re-executes the stored request against current inventory and increments replay_count. For a stateful query this deliberately does not overwrite the frozen snapshot — the response reports both so you can diff then-vs-now.

DELETE /queries/{query_id}

204.


Contact

Veritris Group, Inc.veritris.com

Email energy@veritris.com
Phone +1 580-713-4927
Address 2828 NW 57th Street #207, Oklahoma City, OK 73112
Contact page veritris.com/lawton-managed-msp-contact
Client / support portal veritris.com/clients
Veritris Energy veritris.energy
Axiom platform veritrisaxiom.energy

Also available programmatically at GET /api/v1/contact (unauthenticated).

GET /contact

Unauthenticated. Returns the same details as JSON for programmatic use.

{
  "organization": "Veritris Group, Inc.",
  "product": "Veritris Axiom — Exchange",
  "platform": "Veritris Axiom",
  "module_of": "Veritris Energy",
  "version": "0.1.0",
  "website": "https://www.veritris.com",
  "energy_site": "https://www.veritris.energy",
  "platform_site": "https://www.veritrisaxiom.energy",
  "contact_page": "https://www.veritris.com/lawton-managed-msp-contact/",
  "support_url": "https://www.veritris.com/clients",
  "phone": "+1-580-713-4927",
  "support_hours": "24/7",
  "address": {
    "street": "2828 NW 57th Street #207",
    "city": "Oklahoma City", "state": "OK", "postal_code": "73112", "country": "US"
  },
  "email": "energy@veritris.com"
}