Configure APIs and Response Fields
In the new menu model, business code does not need to create apiBinding records by hand. Put api:* resources on a page load API or an action, and permission-core compiles the internal API contract when the menu config is saved. The same contract is then used by role-menu grants, Vext route guards, and response-field projection.
This page answers three questions:
- Which APIs are called when a page opens.
- Which APIs are called by page buttons, and which buttons are frontend-only permissions.
- Which fields in an API response should be returned only after role authorization.
If you only need to know “which method should I use?”, this table is enough:
Prerequisites
Before configuring APIs and fields incrementally, create the menu config, menu, and view first:
orders-list is the view.id assigned when the view is created, not a name generated by loadApis.add(). It must be unique within the same menu config. See the menus.views.create() example in Manage Menus.
A normal flow looks like this:
To make those permissions effective for a user, bind the role with userRoles.assign() or userRoles.set() after role authorization.
If the config, view, or action does not exist yet, menus.loadApis.add(), menus.actions.create(), and menus.responses.set() fail during preview or execution.
Page load APIs
A page load API is the backend API that must be called when a user opens a view.
In the incremental management API, this is menus.loadApis.add() with input.resource. If you use the advanced config-as-code entrypoint, the same meaning is load.resource; this page focuses on the incremental management API.
For example, if the orders list opens by calling:
register it as the load API of the orders-list view:
This means:
Add
GET /api/ordersas the default load API for theorders-listview in theadminmenu config.
Parameter notes:
The operator and request ID are already bound through pc.scope(scope, defaults). Pass per-call options only when you need to override those defaults.
After saving, you do not need to care about the internal snapshot shape. Just remember this record lands on the load APIs of the orders-list view, equivalent to views[].load[].resource = 'api:GET:/api/orders' in the menu config.
This load entry affects three places:
You do not write action: 'invoke' for API resources here. loadApis.add() compiles api:GET:/api/orders into invoke + api:GET:/api/orders.
For path parameters, use a route template:
Do not put a concrete business ID in the resource:
Page buttons and actions
An action is a button or operation inside a page, such as export, approve, delete, or open details.
In the incremental management API, this is menus.actions.create() with input.resource. If you use the advanced config-as-code entrypoint, the same meaning is actions[].resource.
If the button calls the backend, use api:*:
This means:
Create an “Export orders” action under the
orders-listpage. A user with this action permission may see or click it; if it calls the backend, the backend must still checkinvoke + api:POST:/api/orders/export.
If the button is frontend-only and calls no backend API, use ui:button:*:
The two resource types mean different things:
If a button only opens a modal, and the modal later calls an API, model the modal as a dialog or drawer view and configure its own load API. That keeps the permission meaning clean:
Response field configuration
Response field configuration answers:
Which fields in this API response DTO can be granted to roles?
Keep these two operations separate:
Declaring response fields does not mean users can already see them. Without role field grants, those fields are still removed.
Where response fields attach
The owner in menus.responses.set() only tells permission-core: “these fields belong to this API response.” New users should usually choose load or action by page source, instead of starting with api.
Response fields for a page load API:
This means:
api:GET:/api/ordersreturns paginated data; the projected rows live underitems;totalis a structural pagination field that should be preserved without field authorization;orderNo/status/amountare grantable response fields.
These ordinary add/set operations automatically run an internal preview and commit when safe. If the management UI wants to display the impact first, call previewAdd(), previewCreate(), or previewSet(), then execute with expected/previewToken.
Parameter notes:
After saving, these fields attach to the items response target of api:GET:/api/orders. Later role grants pick from the declared orderNo/status/amount fields.
Array form and object form
Inside MenuConfigInput.load[].response or actions[].response, a direct array is allowed:
But in menus.responses.set(), response uses object form. Even without target, write:
For a paginated response:
prefer:
Do not put sensitive business fields in preserve; preserve bypasses field authorization.
Authorizing response fields
After declaring fields, grant selected fields to roles. This selection means:
order-operatorcan enter the orders list page, call the page load API and action APIs, but the orders list API returns onlyorderNoandstatus.
fields must be declared earlier. For paginated or nested responses, write target, such as items or data.items; if one API has multiple response targets and target is omitted, preview rejects the ambiguous selection.
By default, response fields are not automatically all selected. If a role should receive every field, say it explicitly:
Filtering responses on the backend
Response field projection must happen before returning data from the backend. It should not be only a frontend hide/show rule.
In a hand-written framework integration, keep API entry authorization and response projection separate:
If the current user only has orderNo and status, projected.data is close to:
Responsibility boundary:
filterResponse() also checks whether the current user can invoke the API. Still, business routes should usually call subject.assert() or a framework guard first, because that gives clearer failure points.
With the Vext plugin, routes protected by permission: true can automatically perform API authorization and response-field projection. Hand-written route code can also call req.auth.permission.filterResponse() directly. See Vext Plugin.
What if no response fields are configured?
- If an API has no
responseconfigured,filterResponse()returns the original payload after API permission passes. - If an API has
responseconfigured but the current user has no field grants, only fields listed inpreserveremain. - If an API contains sensitive fields, configure
responseand grant fields explicitly through roles.
Reusing the same API across pages
The same apiResource can be reused by multiple pages or actions, but the response structure must be compatible.
For example, these usually merge cleanly:
If different pages declare incompatible structures for the same API, such as one using target: 'items' and another using target: 'data.rows', preview may reject the config. Prefer splitting the API or making the response shape consistent.
Advanced: config-as-code and batch imports
This page focuses on the incremental management API. If a plugin, CI/CD job, or config file imports the full menu in one pass, see Menu Config as Code and Batch Imports.
There is only one equivalence to remember: MenuConfigInput.load[].resource maps to menus.loadApis.add(), MenuConfigInput.actions[].resource maps to menus.actions.create(), and MenuConfigInput.load[].response or MenuConfigInput.actions[].response maps to menus.responses.set().
Common misunderstandings
For exact field constraints, see Configure APIs and Response Fields API. For the full flow, see Manage Menus and Authorize Role Menus.