Passage is an account linking API for developers. Your users log in on their own device — credentials never touch your servers. Read billing data, payment methods, order history, and more from any online service.
const link = await passage.links.create({
provider: "tmobile",
operations: [
{ type: "mobileBillingStatement:read" },
{ type: "paymentMethod:read" },
],
webhookUrl: "https://api.yourapp.com/passage/webhook",
});
// Give this to your mobile app — opens as an App Clip
return { claimCode: link.claimCode, url: link.appClipUrl };
Your backend calls POST /v1/links with the provider
and operations you need. You get back a claim code and an App Clip
URL — a lightweight entry point that requires no app install.
The user opens the App Clip and logs into the target service in a secure webview. Their credentials stay entirely on their device — Passage never sees or stores them. You get a live session with full telemetry.
Once the automation completes, you receive an ES256-signed webhook with validated, structured results — billing statements, payment methods, order histories, whatever you requested. Verify the signature and you're done.
Users authenticate directly in a secure webview on their device. Passwords, MFA codes, and session tokens never leave the phone. Your servers only receive the structured data you asked for.
credentials: never_transmitted Navigate, click, fill, wait for selectors — familiar browser automation primitives, relayed in real-time over WebSockets.
Request billing data and payment methods in a single link. Operations execute sequentially — one authentication, multiple results.
Console logs, network requests, navigation events, screenshots — streamed in real-time. Debug sessions as they happen.
ES256 JWT signatures on every webhook payload. Fetch the public
key by kid, verify the signature, and trust the data.
Links open as iOS App Clips — instant, frictionless entry points that require zero download. Users are connecting in seconds.
Built on Cloudflare Durable Objects. Sessions run at the edge, close to your users. Sub-50ms relay latency worldwide.
Pull billing statements from carriers and utilities. Verify amounts, due dates, and payment history — without asking users for screenshots or PDFs.
Read saved payment methods from one service and add them to another. Let users move their cards between platforms without re-entering details.
Confirm a user owns an account at a third-party service — telecom, utility, or retailer — without ever touching their credentials.
Access order histories from delivery apps, e-commerce platforms, and subscription services. Power cashback, receipt tracking, or spend analytics.
Verify insurance coverage, policy details, or claims status by connecting to carrier portals. Replace manual document uploads.
Use the Sessions API to build any workflow against any website with a login. If a user can do it in a browser, Passage can automate it.
Write custom automations against any website. The session primitive is generic — if a user can log in, Passage can connect.
import { verifyWebhook } from "@passage/connect";
export async function handlePassageWebhook(req: Request) {
const payload = await verifyWebhook(req, {
publicKeyUrl: "https://connect.getpassage.ai/webhook_verification_key/get",
});
if (payload.status === "complete") {
const billing = payload.results["mobileBillingStatement:read"];
// Structured, validated data — ready to use
console.log(billing.amountDue); // "142.50"
console.log(billing.periodStart); // "2025-02-01"
console.log(billing.currency); // "USD"
}
} import PassageSDK
struct ConnectView: View {
var claimCode: String
var body: some View {
PassageConnectView(claimCode: claimCode)
.onConnectionComplete { result in
// User authenticated, automation ran, data returned
print("Billing: \(result.billing.amountDue)")
}
.onConnectionError { error in
print("Error: \(error.localizedDescription)")
}
}
} Passage Connect gives you pre-built automations and structured data. But underneath it is a general-purpose session primitive — and you can use it directly.
Both APIs share the same session infrastructure — Cloudflare Durable Objects, WebSocket relay, on-device webview. Connect is built on Sessions. Use whichever fits your use case.
const session = await passage.sessions.create();
const browser = await session.connect();
// Navigate, click, fill — Playwright-style commands
await browser.navigate({ url: "https://example.com/login" });
// Hand control to the user — they log in on their device
const result = await browser.yieldToUser({
status: { subheading: "Log in to continue..." },
conditions: [
{ type: "networkRequest", urlContains: "api/account", captureBody: true },
],
timeout: 120_000,
});
// Extract data from the captured response
const account = JSON.parse(result.networkCapture.responseBody);
// Keep going — scrape, navigate, extract more data
await browser.navigate({ url: "https://example.com/settings" });
const html = await browser.innerHTML({ selector: ".account-details" }); Get API keys, explore the docs, and ship your first integration. Developer preview is free.