The developer portal listed this API with no reference documentation: catalog was the only domain that had a spec anywhere in the tree. Generated from the backend's own drf-spectacular schema (/classic-models/api/schema/, version 4.7.0), filtered to the operations this domain's gateway actually exposes — derived from the Kong routes and their request-transformer rewrites in Konnect, not from guesswork. Every route resolved to a backend operation; the counts match one for one. Paths and server are the **gateway's**, not the backend's. A portal reader calls https://workforce-dev.sttlab.local/workforce/v1/..., and the gateway rewrites that onto /classic-models/api/v1/... upstream. Path parameters are renamed to the gateway's capture names where the backend used a different case. Components are pruned to what these paths transitively reference. Note: catalog-api/openapi.yaml does NOT follow this convention — it documents the backend paths and http://localhost:8000 as its server, so its operations cannot be called as written from the portal. Left alone here; worth aligning separately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
workforce-api
Kong routing/policy layer for the workforce slice of
classic-models-api (see /Users/stephanetailland/git/classic-models-api),
split out per the DDD decomposition in ../kong-konnect-deployment-strategy.md.
The backend stays a single monolith — only Kong does the domain split.
Structure mirrors ../sales-api/../payments-api — single MCP route from
the start, not ../catalog-api's original two-route pattern (see
../STATUS.md section 18).
Two resources, both with a REST surface and MCP tools:
Office(single keyofficecode) — full CRUD + anemployeessub-resource.Employee(single keyemployeenumber) — full CRUD +reports(direct reports, self-referential FK) andcustomers(assigned sales rep's customers) sub-resources.
| Route | Method | Backend path |
|---|---|---|
/workforce/v1/offices/ |
GET, POST | /classic-models/api/v1/offices/ |
/workforce/v1/offices/{officecode}/ |
GET, PUT, PATCH, DELETE | /classic-models/api/v1/offices/{officecode}/ |
/workforce/v1/offices/{officecode}/employees/ |
GET | /classic-models/api/v1/offices/{officecode}/employees/ |
/workforce/v1/employees/ |
GET, POST | /classic-models/api/v1/employees/ |
/workforce/v1/employees/{employeenumber}/ |
GET, PUT, PATCH, DELETE | /classic-models/api/v1/employees/{employeenumber}/ |
/workforce/v1/employees/{employeenumber}/reports/ |
GET | /classic-models/api/v1/employees/{employeenumber}/reports/ |
/workforce/v1/employees/{employeenumber}/customers/ |
GET | /classic-models/api/v1/employees/{employeenumber}/customers/ |
/workforce-mcp |
GET, POST, DELETE | MCP streamable-http |
Auth & MCP tool ACLs — the one domain where customer gets zero access
Both viewsets use ReferenceDataPermission
(authentication/permissions.py): read_only/product_manager/support
get read access, writes are admin-only, and — unlike every other
domain built this session — customer is not in the allow-list at
all, not even for reads. Verified directly against the permission
class, not assumed: this is genuinely internal staff/organizational data,
and the backend enforces that explicitly. All 7 read tools
(list-offices, get-office, get-office-employees, list-employees,
get-employee, get-employee-reports, get-employee-customers) have ACL
[admin, read_only, product_manager, support] — no customer. Both write
tools (create-office, create-employee) are admin only.
openid-connect on both surfaces (workforce-api REST audience,
mcp-workforce MCP audience), RFC 8693 exchange chain
MCP → workforce-api → classic-models-api. IdP: local Keycloak.
Structure
Same dev/ + kong/rest/ + kong/mcp/ layout as sales-api. Deploy key
and broker secret reused/mirrored from catalog-api and the live broker
client, same pattern as every domain since payments-api.