Developers

Sign in with Synergy Login

Reward players whose devices run on green electricity. Synergy Login is standard OAuth 2.0 (authorization code + PKCE): your app sends the user to Umweltify, they approve in ThingsApp on their device, and you receive a signed token that says how green that device is — never the device id or any personal data.

Build with Synergy OS

1. Register your app

In Creator Studio → Synergy Login, register a client:

Your appClient typeYou get
Has a backend that can keep a secretConfidentialclient id + secret
Browser game, desktop or mobile app without a backendPublicclient id (PKCE only)

Redirect URIs match exactly: https only, except http://127.0.0.1:{port}/… for desktop apps (register the exact port) and custom schemes for mobile apps.

2. Endpoints

Authorizehttps://api.umweltify.com/connect/authorize
Tokenhttps://api.umweltify.com/connect/token
Revokehttps://api.umweltify.com/connect/revoke
JWKShttps://api.umweltify.com/.well-known/jwks
Discoveryhttps://api.umweltify.com/.well-known/openid-configuration
Current statusGET https://api.umweltify.com/v4/partner/climate-status
Live statushttps://api.umweltify.com/hubs/climate-status (SignalR)

Scopes: climate_status (required) and offline_access (refresh tokens). PKCE with S256 is required for every client.

3. The flow

1. Your app → browser: https://api.umweltify.com/connect/authorize
     ?client_id=…&redirect_uri=…&response_type=code
     &scope=climate_status%20offline_access&state=…
     &code_challenge=…&code_challenge_method=S256
2. Umweltify shows a waiting page and opens ThingsApp on the user's computer.
3. The user approves your app (by its registered name) in ThingsApp.
4. The browser goes to redirect_uri?code=…&state=…      (check state!)
   — or redirect_uri?error=access_denied&state=… if they declined.
5. POST /connect/token  grant_type=authorization_code&code=…&redirect_uri=…
     &client_id=…[&client_secret=…]&code_verifier=…
   ← { access_token, refresh_token, expires_in: 3600 }

Users can disconnect your app any time in ThingsApp → About → Connected apps.

4. The access token

An ES256 JWT, valid for one hour. Verify it against the JWKS (issuer https://api.umweltify.com/) and read:

subAnonymous device id for your app — stable, and different for every app
tiergreen · amber · red · unknown
climate_statusDetailed status 0–9 (prefer tier)
last_verified_atUnix time the status was last computed from fresh data
tierMeaningSay
greenPowered by renewable electricity (green contract or EACs)Green
amberWithin a carbon budget only — not renewable1.5°C aligned
redNeitherNot green
unknownNot verified yetNot verified

Most apps turn rewards on for green and amber. Treat an old last_verified_at (e.g. over 24 h) as unknown, and never call amber "green".

5. Keeping the status fresh

  • Refresh the token to get the current tier. It fails with invalid_grant once the user disconnected your app.
  • Pull GET /v4/partner/climate-status with the access token.
  • Webhooks: set a webhook URL on your client to receive device.status_changed and grant.revoked. Verify Umweltify-Signature: t=…,v1=… — v1 = HMAC-SHA256(webhook secret, "{t}.{raw body}") — in constant time and reject timestamps older than 5 minutes. Retries: 1 m, 5 m, 30 m, 2 h, 12 h.
  • Live (games without a backend): SignalR to /hubs/climate-status?access_token=…, messages status and revoked.

6. Browser example (public client)

const verifier = b64url(crypto.getRandomValues(new Uint8Array(32)));
const challenge = b64url(new Uint8Array(await crypto.subtle.digest("SHA-256",
  new TextEncoder().encode(verifier))));
const state = b64url(crypto.getRandomValues(new Uint8Array(16)));
sessionStorage.setItem("gl", JSON.stringify({ verifier, state }));
location.assign("https://api.umweltify.com/connect/authorize?" + new URLSearchParams({
  client_id: CLIENT_ID, redirect_uri: REDIRECT_URI, response_type: "code",
  scope: "climate_status offline_access", state,
  code_challenge: challenge, code_challenge_method: "S256" }));

// on REDIRECT_URI
const { verifier: v, state: s } = JSON.parse(sessionStorage.getItem("gl"));
const q = new URLSearchParams(location.search);
if (q.get("state") !== s) throw new Error("state mismatch");
const tokens = await (await fetch("https://api.umweltify.com/connect/token", {
  method: "POST", body: new URLSearchParams({ grant_type: "authorization_code",
    code: q.get("code"), redirect_uri: REDIRECT_URI, client_id: CLIENT_ID, code_verifier: v })
})).json();

function b64url(bytes) {
  return btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

Decoding the token in the browser is fine for UI. Anything that grants value — coins, items — should be decided on a server that verifies the token's signature.

7. Errors

error=access_deniedThe user declined in ThingsApp, or the request expired
invalid_grantCode used or expired, wrong verifier, or the user disconnected your app
401Token missing or expired — refresh
403Missing climate_status scope, or access was revoked

We use essential cookies to make ClimateIn work, and optional analytics cookies to understand how the site is used so we can improve it. You can accept all cookies or reject non-essential ones at any time. See our cookie policy for details.