Integration guide

Make your app agent-ready.

Everything you need to connect a codebase, choose what agents can do, wire up authentication, and ship a clean API — end to end.

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:

1 · Connect
Point Ejector at your code or app. It detects the stack and extracts every operation.
2 · Configure
Toggle what agents can reach, provide server-side keys, and apply the auth patch.
3 · Integrate
Hand agents an API key + OpenAPI/MCP URL. They go live in minutes.

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.

AgentAI client
Bearer ejector_key
Ejector proxyEjector cloud
user token
Your appyour servers
query
DB / Stripe
Ejector sits in the path — it authenticates as the user, then forwards to your live app.

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.

AgentAI client
Bearer token
Your proxyyou deploy it
user token
Your appyour servers
query
DB / Stripe
Ejector generates the proxy; you deploy it next to your app. Ejector is never in the request path.

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.

AgentAI client
Bearer token
Your appyour servers
query
DB / Stripe
The auth patch lets your app accept Bearer tokens directly. Ejector only hosts the spec for discovery — out of the data path entirely.

What's hosted where

OpenAPI spec + MCP tool listEjectorDiscovery only
Your app, database, Stripe, emailYou (unchanged)Always
The auth-injecting proxyEjector (m1) · You (m2) · none (m3)Mode-dependent
Server-side secretsEjector (encrypted) or You (m2/m3)Your choice
Which should I pick?
Start with Mode 1 to get running with zero deploys. Move to Mode 3 (the patch) for the lowest latency once you're ready to ship a 20-line change, or Mode 2 if your security policy requires that nothing — not even the proxy — runs outside your infrastructure.

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:

Carries every agent call
The proxy receives each request, attaches valid credentials, forwards it to your live app, and returns the result — 24/7 infrastructure in the request path.
if the subscription ends → Stops forwarding → agents get 401
Keeps auth alive
Tokens expire in hours and sessions rotate. Ejector holds credentials encrypted and keeps minting fresh, valid tokens so the agent never hits a dead login.
if the subscription ends → Tokens expire → every call fails
Keeps the API in sync
You ship code constantly. Ejector re-analyzes on each deploy, updates endpoints, and flags breaking changes so the agent's map never goes stale.
if the subscription ends → Map drifts → calls hit dead endpoints
Control plane
Logs of exactly what agents did, rate limits, usage analytics, and instant revoke.
if the subscription ends → You lose visibility + control

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.

Modes 2 & 3 are billed differently — on purpose
If you self-host the proxy (Mode 2) or apply the auth patch (Mode 3), Ejector is notin your request path and isn't doing the per-call work above — so those aren't metered per call. They're flat-license / Enterprise instead. You only pay recurring usage when Ejector is recurring infrastructure.

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

terminal
# 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.json
That's the whole loop
Connect → analyze → review. Most repos finish in seconds because extraction is AST-based, not LLM-based.

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:

tRPCProcedures with full Zod input/output schemas, auth middleware, query vs mutation
Next.js routesapp/**/route.ts handlers, HTTP methods, path params, Zod validation
Server Actions"use server" functions, FormData fields, typed params
Server ComponentsInline data fetching (fetch, Prisma, Supabase) in RSC
DatabaseDirect supabase.from(), prisma.model.find(), Drizzle queries
Express / Fastify / Honoapp.get(), router.post() with middleware + validation
GraphQLQueries, mutations, subscriptions from schema or gql tags
PythonDjango urls/ViewSets, FastAPI decorators, Flask routes

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.

Principle of least privilege
Start narrow. Expose the handful of actions your agent genuinely needs, then widen as you build trust. You can change the selection anytime — it takes effect immediately.

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:

SupabaseScrapes the Supabase URL + anon key from your app's JS bundle, calls /auth/v1/token
FirebaseScrapes the API key, calls Google Identity Toolkit
Auth0Scrapes domain + client ID, calls /oauth/token
ClerkScrapes the publishable key, calls the Clerk sign-in API
NextAuthFetches the CSRF token, posts to /api/auth/callback/credentials
CustomCalls your detected login endpoint, then auto-discovers common login paths

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:

middleware.ts
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:

lib/supabase/server.ts
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: { /* … */ } });
}
20 lines, zero breaking changes
Every route that calls 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.

Prefer the patch over sharing keys
Where possible, deploy the auth patch and keep secrets on yourserver — Ejector forwards the user's token and your server uses its own keys. Only store a secret with Ejector when an operation genuinely can't run on your infrastructure.

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.

terminal
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:

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.

agent.sh
# 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.

Ready to connect your first repo?

It takes about two minutes.

Get started