Admaxxer · Documentation · Developer

Anything a human can do, the API can do.

REST under /api/v1/*, zod-validated, session-authenticated, rate-limited. The same surface the dashboard uses — and the same surface the Claude agent calls through.

Jump to endpoints AI Agent docs

Overview

The Admaxxer public API is a session-authenticated, zod-validated REST surface mounted at /api/v1/*. Every route is workspace-scoped, encrypted at rest, and rate-limited. Payloads are JSON; errors return structured bodies with codes and next-step hints.

The same routes serve three consumers: the React dashboard, the Claude agent's tool layer, and (in v1.5) external API key holders. There is no separate "internal" or "agent-only" surface — anything the human dashboard can read or write, the agent can read or write under the same authorization.

Authentication today is the session cookie. Bearer-key auth lands with the API-key milestone in v1.5; the handler layer is already key-aware, so the public-API migration will be transparent.

Authentication

Session cookie (v1)

HTTP-only, Secure in production, SameSite=Lax so cross-site CTAs from marketing pages still attach the cookie while CSRF on POST routes is blocked. Issued by the magic-link, Google OAuth, and Apple OAuth flows. Default lifetime is 30 days, controlled by SESSION_MAX_AGE_DAYS.

Every /api/v1/* handler reads the session, looks up workspaceId and member role, and rejects with 401 when missing. The session table is the single source of truth — there is no separate JWT or stateless token in v1.

API keys (v1.5 roadmap)

Bearer-token authentication via Authorization: Bearer <key>. Workspace-scoped, rotateable from Settings, audited per call. The apiKeys table is already in the schema (key hash, prefix, name, expiresAt, lastUsedAt). Migration path will be transparent — same /api/v1/* routes, just an additional auth layer in the middleware stack.

Endpoints

All routes are session-auth, zod-validated, and rate-limited. All payloads are JSON. All responses use snake_case for keys that round-trip to platform APIs (campaign_id, daily_budget) and camelCase for Admaxxer-internal keys (workspaceId, createdAt).

Connections

CRUD over encrypted Meta + Google credentials. Tokens are sealed with AES-256-GCM at rest and never logged. All routes require session auth and resolve to the active workspace.

Method Path Purpose
POST /api/v1/connections Create a new ad-platform connection. Body accepts the long-lived Meta user token or a Google OAuth refresh token + developer token. Response returns connection ID and a status probe.
GET /api/v1/connections List connections in the active workspace. Returns id, platform, status, expiry, last-sync timestamp, and decrypt-readable boolean.
DELETE /api/v1/connections/:id Soft-delete a connection. Triggers a meta-purge job for the 7-day data deletion SLA on Meta-side data.
POST /api/v1/connections/:id/test Force a credential probe — calls a single read endpoint on the platform and returns status. Useful when the user has just rotated a token.

Campaigns

Read and mutate campaigns on the active workspace's connections. Mutations are gated by the platform-side rate limit and audited to ad_sync_logs.

Method Path Purpose
GET /api/v1/ads/campaigns List campaigns. Query params: platform, connectionId, status. Uses the 15-minute insights cache where possible.
POST /api/v1/ads/campaigns Create a new campaign. Body validates objective, daily_budget, name, and status (default PAUSED). Same surface the agent uses.
PATCH /api/v1/ads/campaigns/:id Partial update — name, status, daily_budget. Whatever you don't send stays untouched.
DELETE /api/v1/ads/campaigns/:id Pause or archive a campaign depending on platform semantics. Always reversible from the dashboard.

Insights

The metrics layer. Backed by the 15-minute ad_sync_logs cache and platform APIs as fallback.

Method Path Purpose
GET /api/v1/ads/insights Aggregated metrics (spend, impressions, clicks, conversions, ROAS, CTR, CPC). Params: platform, since (ISO date), until (ISO date), campaignId (optional), breakdown ('day' | 'campaign' | 'platform' | omitted).

Chat

Streaming SSE endpoints that wrap the Claude agent. Same routes the in-product chat panel uses.

Method Path Purpose
POST /api/v1/ads/chat Send a user turn to the ad-ops agent. SSE response with text deltas, tool_use events, confirm_required envelopes, and a final usage block.
POST /api/v1/analytics/chat Read-only analytics chat (the ⌘J surface). Restricted to PIPE_ALLOWLIST tools; cannot mutate ads.

Rate limits

Per-route rate limits
Surface Limit Window Notes
Connections (CRUD) 30 req per minute, per user Burst-tolerant — protects against paste-loop bugs.
Campaign mutations (POST / PATCH / DELETE) 60 req per minute, per workspace Co-shared across all members of a workspace.
Chat (POST /chat) 20 req per minute, per user One request equals one user turn regardless of internal tool iterations.

Underlying platform limits. Meta long-lived user tokens cap at roughly 200 calls per hour. Google Ads API sits at 15,000 ops per day on the default developer-token tier. Admaxxer's per-user limits sit well below these — but be aware of them when designing high-volume workflows. A 429 from /api/v1/* can come from either layer; the body's retryAfter field tells you how long to wait.

Webhooks

Inbound — Stripe

Endpoint: /api/stripe/webhook. Signature verified with STRIPE_WEBHOOK_SECRET. Drives the billing mirror — checkout, subscription, invoice, and charge events. Metadata keys are snake_case across the wire (user_id, plan_key, ga4_client_id, utm_source, etc.) — see CLAUDE.md GL#41 for the canonical key list.

Outbound — Roadmap (v1.5)

Customer-facing outbound webhooks (campaign-state-changed, daily-summary, anomaly-detected) are scoped for v1.5. The webhooks table is in the schema. The delivery loop and HMAC signing land alongside the API-key milestone — both target the same v1.5 release.

Architecture

Server

TypeScript strict + Express. Drizzle ORM over Postgres on Neon (single canonical URL — see CLAUDE.md). Tinybird hosts 33+ analytics pipes (visitors, revenue, MER, LTV, cohorts, MMM, forecast, incrementality). Anthropic SDK (@anthropic-ai/sdk) powers the Claude agent. Stripe SDK handles billing. BullMQ workers run on Upstash Redis with ioredis (maxRetriesPerRequest:null, handled retryStrategy, shared connection across queues — see GL#67).

Client

React 18 + Vite. TailwindCSS + shadcn/ui (radix primitives) + framer-motion + Recharts. wouter for routing. react-helmet-async for per-route meta. TanStack Query for server state. The motion vocabulary on landing.tsx is the canonical reference for premium UI elsewhere in the product.

Storage

Postgres on Neon (one canonical URL, see CLAUDE.md). Migrations are idempotent raw SQL (CREATE TABLE IF NOT EXISTS, ADD COLUMN IF NOT EXISTS, INSERT ... ON CONFLICT DO UPDATE) executed via a pg Pool, or via drizzle-kit push. Token storage uses AES-256-GCM via server/crypto.ts; ENCRYPTION_KEY rotates without reissuing connections.

Edge

SSR_MODE='crawlers-only' middleware. The crawler-detector sniffs the User-Agent; SSR templates render full semantic HTML for AI ingesters; humans get the SPA. sitemap.xml + robots.txt + llms.txt are published from server/.

Background jobs

BullMQ workers backed by Upstash Redis. All jobs are idempotent — re-queue is safe.

Caching

SSR & crawlability

Admaxxer ships SSR templates for every public page so AI crawlers see full semantic HTML instead of an empty SPA shell. Detection is User-Agent based; rendering is handled by server/ssr/middleware.ts with SSR_MODE = 'crawlers-only'. Never change to 'all' — that breaks the entire site UI for human visitors.

Allowed crawlers

GPTBot, ClaudeBot, PerplexityBot, DeepSeek, Googlebot, Bingbot, FacebookBot, and the long tail. See server/robots.ts for the canonical Allow list.

Discovery surfaces

/sitemap.xml — every public page with priority and hreflang. /llms.txt — structured content index for AI ingesters. /robots.txt — explicit crawler policy.

Adding a new public page requires updating the route-matcher, the SSR template, the robots Allow list, the sitemap, and the llms.txt index. The crawler detector itself does not need changes for new routes — only for new bots.

Errors & status codes

HTTP status codes returned by /api/v1/*
Code Meaning Body / next step
401 Unauthorized Session cookie missing or expired, or bearer API key invalid. Re-auth via /login or rotate the key.
402 Quota exceeded Plan-level monthly cap hit. Body includes upsellUrl pointing at /pricing for the in-product upgrade flow.
422 credentials_unreadable AES-GCM decrypt failed — token bytes are corrupted or the encryption key has rotated. Prompt the user to reconnect via /integrations.
429 Rate-limited Either a local Admaxxer limit or an upstream Meta/Google limit. Body includes retryAfter in seconds.

Every error body includes code and message at minimum. Quota errors include upsellUrl. Rate-limit errors include retryAfter in seconds. Decrypt errors include connectionId so the UI can deep-link the user to the reconnect modal.

Observability

Frequently asked

Is there a public API key flow?
Not in v1. All /api/v1/* calls are authenticated via the session cookie that the dashboard uses. A bearer-key flow (workspace-scoped, rotateable, audited) is on the v1.5 roadmap. The handler layer is already key-aware so the migration will be transparent.
What's the underlying rate limit on Meta and Google?
Meta long-lived user tokens: roughly 200 calls per hour. Google Ads API: 15,000 ops per day on the default developer-token tier. Admaxxer's per-user rate limits sit well below these so a single user almost never hits a platform-side throttle.
How long do session cookies last?
Default 30 days, controlled by SESSION_MAX_AGE_DAYS. Cookies are HTTP-only, Secure (in production), and SameSite=Lax to permit cross-site CTAs from marketing pages while blocking CSRF.
Are insights cached?
Yes. Each (workspace, platform, query-shape, date range) tuple is cached for 15 minutes via ad_sync_logs(cacheKey). Google access tokens are cached in-memory for 50 minutes — just under their 60-minute TTL — to avoid a refresh round-trip on every campaign call.
How do I subscribe to outbound webhooks?
Outbound webhooks are not in v1. Stripe webhooks are inbound (we receive payment events at /api/stripe/webhook). Customer-facing outbound subscriptions (campaign-state-changed, daily-summary, anomaly-detected) are scoped for v1.5.
Where does observability data go?
Token usage persists per chat message. Tool side-effects audit to ad_sync_logs. Error tracking goes to Sentry with redacted payloads. Raw token bytes never leave the encryption boundary.
What's the SSR mode?
crawlers-only. The middleware sniffs the User-Agent — if it's a known crawler (GPTBot, ClaudeBot, PerplexityBot, Googlebot, etc.) we render the SSR template; otherwise the SPA hydrates normally. SSR_MODE = 'crawlers-only' — never change to 'all', it breaks the entire site UI.

Build something

Read the agent reference next — same routes, different consumer. The Claude agent calls the same /api/v1/* endpoints you do; its tools wrap them in a tool-use loop with per-step server-side authorization.

AI Agent docs Back to Documentation