Skip to Content

Service Accounts

Use the OAuth 2.0 client credentials flow when your integration runs server-side with no human in the loop — MSP automation, reseller provisioning, scheduled sync jobs, or backend-to-backend integrations.

Managing users or orgs? Service accounts are the right choice for Partner API calls. Acting on behalf of a user for Drive or Sites? Use OAuth auth code + PKCE or a PAT bound to a user.


Prerequisites

A platform superadmin must register your service account in Keycloak and create a service principal in EUnifyer:

  1. In Back-office → Service Accounts: create a Keycloak confidential client with the service account role enabled.
  2. Note your client_id and client_secret.
  3. Create a service principal in Admin → API Access linked to that Keycloak client ID.
  4. Assign scopes to the service principal.

For org-level (non-reseller) service accounts, an org admin can create them in Admin → API Access.


Get a token

curl -X POST https://auth.eunifyer.com/realms/eunify/protocol/openid-connect/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET" \ -d "scope=partner:orgs:read partner:orgs:write partner:users:write"
const tokenRes = await fetch( "https://auth.eunifyer.com/realms/eunify/protocol/openid-connect/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "client_credentials", client_id: CLIENT_ID, client_secret: CLIENT_SECRET, scope: "partner:orgs:read partner:orgs:write partner:users:write", }), }, ); const { access_token } = await tokenRes.json();

Response:

{ "access_token": "eyJ...", "expires_in": 900, "token_type": "Bearer", "scope": "partner:orgs:read partner:orgs:write partner:users:write" }

Access tokens are short-lived (15 minutes). Re-grant when they expire — there is no refresh token for client credentials.


Call the API

TOKEN=$(curl -s -X POST https://auth.eunifyer.com/realms/eunify/protocol/openid-connect/token \ -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET" \ | jq -r .access_token) curl https://api.eunifyer.com/api/v1/partner/orgs \ -H "Authorization: Bearer $TOKEN"
import httpx TOKEN_URL = "https://auth.eunifyer.com/realms/eunify/protocol/openid-connect/token" API_BASE = "https://api.eunifyer.com/api/v1" def get_token(client_id: str, client_secret: str) -> str: r = httpx.post(TOKEN_URL, data={ "grant_type": "client_credentials", "client_id": client_id, "client_secret": client_secret, }) r.raise_for_status() return r.json()["access_token"] token = get_token(CLIENT_ID, CLIENT_SECRET) orgs = httpx.get(f"{API_BASE}/partner/orgs", headers={"Authorization": f"Bearer {token}"}) print(orgs.json())

Token caching

Re-issuing a token on every API call is wasteful. Cache the token and re-grant when it is about to expire:

import time _cached_token: str | None = None _token_expires_at: float = 0.0 def get_cached_token(client_id: str, client_secret: str) -> str: global _cached_token, _token_expires_at if _cached_token and time.time() < _token_expires_at - 30: return _cached_token r = httpx.post(TOKEN_URL, data={ "grant_type": "client_credentials", "client_id": client_id, "client_secret": client_secret, }) r.raise_for_status() data = r.json() _cached_token = data["access_token"] _token_expires_at = time.time() + data["expires_in"] return _cached_token

Managing service principals via the API

List your service principals and their tokens from the Admin → API Access page in the UI, or via the API:

# List service principals GET /api/v1/developer/service-principals # Create a new service principal POST /api/v1/developer/service-principals { "name": "MSP Provisioning Bot", "description": "…", "scopes": ["partner:orgs:write"] } # Add a PAT to a service principal POST /api/v1/developer/service-principals/{sp_id}/tokens { "name": "prod-key-1", "scopes": ["partner:orgs:write"] }

These management endpoints require admin:access. Use a first-party admin session or a superadmin PAT to set them up.


Security checklist

  • Store client_secret in a secrets manager — never in source code or .env files committed to git.
  • Use the narrowest scope set that covers what your service account does.
  • Rotate client_secret regularly; invalidate old tokens after rotation.
  • Monitor X-RateLimit-Remaining — service accounts share the org’s rate-limit bucket.