API
Accepted architecture · 1 October 2026, option 2
Agentfy hosts the harness; Store Deputy API exposes the single MCP; the OpenCart module exposes a protected REST/JSON API. Store Deputy governs connections, actor/shopper context, permissions, approvals and audit without building another harness. Connect → login/register → secure pairing → automatic agent provisioning in Agentfy with Store Deputy MCP → return to the module. App contains teams, agents, account, billing and per-agent chat.
This replaces the earlier same-day direct-module-MCP design. Existing code and prototypes below do not implement the new integration. Shopper sessions and the real OpenCart cart have separate scoped bindings; cookies stay in the shop. See the accepted central MCP decision for boundaries, delivery stages and remaining contract work.
Built; runs on a developer's machine · 1 October 2026. Not deployed. It has only ever talked to the two local test shops. It has no catalog, price or agent features.
The API is the one service that knows who a person is, which team they belong to, what their role allows and which stores the team has connected. Every other part asks it. Nothing else talks to a shop.
What it does today
- Accounts. Register, sign in, renew a session, sign out. Signing out takes effect at once: every request is checked against the live session, not only against the token it carries.
- Teams and roles. A team owns stores. Its members are owners, admins or members. Owners and admins invite people; only an owner changes roles, removes another owner or deletes the team. A team cannot be left without an owner.
- Stores. Connect a shop with the one-time code from its module, list a team's stores, look at one, disconnect it. The secret shared with the shop is stored encrypted.
- Service checks. A health check, and an interactive description of every route.
| Area | Routes |
|---|---|
| Sign-in | /auth/register, /auth/login, /auth/refresh, /auth/logout, /auth/me |
| Own profile | /users/me |
| Teams | /teams, /teams/:teamId |
| Membership | /teams/:teamId/members, /teams/:teamId/members/:memberId |
| Stores | /teams/:teamId/stores, /teams/:teamId/stores/connect, /teams/:teamId/stores/:storeId |
| Health | /health |
Registering, signing in, renewing a session and the health check are open. Every other route needs a signed-in person.
How the code is arranged
All code lives in slices. There are two groups of feature slices and one group of setup slices.
api/src/slices/
setup/ shared plumbing, no business rules
prisma/ the database connection
error/ one format for every failure
response/ wraps every success as { data, success: true }
crypto/ encrypts stored connection secrets
core/ the marker for routes that need no sign-in
health/ the health check
user/ who is acting — a namespace folder with no code of its own
user/ the person
auth/ sign-in, sessions and the check on every route
team/ the team that owns stores
userTeam/ membership and roles
store/
store/ a connected shop, and the client that talks to its moduleA feature slice has the same shape every time. The store slice, as an example:
store/store/
store.module.ts wires each contract to its implementation
store.controller.ts presentation: routes, input, output
store.prisma this slice's database tables
dtos/ presentation: request and response shapes
domain/ rules and contracts
store.service.ts who may connect, which addresses are acceptable
store.gateway.ts contract: saving and loading stores
bridge.gateway.ts contract: talking to a shop's module
store.types.ts
errors/ one file per failure
data/ implementations
store.gateway.ts PostgreSQL through Prisma
store.mapper.ts database rows into domain types
bridge.gateway.ts turns the module's answers into domain results
repositories/bridge/ the HTTP client for Bridge Protocol v1The database is reached directly through Prisma inside a gateway; there is no separate repository class for it. An outside service is different: the shop module is wrapped in a repository with its own types, so the protocol's wire format stays out of the rules.
One request, layer by layer
An owner confirms connecting a shop. This is what happens to that request.
- Sign-in check. Before any slice runs, a check registered for every route establishes who is calling and that their session is still live.
- Controller. Accepts the request, validates its shape and hands it to the service. It holds no rules.
- Service — the rules. The caller must be an owner or an admin of the team. The shop must be reachable over HTTPS; plain HTTP is accepted only for a local address in development and tests. The address the owner returns to must belong to the same shop that is being connected.
- Bridge contract. The service asks "pair with this shop". The data layer signs the request and sends it. A reply without a valid signature counts as "shop unreachable", even if the shop answered with a success status.
- Store contract. The service asks "save this store". The data layer encrypts the secret and writes the row. An earlier connection to the same shop is marked disconnected.
- Controller. Returns the store and the address to send the owner back to.
The service never imports the database client or the HTTP client. It knows two contracts and nothing about how they are met.
Rules every slice follows
- Slice folders are singular; routes are plural.
- A contract is an abstract class in
domain/. Its implementation sits indata/, and the slice's module ties the two together. - A slice uses another slice through its domain. The store slice asks the membership service whether the caller's role is allowed; it never reads another slice's data layer.
- Every route carries an operation name. The app's generated client takes its method names from it.
- Every route requires sign-in unless it is explicitly marked as open.
- Each slice owns the schema of its own tables. A script assembles them into one schema before the database tools run.
- Imports between slices use short aliases —
#setup/…,#user/…,#store/…— not long relative paths.
The points where this API knowingly differs from CleanSlice are listed in the overview.
How it is checked
End-to-end scenarios run against a real PostgreSQL in Docker, not against mocks. The store scenarios also need the two local test shops running. Their last recorded results are in the repository's plan, dated 29 September 2026. They were not re-run for this page.
Not built yet
- Tools for agents, and any link to Agentfy.
- Reading a shop's catalog.
- Price proposals, approvals, jobs and the operation ledger described in the implementation proposal.
- Email confirmation and password reset.
- A limit on sign-in and registration attempts.
- Rotation of the key that signs connection requests.
- Anything for platform operators. Each person has a platform-role field, but nothing grants it and nothing checks it. See Admin.
- Deployment. No hosting has been chosen.