Skip to main content

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"]
}
FieldTypeRequiredDescription
uploadTypestringYeshtml, blog, or amp
htmlContentstringYes (html/amp)The email HTML
descriptionstringYes (html/amp)Content description
techniquestringNoKinetic technique (tabs, carousel, etc.)
complexitystringNobeginner, intermediate, advanced
emailPurposestringNoe.g. ecommerce, newsletter, transactional
exampleTypestringNopositive (default) or negative example
keyFeatures / bestPracticeTagsstring[]NoTags for categorization
embeddingTextstringNoHuman-reviewed embedding text (overrides the auto-built text)

The endpoint:

  1. Builds embedding text (technique-specific semantic descriptions injected), or uses the provided embeddingText
  2. Generates a single embedding with OpenAI text-embedding-3-large (3072 dims)
  3. Stores the full document — HTML, metadata, and embedding text — in the Supabase rag_documents table (service-role only, no size cap)
  4. 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).

CallAction
GETList 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
}