Skip to Content
Developer APIAuthenticationPersonal Access Tokens

Personal Access Tokens (PAT)

Personal Access Tokens (PATs) are opaque bearer tokens — prefixed eu_sk_… — suitable for scripts, CLI tools, automation that cannot run an OAuth flow, and legacy integrations.

Running server-side automation at scale? Prefer service accounts — they use short-lived tokens and Keycloak’s credential management. Acting as a specific user for Drive or Sites? Bind the PAT to a user_id when creating it.


Create a PAT

PATs are managed in Admin → API Access (/admin/api-access).

  1. Go to Admin → API Access → API Tokens.
  2. Click New token.
  3. Give it a name, select the scopes it needs, and optionally set an expiry date and IP allowlist.
  4. Click Create. The secret is shown once — copy it now.

Alternatively, create via the API (requires an existing admin session or admin PAT):

# First list service principals to get {sp_id} GET /api/v1/developer/service-principals # Create a PAT under a service principal curl -X POST https://api.eunifyer.com/api/v1/developer/service-principals/{sp_id}/tokens \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "my-sync-script", "scopes": ["drive:read", "drive:write"], "expires_at": "2027-04-24T00:00:00Z", "user_id": "optional-user-uuid-for-drive-access" }'

Response includes secret — the eu_sk_… token — shown once only:

{ "id": "tok_…", "name": "my-sync-script", "secret": "eu_sk_…", "scopes": ["drive:read", "drive:write"], "expires_at": "2027-04-24T00:00:00Z", "created_at": "2026-04-24T10:00:00Z" }

Use a PAT

Pass it as a bearer token:

curl https://api.eunifyer.com/api/v1/drive/browse \ -H "Authorization: Bearer eu_sk_YOUR_TOKEN_HERE"
import httpx client = httpx.Client( base_url="https://api.eunifyer.com/api/v1", headers={"Authorization": "Bearer eu_sk_YOUR_TOKEN_HERE"}, ) resp = client.get("/drive/browse") resp.raise_for_status() print(resp.json())
const API = "https://api.eunifyer.com/api/v1"; const TOKEN = process.env.EUNIFYER_PAT!; const resp = await fetch(`${API}/drive/browse`, { headers: { Authorization: `Bearer ${TOKEN}` }, }); const data = await resp.json();

Token lifecycle

ActionAdmin UIAPI endpoint
CreateAdmin → API Access → New tokenPOST /developer/service-principals/{sp_id}/tokens
ListAdmin → API Access → Token listGET /developer/service-principals/{sp_id}/tokens
RotateToken row → RotatePOST /developer/service-principals/{sp_id}/tokens/{token_id}/rotate
RevokeToken row → RevokeDELETE /developer/service-principals/{sp_id}/tokens/{token_id}
Update name/expiryToken row → EditPATCH /developer/service-principals/{sp_id}/tokens/{token_id}

Rotate invalidates the old secret immediately and returns a new eu_sk_… secret (shown once). Update all consumers before rotating.

Revoke invalidates the token with no replacement. A revoked token returns 401 Unauthorized.


Security properties

  • Secret is stored SHA-256 hashed at rest — EUnifyer cannot recover a lost token.
  • Optional expiry date — expired tokens return 401.
  • Optional IP allowlist — requests from other IPs return 401 even with a valid token.
  • Every use is recorded in the audit log.
  • Secret is shown once at creation or rotation. If you lose it, rotate.

Binding a PAT to a user

Drive and Sites require an acting user. Bind a PAT to a specific user’s user_id at creation:

{ "name": "alice-drive-sync", "scopes": ["drive:read"], "user_id": "3a91f…user-uuid…" }

The user_id must belong to the same organisation as the service principal. The PAT then acts on behalf of that user for all Drive and Sites requests. Rotation preserves the binding.

A PAT without user_id is rejected by Drive and Sites with 403 Forbidden ("Drive requires user-delegated access").