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. Or use the CLI
# analyze a public repo
npx ejector analyze https://github.com/owner/repo --mcp
# a private repo
npx ejector analyze https://github.com/owner/repo \
--token ghp_xxx -o openapi.jsonThe 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. Use it with GPT Actions, LangChain, or any tool that speaks OpenAPI.
curl https://app.ejector.dev/api/repos/<REPO_ID>/openapi \
-H "Authorization: Bearer ejector_xxx"ChatGPT / GPT Actions: in a custom GPT, add an Action and import the schema from the URL above, with an API key header of Authorization: Bearer ejector_xxx.
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://app.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
Agents act through the proxy. They authenticate once with the user's credentials, then call operations by name — Ejector handles cookies, CSRF, sessions, and token refresh under the hood.
# 1. authenticate as the end user
TOKEN=$(curl -s https://app.ejector.dev/api/proxy/<REPO_ID>/auth/login \
-H "Authorization: Bearer ejector_xxx" \
-d '{"email":"user@acme.com","password":"…"}' | jq -r .token)
# 2. discover available actions
curl https://app.ejector.dev/api/proxy/<REPO_ID>/actions \
-H "Authorization: Bearer $TOKEN"
# 3. do anything the UI can do
curl -X POST https://app.ejector.dev/api/proxy/<REPO_ID> \
-H "Authorization: Bearer $TOKEN" \
-d '{"endpoint":"/api/stripe/checkout","method":"POST",
"body":{"planId":"pro","cycle":"monthly"}}'
# → { "url": "https://checkout.stripe.com/…" }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.
- 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.