Before you build — choose your auth context
Aptly supports several distinct auth patterns depending on who is using the app you’re building. Pick the one that matches your use case before writing any code. Getting this wrong is the most common source of integration problems.Context 1 — Customer portal (contact verification SSO) ✅ Recommended starting point
Who this is for: You’re an Aptly customer building an app for your contacts — a client portal, a booking page, a status tracker. Your end users are people stored as contacts in your Aptly account. How auth works:- Aptly handles the initial identity check (email + 6-digit code)
- Your app creates and manages its own session after verification succeeds
- All Aptly API calls happen server-side, invisibly to the end user
Context 2 — Aptly user session (plugin or embedded app)
Who this is for: You’re building a plugin, iframe embed, or extension that runs inside the Aptly UI for logged-in Aptly users. How auth works: Aptly passes a signed user session token to your app at load time (viapostMessage, a URL parameter on redirect, or another handoff mechanism). Your app uses that token to call Aptly APIs on behalf of the logged-in user — no separate login flow required.
Architecture: Because the token is scoped to the session and carries no persistent API key, these apps can be front-end-only. There is no long-lived credential to protect.
See the Embedded App Authentication guide for the full implementation.
Context 3 — Public / unauthenticated app
Who this is for: You’re building a public-facing surface — a company website, a listings page, a booking widget — where the visitor is anonymous and no login is required. How auth works: Either no auth at all (public endpoints only), or an Aptly API key for read-only public data. Architecture: If the app uses an API key, you still need a server component for the same reasons as Context 1. If the app uses only fully public endpoints, it can be front-end-only.Contact verification SSO — how it works
This is the full implementation guide for Context 1. The flow has two steps:- Your app sends an email address to Aptly → Aptly looks up the contact and sends a 6-digit code
- Your app sends the code back → Aptly confirms it and returns the contact record
Step 1 — Initiate verification
Call this from your server, not the browser. Your API key must not appear in client-side code.requestId to the browser. It is not a secret — the code sent to the user’s email is the secret. Store requestId in your UI state (a hidden field, React state, etc.) so you can submit it in step 2.
Custom email content
{{ verificationCode }} and {{ expirationTime }} are replaced automatically. Omit emailHtml to use the Aptly default.
Error cases
A
404 means the email isn’t in your Aptly contact database. Show a “not found” message or a registration prompt — do not fall back to creating a session anyway.
Step 2 — Confirm the code
The browser submits the code +requestId to your server. Your server calls Aptly and creates the session.
Error cases
After 5 wrong codes the verification is permanently invalidated.
Step 3 — Issue a session cookie
Do not trust a client-suppliedcontactId on subsequent requests. MongoDB ObjectIds are not secrets — they are time-ordered and partially predictable. If you store _id raw in localStorage or an unsigned cookie, any user can swap it for another contact’s ID. Always lock the contactId inside a signed token that your server verifies on every request.
After the confirm step succeeds, mint a JWT, set it as an HttpOnly cookie, and return only safe display fields to the browser.
Validate the session on every protected request
contactId as a query param or body field from the browser on protected endpoints. The only valid source is the value extracted from the verified session cookie.
Session persistence on revisit
On page load, your browser-side code should call a lightweight/auth/me endpoint to check whether a valid session cookie already exists. If it does, the user is still logged in and does not need to re-verify.
Contact object reference
The contact returned on successful verification:Display a contact card
Loading related board cards for a verified contact
Once you have the contact’s_id, pass it as relatedId on the board endpoint to fetch all cards linked to that contact:
_id appears in the card’s references field.