Skip to main content
The Nomos API uses OAuth 2.0. You’ll need a client_id and client_secret issued to your organization. Keep the secret server-side. Most integrations use the Client Credentials flow for server-to-server access against your own organization’s data. If your app instead acts on behalf of a customer (for example, a HEMS provider reading prices for a specific subscription), use the Authorization Code flow.

Client Credentials Grant

Server-to-server flow against your own organization’s data (RFC 6749 §4.4). No customer interaction is involved.

Request a token

curl -X POST https://api.nomos.energy/oauth/token \
  -u "${CLIENT_ID}:${CLIENT_SECRET}" \
  -d grant_type=client_credentials
/oauth/token responds with:
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "21cc84a3ad98736f4e5eddc88a1f4b58a29ae96206027c9b59d874cb2a7f7e02",
  "scope": "read:* write:*"
}
The scope field is a space-delimited list of scopes granted on the token, mirroring the standard OAuth 2.0 scope claim. See Scopes for what each value allows.

Use the access token

Send it in the Authorization header on every API request:
curl https://api.nomos.energy/subscriptions \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"

Refresh

Access tokens last 60 minutes. When yours expires, exchange the refresh token at /oauth/token for a new pair:
curl -X POST https://api.nomos.energy/oauth/token \
  -u "${CLIENT_ID}:${CLIENT_SECRET}" \
  -d grant_type=refresh_token \
  -d refresh_token=${REFRESH_TOKEN}

Authorization Code Grant

If your app acts on a customer’s behalf instead of running server-to-server, use this flow (RFC 6749 §4.1). The token response, header usage, and refresh model are identical to Client Credentials; only the way you obtain the initial token differs. Your callback URI must be registered against your client_id; subpaths under the registered origin are accepted.
1

Redirect the customer to /oauth/authorize

https://api.nomos.energy/oauth/authorize?
  client_id=${CLIENT_ID}&
  response_type=code&
  redirect_uri=${REDIRECT_URI}&
  state=${STATE}
state is a per-request opaque value you generate, store, and verify on callback (CSRF protection).
For PKCE, also send code_challenge and code_challenge_method (use S256). Public clients (no client_secret) must use PKCE.
While the redirect is open, the customer signs in and grants consent on Nomos-hosted screens. See the third-party login guide for the customer journey.
2

Handle the callback

Nomos redirects with a single-use code (10-minute TTL) and your state. Reject the callback if state doesn’t match.
https://your-app.com/callback?
  code=${AUTHORIZATION_CODE}&
  state=${STATE}
3

Exchange the code at /oauth/token

curl -X POST https://api.nomos.energy/oauth/token \
  -u "${CLIENT_ID}:${CLIENT_SECRET}" \
  -d grant_type=authorization_code \
  -d code=${AUTHORIZATION_CODE} \
  -d redirect_uri=${REDIRECT_URI}
For PKCE, also send code_verifier.

PKCE

PKCE (Proof Key for Code Exchange) hardens the flow against code interception (RFC 7636). Prefer S256 over plain.
1

Generate a verifier and challenge

Pick a random code_verifier of 43–128 characters from [A-Za-z0-9-._~]. With S256, the code_challenge is the base64url-encoded SHA-256 of the verifier; with plain, it’s the verifier itself.
2

Send the challenge on /oauth/authorize

Add code_challenge and code_challenge_method to the authorize URL.
3

Send the verifier on /oauth/token

Include the original code_verifier in the token request. Nomos validates it against the challenge.

Scopes

Each API key is issued with one or more scopes. Two scopes are defined today:
ScopeAllows
read:*All GET requests across the API.
write:*All non-GET requests (POST, PATCH, PUT, DELETE).
A key with both scopes (read:* write:*) is full-access, the default. A read-only key carries only read:* and is rejected with 403 FORBIDDEN on any mutating request. Choose read-only when issuing a key for a programmatic agent, ETL job, or BI consumer that should never mutate state. Scopes are set when the key is created and cannot be changed later. To switch a key from full access to read-only (or vice versa), create a new key and rotate. The scope field is * because future iterations may introduce resource-named scopes (read:invoice, write:subscription) that narrow access further. Keys with the wildcard scopes continue to behave as supersets.
Auth failures return 401 UNAUTHORIZED with the standard error envelope. Scope failures return 403 FORBIDDEN. See Errors for the response shape and common causes.