Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions docs/web/apis.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ A Universal API can be exposed through three *independent* axes — they don't s

. *Service point* — the API is mounted into a `site`, `webapp`, or `admin tool` service via that service's descriptor.
. *Web endpoint* — the API opts in with `mount: "web"` and becomes reachable on the *Web* endpoint (the public XP port) under `/api/`.
. *Management endpoint* — the API opts in with `mount: "management"` and becomes reachable on the *Management* endpoint, which listens on its own port.
. *Management endpoint* — the API opts in with `mount: "management"` and becomes reachable on the *Management* endpoint, which listens on its own port, at the root: `+/<app-name>:<api-name>+`.

The `/api/` URL prefix is the same on both endpoints, but the two are served on *different ports*. The Management endpoint is not reachable through the Web endpoint and vice versa.
The two endpoints are served on *different ports*, and the URL forms differ: `/api/` prefix on the Web endpoint, no prefix on the Management endpoint. The Management endpoint is not reachable through the Web endpoint and vice versa.

[#service-point]
== Service point
Expand Down Expand Up @@ -173,7 +173,7 @@ WARNING: The `mount: "management"` feature is experimental and subject to change

NOTE: The Management endpoint is for *advanced use only*. It is intended for extending XP's management capabilities (cluster operations, internal tooling, infrastructure integrations) — not for regular application APIs.

An API can also be exposed on the *Management* endpoint by adding `"management"` to its `mount` list. The Management endpoint is a separate listener on its own port, distinct from the Web endpoint — use it for internal or operational APIs that should not be reachable from the public network.
An API can also be exposed on the *Management* endpoint by adding `"management"` to its `mount` list. The Management endpoint is a separate listener on its own port, distinct from the Web endpoint — use it for internal or operational APIs that should not be reachable from the public network. There the API is served at the root of the port, `+/<app-name>:<api-name>+`, beside XP's own <<https://developer.enonic.com/docs/xp/stable/platform/endpoints/management/resources,management resources>> (`server:app`, `server:snapshot`, ...). Every request must carry an authenticated user matching `allow`.

.src/main/resources/apis/<api-name>/<api-name>.yaml
[source,yaml]
Expand All @@ -187,9 +187,40 @@ allow:

Both mounts can be combined: `mount: ["web", "management"]` makes the API reachable on both endpoints. The `allow` principal list is enforced on every request regardless of which endpoint served it.

[#management-policy]
=== Honouring virtual host policy

image:xp-8020.svg[XP 8.2.0,opts=inline] A <<https://developer.enonic.com/docs/xp/stable/platform/config/vhosts#management-api-policy,virtual host on the management endpoint>> selects what each API exposes through it. The mapping declares settings under `+context.api.<app-name>\:<api-name>.<setting>+` (the colon escaped, as in any properties file); XP copies them into the request context as attributes named `+api.<app-name>:<api-name>.<setting>+`. XP's own resources understand `verbs`, the comma separated list of operations the mapping exposes. An API of your own reads its settings from the same attributes and defines their meaning. An absent setting means unrestricted: a request without a matching mapping, or on the default management port, carries none.

.src/main/resources/apis/cluster/cluster.ts
[source,typescript]
----
import {get as getContext} from '/lib/xp/context';

const KEY = 'com.example.ops:cluster';

function allows(verb: string): boolean {
const value = getContext().attributes[`api.${KEY}.verbs`];
if (typeof value !== 'string' || value.trim() === '') {
return true;
}
const verbs = value.split(',').map((v) => v.trim());
return verbs.includes('*') || verbs.includes(verb);
}

export function GET(request: XP.Request): XP.Response {
if (!allows('list')) {
return {status: 403, body: {message: 'Verb [list] is not exposed on this virtual host'}};
}
return {body: {nodes: listNodes()}};
}
----

With `+mapping.ops.context.api.com.example.ops\:cluster.verbs = list+`, the mapping exposes `list` and nothing else the API guards.

== API discovery

`GET /api` (or `GET /api/`) returns a JSON index of every API mounted on the *endpoint that received the request*. Calling it on the Web endpoint lists APIs with `"web"` in `mount`; calling it on the Management endpoint lists APIs with `"management"` in `mount`. The response is a `resources` array, one entry per API:
`GET /api` (or `GET /api/`) on the Web endpoint returns a JSON index of every API with `"web"` in `mount`. There is no index on the Management endpoint. The response is a `resources` array, one entry per API:

[source,json]
----
Expand Down
1 change: 1 addition & 0 deletions docs/web/images/xp-820.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.