Skip to main content

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. 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:
  1. Aptly handles the initial identity check (email + 6-digit code)
  2. Your app creates and manages its own session after verification succeeds
  3. All Aptly API calls happen server-side, invisibly to the end user
Architecture requirement — server component is mandatory. Your Aptly API key grants access to your entire organization’s data. If it appears anywhere in client-side HTML, JavaScript, or network requests visible in the browser, any user can extract it and query your full contact database. You must build a server-side component (Node.js, Python, a serverless function, etc.) that holds the API key and proxies all Aptly calls. A front-end-only app using an API key puts your entire organization at risk.
This document covers this context end-to-end.

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 (via postMessage, 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:
  1. Your app sends an email address to Aptly → Aptly looks up the contact and sends a 6-digit code
  2. Your app sends the code back → Aptly confirms it and returns the contact record
Your server then issues a signed session cookie. All subsequent requests use that cookie to identify the contact — Aptly is not involved again until the session expires and the user needs to re-verify. Codes expire after 10 minutes. After 5 failed attempts, the verification is permanently invalidated and the user must restart from step 1.

Step 1 — Initiate verification

Call this from your server, not the browser. Your API key must not appear in client-side code.
Response:
Return the 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.
On success:

Error cases

After 5 wrong codes the verification is permanently invalidated.
Do not trust a client-supplied contactId 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

Never accept 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.
On the client:

Contact object reference

The contact returned on successful verification:

Display a contact card


Once you have the contact’s _id, pass it as relatedId on the board endpoint to fetch all cards linked to that contact:
Cards are linked to a contact when the contact’s _id appears in the card’s references field.

Full end-to-end example

A minimal but complete Node.js + Express backend that handles all three auth endpoints, plus a corresponding browser-side flow: