Admin Service Public API Standard
Goal
admin_service is the single servers_v2 API origin configured by BO/BFF:
admin_serviceowns writes, business actions, approvals, state transitions, orchestration, human BO identity/RBAC/data scope, and narrowly-scoped PII reveal actions.- Explicit BO read contracts are published by
admin_service; complex projections may be implemented by an internal service such asreporting_service, which is never a second BO/BFF origin. - External docs must expose only the canonical public surface, not historical compatibility aliases.
Route Style
Use a hybrid style:
- Resource writes use RESTful methods.
- Business commands use explicit
POST .../actions/.... - Sensitive PII reveal uses explicit
POST .../pii/reveal. - BO reads use explicit, tested contracts on the public router; historical broad query handlers remain private unless individually allow-listed.
HTTP Method Rules
| Case | Method | Pattern | Notes |
|---|---|---|---|
| Create resource | POST | /api/v1/{resources} | Do not use /create suffix. |
| Partial field update | PATCH | /api/v1/{resources}/{id} | For ordinary editable fields. |
| Replace subresource/config | PUT | /api/v1/{resources}/{id}/{subresource} | Idempotent replacement only. |
| Delete resource | DELETE | /api/v1/{resources}/{id} | Real delete only. |
| Business action | POST | /api/v1/{resources}/{id}/actions/{action} | Status transitions, audits, money, side effects. |
| Collection action | POST | /api/v1/{resources}/actions/{action} | Batch commands or action without one URL id. |
| PII reveal | POST | /api/v1/{resources}/pii/reveal | Body is exactly { "guids": [...] }; guids are positive integers; batch decrypts phone and bank fields, requires audit, and must not be a GET. |
| BO query | POST | /api/v1/admin/{resources}/{query} | Fixed request model; admin_service injects database-owned dataScope after resolving the BFF's opaque human Session; may proxy an internal read service. |
| Health | GET | /health, /api/v1/health | No authentication required. |
Naming Rules
- Resource names are plural and kebab-case:
players,player-deposits,bank-card-groups,message-templates. - Path parameters are explicit:
{player_id},{deposit_id},{brand_id}. - Action names are kebab-case verbs or verb phrases:
lock,unlock,reset-password,approve,decline,recycle,activate,import. - Do not use singular legacy prefixes such as
/player/depositor/coupon. - Do not use RPC-style suffixes directly under resources:
/edit-phone,/update-commission,/create,/send. - Do not expose historical broad query terms in public routes unless the exact
route is a reviewed BO contract in the allow-list:
list,search,stats,logs,details,condition,history,meta.
Auth And Audit Rules
Every public non-health route must require:
- Admin JWT.
- Calls from the fixed
bo_admin_v2service subject additionally require an opaque human Session resolved from PostgreSQL on every request. - BO query dataScope derived by
admin_servicefrom that human account, never accepted from the browser or BFF request body. - Brand context when the target is brand-scoped. BFF PII reveal additionally requires the explicit permission and verifies every target against the database-owned human scope before decryption.
- JWT-derived immutable admin account for writes/actions. Do not accept caller-supplied operator identity in request bodies for business attribution.
- Audit for money, approvals, state transitions, PII reveal, and security sensitive changes.
PII reveal responses must return only guid, phone_num, and bank_num, and
must not include full entity rows.
Because the reveal endpoints have a fixed meaning, they always return
phone_num and bank_num; callers must not send a fields selector or
encrypted PII values in the request body.
Current Public Surface Status
Current public_router has:
- canonical write/action routes and narrowly scoped Admin UI reads.
- BO identity endpoints for login, logout, current identity and forced password change, plus permission-gated account/role/scope/auth-event administration.
- four player-list reporting gateways:
POST /api/v1/admin/players/queryPOST /api/v1/admin/players/metaPOST /api/v1/admin/players/summaryPOST /api/v1/admin/players/stream
- no unreviewed broad query/list/log/stat routes.
0internal operational routes.0legacy-style public aliases.0public WebSocket routes.- PII reveal actions:
POST /api/v1/players/pii/revealPOST /api/v1/agents/pii/reveal
Published Cleanup Status
The public surface is normalized before external publication. Historical route
handlers are still retained on the internal router for compatibility tests and
controlled transition work, but public_router hides them from OpenAPI and from
the externally mounted app.
Player Routes
| Current | Canonical | Action |
|---|---|---|
POST /api/v1/players/{player_id}/lock | POST /api/v1/players/{player_id}/actions/lock | Add canonical, deprecate current. |
POST /api/v1/players/{player_id}/unlock | POST /api/v1/players/{player_id}/actions/unlock | Add canonical, deprecate current. |
POST /api/v1/players/{player_id}/unlock-freeze | POST /api/v1/players/{player_id}/actions/unlock-freeze | Add canonical, deprecate current. |
POST /api/v1/players/{player_id}/edit-password | POST /api/v1/players/{player_id}/actions/reset-password | Rename action. |
POST /api/v1/players/{player_id}/edit-phone | PUT /api/v1/players/{player_id}/phone | Use explicit audited PII subresource replace. |
POST /api/v1/players/{player_id}/edit-level | PATCH /api/v1/players/{player_id}/level | Use explicit field subresource update. |
POST /api/v1/players/{player_id}/edit-group | PATCH /api/v1/players/{player_id}/group | Use explicit field subresource update. |
POST /api/v1/players/{player_id}/edit-agent | PATCH /api/v1/players/{player_id}/agent | Use explicit assignment update. |
POST /api/v1/players/{player_id}/edit-bank | PUT /api/v1/players/{player_id}/bank-account | Treat as sensitive subresource replace. |
POST /api/v1/players/edit-level-multi | PATCH /api/v1/players/bulk | Body contains ids and patch. |
POST /api/v1/players/edit-group-multi | PATCH /api/v1/players/bulk | Body contains ids and patch. |
POST /api/v1/players/{player_id}/tags | PUT /api/v1/players/{player_id}/tags | Remove duplicate tag update. |
POST /api/v1/players/tags/create | POST /api/v1/player-tags | Use resource collection. |
POST /api/v1/players/groups | POST /api/v1/player-groups | Use resource collection. |
DELETE /api/v1/players/groups/{group_id} | DELETE /api/v1/player-groups/{group_id} | Use resource collection. |
Finance And Approval Routes
| Current | Canonical | Action |
|---|---|---|
POST /api/v1/players/deposits/{deposit_id}/agree | POST /api/v1/player-deposits/{deposit_id}/actions/approve | Rename. |
POST /api/v1/players/deposits/{deposit_id}/decline | POST /api/v1/player-deposits/{deposit_id}/actions/decline | Rename. |
POST /api/v1/players/deposits/{deposit_id}/recycle | POST /api/v1/player-deposits/{deposit_id}/actions/recycle | Rename. |
POST /api/v1/players/deposits/{deposit_id}/coin-agree | POST /api/v1/player-deposits/{deposit_id}/actions/approve-coin | Rename. |
POST /api/v1/player/deposit/coin/agree | Remove from external docs | Legacy singular alias. |
POST /api/v1/player/deposit/decline | Remove from external docs | Legacy singular alias. |
POST /api/v1/player/deposit/recycle | Remove from external docs | Legacy singular alias. |
POST /api/v1/players/deposits/shooter-agree | POST /api/v1/player-deposits/actions/approve-shooter | Collection action. |
POST /api/v1/players/withdrawals/{withdraw_id}/review-agree | POST /api/v1/player-withdrawals/{withdraw_id}/actions/review-approve | Rename. |
POST /api/v1/players/withdrawals/{withdraw_id}/review-decline | POST /api/v1/player-withdrawals/{withdraw_id}/actions/review-decline | Rename. |
POST /api/v1/players/withdrawals/{withdraw_id}/pay-agree | POST /api/v1/player-withdrawals/{withdraw_id}/actions/pay-approve | Rename. |
POST /api/v1/players/withdrawals/{withdraw_id}/pay-decline | POST /api/v1/player-withdrawals/{withdraw_id}/actions/pay-decline | Rename. |
POST /api/v1/players/rolling/{rolling_id}/force-complete | POST /api/v1/player-rollings/{rolling_id}/actions/force-complete | Rename resource. |
Agent Routes
| Current | Canonical | Action |
|---|---|---|
POST /api/v1/agents/create | POST /api/v1/agents | Remove /create. |
POST /api/v1/agents/{agent_id}/reset-password | POST /api/v1/agents/{agent_id}/actions/reset-password | Add actions namespace. |
POST /api/v1/agents/{agent_id}/lock | POST /api/v1/agents/{agent_id}/actions/lock | Add actions namespace. |
POST /api/v1/agents/{agent_id}/unlock | POST /api/v1/agents/{agent_id}/actions/unlock | Add actions namespace. |
POST /api/v1/agents/{agent_id}/update-commission | PATCH /api/v1/agents/{agent_id}/commission | Treat as subresource update. |
POST /api/v1/agents/{agent_id}/adjust-balance | POST /api/v1/agents/{agent_id}/actions/adjust-balance | Money action. |
POST /api/v1/agents/{agent_id}/add-coupon | POST /api/v1/agents/{agent_id}/actions/add-coupon | Action. |
POST /api/v1/agents/{agent_id}/domains/create | POST /api/v1/agents/{agent_id}/domains | Remove /create. |
Promotion Routes
| Current | Canonical | Action |
|---|---|---|
POST /api/v1/coupons/create | POST /api/v1/coupons | Remove /create. |
POST /api/v1/coupons/grant | POST /api/v1/coupons/actions/grant | Collection action. |
POST /api/v1/coupons/recycle | POST /api/v1/coupons/{coupon_id}/actions/recycle | Prefer URL id. |
POST /api/v1/coupon/recycle | Remove from external docs | Legacy singular alias. |
POST /api/v1/coupons/send | POST /api/v1/coupons/actions/send | Collection action. |
POST /api/v1/coupon/send | Remove from external docs | Legacy singular alias. |
POST /api/v1/promotions/create | POST /api/v1/promotions | Remove /create. |
POST /api/v1/events/deposit/create | Not public | deposit_event is not present in the v2 schema; the legacy handler remains an internal not-implemented placeholder only. |
PUT /api/v1/events/deposit/{event_id} | Not public | Same blocker: no production deposit_event owner/table/route contract exists yet. |
POST /api/v1/events/register/create | POST /api/v1/register-events | Resource collection. |
POST /api/v1/events/register/update | PUT /api/v1/register-events/{event_id} | URL id required. |
POST /api/v1/events/attendance/create | POST /api/v1/attendance-events | Resource collection. |
PUT /api/v1/events/attendance/{event_id} | PUT /api/v1/attendance-events/{event_id} | Resource collection. |
POST /api/v1/message-templates/create | POST /api/v1/message-templates | Remove /create. |
POST /api/v1/message-templates/update | PUT /api/v1/message-templates/{template_id} | URL id required. |
System, Brand, Wallet, Upload Routes
| Current | Canonical | Action |
|---|---|---|
POST /api/v1/ip-blacklist | POST /api/v1/ip-blacklists | Pluralize. |
PUT /api/v1/ip-blacklist/{entry_id} | PUT /api/v1/ip-blacklists/{entry_id} | Pluralize. |
DELETE /api/v1/ip-blacklist/{entry_id} | DELETE /api/v1/ip-blacklists/{entry_id} | Pluralize. |
POST /api/v1/upload/image | POST /api/v1/uploads/images | Resource collection. |
POST /api/v1/brands/{brand_id}/enable | POST /api/v1/brands/{brand_id}/actions/enable | State action. |
POST /api/v1/brands/{brand_id}/disable | POST /api/v1/brands/{brand_id}/actions/disable | State action. |
POST /api/v1/brands/{brand_id}/config/import | POST /api/v1/brands/{brand_id}/config/actions/import | Action. |
POST /api/v1/agent-brand | POST /api/v1/agent-brands | Pluralize or model as /agents/{agent_id}/brands. |
PATCH /api/v1/agent-brand/{agent_id}/{brand_id} | PATCH /api/v1/agent-brands/{agent_id}/{brand_id} | Pluralize. |
DELETE /api/v1/agent-brand/{agent_id}/{brand_id} | DELETE /api/v1/agent-brands/{agent_id}/{brand_id} | Pluralize. |
PUT /api/v1/wallet/topologies/{code}/activate | POST /api/v1/wallet/topologies/{code}/actions/activate | State action. |
PUT /api/v1/wallet/policies/{policy_key}/activate | POST /api/v1/wallet/policies/{policy_key}/actions/activate | State action. |
Implemented Policy
- Internal
routerkeeps legacy handlers for compatibility tests and controlled migration. - External
public_routerexposes canonical aliases only. public_routerprunes:- broad reads not present in the explicit Admin UI allow-list,
- internal operational paths,
- singular legacy aliases,
/create,/edit-*,- historical action verbs outside
/actions.
- Public surface tests fail when a canonical alias is missing or a legacy alias leaks.
- External API docs must be generated from
public_router, not the internalrouter. - Placeholder routes whose backing table/domain contract does not exist, such
as
deposit_event, must not be registered as canonical aliases or published in OpenAPI. A future deposit-event migration must add the schema, owner, route tests, and OpenAPI contract in the same change.
Test Standard
Every public non-health operation must have either:
- a direct route test, or
- a documented exception in the public API contract test.
Minimum direct test coverage per operation:
- auth required,
- brand required when brand-scoped,
- happy-path envelope,
- audit/side-effect assertion for money, approval, state transition, and PII.