Account API expansion
Which account capabilities the Shop API serves and which the storefront runs itself.
Account capabilities split by who performs them. The Shop API serves the shared ones: addresses (list, add, delete), the
card list, the profile read, orders, identity and the three checkout reads. The storefront calls them through the typed
@retail-demo/api/client (callAsAccount(actor, (api, options) => api.me.profile.get({}, options))), with a minified, route-only contract. The client provides TypeScript types and routing; the API validates
inputs at runtime. Full-contract clients, such as MCP and the backoffice, also validate before sending. Failures arrive as ORPCErrors whose problem has an
error member, and the storefront maps on that member, not on the status. Everything only the storefront performs runs
in the storefront Worker through shopOperation against the shop database and the shared AccountWriter
(see docs/architecture.md in the repository): the profile page,
every card control, address update and default, activity, connected agents, invitations, delegated sign-in and the
account-security family. Human-only operations require account:security and refuse invited agents, Poppy, delegated
OAuth clients and operators; they are not API routes and are excluded from agent discovery and grants.
Embedded verification and credentials
The storefront runs embedded verification itself: the shared account operations own operation creation, encrypted provider state, attempt leases and proof transitions, and the storefront executes the OAuth/MFA SDK call with its own cookie. It verifies the returned ID token and checks subject, MFA, freshness and the original authority before proving the offer. Concurrent or stale completions cannot settle another attempt. Provider jobs never enter browser action results. The storefront retains refresh tokens only after rechecking the original account and its own session cookie.
Invited agents and My Account
An invited agent’s Auth0 callback is recorded by the storefront itself (delegation.record): it checks the ID token it
just verified against the new cookie (subject, session, website audience, a sign-in at most ten minutes old) and
records the session through @retail-demo/identity/delegation, which checks the live approval, both lifecycles and bounded
expiry. Sign-out ends that session the same way (delegation.end). The storefront reads the stable claim namespace even
when the login issuer changes. Later invited-agent requests for shared capabilities reach the API with the web agent forwarder and
a signed invited-agent+jwt context; identity reads compare raw verified token claims with the retained cookie
snapshot. Live identity returns only the account display name for an agent banner; it does not expose the customer’s
email.
Auth0 My Account remains entirely website-owned. The API returns a boolean describing canonical database identity eligibility. The storefront applies origin/RP topology rules, acquires My Account tokens from its SDK, holds step-up credentials and executes provider requests. No My Account token, URL or transport moves to the API.
The procedure tests in apps/api/src/procedures cover the shared endpoints, and the storefront capability tests
(apps/webshop/tests/support/shop-capability.ts) cover the rest.
Routes
Routes are designed as resources: plural nouns, state changes as sub-resources, 201 with Location for creates and
204 for deletes. Nothing aliases the old paths. The contract is the single place a route is named, so a rename changes
the contract and the e2e URLs only.
| Procedure | Route |
|---|---|
getIdentity |
GET /v1/me/identity |
getCheckoutAddress |
GET /v1/me/checkout-address |
getCheckoutMethods |
GET /v1/me/checkout-methods |
createCheckoutPreview |
POST /v1/checkout-previews |
The three checkout reads sit with the shared capabilities rather than under a website prefix, and they are in the Poppy document. Agents must call these paths; the earlier website-prefixed URLs no longer exist.
Private activity, connected agents, agent invitations and guest invitation acceptance and inspection are not API routes
any more: the storefront runs them itself through the account domain (activity.*, agents.* and invitations.*
operations). Disconnecting an agent revokes a personal connection in the shop database, or the Poppy grant at the
Auth0 proxy with the service credential, and records it in the private activity. The storefront also sends the invitation
email with its own email binding. Nothing retries a failed send: the owner can resend the invitation with a fresh link
once its send claim expires.
The fresh-intent id a request is approved by is the freshIntentId input field (not operationId, which names the
OpenAPI operation).
Endpoints that replace the web-session surface
The API’s web-session operations are gone, and so is every customer endpoint that took over a part of them: the Shop API holds no web-session surface at all. The guest-cart merge, the status receipt pages, the profile page, the email-verification sync after sign-in, the claim of verified guest orders and the whole account-security family (fresh verifications and the acceptance of their proof, security methods and topology, embedded verification, phone and email contact changes, contact methods and account deletion) are not endpoints: the storefront runs them itself through the shared domain operations. The sync is the one operation that needs no active account, because it finishes the email change that holds the account in a contact transition. Email change and deletion synchronise the Auth0 proxy lifecycle through the storefront’s Auth0 proxy connection, and wake or end the open agent conversations through the shared ConversationHub.
Provider failures and explicit retry
Email changes and account deletions attempt provider work during the request. A partial failure preserves the approved operation, progress checkpoints and the account’s denial state. The account may remain restricted until that same operation is explicitly resumed; hourly jobs do not retry this provider work.
The customer can retry through the signed-out status page while their receipt remains valid. An operator can use the dedicated recovery endpoint without needing the deleted user to sign in. Neither path creates a new authorization or accepts an arbitrary provider command. See account recovery for credentials, the exact request and response handling. Payment recovery continues through the scheduled jobs.
Sign out everywhere
Sign out everywhere runs in the storefront, through the Management API, not in the Shop API. Disconnecting a connected agent runs in the storefront too.
The cutoff is written first, so this device is signed out even if the Management API fails; the page then says “We
couldn’t sign out your other devices”. Auth0 sessions used only by another application stay outside sign out everywhere; Poppy grants linked to the revoked storefront browser sign-ins are denied first. Independent grants remain connected.
The storefront requests a Management token with exactly read:sessions, delete:sessions and delete:refresh_tokens.
Refresh tokens are revoked, but customer API tokens already issued stay valid until they expire.
The human-only sessions page combines Auth0 browser sign-ins and active signed-in Poppy sessions. Bounded provider reads expose continuation, including empty historical batches, and partial failures remain visible. Approval links identify the approving browser, never the agent’s device. Missing approval browsers do not hide active agents. Fresh verification binds each selected browser/session/grant action to its exact source ID. Ending a Poppy session leaves its Account Token usable; disconnecting its exact grant signs out every session using that grant. Ending a browser sign-in records the local cutoff and denies linked Poppy grants before requesting Auth0 revocation with refresh tokens. Poppy-only actions leave the approving browser alone. Current-browser cleanup failures preserve their warning and owned operation through logout and sign-in; expired verification can restart for the same target.