docs
/
AppEngine API

Client account (client-data)

The signed-in customer's own account — profile, orders, forms, reservations, tickets, messages, addresses, wishlists, files, benefits, shared accounts and payment links.

ClientAccountModule serves /client-data/* (63 routes): everything a "My account" page needs, always for the person on the request. Customer-facing sites and apps call it; the admin side uses the CRM and storefront modules instead.

Who "me" is

No route takes a customer id. The person is resolved from the request:

  1. x-client-authorization: Bearer <token> — the customer's session, sent next to the site's app token. It must be a customer or user token of this org, or the request is refused 401.
  2. With no client header, the bearer token itself — unless it is a System identity (the site's app token), which is no one.
  3. An expired client token is treated as signed out, not as the app: routes answer as if nobody is signed in rather than returning the integration account's empty data.

So a request with only the app token reaches the controller but has no "me". dashboard and profile then return null; files, all, forms, reservations, tickets, profile update and benefit enrollment answer 401; one order by id answers 403. Send the client header on every call. See customer authentication and endpoints.

How it differs from /repository

/repository is generic CRUD on any datatype, governed by permissions and whatever query the caller sends. /client-data decides the owner itself — orders and addresses by the session's author, forms by its email, wishlists by any of its identifiers, files by a folder named for it — and returns shaped views (a profile patch limited to three fields, a form row that hides its access token). Nothing in the body or path can widen the scope to another customer.

Guards

Every route is jwt except one @PublicRoute (invitation validation). There is no @StaffOnly or @Roles here; the class-level @Roles(Customer) is commented out. Rules are enforced per route in the service: shared-account routes need a manager, PUT profile needs a customer (a staff user gets 403), wishlist ids must belong to the caller.

Dashboard

GET/client-data/dashboardJWT
GET/client-data/allJWT

dashboard bundles profile, addresses, recent orders, reservations, open tickets, notifications, analytics, benefits and affiliate stats with a summary. all returns every section for an account page; each section stands alone — one the caller may not read (for example tickets answering 403) comes back empty instead of failing the page.

Profile and verification

GET/client-data/profileJWT
PUT/client-data/profileJWT
GET/client-data/verification/statusJWT
POST/client-data/verification/email/sendJWT
POST/client-data/verification/email/verifyJWT
POST/client-data/verification/phone/sendJWT
POST/client-data/verification/phone/verifyJWT

PUT profile accepts only firstName, lastName and phone (1–120 characters; phone 7–15 digits, empty removes it). Anything else is 400. Changing the phone resets phoneVerified.

Orders, forms and transactions

GET/client-data/ordersJWT
GET/client-data/orders/:orderIdJWT
GET/client-data/formsJWT
GET/client-data/transactionsJWT

Orders are those authored by the session's email, username or id. forms returns the customer's form_submission rows matched on their email — ?status=, ?page=, ?limit= (max 100) — including pending rows (a form link asked for and not yet used), each with the form's schema so values can be labelled.

Reservations

GET/client-data/reservationsJWT
GET/client-data/reservations/:reservationIdJWT
POST/client-data/reservationsJWT
PUT/client-data/reservationsJWT
DELETE/client-data/reservations/:reservationIdJWT
POST/client-data/reservations/available-slotsJWT

Delegates to the reservations service (CRM). Reading one by id needs a customer session.

Tickets, messages and notifications

GET/client-data/ticketsJWT
GET/client-data/tickets/:ticketNumberJWT
POST/client-data/ticketsJWT
POST/client-data/tickets/with-attachmentsJWT
PUT/client-data/ticketsJWT
GET/client-data/messagesJWT
POST/client-data/messagesJWT
PUT/client-data/messages/:messageId/status/:statusJWT
GET/client-data/conversationsJWT
GET/client-data/notificationsJWT
POST/client-data/notifications/push-tokenJWT

Tickets go through the CRM ticket desk: a customer sees only their own. with-attachments is multipart. Messages are the CRM inbox, newest first by createdate; ?conversationWith= narrows to one thread.

Addresses and wishlists

GET/client-data/addressesJWT
POST/client-data/addressesJWT
DELETE/client-data/addresses/:addressIdJWT
POST/client-data/addresses/autocompleteJWT
GET/client-data/addresses/place/:placeIdJWT
GET/client-data/wishlistJWT
POST/client-data/wishlistJWT
POST/client-data/wishlist/:productIdJWT
DELETE/client-data/wishlist/:productIdJWT
DELETE/client-data/wishlist-list/:wishlistIdJWT

Saving an address that matches an existing one updates it instead of duplicating. autocomplete ({ input, sessiontoken?, components? }) and place/:placeId are Google Places lookups.

A customer may own several named sf_wishlist records. POST wishlist/:productId adds a SKU (idempotent) to wishlistId from the body, or to their oldest wishlist, creating one if none exists. A wishlistId that belongs to someone else is 403.

Files

GET/client-data/filesJWT
POST/client-data/files/uploadJWT
DELETE/client-data/files/:pathJWT

Each customer owns a private folder, client-account/<identity>/, chosen from the session and never from the request. Upload is multipart field file, with an optional location sub-folder. The list returns signed URLs valid for 15 minutes. Paths with .., encoded separators or another customer's prefix are 400.

Benefits

GET/client-data/benefitsJWT
POST/client-data/benefits/enrollJWT
GET/client-data/benefits/enrollmentsJWT

benefits returns the customer's benefits (from their groups and those attached directly), their enrollments split into pending, active and rejected, and availableBenefits they can still apply for. enroll takes { benefit, documents?, notes?, formData?, agreementAccepted? }; IP address and user agent are recorded.

Shared accounts

GET/client-data/shared-accounts/:accountId/membersJWT
GET/client-data/shared-accounts/:accountId/ordersJWT
PUT/client-data/shared-accounts/:accountId/members/:associationIdJWT
POST/client-data/shared-accounts/:accountId/invitesJWT
GET/client-data/shared-accounts/invites/validate/:invitationTokenNo auth
POST/client-data/shared-accounts/invites/accept/:invitationTokenJWT

Managing needs an active manager association (customer_association) on that account — a buyer is refused 403, not shown a filtered list. PUT takes { role: manager | buyer, status: active | suspended }; the account must keep at least one manager. The invite link is validated publicly before sign-up; accepting happens once the invitee is signed in.

GET/client-data/pay/:idJWT
POST/client-data/pay/:id/intentJWT
POST/client-data/pay/:idJWT

:id is a payment token, an sf_transaction or an sf_invoice, looked up in that order. For an invoice, the paid amount and balance are worked out from the payments recorded against it, never its stored amountPaid. Gateways come back with public keys only; a half-configured gateway is left out.

The amount is always the server's balance: intent answers { alreadySettled: true } when nothing is owed, and POST pay/:id ({ gateway, ref, method?, email?, name? }) records the payment against the balance, so a settled link cannot collect twice. The payer is the signed-in customer, or the email given.

Also here

GET/client-data/payment-methodsJWT
POST/client-data/payment-methodsJWT
DELETE/client-data/payment-methods/:paymentMethodIdJWT
PUT/client-data/payment-methods/:paymentMethodId/defaultJWT
GET/client-data/analyticsJWT
POST/client-data/expert/messagesJWT
GET/client-data/expert/messagesJWT
POST/client-data/expert/hireJWT
Not implemented yet

The payment-method routes do nothing (GET returns []). analytics returns zeros. GET transactions and GET expert/messages return no data. The email verification routes and phone send return success without sending or checking a code. Do not build on these.

The expert/* routes write messages and tickets to the platform's shared org (SHARED_ORG), not the site's own org.