Passpage lets your app request a user's onboarding data (name, email, phone, address) with their explicit, field-level consent, instead of collecting it yourself.
Passpage sits after your existing sign-in flow. It is not an authentication provider. If you need login, keep using whatever you already use (email/password, Google, etc.). Passpage only handles the "now that they're signed in, get their profile details" step.
Prerequisites
- A registered app in the Passpage Developer Portal (inside the Passpage app, under Developer → API Keys & Apps)
- At least one redirect URI registered for your app, matching exactly (protocol, host, port, path) where your app will receive the authorization code
- A backend capable of making a server-to-server HTTPS request, the token exchange must never happen in the browser
- A place to securely store your
client_secretas a backend environment variable. It must never appear in frontend code, a public repo, or any client-side bundle
Step 1: Register your app
- Sign in to Passpage at app.passpage.outercirclelab.com
- Go to Developer → API Keys & Apps → Register New App
- Fill in your app name, category, and redirect URI(s), the exact URL(s) Passpage is allowed to send users back to
- On submit, you'll receive a
client_id(public, safe for frontend code) and aclient_secret(shown once, copy it immediately into your backend environment variables)
Redirect URI matching is exact. https://myapp.com/callback and https://myapp.com/callback/ are different values, and a mismatch fails at authorization time. Get this exactly right during registration.
Step 2: Decide what data you need
Requests are split into required_scopes (fields you cannot function without, the user can't deselect these) and optional_scopes (nice to have, the user chooses). Only mark a field required if your app genuinely cannot work without it.
| Scope | Returns |
|---|---|
profile:name | full_name |
profile:email | email |
profile:phone | phone (may be null) |
profile:address | address object: line1, line2, city, state, postal_code, country |
Step 3: Trigger the authorization popup
From your frontend, open a popup to:
https://app.passpage.outercirclelab.com/authorize
?client_id=YOUR_CLIENT_ID
&redirect_uri=YOUR_REGISTERED_REDIRECT_URI
&required_scopes=profile:name,profile:email
&optional_scopes=profile:address
function connectWithPasspage() {
const params = new URLSearchParams({
client_id: 'pas_client_xxxxxxxxxxxx',
redirect_uri: 'https://myapp.com/callback',
required_scopes: 'profile:name,profile:email',
optional_scopes: 'profile:address',
});
window.open(
`https://app.passpage.outercirclelab.com/authorize?${params}`,
'PasspageAuth',
'width=450,height=650'
);
}
If the user isn't logged into Passpage, they'll see a login/register screen first, on the same page, no separate redirect. Once authenticated, they see the consent screen with your app name, the required and optional fields, and which saved profile they're sharing from.
Step 4: Receive the redirect
After the user approves, Passpage redirects the popup to your registered redirect_uri with a code parameter:
https://myapp.com/callback?code=8f3a9b2c1d4e5f6a7b8c9d0e1f2a3b4c...
This code is single-use and expires 2 minutes after issuance. Send it immediately to your own backend, never call the exchange endpoint directly from the browser.
Step 5: Exchange the code (server-to-server only)
POST https://myipkticsugptcqldgmx.supabase.co/functions/v1/exchange
Content-Type: application/json
{
"client_id": "pas_client_xxxxxxxxxxxx",
"client_secret": "pas_sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"code": "8f3a9b2c1d4e5f6a7b8c9d0e1f2a3b4c..."
}
Success response:
{
"profile": {
"full_name": "Sachin R.",
"email": "sachin@example.com",
"address": {
"line1": "104, Green Park Society",
"line2": null,
"city": "Ahmedabad",
"state": "Gujarat",
"postal_code": "380015",
"country": "IN"
}
}
}
Only fields the user actually approved are present in the response.
Error responses
| Status | Error | Meaning |
|---|---|---|
| 401 | invalid_client | client_id not found, or client_secret doesn't match |
| 400 | invalid_grant | Code is invalid, unknown, or belongs to a different app |
| 400 | consent_revoked | User revoked this grant before you exchanged it |
| 400 | consent_expired | The code's 2-minute window passed before exchange |
| 500 | server_error | Something failed on Passpage's side, rare, retry once |
On consent_expired: this means a fresh authorization is needed, not a retry of the same code, expired codes are permanently dead.
Security requirements
client_secretnever touches the browser. If it appears in frontend code or a public repo, regenerate it immediately from the Developer Portal- The exchange call must originate from your backend, there's no legitimate reason for a browser to call it directly
- Redirect URI must match exactly what you registered, enforced server-side
- A code is single-use, design your callback handling to be idempotent rather than assuming a code arrives exactly once
What Passpage does not currently support
- No "fetch again" for expired data, once a code is exchanged, that's the only data transfer for that grant
- No webhook on revoke, check consent status in your own records if this matters for your use case
- Revoking does not delete data you've already received, you're responsible for your own retention and deletion practices
Testing
There is currently no separate sandbox/test mode. Integration testing happens against real Passpage accounts, use a personal test account with a dedicated test profile rather than real user data while building your integration.
Questions or integration issues: support@outercirclelab.com