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.
1. Register your app
In Creator Studio → Synergy Login, register a client:
| Your app | Client type | You get |
|---|---|---|
| Has a backend that can keep a secret | Confidential | client id + secret |
| Browser game, desktop or mobile app without a backend | Public | client 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
| Authorize | https://api.umweltify.com/connect/authorize |
| Token | https://api.umweltify.com/connect/token |
| Revoke | https://api.umweltify.com/connect/revoke |
| JWKS | https://api.umweltify.com/.well-known/jwks |
| Discovery | https://api.umweltify.com/.well-known/openid-configuration |
| Current status | GET https://api.umweltify.com/v4/partner/climate-status |
| Live status | https://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:
sub | Anonymous device id for your app — stable, and different for every app |
tier | green · amber · red · unknown |
climate_status | Detailed status 0–9 (prefer tier) |
last_verified_at | Unix time the status was last computed from fresh data |
| tier | Meaning | Say |
|---|---|---|
| green | Powered by renewable electricity (green contract or EACs) | Green |
| amber | Within a carbon budget only — not renewable | 1.5°C aligned |
| red | Neither | Not green |
| unknown | Not verified yet | Not 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_grantonce the user disconnected your app. - Pull
GET /v4/partner/climate-statuswith the access token. - Webhooks: set a webhook URL on your client to receive
device.status_changedandgrant.revoked. VerifyUmweltify-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=…, messagesstatusandrevoked.
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_denied | The user declined in ThingsApp, or the request expired |
invalid_grant | Code used or expired, wrong verifier, or the user disconnected your app |
| 401 | Token missing or expired — refresh |
| 403 | Missing climate_status scope, or access was revoked |