Overview
Ejector turns any web app into a clean, typed API that AI agents can call. You connect a source — a GitHub repo, a platform URL, or any live web app — and Ejector produces an OpenAPI 3.1 spec and a hosted MCP server exposing every callable action your app supports. Agents authenticate as a real user and do anything the UI can: read data, change settings, even make payments. No browser automation, no scraping.
The full lifecycle has three phases:
Hosting & data flow
A common first question: when my app becomes an API, who hosts it and where do requests go? The short answer — Ejector never hosts your app or runs your code.Your app keeps running exactly where it already does. Your database queries, Stripe calls, and emails all still execute on your own infrastructure. What "becomes an API" is a thin, authenticating access layer in front of it.
Ejector always hosts two things: the OpenAPI spec and the MCP server— the "menu" that tells an agent what it can call. Where the actual execution requests travel depends on which of three modes you choose.
Mode 1 — Ejector-hosted proxy (default)
Agents call a proxy URL on Ejector. Ejector looks up the stored credential, authenticates as the user, and forwards the request to your live app. Easiest setup — nothing to deploy. Ejector is in the request path (routing + auth injection), but your app still does all the work.
Mode 2 — Self-hosted proxy
Ejector generates the proxy server (a small Hono app); you deploy it next to your app on Docker, Fly, Railway, or Vercel. Ejector is not in the request path, and your secrets never leave your infrastructure. Best for privacy and compliance.
Mode 3 — Direct (auth patch)
You apply the small auth patchso your app accepts Bearer tokens directly. Agents call your app straight, and Ejector only hosts the spec + MCP server for discovery — it's out of the data path entirely. Lowest latency.
What's hosted where
What your subscription keeps running
One thing worth being explicit about: you are not paying to generate a spec. Generation happens once and is the easy part. The subscription pays for the work Ejector does continuously to keep your app reliably callable by agents — every day, on every request. On Mode 1(the default), four jobs run on Ejector's infrastructure for as long as your plan is active:
Because that work happens per-call, paid plans are usage-metered (by agent calls). The artifacts you can export — the OpenAPI JSON, the MCP config — are just a descriptionof your own app; they can't authenticate or execute anything on their own. The live bridge is the product.
Quickstart
The fastest path: connect a public repo and watch the endpoints appear.
1. Add a repository
From your dashboard, click Add repository and paste a GitHub URL. For a private repo, toggle private and provide a fine-grained token with Contents: read. Ejector clones it with a shallow checkout — your code is analyzed, never stored.
2. Review & go live
When analysis finishes — usually in seconds, since extraction is AST-based, not LLM-based — the dashboard lists every endpoint. Toggle exactly what agents can reach, then hand an agent the source's OpenAPI URL (for GPT Actions / LangChain) or its MCP URL (for Claude Desktop / Cursor) along with an API key.
The three engines
Ejector auto-detects which engine to use from whatever you give it.
Engine 1 — Source code
A GitHub URL or local path. Ejector parses the abstract syntax tree and extracts tRPC procedures, Next.js route handlers, server actions, direct database queries, GraphQL operations, and Python backends. This is the deepest engine — it sees operations that have no public API.
Engine 2 — Platform
A known platform URL like mystore.myshopify.com or dashboard.stripe.com. Ejector maps the platform's official API into agent tools. No repo, no code changes — just an API key. Supported: Shopify, Stripe, Notion, WordPress, WooCommerce, Airtable.
Engine 3 — Network capture
Any other URL. A headless browser navigates the live app and reverse-engineers the API from observed traffic — useful when you have neither source nor a documented API.
What gets extracted
For source-code analysis, each extractor maps your code to clean operations:
Each operation becomes a path in the OpenAPI spec with its method, input schema, auth requirement, and a description. Ejector also detects every environment variable your code references — see Configuring secrets.
Choosing what to expose
Not every operation should be agent-callable. On the repository page, each endpoint has a toggle. Disable anything internal — admin routes, destructive operations, webhooks — and hit Save selection. The excluded set is stripped from both the OpenAPI spec and the MCP tool list, so agents never even see them.
Authentication
The hard part of agent access is auth: web apps authenticate humans with cookies, but agents speak tokens. Ejector bridges this automatically. When an end user connects, they provide the same email + password they use in your app. Ejector then runs an auth strategy chain until one works:
The token Ejector obtains represents that specific user— it inherits exactly their permissions. An agent acting for user A can never see user B's data.
The auth patch
Many apps — especially Next.js + Supabase — only accept cookies, so their API routes reject the Bearer token Ejector sends. The fix is a tiny, non-breaking patch you add to your own codebase: accept a Bearer token in addition to cookies. Browser users are unaffected.
Next.js + Supabase
In your middleware.ts, let Bearer-authed API calls through:
export async function middleware(request: NextRequest) {
// ─── Ejector: allow agent (Bearer) access to API routes ───
const bearer = request.headers
.get("authorization")?.replace("Bearer ", "");
if (bearer && request.nextUrl.pathname.startsWith("/api/")) {
return NextResponse.next();
}
// ─── end Ejector patch ───
// …your existing cookie-based middleware…
}Then make your server Supabase client read the Bearer token when present:
import { createServerClient } from "@supabase/ssr";
import { cookies, headers } from "next/headers";
export async function createClient() {
const bearer = (await headers())
.get("authorization")?.replace("Bearer ", "");
if (bearer) {
// Agent request — authenticate with the token
return createServerClient(URL, ANON_KEY, {
global: { headers: { Authorization: `Bearer ${bearer}` } },
cookies: { get: () => undefined, set: () => {}, remove: () => {} },
});
}
// Browser request — use cookies (unchanged)
const store = await cookies();
return createServerClient(URL, ANON_KEY, { cookies: { /* … */ } });
}createClient() now works for both browsers and agents automatically. Deploy, and agents can reach the routes that were previously cookie-only.For NextAuth, Express, or custom frameworks, Ejector generates the equivalent patch for your stack — find it on the repo's Configure keys tab.
Configuring secrets
Some actions need server-side keys your code already uses — a Stripe secret key for checkout, SMTP credentials for email, a service-role key for admin operations. Ejector detects every process.env.* reference during analysis and lists exactly which keys are required, where to find each one, and which files use it.
Provide them on the Configure keyspage. They're encrypted at rest with AES-256-GCM. These are the same values you already store in your hosting environment — you're just making them available to the operations agents call.
Connect via OpenAPI
Every analysis produces an OpenAPI 3.1 spec at a stable URL, with a servers block pointing at the managed proxy — so an imported operation calls through Ejector and the credential is injected for you. Read it with a Bearer key, or import it publicly with ?k=:
# authenticated read (your secret key)
curl https://www.ejector.dev/api/repos/<REPO_ID>/openapi \
-H "Authorization: Bearer ejector_xxx"
# public, importable URL (publishable key) — for GPT Actions import
https://www.ejector.dev/api/repos/<REPO_ID>/openapi?k=<PUBLISHABLE_KEY>?k=… URL (the publishable key only exposes operation shapes), then set Authentication → API Key → Bearer ejector_xxx for the actual calls. The repo page gives you the exact import URL.LangChain: load the spec with the OpenAPI toolkit and your agent gets one tool per operation.
Connect via MCP
Ejector hosts a Model Context Protocol server per repo, so Claude Desktop, Cursor, and any MCP-aware agent can discover and call your tools directly.
Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"ejector": {
"url": "https://www.ejector.dev/api/repos/<REPO_ID>/mcp",
"headers": { "Authorization": "Bearer ejector_xxx" }
}
}
}Restart Claude and every enabled endpoint appears as a callable tool. The same URL works in Cursor's MCP settings.
Making calls
The account is connected once (in the dashboard, or by a customer via Customer Connect), so the credential is already bound to the API key. An agent just calls the proxy with that key — Ejector injects the session, refreshes tokens, and forwards to your app. Two equivalent forms:
# Body form — { endpoint, method, body }
curl -X POST https://www.ejector.dev/api/proxy/<REPO_ID> \
-H "Authorization: Bearer ejector_xxx" \
-d '{"endpoint":"/api/stripe/checkout","method":"POST",
"body":{"planId":"pro","cycle":"monthly"}}'
# → { "status": 200, "data": { "url": "https://checkout.stripe.com/…" } }
# RESTful form — the path goes straight on the proxy URL (what GPT Actions uses)
curl https://www.ejector.dev/api/proxy/<REPO_ID>/api/user/profile \
-H "Authorization: Bearer ejector_xxx"Discovery is the OpenAPI spec / MCP tools/list above — not a per-call login. (The downloadable self-hosted proxy is the one that exposes its own /auth/login + /actions endpoints.)
Reading data the UI shows
Some data has no API at all — it's read straight from the database by the page, or only ever rendered server-side (a plan on a billing page, say). Ejector reaches it two ways, both acting as the signed-in user, exposed as the read_data and read_page tools:
Customer Connect
Beyond your own agent, you can let each of your customers connect theirs. Drop one snippet into your app — or share a link — and each customer signs in (or signs up) with their own account and gets a personal key bound to their session. Their agent acts only as them, fully isolated; usage bills to you; you can revoke any connection instantly.
<script src="https://www.ejector.dev/embed.js"
data-ejector-key="ej_pub_…"
data-theme="light"
data-label="Connect your AI agent" defer></script>Customer Connect is available on every plan— your customers' usage counts toward your account's call quota. It requires a verified domainon any plan — collecting your customers' logins is only allowed for an app you've proven you control.
Verifying your domain
Before a live app is exposed to agents — and before Customer Connect — you verify you own its domain. Add a DNS TXT record (Ejector auto-detects your DNS provider and, for Cloudflare, can add it in one click with a scoped token), or host a small /.well-known/ejector-verify.txtfile. This gate is what stops Customer Connect from being usable to harvest credentials for an app you don't control.
Security model
A few principles keep this safe:
- Per-user scope. Agents inherit the exact permissions of the user who authenticated. Your app's existing authorization rules — RLS, middleware, role checks — still apply.
- You choose the surface. Only the endpoints you enable are reachable. Everything else is invisible to agents.
- Domain-verified. Exposing a live app — and Customer Connect — requires proving you control the domain (DNS-TXT), so it can't be used to collect logins for apps you don't own.
- Per-customer isolation. With Customer Connect, every customer gets their own key bound to their own session; one customer's agent can never act as another. Revoke any of them instantly.
- Keys stay yours. With the auth patch, server-side secrets never leave your infrastructure. Stored secrets are encrypted with AES-256-GCM.
- Revocable keys. Ejector API keys are hashed, scoped per account, and revocable instantly from Settings.
- Code is not stored. Repos are shallow-cloned to a temp directory, analyzed, and deleted. Ejector keeps the resulting spec, not your source.