API Keys for External Systems
Overview
API keys let a customer's external systems — a POS, a CRM, their own website — call into client-api without a CMS user's JWT. This module is the management surface for those keys: creating them, renaming them, enabling or disabling them, and deleting them.
All the routes sit on the public group combined with JwtLoginAuth and ModuleGate("api-key").
In other words the caller still needs a logged-in token, but is not required to have selected a
LINE OA first.
Important caveat: this module was ported one-for-one along with several bugs from the TypeScript original, each marked in the code as not to be fixed. Anyone debugging here should know about them in advance.
Business Flow
GET /api/api-keylists the current OA's keys, paginated in the{data, total}shape.POST /api/api-keycreates a new key.GET /api/api-key/:idreturns a key's details.PUT /api/api-key/:idedits a key, andPUT /api/api-key/:id/statusenables or disables it.DELETE /api/api-key/:iddeletes a key.- The external system attaches the key value to requests it sends to client-api, which validates it
against the
api_keytable.
Behaviours and bugs preserved from the original code
These are all documented in service.go.
- A
POSTwithout akeyfield in the body produces an INSERT missing the NOT NULLkeycolumn, raising a Postgres error and returning 500 withAPP_000. Creation only succeeds when the client supplieskeyitself. findAllsearches on the columnsapi_key.nameandapi_key.keywords, which do not exist in the table (they were copied from the auto-response module). So a non-empty?search=or?orderBy=namereturns 500.- Omitting
?lineOaIdturns the SQL intoline_oa_id = NULL, which returns{"data":[],"total":0}. - Passing multiple
?status=values compares an array literal against an enum column and returns 500. update()checks for a duplicate name but never uses the result (kept for parity), and a body containing a property that is not a real column returns 500.updateStatus()does not invalidate the find-all cache.
Key Files & Functions
The code lives in internal/modules/apikey/, comprising controller.go, service.go, and dto.go.
| Method | Route | Handler | Guard |
|---|---|---|---|
| GET | /api/api-key | ct.findAll | JwtLogin + ModuleGate |
| GET | /api/api-key/:id | ct.findById | JwtLogin + ModuleGate |
| POST | /api/api-key | ct.create | JwtLogin + ModuleGate |
| PUT | /api/api-key/:id | ct.update | JwtLogin + ModuleGate |
| PUT | /api/api-key/:id/status | ct.updateStatus | JwtLogin + ModuleGate |
| DELETE | /api/api-key/:id | ct.deleteById | JwtLogin + ModuleGate |
The middleware order is jwtLogin, then gate, then the handler. The guard has to run first so
that selfId and organizationId are present in CLS for the gate to resolve against; without them
the gate fails open.
Connections to Other Services
- Permission — requires
JwtLoginAuthplusModuleGate(d, "api-key"). Customers whose organization has theapi-keymodule disabled get a 403, while onemoby users bypass the gate.PolicyModuleApiKeyexists in the enum but is not used to annotate these routes. - Tables —
api_key. - Redis — caches find-all results (with the known bug that
updateStatusdoes not invalidate it). - Key consumer — line-management-client-api-go, a separate service.
- Companion module without routes — the API Client Registry.