Admin API Reference

Complete reference for the admin_backend_api server endpoints. All routes (except /health and auth endpoints) require a valid Keycloak Authorization: Bearer <token> header.

Authentication

All auth requests are sent to Keycloak via the admin backend.

POST /api/admin/auth/login

Login as an admin user.

Request:

json
{
  "email": "[email protected]",
  "password": "securepassword"
}

Response:

json
{
  "access_token": "eyJhbGci...",
  "refresh_token": "eyJhbGci...",
  "expires_in": 300,
  "token_type": "Bearer"
}

POST /api/admin/auth/refresh

Refresh an expired access token.

Request:

json
{
  "refresh_token": "eyJhbGci..."
}

Response:

json
{
  "access_token": "eyJhbGci...",
  "refresh_token": "eyJhbGci..."
}

POST /api/admin/auth/logout

Invalidate the current session.

Request:

json
{
  "refresh_token": "eyJhbGci..."
}

Response:

json
{
  "message": "Logout successful"
}

POST /api/admin/auth/verify-session

Verify the current token is valid.

Headers: Authorization: Bearer <token>

Response:

json
{
  "valid": true,
  "subject": "keycloak-user-id",
  "username": "[email protected]"
}

GET /api/admin/auth/me

Get the authenticated admin's profile.

Headers: Authorization: Bearer <token> (read access)

Response:

json
{
  "subject": "keycloak-user-id",
  "username": "[email protected]",
  "role": "full_admin"
}

Users

GET /api/admin/users

List all users with pagination, search, and status filtering.

Auth: Read

Query Parameters:

  • page (int, default: 1)
  • page_size (int, default: 20, max: 100)
  • search (string, optional) — Search in email, first_name, last_name
  • status (string, optional) — Filter by status: active, blocked, deleted

Response:

json
{
  "items": [
    {
      "uuid": "user-uuid",
      "email": "jo***@example.com",
      "first_name": "John",
      "last_name": "Doe",
      "status": "active",
      "tier_name": "Pro",
      "organization_name": "Acme Corp",
      "created_at": "2024-01-15T10:30:00Z"
    }
  ],
  "page": 1,
  "page_size": 20,
  "total": 150
}

GET /api/admin/users/<userUuid>

Get detailed user information.

Auth: Read

Response:

json
{
  "uuid": "user-uuid",
  "email": "[email protected]",
  "first_name": "John",
  "last_name": "Doe",
  "status": "active",
  "has_temporary_password": false,
  "tier": {
    "uuid": "tier-uuid",
    "name": "Pro"
  },
  "organizations": [
    {
      "uuid": "org-uuid",
      "name": "Acme Corp",
      "role": "member"
    }
  ],
  "feature_roles": ["functions:read", "functions:write"],
  "created_at": "2024-01-15T10:30:00Z"
}

PATCH /api/admin/users/<userUuid>

Update user fields.

Auth: Write (full_admin)

Request:

json
{
  "first_name": "Jonathan",
  "last_name": "Doe",
  "email": "[email protected]"
}

Response:

json
{
  "success": true,
  "message": "User updated successfully",
  "user": { /* updated user */ }
}

POST /api/admin/users/<userUuid>/disable

Block a user account (sets status = 'blocked').

Auth: Write (full_admin)

Response:

json
{
  "success": true,
  "message": "User disabled successfully"
}

POST /api/admin/users/<userUuid>/enable

Re-enable a blocked user (sets status = 'active').

Auth: Write (full_admin)

Response:

json
{
  "success": true,
  "message": "User enabled successfully"
}

DELETE /api/admin/users/<userUuid>

Soft-delete a user (sets status = 'deleted'). Never physically removes the record.

Auth: Write (full_admin)

Response:

json
{
  "success": true,
  "message": "User deleted successfully"
}

POST /api/admin/users/<userUuid>/send-temporary-password

Generate and email a temporary password to the user.

Auth: Write (full_admin)

Response:

json
{
  "success": true,
  "message": "Temporary password sent to jo***@example.com"
}

Notes:

  • Passwords are generated with Random.secure(), hashed with BCrypt
  • The temporary_password flag is set to true on the user record
  • Emails are sent via ForwardEmail

Keycloak User Feature Roles

GET /api/admin/keycloak-users/<keycloakUserId>/feature-roles

Get feature roles for a Keycloak user.

Auth: Read

Response:

json
{
  "keycloak_user_id": "kc-user-id",
  "assigned_roles": ["container:read"],
  "default_roles": ["functions:read", "functions:write", "sites:read", "sites:write"],
  "grantable_roles": ["container:read", "container:write", "webhook:read", "webhook:write"],
  "effective_roles": ["functions:read", "functions:write", "sites:read", "sites:write", "container:read"]
}

POST /api/admin/keycloak-users/<keycloakUserId>/feature-roles/grant

Grant feature roles to a user. Only roles in the grantable allowlist can be granted.

Auth: Write (full_admin)

Request:

json
{
  "roles": ["container:read", "webhook:write"]
}

Response:

json
{
  "success": true,
  "message": "Roles granted successfully",
  "granted_roles": ["container:read", "webhook:write"]
}

POST /api/admin/keycloak-users/<keycloakUserId>/feature-roles/remove

Remove feature roles from a user.

Auth: Write (full_admin)

Request:

json
{
  "roles": ["container:read"]
}

Response:

json
{
  "success": true,
  "message": "Roles removed successfully",
  "removed_roles": ["container:read"]
}

GET /api/admin/feature-roles/stats

Get aggregate statistics for all feature roles across users. Full admins are excluded from counts.

Auth: Read

Response:

json
{
  "roles": {
    "container:read": { "assigned_count": 42 },
    "container:write": { "assigned_count": 15 },
    "webhook:read": { "assigned_count": 38 },
    "webhook:write": { "assigned_count": 12 }
  },
  "total_users": 150,
  "total_full_admins": 3
}

Tiers

GET /api/admin/tiers

List all service tiers.

Auth: Read

Response:

json
{
  "items": [
    {
      "uuid": "tier-uuid",
      "name": "Pro",
      "description": "Professional tier",
      "is_active": true,
      "user_count": 45,
      "created_at": "2024-01-01T00:00:00Z"
    }
  ]
}

POST /api/admin/tiers

Create a new tier.

Auth: Write (full_admin)

Request:

json
{
  "name": "Enterprise",
  "description": "Enterprise tier with full features",
  "metadata": { "max_functions": 100 }
}

Response:

json
{
  "success": true,
  "tier": { /* created tier */ }
}

PATCH /api/admin/tiers/<tierId>

Update a tier.

Auth: Write (full_admin)

Request:

json
{
  "name": "Enterprise Plus",
  "description": "Updated description"
}

Response:

json
{
  "success": true,
  "tier": { /* updated tier */ }
}

DELETE /api/admin/tiers/<tierId>

Delete a tier.

Auth: Write (full_admin)

Response:

json
{
  "success": true,
  "message": "Tier deleted successfully"
}

POST /api/admin/tiers/<tierId>/assign-user

Assign a user to a tier.

Auth: Write (full_admin)

Request:

json
{
  "user_uuid": "user-uuid"
}

Response:

json
{
  "success": true,
  "message": "User assigned to tier successfully"
}

DELETE /api/admin/tiers/<tierId>/assign-user/<userUuid>

Remove a user from a tier.

Auth: Write (full_admin)

Response:

json
{
  "success": true,
  "message": "User unassigned from tier successfully"
}

Organizations

GET /api/admin/organizations

List all organizations with pagination.

Auth: Read

Query Parameters:

  • page (int, default: 1)
  • page_size (int, default: 20)
  • search (string, optional)

Response:

json
{
  "items": [
    {
      "uuid": "org-uuid",
      "name": "Acme Corp",
      "owner_email": "ad***@acme.com",
      "member_count": 12,
      "created_at": "2024-01-15T10:30:00Z"
    }
  ],
  "page": 1,
  "page_size": 20,
  "total": 8
}

POST /api/admin/organizations

Create a new organization.

Auth: Write (full_admin)

Request:

json
{
  "name": "New Corp",
  "owner_uuid": "user-uuid"
}

Response:

json
{
  "success": true,
  "organization": { /* created org */ }
}

GET /api/admin/organizations/<organizationUuid>

Get organization details including members.

Auth: Read

Response:

json
{
  "uuid": "org-uuid",
  "name": "Acme Corp",
  "owner_email": "[email protected]",
  "members": [
    {
      "uuid": "user-uuid",
      "email": "jo***@acme.com",
      "role": "member",
      "joined_at": "2024-01-15T10:30:00Z"
    }
  ],
  "created_at": "2024-01-15T10:30:00Z"
}

PATCH /api/admin/organizations/<organizationUuid>

Update organization details.

Auth: Write (full_admin)

Request:

json
{
  "name": "Acme Corp Updated"
}

POST /api/admin/organizations/<organizationUuid>/members

Add a member to an organization.

Auth: Write (full_admin)

Request:

json
{
  "user_uuid": "user-uuid",
  "role": "member"
}

DELETE /api/admin/organizations/<organizationUuid>/members/<userUuid>

Remove a member from an organization.

Auth: Write (full_admin)

Observability

GET /api/admin/observability/overview

Get system overview metrics.

Auth: Read

Query Parameters:

  • period (string) — 1h, 24h, 7d (default: 24h)

Response:

json
{
  "error_rate": 2.5,
  "total_executions": 15000,
  "active_functions": 342,
  "avg_latency_ms": 120
}

GET /api/admin/observability/logs

Get function execution logs.

Auth: Read

Query Parameters:

  • function_uuid (string, optional) — Filter by function
  • level (string, optional) — info, warn, error
  • limit (int, default: 100)
  • since (ISO 8601, optional)

Response:

json
{
  "logs": [
    {
      "timestamp": "2024-01-15T10:30:00Z",
      "function_uuid": "func-uuid",
      "level": "error",
      "message": "Connection timeout"
    }
  ],
  "total": 150
}

GET /api/admin/observability/errors

Get filtered error logs.

Auth: Read

Query Parameters:

  • function_uuid (string, optional)
  • limit (int, default: 50)
  • since (ISO 8601, optional)

Response:

json
{
  "errors": [
    {
      "timestamp": "2024-01-15T10:30:00Z",
      "function_uuid": "func-uuid",
      "message": "Unhandled exception: Null check operator used on a null value"
    }
  ],
  "total": 23
}

GET /api/admin/observability/load

Get system load metrics over time.

Auth: Read

Query Parameters:

  • period (string) — 1h, 24h, 7d (default: 24h)
  • interval (string) — 5m, 15m, 1h (default: 15m)

Response:

json
{
  "data": [
    {
      "timestamp": "2024-01-15T10:00:00Z",
      "request_count": 250,
      "avg_latency_ms": 115,
      "error_count": 3
    }
  ],
  "period": "24h",
  "interval": "15m"
}

Early Access Features (Feature Flags)

GET /api/admin/early-access-features

List all feature flags.

Auth: Read

Response:

json
{
  "items": [
    {
      "uuid": "flag-uuid",
      "name": "preview_registration_enabled",
      "enabled": true,
      "value": { "target_users": 100 },
      "created_at": "2024-01-01T00:00:00Z"
    }
  ]
}

GET /api/admin/early-access-features/<name>

Get a specific feature flag by name.

Auth: Read

PUT /api/admin/early-access-features/<uuid>

Update a feature flag.

Auth: Write (full_admin)

Request:

json
{
  "enabled": false,
  "value": { "target_users": 200 }
}

GET /api/admin/early-access-features/<featureUuid>/overview

Get feature overview with registration statistics.

Auth: Read

Early Access Form Schemas

GET /api/admin/early-access-form-schemas

List all form schemas.

Auth: Read

Response:

json
{
  "items": [
    {
      "uuid": "schema-uuid",
      "feature_uuid": "feature-uuid",
      "feature_name": "preview_registration",
      "schema_version": 2,
      "created_at": "2024-01-15T10:30:00Z"
    }
  ]
}

GET /api/admin/early-access-form-schemas/<schemaUuid>

Get a specific schema by UUID.

Auth: Read

GET /api/admin/early-access-form-schemas/by-feature/<featureUuid>

Get schemas for a specific feature.

Auth: Read

POST /api/admin/early-access-form-schemas

Create or update a form schema (upsert).

Auth: Write (full_admin)

Request:

json
{
  "feature_uuid": "feature-uuid",
  "schema": {
    "fields": [
      {
        "name": "company",
        "type": "text",
        "required": true,
        "label": "Company Name"
      }
    ]
  }
}

DELETE /api/admin/early-access-form-schemas/<schemaUuid>

Delete a form schema.

Auth: Write (full_admin)

POST /api/admin/early-access-form-schemas/validate

Validate form data against a schema.

Auth: Read

Request:

json
{
  "schema_uuid": "schema-uuid",
  "data": {
    "company": "Acme Corp"
  }
}

Early Access Request Management

For detailed documentation on early access request management, see Early Access Admin.

Summary of Endpoints

MethodPathAuthDescription
GET /api/admin/early-access/requests Read List requests with pagination, search, status filter
GET /api/admin/early-access/requests/<uuid> Read Get request details
POST /api/admin/early-access/requests/<uuid>/approve Write Approve request (with capacity check)
POST /api/admin/early-access/requests/<uuid>/reject Write Reject request with reason
POST /api/admin/early-access/requests/<uuid>/send-temporary-password Write Generate and email temp password
GET /api/admin/early-access/requests/<uuid>/audit-logs Read Get audit log for a request
GET /api/admin/early-access/settings Read Get capacity/status settings
PUT /api/admin/early-access/settings/capacity Write Update capacity limit
PUT /api/admin/early-access/settings/toggle Write Enable/disable registration

Health Check

GET /health

Check admin backend server health.

Auth: None

Response:

json
{
  "status": "ok",
  "timestamp": "2024-01-15T10:30:00Z"
}

Error Responses

400 Bad Request

json
{
  "error": "Invalid request",
  "details": "Missing required field: email"
}

401 Unauthorized

json
{
  "error": "Missing or invalid authorization header"
}

403 Forbidden

json
{
  "error": "Insufficient permissions"
}

Note: Returned when a viewer tries a write operation, or a user has no admin role.

404 Not Found

json
{
  "error": "User not found"
}

500 Internal Server Error

json
{
  "error": "Internal server error"
}

All unhandled errors are reported to Sentry (when configured).

Next Steps