Overview
The AgentPowers admin panel is available at /dashboard/admin on the frontend. Access requires the is_admin flag set to TRUE in the users database table for the authenticated user.
Admin capabilities include:
- User management — view all users, seller status, Stripe connection status, and account status
- Admin promotions — promote or demote other users to/from admin
- Claim review — approve or reject sandboxed skill ownership claims
- Abuse management — view and resolve abuse flags, hold or release user accounts
- Category management — create, update, and delete marketplace categories
- Refunds — issue full refunds for completed purchases
- Account deletion — view pending deletions and cancel them on behalf of users
- Marketing email — send marketing emails to opted-in users
Dashboard UI vs API-only: Only user management has a frontend dashboard UI (at /dashboard/admin). All other admin features (abuse flags, categories, refunds, marketing email, pending deletions, claim review, admin promotions) are API-only and must be accessed via curl or an HTTP client.
Hidden from OpenAPI spec: All admin endpoints use include_in_schema=False, meaning they do not appear in the public OpenAPI documentation or auto-generated API reference. This page is the canonical reference for admin API usage.
Bootstrapping the First Admin
The API promotion endpoint (POST /v1/admin/admins/{user_id}) requires an existing admin to call it, so the very first admin must be set directly via SQL.
Step 1 — Find the User ID
The user ID is the Clerk user ID (starts with user_). You can find it in the Clerk Dashboard under Users, or query the database directly:
Step 2 — Grant Admin Access
Production (Turso Cloud):
turso db shell agentpowers "UPDATE users SET is_admin = TRUE WHERE id = 'user_xxxxx'"
Local development:
sqlite3 agentpowers-api/agentpowers.db "UPDATE users SET is_admin = TRUE WHERE id = 'user_xxxxx'"
Replace user_xxxxx with the actual Clerk user ID from Step 1.
Step 3 — Verify
Log out and log back in. The Admin nav item will appear in the dashboard sidebar.
Once a first admin exists, subsequent admin management is done through the API. These endpoints are hidden from the public OpenAPI spec.
curl -X POST "https://api.agentpowers.ai/v1/admin/admins/{user_id}" \
-H "Authorization: Bearer <admin-token>"
Demote an Admin
curl -X DELETE "https://api.agentpowers.ai/v1/admin/admins/{user_id}" \
-H "Authorization: Bearer <admin-token>"
The API prevents removing the last admin. If only one admin exists, the demote request returns a 409 Conflict error.
List All Admins
curl "https://api.agentpowers.ai/v1/admin/admins" \
-H "Authorization: Bearer <admin-token>"
Admin Panel Features
User Management
GET /v1/admin/users returns a paginated list of all users with:
- Email and display name
- Admin status (
is_admin)
- Seller status (derived from
tos_accepted_at)
- Stripe Connect status (whether
stripe_connect_id is set)
- Account status (
active, held, pending_deletion)
- Deletion timestamps if applicable
Supports sorting by email, created_at, is_admin, is_seller, or stripe_connected.
Claim Review
Sellers can claim ownership of skills imported from external registries. Claims that need manual review land in a “sandbox” state.
GET /v1/admin/claims/sandbox — list all sandboxed claims
POST /v1/admin/claims/{claim_id}/approve — approve a claim, transferring skill ownership to the claimant
Abuse Flags
The platform automatically flags suspicious activity (e.g., buyer-seller bursts). Admins review and resolve these flags.
GET /v1/admin/abuse/flagged — list abuse flags (filterable by status and flag_type)
POST /v1/admin/abuse/{flag_id}/resolve — resolve a flag with a resolution status and optional notes
POST /v1/admin/abuse/hold-account/{user_id} — freeze a user account for investigation
POST /v1/admin/abuse/release-account/{user_id} — release a held account
Resolve an Abuse Flag
curl -X POST "https://api.agentpowers.ai/v1/admin/abuse/{flag_id}/resolve" \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"resolution": "resolved_legit",
"notes": "Reviewed transaction history, activity is legitimate."
}'
Request body (ResolveFlagRequest):
| Field | Type | Required | Description |
|---|
resolution | string | Yes | Resolution status. Use resolved_legit or resolved_fraud. |
notes | string | No | Optional notes explaining the resolution decision. |
Response: Returns the updated flag object with id, flag_type, reference_id, reference_type, details, status, resolved_by, resolved_at, resolution_notes, and created_at.
Hold a User Account
curl -X POST "https://api.agentpowers.ai/v1/admin/abuse/hold-account/{user_id}" \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"reason": "Suspected fraudulent purchase pattern"
}'
Request body (HoldAccountRequest):
| Field | Type | Required | Description |
|---|
reason | string | Yes | Reason for holding the account. Stored in the audit log. |
Response:
{"status": "held", "user_id": "user_xxxxx"}
Category Management
GET /v1/admin/categories — list all categories
POST /v1/admin/categories — create a new category (slug auto-generated from name)
PUT /v1/admin/categories/{slug} — update category metadata (name, description, icon, keywords)
DELETE /v1/admin/categories/{slug} — delete a category (blocked if active skills reference it)
Create a Category
curl -X POST "https://api.agentpowers.ai/v1/admin/categories" \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Developer Tools",
"description": "Skills for building and debugging software",
"icon": "code",
"sample_keywords": "debug, lint, test, build"
}'
Request body (CategoryCreateRequest):
| Field | Type | Required | Default | Description |
|---|
name | string | Yes | — | Display name. The URL slug is auto-generated from this. |
description | string | No | "" | Short description of the category. |
icon | string | No | "grid" | Lucide icon name. See valid icons below. |
sample_keywords | string | No | "" | Comma-separated keywords for discovery. |
Valid icons: bar-chart, code, grid, megaphone, palette, shield, trending-up, zap
Response:
{
"id": "cat-developer-tools",
"category": "developer-tools",
"name": "Developer Tools",
"description": "Skills for building and debugging software",
"icon": "code",
"sample_keywords": "debug, lint, test, build"
}
Returns 409 Conflict if the slug already exists or conflicts with an existing skill slug.
Update a Category
curl -X PUT "https://api.agentpowers.ai/v1/admin/categories/{slug}" \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"description": "Updated description",
"icon": "zap"
}'
Request body (CategoryUpdateRequest):
| Field | Type | Required | Description |
|---|
name | string | No | Updated display name. The slug is immutable. |
description | string | No | Updated description. |
icon | string | No | Updated Lucide icon name. Must be a valid icon. |
sample_keywords | string | No | Updated keywords. |
All fields are optional, but at least one must be provided. Returns 400 if no fields are supplied.
Response: Returns the full updated category object including id, category, name, description, icon, sample_keywords, and updated_at.
Refunds
POST /v1/admin/refunds/{purchase_id} — issue a full Stripe refund for a completed purchase
The refund is processed through Stripe. The existing charge.refunded webhook handler automatically updates the database, deactivates the license, and emails the buyer.
Issue a Refund
curl -X POST "https://api.agentpowers.ai/v1/admin/refunds/{purchase_id}" \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"reason": "Customer requested refund due to compatibility issue"
}'
Request body (AdminRefundRequest):
| Field | Type | Required | Default | Description |
|---|
reason | string | No | "" | Reason for the refund. Stored in the audit log. |
Response:
{
"refund_id": "re_xxxxx",
"status": "succeeded",
"amount_cents": 999
}
Returns 400 if the purchase is not in completed status or has no PaymentIntent. Returns 404 if the purchase does not exist. Returns 503 if Stripe is unavailable.
Marketing Email
POST /v1/admin/marketing-email sends a marketing email to all users who have opted in to marketing communications.
curl -X POST "https://api.agentpowers.ai/v1/admin/marketing-email" \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"subject": "New skills available on AgentPowers",
"html_body": "<h1>Check out our latest skills</h1><p>We have added 10 new skills this week.</p>"
}'
Request body (MarketingEmailRequest):
| Field | Type | Required | Description |
|---|
subject | string | Yes | Email subject line. |
html_body | string | Yes | HTML content of the email body. |
Response: Returns the result from the email service, including the number of recipients the email was sent to.
Pending Deletions
GET /v1/admin/pending-deletions — list users with pending account deletion
POST /v1/admin/cancel-deletion/{user_id} — cancel a pending deletion on behalf of a user
Seller + Admin Dual Role
A user can be both a seller and an admin simultaneously. When both flags are true, the dashboard sidebar shows all navigation items: Overview, My Skills, Earnings, Purchases, Settings, and Admin.
There is no conflict between the two roles. The frontend sidebar filter logic ensures all items pass when both isSeller and isAdmin are true.
Admin Check Endpoint
To verify whether the current user has admin access programmatically:
curl "https://api.agentpowers.ai/v1/admin/check" \
-H "Authorization: Bearer <token>"
Returns {"is_admin": true} or {"is_admin": false}. This endpoint does not require admin access — any authenticated user can call it.