P Pepy Hat ← Back home
API Reference

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

  1. User clicks "Sign in" on your app → redirect to authentik (OIDC).
  2. authentik authenticates the user and redirects back to your app's callback with an authorization code.
  3. Your app exchanges the code for an ID token containing the user's email.
  4. Your app calls GET /api/v1/authz/<email>?app=<your-slug> on Pepy Hat with your app's API key.
  5. 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

GET /api/v1/authz/{email}?app={slug}

Returns the roles and permissions assigned to the given user for the given app.

Path parameters
emailThe user's email (case-insensitive). The user must exist in Pepy Hat.
Query parameters
appYour app's slug (must match the X-API-Key).
Headers
X-API-KeyYour 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"]
}
FieldDescription
foundfalse if the user is not registered in Pepy Hat. Treat as no access.
activefalse if the user has been deactivated. Treat as no access.
rolesSlugs of roles granted to this user for this app. Empty array if none.
permissionsDeduplicated union of permissions across all granted roles.

Org chart endpoint

GET /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.

Path parameters
emailThe manager's email (case-insensitive). Must exist in Pepy Hat.
Query parameters
appYour app's slug (must match the X-API-Key).
treeOptional. true to return a nested tree of direct reports (each with their own children). Defaults to a flat list of all descendants.
Headers
X-API-KeyYour 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

StatusMeaning
400Missing app query parameter.
401Missing X-API-Key header.
403API key is invalid for this app.
404App 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