Pepy Hat API
Centralized authorization for your internal apps. Use authentik to authenticate users, then call Pepy Hat to fetch their roles and permissions for your app.
How it fits together
- User clicks "Sign in" on your app → redirect to authentik (OIDC).
- authentik authenticates the user and redirects back to your app's callback with an authorization code.
- Your app exchanges the code for an ID token containing the user's email.
- Your app calls
GET /api/v1/authz/<email>?app=<your-slug>on Pepy Hat with your app's API key. - Your app receives the user's roles and permissions for your app, and uses them to gate access locally.
Authentication
Each app registered in Pepy Hat gets a unique API key. Send it on every request:
X-API-Key: phk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
The current key is always viewable on the app's detail page under API key. Store it securely (env var, secret manager) and rotate via the same page if compromised.
Authorization endpoint
/api/v1/authz/{email}?app={slug}
Returns the roles and permissions assigned to the given user for the given app.
| The user's email (case-insensitive). The user must exist in Pepy Hat. |
| app | Your app's slug (must match the X-API-Key). |
| X-API-Key | Your app's API key. |
Example request
curl -H "X-API-Key: phk_..." \ "https://pepy-hat.example.com/api/v1/authz/[email protected]?app=crm"
Example response
{
"email": "[email protected]",
"app": "crm",
"found": true,
"active": true,
"roles": ["admin", "viewer"],
"permissions": ["users.read", "users.write"]
}
| Field | Description |
|---|---|
| found | false if the user is not registered in Pepy Hat. Treat as no access. |
| active | false if the user has been deactivated. Treat as no access. |
| roles | Slugs of roles granted to this user for this app. Empty array if none. |
| permissions | Deduplicated union of permissions across all granted roles. |
Org chart endpoint
/api/v1/org-chart/{email}?app={slug}
Returns every user reporting under the given email at any depth, walking the report_to graph. The requested user is not included in the response.
| The manager's email (case-insensitive). Must exist in Pepy Hat. |
| app | Your app's slug (must match the X-API-Key). |
| tree | Optional. true to return a nested tree of direct reports (each with their own children). Defaults to a flat list of all descendants. |
| X-API-Key | Your app's API key. |
Org chart — example request
curl -H "X-API-Key: phk_..." \ "https://pepy-hat.example.com/api/v1/org-chart/[email protected]?app=crm"
Org chart — flat response (default)
[
{ "email": "[email protected]", "name": "Bob Lee" },
{ "email": "[email protected]", "name": "Carol Tan" },
{ "email": "[email protected]", "name": "Dave Kim" }
]
Every descendant of the requested user, flattened. Order is breadth-first, with siblings sorted by name.
Org chart — nested response (?tree=true)
[
{
"email": "[email protected]",
"name": "Bob Lee",
"children": [
{ "email": "[email protected]", "name": "Dave Kim", "children": [] }
]
},
{
"email": "[email protected]",
"name": "Carol Tan",
"children": []
}
]
The top level contains the requested user's direct reports; each node carries its own children recursively.
Errors
| Status | Meaning |
|---|---|
| 400 | Missing app query parameter. |
| 401 | Missing X-API-Key header. |
| 403 | API key is invalid for this app. |
| 404 | App slug not registered, or — on the org chart endpoint — the requested user does not exist. |
On the authz endpoint, an unknown user is not a 404 — it returns 200 with found: false and empty roles/permissions so apps can deny access uniformly. The org chart endpoint, by contrast, returns 404 for an unknown user.
Go example
// after exchanging the OIDC code for an ID token, fetch authz req, _ := http.NewRequest("GET", "https://pepy-hat.example.com/api/v1/authz/"+email+"?app=crm", nil) req.Header.Set("X-API-Key", os.Getenv("PEPY_HAT_API_KEY")) resp, err := http.DefaultClient.Do(req) // ... handle err, decode JSON, gate routes by resp.Permissions