Admin API Endpoints
All admin endpoints require verifyAdmin() authentication — the user must have a valid JWT and their email must exist in the admin_users table.
RAG Content Management
POST /api/admin/submit-content
Upload new content to the RAG knowledge base (v2 architecture: Supabase is the source of truth, Pinecone holds vectors).
{
"uploadType": "html",
"htmlContent": "<!DOCTYPE html>...",
"description": "Product showcase with tabbed navigation",
"technique": "tabs",
"complexity": "intermediate",
"emailPurpose": "ecommerce",
"keyFeatures": ["lightswitch", "mobileResponsive"],
"bestPracticeTags": ["tableStructure", "msoConditionals"]
}
| Field | Type | Required | Description |
|---|---|---|---|
uploadType | string | Yes | html, blog, or amp |
htmlContent | string | Yes (html/amp) | The email HTML |
description | string | Yes (html/amp) | Content description |
technique | string | No | Kinetic technique (tabs, carousel, etc.) |
complexity | string | No | beginner, intermediate, advanced |
emailPurpose | string | No | e.g. ecommerce, newsletter, transactional |
exampleType | string | No | positive (default) or negative example |
keyFeatures / bestPracticeTags | string[] | No | Tags for categorization |
embeddingText | string | No | Human-reviewed embedding text (overrides the auto-built text) |
The endpoint:
- Builds embedding text (technique-specific semantic descriptions injected), or uses the provided
embeddingText - Generates a single embedding with OpenAI
text-embedding-3-large(3072 dims) - Stores the full document — HTML, metadata, and embedding text — in the Supabase
rag_documentstable (service-role only, no size cap) - Upserts the vector plus small metadata into the Pinecone v2 index
POST /api/admin/update-content
Update existing RAG content: { "id": "...", "metadata": { ...updated fields } }. Rebuilds the embedding text and re-upserts with fresh embeddings.
POST /api/admin/delete-content
{
"id": "content-uuid"
}
GET /api/admin/list-content
Browse the full RAG library. Lists every vector ID via Pinecone listPaginated (no topK cap — the old dummy-vector query silently dropped data past 10,000 items), then batch-fetches metadata.
{
"success": true,
"count": 156,
"items": [{ "id": "...", "metadata": { ... } }]
}
Items are sorted by submission date, newest first.
GET /api/admin/rag-stats
Returns statistics about the RAG knowledge base:
{
"totalDocs": 156,
"totalChars": 2450000,
"htmlDocs": 98,
"blogDocs": 42,
"ampDocs": 16,
"avgCharsPerDoc": 15700,
"documents": [{ "id": "...", "type": "html", "chars": 12400 }]
}
Eval Harness
GET/POST /api/admin/eval
Service-role storage for the eval harness — the eval tables stay locked behind the server (no client RLS).
| Call | Action |
|---|---|
GET | List recent runs (up to 50) |
GET ?run=<id> | One run plus its results |
POST { "action": "createRun", "label": "...", "promptSetVersion": "...", "promptCount": 12 } | Create a run, returns { runId } |
POST { "action": "saveResult", "runId": "...", "result": { ... } } | Store one prompt's result (QA score, judge score, gen time, error) |
POST { "action": "finalizeRun", "runId": "..." } | Compute averages and mark complete. Errored prompts count as 0 in averages |
POST /api/eval/judge
LLM judge (Claude Haiku) scores a generated email for the eval harness. Admin-only.
Utilities
POST /api/admin/auto-tag
Uses Claude to automatically generate metadata tags for content:
{
"htmlContent": "<!DOCTYPE html>...",
"contentType": "html"
}
Returns:
{
"description": "Tabbed product showcase with radio button navigation and lightswitch fallback",
"technique": "tabs",
"complexity": "intermediate",
"emailPurpose": "promotional",
"keyFeatures": ["lightswitch", "mobileResponsive"],
"bestPracticeTags": ["tableStructure", "msoConditionals"],
"embedding_text": "Dense structural + intent paragraph for retrieval..."
}
POST /api/admin/submit-feedback
Collect feedback on generated emails:
{
"prompt": "Original generation prompt",
"rating": "positive",
"ragUsed": true,
"ragExamplesCount": 5
}