CorroborateMe

Passkey login API

For developers who want passkey sign-in without building WebAuthn themselves. Included in Pro, $5.99 a month or $55 a year. Passkeys are one method on the hosted login page, next to email code, password, Google, and Apple. Below are the endpoints that page uses.

Recommended: use the hosted page

Create a login link (see authentication for AI agents) and send the person to loginUrl. The hosted page collects the email and runs the passkey ceremony. Your server then polls GET /api/login-links/{sessionId} and reads user_id.

Auth endpoints

The browser page that calls them must be served from corroborateme.com or from the company host {slug}.corroborateme.com. The passkey relying party id is the apex hostname, so a page on your own domain cannot finish the WebAuthn ceremony against this server.

  • POST /api/login-links/{id}/passkey/auth/options with JSON company_id and email. Response: { ok: true, options } or an error such as email_required, company_id_required, not_registered, or no_passkey.
  • The hosted page passes options to @simplewebauthn/browser startAuthentication.
  • POST /api/login-links/{id}/passkey/auth/verify with JSON company_id and response (the assertion from startAuthentication). A success body includes user_id and email and completes the login link.
import { startAuthentication } from "@simplewebauthn/browser";

const optRes = await fetch(
  "/api/login-links/" + sessionId + "/passkey/auth/options",
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ company_id: companyId, email }),
  },
);
const optJson = await optRes.json();
const assertion = await startAuthentication({ optionsJSON: optJson.options });

const verRes = await fetch(
  "/api/login-links/" + sessionId + "/passkey/auth/verify",
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    credentials: "include",
    body: JSON.stringify({ company_id: companyId, response: assertion }),
  },
);
const verJson = await verRes.json();

sessionId, companyId, and email in that sample are the login-link session, the company id (co_...), and the person's email. The fetches are relative, so they hit the CorroborateMe host. Copying this file onto another origin will not satisfy the relying party check.

Enrollment

Adding a passkey happens after the person already has a company session. The hosted page calls POST /api/login-links/{id}/passkey/register/options and .../register/verify with credentials included (the company session cookie). Those two routes require that session. The options call returns { ok: true, options }. The verify call takes { company_id, response } and returns { ok: true }.

Questions

Can I run the passkey ceremony on my own website?

Not against this relying party id. Send the person to the login URL. A custom ceremony has to be hosted on corroborateme.com or the company subdomain.

Does passkey login need the API key in the browser?

No. The options and verify auth routes are public for that login-link session. The API key stays on your server, which created the login link and polls it.