Skip to content

An agent-facing AutoGTM API: keys, dollar budgets, inbox, suppress lists, import - #77

Merged
ralyodio merged 1 commit into
mainfrom
worktree-autogtm-api
Sep 21, 2026
Merged

ralyodio merged 1 commit into
mainfrom
worktree-autogtm-api

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

What this is

An agent-facing API shaped the way Explee's AutoGTM API is shaped, so an agent that already knows how to drive that product can drive ours with a base URL and a key. Everything on it is a translation onto code that was already here: campaigns, the policy engine, the approval path, credits, the importer, analytics. There is still exactly one way to send.

Read it the way an agent would: GET /api/v1/public/llms.txt then GET /api/v1/public/openapi.json. Both are keyless and rendered from one table (apps/api/src/autogtm-docs.ts); a test refuses a documented route that does not exist.

Surface

/api/v1/autogtm/*

  • Projects = offerings. Daily ceiling in USD, autopilot switch, analytics with a row per campaign.
  • Campaigns list/get/patch (name, instructions, targeting, daily limit), start/stop, budget, analytics.
  • Inbox per campaign with need_reply / replied / sent / unsubscribed tabs, full thread, reply, per-lead note. GET /autogtm/hot-leads across campaigns with since polling.
  • Suppress lists named, for people (email) and companies (domain). Adding one halts what is queued for anyone already on file.
  • Import a campaign from your own leads (≤5,000 per call): people with consented addresses, campaign membership, and one research crawl per company domain under that campaign.
  • Billing balance.

What had to be built underneath

  • Workspace API keys (api_keys, migration 0038). og_live_… secrets hashed like sessions, presented as X-API-Key or Bearer, acting with the owner's current role. A key cannot mint keys. Minted on /settings or POST /api/v1/api-keys. RequestActor.credential says which path a caller came in by.
  • Dollars → caps. CONTACT_PRICE_USD is the cheapest credit pack per credit. A daily USD limit is stored on budget_json beside maxActionsPerDay = floor(usd / price), so the existing engine enforces it on every send. packages/pipeline/src/project-budgets.ts reconciles project ceilings into campaign caps each worker tick and synchronously after any budget or autopilot change: autopilot on splits by smoothed reply rate with a 20% even exploration slice; off only scales hand-set budgets down under the ceiling.
  • Policy: answering is not cold outreach. Gate 7 now skips the per-prospect weekly cap and cooldown when isFollowUp && conversationOpen. Nothing set isFollowUp before; recheckPolicy now sets it for reply cards and for expected_goal = 'continue_conversation'. 7b still routes such a send to allow_with_approval, and the daily cap, budget and suppression gates still bind. The API reply card is send_email because the capability matrix has no email/reply pair.
  • Suppression by address and domain. Match keys gained email: and domain:; matchKeysForPerson is the single builder the pipeline, cadence runner and API repo now share.
  • Attribution fix. The hand-recorded execute path left interactions.campaign_id null; it now carries the card's campaign.

Tests

bun run check is green: format, typecheck, 1,594 tests across 112 files (baseline 1,544). New: domain arithmetic and allocator, pipeline budgets and suppression keys, policy follow-up exemption, API keys end to end through the real resolver, and the AutoGTM routes end to end against a migrated database including a reply that leaves through a stub mailer and one refused for a suppressed lead.

Deliberately not in this PR

  • Lead data. Explee's 536M-person index is bought, not built. Apollo per credit is the obvious supplier; that is an adapter in packages/providers.
  • A pre-warmed sending domain pool. This is Explee's real moat and an ongoing ops burden (warming, DKIM/SPF/DMARC per domain, complaint loops, blocklists). Sends still leave through the workspace's own mailbox or the platform mailer. Start with a handful of warmed domains through a mailbox provider when volume justifies it.
  • MCP and CLI tools for the new routes. The API is the surface; the MCP app can grow tools that call it.

🤖 Generated with Claude Code

…sts, import

Adds /api/v1/autogtm/*, shaped like Explee's AutoGTM API so an agent that
already drives that product can drive this one with a base URL and a key.
Every route translates onto code that was already here — campaigns, the
policy engine, the approval path, credits, the importer, analytics — and
there is still exactly one way to send.

Underneath: workspace API keys (migration 0038, hashed, shown once, cannot
mint keys); USD daily limits stored beside the action caps the engine
enforces, with CONTACT_PRICE_USD as the exchange rate; a project-level
allocator that splits a ceiling across campaigns by reply rate on every
worker tick; suppression by email and domain through one shared key
builder; and a policy change so that answering someone who wrote to us is
not paced like cold outreach (still human-approved, still capped).

llms.txt and openapi.json are rendered from one table and served keyless;
a test refuses a documented route that does not exist. The hand-recorded
execute path now attributes its interaction to the card's campaign.

Not built on purpose: lead data (buy per credit) and a pre-warmed sending
domain pool (the real ops burden). See the README section.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ralyodio
ralyodio marked this pull request as ready for review September 21, 2026 17:30
@ralyodio
ralyodio merged commit 10a3fbf into main Sep 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant