seamless-auth-api is the contract source for a family of sibling repos under the same
parent directory (fells-code org). This document is the deep coupling map:
what depends on this API, how, and what changes here ripple outward. CLAUDE.md carries
the short version + the ripple protocol; this file is the detail to read before/while
making a contract-affecting change.
Last surveyed: 2026-09-08. Versions and line numbers drift — treat specifics as leads to re-verify, not gospel. Re-run the survey when the dependency graph changes.
browser
└─ @seamless-auth/react ............ client SDK (cookie-based session)
└─ @seamless-auth/express ..... server adapter (cookies ⇄ Bearer, JWKS verify)
└─ THIS API (seamless-auth-api) ... Bearer/JSON contract, no cookies
├─ @seamless-auth/types ..... shared Zod schemas (this API consumes)
└─ @seamless-auth/messaging* . email/SMS adapter contract (this API consumes)
Note the auth-style boundary: the React SDK uses cookies (credentials: 'include'),
while this API is Bearer/JSON with no cookies (see README "Bearer Token Contract").
The server adapter (@seamless-auth/express / @seamless-auth/core) is what bridges
them — it holds seamless-access / seamless-refresh / seamless-ephemeral cookies on
the browser side and speaks Bearer + service tokens + JWKS to this API. Direct
browser→API integration is possible ("Direct HTTP APIs (advanced)") but the recommended
path goes through the adapter.
seamless-auth-server — @seamless-auth/core + @seamless-auth/express (v0.13.0), @seamless-auth/fastify (v0.4.0)
The server-side adapter SDK; a thin stateless proxy + cookie manager. Highest coupling.
- Calls ~50 of this API's routes via
authFetch()withAuthorization: Bearer,x-seamless-service-token,x-seamless-client-ip. Does not yet forwardx-seamless-client-user-agent, which this API honours under the same service-token rule and which the device class telemetry depends on. Covers/login,/registration/register, all/otp/*,/webAuthn/*,/magic-link*,/refresh,/users/*,/organizations/*,/step-up/*,/sessions*, all/admin/*, all/internal/*,/system-config/*. - JWKS: fetches
/.well-known/jwks.json(createRemoteJWKSet+jwtVerify, RS256), validatesissagainst the auth server URL. Reads claimssub,sid,aud,iss. - Refresh: POST
/refreshresponse unpacked directly into cookies — expectssub, token, refreshToken, ttl, refreshTtl, roles?, email?, phone?, organizationId?. - Status-code coupling: branches on exact codes, e.g. magic-link poll treats
204as "not yet verified". - The Fastify adapter carries the same route table and identity choices, in
packages/fastify/src/routes/proxyRoutes.ts. A route change has to land in both or the two adapters diverge;packages/fastify/tests/parity.test.jsis what catches that. - No shared types package — coupling is 100% string-literal route paths + response shapes.
- Breaks if this API changes: any route path/method, JWKS path or key format/alg, token
claim/field names (
sub/sid/...), the/refreshresponse shape, or branch-significant status codes.
seamless-auth-js — @seamless-auth/client, @seamless-auth/react (v0.12.0), @seamless-auth/react-native
One repository, three packages. @seamless-auth/client is the framework-agnostic core: the
headless client, the session store, and the transport (cookie for browsers, bearer for native
apps, which mirrors the adapter's x-seamless-auth-transport: bearer contract and calls
POST /auth/refresh). @seamless-auth/react and @seamless-auth/react-native are thin bindings
over it. Route strings, parsed response fields and the Omit<..., 'token' | 'sub'> result
types all live in the client package now, so a contract change here ripples into one place and
both bindings pick it up. The notes below describe that shared surface.
Drop-in React auth UI (email/phone OTP, magic link, WebAuthn/passkeys, OAuth, step-up,
organizations). Hardcodes ~38 endpoint paths in src/createSeamlessAuthClient.ts.
- Parses response fields for flow control:
message === 'Success', plustoken,refreshToken,delivery, and the/users/meshape (user,credentials[],organizations[],activeOrganization). - Generic error handling (trusts
response.ok), so contract breaks often fail silently. - Breaks if this API changes: route renames,
message/user/credential field renames, switching an endpoint's auth mode (ephemeral ↔ access), or response-shape changes to/users/me, OTP, or organization endpoints.
Shared Zod schemas / TS types — the contract's source of truth. Reverse coupling: changes here propagate into this API and the SDKs.
- This API imports it at 5 sites:
schemas/internal.responses.ts(AuthEventSchema),schemas/credential.responses.ts(CredentialApiSchema, extended),schemas/admin.responses.ts(AuthEventSchema,SessionSchema),schemas/session.responses.ts(SessionSchema),routes/users.routes.ts(UpdateCredentialRequestSchema,DeleteCredentialRequestSchema). - Highest-risk exports: credential request/response schemas (used in route validation),
AuthEventSchema(snake_case, serialized directly),SessionSchema,RoleSchema. ⚠️ Known drift:RoleSchema's regex is duplicated in this API atsrc/lib/scopedRoles.ts:6(ROLE_NAME_PATTERN) instead of imported — they can silently diverge. Consider importing from the types package.- Breaks if changed here: rename/remove an exported schema, change/tighten a field or
validator (e.g.
RoleSchemaregex), changeIsoDatetransform → forces coordinated releases across this API + both SDKs.
Provider-agnostic email/SMS adapter contract. Reverse coupling.
- This API consumes it at
src/config/directMessaging.ts(factory + AWS/Twilio transports,MessagingConfigurationError) andsrc/services/messagingService.ts(callssendOtpEmail,sendOtpSms,sendMagicLinkEmail1:1).sendBootstrapInviteEmailis part of the adapter contract but no longer called by this API. - Core contract:
EmailTransport/SmsTransport(send()+name),AuthMessagingService(the 4 send methods), and theSend*Inputpayload types (to,token,magicLinkUrl, …). - Breaks if changed here:
AuthMessagingServicemethod signatures/payloads, transportsend()signature, package renames, new required adapter config, or removingMessagingConfigurationError.
The public login edge for apps that speak OpenID Connect (ADR 0001).
It builds on @seamless-auth/express for first-party sign-in and calls the
/oidc/* routes with its own service token (SEAMLESS_OIDC_SERVICE_TOKEN) for provider storage, signing, client metadata, claims and auth context.
- Contract: the
oidcEdgemodule of@seamless-auth/types(OidcEdgeRoutes,OIDC_EDGE_CONTRACT_VERSION). The edge refuses to start against an API on another contract version, or one that answers 404 becauseoidc_provider_enabledis off. - Breaks if changed here: any
/oidc/*route, request or response shape, the/oidc/signrefusal codes, theacr/amrderivation, or the service-token check.
| Repo | What it is | Coupling |
|---|---|---|
seamless-auth-admin-dashboard |
Admin UI | @seamless-auth/react + @seamless-auth/types; consumes /admin/* + /internal/* via the SDK |
seamless-templates |
Express + React starter templates (scaffolded by the CLI) | @seamless-auth/express and @seamless-auth/react; break only if those SDK public APIs change |
seamless-auth-internal-api |
Internal/enterprise API variant | Tracks this codebase closely; likely needs parallel changes for shared logic |
seamless-auth-docs |
Public docs | Documents the HTTP contract — update when routes/schemas change |
seamless-cli (create-seamless) |
Scaffolding CLI | Generates projects wired to the SDKs |
- Route paths / methods —
src/routes/*.routes.ts. Hardcoded as strings in both SDKs. - Response schemas —
src/schemas/*.responses.ts. Field renames/removals break parsing. - Token contract — access/refresh/ephemeral/service tokens, claim names (
sub,sid), the/refreshresponse shape, JWKS at/.well-known/jwks.json(RS256). - Status / error codes — adapters branch on specific codes (e.g. magic-link
204). - Shared schemas in
@seamless-auth/typesand the messaging adapter contract. - Auth mode of a route (ephemeral vs access vs service) — see
src/middleware/attachAuthMiddleware.ts.
An auth-mode change has no safe release order, because the adapter decides which cookie to
read from its own table and the API decides which token type to accept. Ship the API first
and an older adapter still sends the old token; ship the adapter first and it sends a token
the older API refuses. Either way the route answers 401 until both sides land.
So an auth-mode change is a single coordinated release, called out in every changeset involved, and adopters upgrade the API and the adapter together. Ordering only becomes a free choice if the API accepts both identities for a deprecation window first, which is a deliberate extra step, not the default.
Worked example: moving /webauthn/register/* from ephemeral to access (2026-09-08)
touched this API, @seamless-auth/core, @seamless-auth/express, @seamless-auth/fastify
and @seamless-auth/react in one coordinated minor across all three repos.