Skip to main content

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:
SELECT id, email, display_name FROM users WHERE email = '[email protected]';

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.

Promoting and Demoting Admins via API

Once a first admin exists, subsequent admin management is done through the API. These endpoints are hidden from the public OpenAPI spec.

Promote a User to Admin

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):
FieldTypeRequiredDescription
resolutionstringYesResolution status. Use resolved_legit or resolved_fraud.
notesstringNoOptional 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):
FieldTypeRequiredDescription
reasonstringYesReason 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):
FieldTypeRequiredDefaultDescription
namestringYesDisplay name. The URL slug is auto-generated from this.
descriptionstringNo""Short description of the category.
iconstringNo"grid"Lucide icon name. See valid icons below.
sample_keywordsstringNo""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):
FieldTypeRequiredDescription
namestringNoUpdated display name. The slug is immutable.
descriptionstringNoUpdated description.
iconstringNoUpdated Lucide icon name. Must be a valid icon.
sample_keywordsstringNoUpdated 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):
FieldTypeRequiredDefaultDescription
reasonstringNo""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):
FieldTypeRequiredDescription
subjectstringYesEmail subject line.
html_bodystringYesHTML 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.