README - adminUsers Edge Function

Documentation for the adminUsers Edge Function REST API for admin user management operations. Modified: 2026-Jan-19 20:29:29 UTC

adminUsers Edge Function

REST API for admin user management operations. Provides endpoints for managing the waiting list, users, and authentication links.

Last Updated: January 15, 2026

Base URL

POST/GET https://<supabase-project>.supabase.co/functions/v1/adminUsers/<endpoint>

Authentication

All endpoints require a valid JWT token with admin access level (9+):

Authorization: Bearer <jwt_token>

The JWT must contain app_claims.access_level >= 9.


Endpoints

Statistics

GET /stats

Get waiting list and user statistics.

Response:

{
  "pending": 5,
  "approved": 100,
  "rejected": 10,
  "expired": 2,
  "totalUsers": 95
}

Waiting List

GET /waiting-list

Get waiting list entries with optional status filter.

Query Parameters:

Parameter Type Description
status string Filter by: pending, approved, rejected, expired

Response:

{
  "entries": [
    {
      "id": "uuid",
      "email": "user@example.com",
      "full_name": "John Doe",
      "status": "pending",
      "signup_source": "web",
      "created_at": "2026-01-15T00:00:00Z"
    }
  ]
}

User Management

GET /users

Get all users with their email addresses.

Response:

{
  "users": [
    {
      "id": "uuid",
      "auth_id": "auth-uuid",
      "email": "user@example.com",
      "full_name": "John Doe",
      "access_level": 5,
      "org_id": "fpp",
      "active": true,
      "created_at": "2026-01-15T00:00:00Z"
    }
  ]
}

POST /lookup-user

Look up a user by email address.

Body:

{
  "email": "user@example.com"
}

Response:

{
  "authUser": {
    "id": "auth-uuid",
    "email": "user@example.com"
  },
  "user": {
    "id": "public-user-uuid",
    "full_name": "John Doe",
    "access_level": 5,
    "org_id": "fpp"
  }
}

POST /delete-user

Delete a user from both public.users and auth.users.

Body:

{
  "userId": "public-user-uuid"
}

Response:

{
  "message": "User deleted successfully",
  "deletedPublicUser": true,
  "deletedAuthUser": true
}

Approval Workflow

POST /approve

Approve a waiting list entry and create a user account.

Body:

{
  "entryId": "waiting-list-uuid",
  "accessLevel": 5,
  "orgId": "fpp",
  "transferDocs": true,
  "redirectTo": "https://your-app.com/welcome"
}
Field Type Default Description
entryId string required Waiting list entry ID
accessLevel number 5 User's access level (1-9)
orgId string "fpp" Organization ID
transferDocs boolean false Transfer documents created on behalf of this user
redirectTo string - Redirect URL after user sets password

Response:

{
  "message": "User approved successfully",
  "user": {
    "id": "public-user-uuid",
    "auth_id": "auth-uuid",
    "access_level": 5,
    "org_id": "fpp"
  },
  "documentsTransferred": 0,
  "inviteLink": "https://xxx.supabase.co/auth/v1/verify?token=..."
}

Important: The inviteLink is a one-time use link. Share it with the user so they can set their password. If you miss it, use /generate-link to create a new one.

POST /reject

Reject a waiting list entry.

Body:

{
  "entryId": "waiting-list-uuid",
  "reason": "Optional rejection reason"
}

POST /delete-waiting-list-entry

Delete a waiting list entry (rejected/expired only).

Body:

{
  "entryId": "waiting-list-uuid"
}

Note: Cannot delete pending or approved entries.


POST /generate-link

Generate an invite or recovery link for a user. Use this if you missed the inviteLink from approval or need to send a new password reset link.

Body:

{
  "email": "user@example.com",
  "type": "invite",
  "redirectTo": "https://your-app.com/welcome"
}
Field Type Default Description
email string required User's email address
type string "invite" Link type: invite or recovery
redirectTo string - Redirect URL after authentication

Link Types:

Type Use Case Creates User?
invite New users who need to set their password Yes (if not exists)
recovery Existing users who forgot their password No

Response:

{
  "message": "Generated invite link for user@example.com",
  "link": "https://xxx.supabase.co/auth/v1/verify?token=...",
  "type": "invite",
  "email": "user@example.com"
}

Example Usage:

# Generate invite link for newly approved user
curl -X POST https://xxx.supabase.co/functions/v1/adminUsers/generate-link \
  -H "Authorization: Bearer YOUR_ADMIN_JWT" \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "type": "invite"}'

# Generate password reset link for existing user
curl -X POST https://xxx.supabase.co/functions/v1/adminUsers/generate-link \
  -H "Authorization: Bearer YOUR_ADMIN_JWT" \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "type": "recovery"}'

Document Transfer

Note: Document transfer endpoints use the admin user's org_id from their JWT claims to determine which document table to search/update. The table name is derived using the pattern: ${SUPABASE_VECTOR_TABLENAME}_${org_id} (e.g., documents_ctrl-shift for org_id: "ctrl-shift").

GET /pending-docs

Preview documents pending transfer for an email.

Query Parameters:

Parameter Type Description
email string Email to search in document metadata (onBehalfOf)

Example:

GET /pending-docs?email=user@example.com

Response:

{
  "documents": [
    {
      "id": "doc-uuid",
      "metadata": { "onBehalfOf": "user@example.com" },
      "user_id": "original-owner-uuid"
    }
  ],
  "total": 5,
  "tableName": "documents_ctrl-shift"
}

POST /transfer-docs

Transfer document ownership to a user.

Body:

{
  "email": "user@example.com",
  "newOwnerId": "public-user-uuid"
}
Field Type Description
email string Email to search in document metadata
newOwnerId string Target user's public.users.id

Response:

{
  "message": "Transferred 5 documents",
  "transferred": 5,
  "tableName": "documents_ctrl-shift"
}

Performance:

  • Uses admin_transfer_documents RPC for bulk transfers (single DB call)
  • 5500+ documents transfer in ~8 seconds (vs ~90 seconds with individual updates)
  • The transfer is idempotent - re-running updates remaining rows to same user_id

Supabase Auth Password Requirements

When users set their password via invite/recovery links, Supabase enforces these requirements:

Requirement Value Notes
Minimum length 6 characters (default) Configurable in Supabase Dashboard
Maximum length 72 characters Hard limit
Recommended minimum 8+ characters Security best practice

Configurable Options (Supabase Dashboard → Auth → Providers → Email)

  • Minimum password length
  • Required character types (digits, lowercase, uppercase, symbols)
  • Leaked password protection (Pro plan+)

UI Hint Text

For frontend password fields, use:

Password must be at least 6 characters (8+ recommended)

Sources:


Error Responses

All errors return:

{
  "error": "Error message description"
}
Status Description
400 Bad request (missing required fields)
401 Unauthorized (missing/invalid JWT)
403 Forbidden (access level < 9)
404 Not found (user/entry not found)
500 Server error

Typical Workflow

Approving a New User

  1. User signs up via waiting list form → stored in waiting_list table
  2. Admin reviews pending entries via GET /waiting-list?status=pending
  3. Admin approves via POST /approve → receives inviteLink
  4. Admin shares link with user via email/chat
  5. User clicks link → sets their password → can now sign in
  1. Call POST /generate-link with type: "invite" or type: "recovery"
  2. Share the new link with the user
  3. Links are single-use; generate a new one if needed

Resetting an Existing User's Password

  1. Call POST /generate-link with type: "recovery"
  2. Share the recovery link with the user
  3. User clicks link → sets new password

  • registerUser - Public endpoint for waiting list signup
  • rag - RAG endpoint that users access after approval
  • ui/auth/admin.html - Admin dashboard for user management
  • supabase/functions/_supabase-auth/test-user-management.html - Developer testing UI

Change Log

Date Description
2026-01-15 Created users_with_email view and updated /users, /lookup-user, /approve, /generate-link to use it instead of slow listUsers()
2026-01-15 Optimized /transfer-docs to use admin_transfer_documents RPC for ~37x performance improvement (5500 docs in 8s vs 1500 in 90s)
2026-01-15 Added pagination to /pending-docs to handle >1000 documents (Supabase default limit)
2026-01-15 Refactored /pending-docs and /transfer-docs to use org_id from JWT claims (via getTableName()) instead of explicit tableName parameter
2026-01-15 Added POST /generate-link endpoint for invite/recovery link generation
2026-01-15 Updated POST /approve to use invite links instead of random passwords
2026-01-15 Added inviteLink to approval response
2026-01-15 Initial README documentation