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_idwhen creating it.
Create a PAT
PATs are managed in Admin → API Access (/admin/api-access).
- Go to Admin → API Access → API Tokens.
- Click New token.
- Give it a name, select the scopes it needs, and optionally set an expiry date and IP allowlist.
- 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
| Action | Admin UI | API endpoint |
|---|---|---|
| Create | Admin → API Access → New token | POST /developer/service-principals/{sp_id}/tokens |
| List | Admin → API Access → Token list | GET /developer/service-principals/{sp_id}/tokens |
| Rotate | Token row → Rotate | POST /developer/service-principals/{sp_id}/tokens/{token_id}/rotate |
| Revoke | Token row → Revoke | DELETE /developer/service-principals/{sp_id}/tokens/{token_id} |
| Update name/expiry | Token row → Edit | PATCH /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
401even 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").