Architecture
Authentication
Three ways in, depending on who is calling: passkey sessions for humans, signed requests for registered agents, and x402 payments for anonymous agents — where the payment itself is the authentication.
Human sessions
Sign-in mints a short-lived signed JWT delivered as an HttpOnly cookie. The middleware verifies the signature (not just cookie presence) on every request; roles are then re-resolved from the user record — never trusted from the token alone — so a role change takes effect immediately.
| Method | Path | Description |
|---|---|---|
| POST | /api/auth/webauthn | Register / authenticate a passkey (FIDO2) |
| POST | /api/auth/otp/start | Phone / email OTP |
| POST | /api/auth/google | Sign in with Google Identity Services |
| POST | /api/auth/apple | Sign in with Apple |
| POST | /api/auth/guest | Demo guest session (never admin in production) |
- Passkeys are the primary factor — phishing-resistant, bound to the registrable domain (RP ID from server config, never the
Hostheader). - Privileged operations require step-up TOTP; MFA state is only writable through
/api/security/mfa. - The
adminrole is pinned to a single founder account in production, and guest sessions can never hold it. - A locked account returns
423 Lockedon every authenticated route until unlocked through the recovery flow.
Registered agents: signed requests
An agent registered via POST /api/agents/keys holds its own Ed25519 key bound to your account with a spend policy. Requests are signed with RFC 9421 HTTP Message Signatures (Signature-Input + Content-Digest), and the policy — per-transaction limit, daily budget, category allowlist — is enforced server-side on every spend. See Agent Trust & Mandates.
Anonymous agents: payment is the auth
Pay-per-call endpoints need no account and no API key. The signed EIP-3009 authorization in the X-PAYMENT header proves control of the paying wallet cryptographically — a stronger claim than a bearer key, scoped to exactly one payment:
curl "https://furlpay.com/api/x402/fx?from=USD&to=EUR&amount=100"
# -> 402 with a quote. Sign it, retry with X-PAYMENT, get the resource.API keys
The SDK quickstart uses sk_test_… keys against the sandbox. Production API-key issuance ships with the developer dashboard; server-side sessions and x402 cover every current production surface.
Never send credentials in query strings