OpenKeel: the sovereign French Cloud solution of the inherent group, with a European reach Discover inherent

Reverse-spec · Services

skyline-apiserver — REST API

REST API — skyline-apiserver — branch stable/2026.2

Branch stable/2026.2 @ 4af72bfd4577 — repository https://opendev.org/openstack/skyline-apiserver — analysed on 2026-10-28

The API is a FastAPI application (skyline_apiserver/main.py:68) whose routers are all mounted under /api/v1 (skyline_apiserver/main.py:188, skyline_apiserver/types/constants.py:47). Behind nginx the public prefix is /api/openstack/skyline/api/v1 (skyline_apiserver/templates/nginx.conf.j2:91, skyline-console:src/client/client/constants.js:60). The route table was extracted from the running application (_rewrite/routes_stable-2026.2.json) and matches the decorators one to one; the OpenAPI document is FastAPI's own (_rewrite/openapi_stable-2026.2.json). There is no api-ref, no microversion and no oslo.policy rule protecting these endpoints: authorisation is a role check in code.

Common behaviour

Authentication (middleware validate_token, skyline_apiserver/main.py:76)

Every request whose path starts with /api/v1 goes through the middleware, except the paths that start with one of: /api/v1/login (so also /api/v1/login/totp), /api/v1/websso, /static, /docs, /api/v1/openapi.json, /favicon.ico, /api/v1/sso, /api/v1/config, /api/v1/contrib/keystone_endpoints, /api/v1/contrib/regions (skyline_apiserver/main.py:81). For the others:

  1. The credential is the session cookie (name: default.session_name) or, if absent, the X-Auth-Token header carrying a raw Keystone token (skyline_apiserver/main.py:107). Neither → 401 {"message": "Unauthorized: Token not found"}.
  2. Expired rows of revoked_token are deleted (skyline_apiserver/main.py:117, skyline_apiserver/db/api.py:65).
  3. With the cookie: the JWT is decoded with default.secret_key, HS256 (skyline_apiserver/core/security.py:31); its uuid is looked up in revoked_token (skyline_apiserver/main.py:124) → 401 {"message": "Unauthorized: Token revoked"} if present.
  4. In both cases the Keystone token is validated live with GET /v3/auth/tokens using the system session (skyline_apiserver/core/security.py:66), which returns the user, project and roles; this builds the Profile placed in request.state.profile (skyline_apiserver/main.py:157). With the header, the region is openstack.default_region (skyline_apiserver/main.py:139).
  5. If the JWT has less than access_token_renew seconds (default 1 800) left, a new JWT valid access_token_expire seconds (default 3 600) is set in the response cookies session and time_expired (skyline_apiserver/main.py:160, skyline_apiserver/main.py:181). The header path never renews (its exp is always now + 3 600).
  6. Any exception in steps 2–5 → 401 {"message": "Unauthorized: <text>"}; an expired JWT gives Unauthorized: Token expired - Signature has expired. (skyline_apiserver/main.py:167).

Note the two error shapes: the middleware answers {"message": …}, the endpoints answer {"detail": …} (02).

Roles

Authorisation is decided from the role names of the current token (skyline_apiserver/utils/roles.py:23):

CheckRuleDefault roles
system adminany role in openstack.system_admin_rolesadmin, system_admin (skyline_apiserver/config/openstack.py:107)
system admin or readerany role in system_admin_roles + system_reader_roles+ system_reader (skyline_apiserver/config/openstack.py:114)

The scope of the token (project, domain, system) is not considered. A user with the admin role in any project is "system admin" here.

Shared headers and parameters

  • X-Openstack-Request-Id (optional): must be empty or match ^req-\w{8}(-\w{4}){3}-\w{12} (skyline_apiserver/types/constants.py:26), otherwise 422. It is forwarded as X-OpenStack-Request-ID (global request ID) to Keystone, Nova, Cinder, Glance and Neutron. The console sends req-<uuid4> on every call (skyline-console:src/client/client/request.js:53).
  • The client address (default.secure_proxy_addr_header, otherwise the socket peer, skyline_apiserver/api/deps.py:28) is forwarded to OpenStack in a Forwarded: for=<ip>;by=<client> header by keystoneauth (03).
  • List endpoints accept limit (> 0, skyline_apiserver/types/constants.py:36), marker, sort_dirs (asc/desc) and repeated sort_keys from per-resource enums (skyline_apiserver/schemas/extension.py:131); an unknown enum value gives 422.

Summary

MethodPathHandlerAuthorisationSuccessErrors
POST/api/v1/loginskyline_apiserver/api/v1/login.py:395 loginpublic200 Profile + cookies401, 422
POST/api/v1/login/totpskyline_apiserver/api/v1/login.py:448 login_totppublic200 Profile + cookies401, 422
GET/api/v1/configskyline_apiserver/api/v1/login.py:501 get_configpublic200—
GET/api/v1/ssoskyline_apiserver/api/v1/login.py:518 get_ssopublic200—
POST/api/v1/webssoskyline_apiserver/api/v1/login.py:577 webssopublic (form token)302 → /base/overview + cookies401, 422
GET/api/v1/profileskyline_apiserver/api/v1/login.py:638 get_profileauthenticated200 Profile401
POST/api/v1/logoutskyline_apiserver/api/v1/login.py:664 logoutauthenticated200 Message401 (middleware)
POST/api/v1/switch_project/{project_id}skyline_apiserver/api/v1/login.py:699 switch_projectauthenticated200 Profile + cookies401
POST/api/v1/switch_region/{region}skyline_apiserver/api/v1/login.py:756 switch_regionauthenticated200 Profile + cookies401
GET/api/v1/extension/serversskyline_apiserver/api/v1/extension.py:62 list_serversauthenticated; all_projects needs admin or reader200400, 401, 403, 422, 500
GET/api/v1/extension/recycle_serversskyline_apiserver/api/v1/extension.py:284 list_recycle_serversidem200400, 401, 403, 422, 500
GET/api/v1/extension/volumesskyline_apiserver/api/v1/extension.py:501 list_volumesidem200401, 403, 422, 500
GET/api/v1/extension/volume_snapshotsskyline_apiserver/api/v1/extension.py:677 list_volume_snapshotsidem200401, 403, 422, 500
GET/api/v1/extension/portsskyline_apiserver/api/v1/extension.py:867 list_portsidem200401, 403, 422, 500
GET/api/v1/extension/compute-servicesskyline_apiserver/api/v1/extension.py:1055 compute_servicesadmin or reader200401, 403, 500
GET/api/v1/contrib/keystone_endpointsskyline_apiserver/api/v1/contrib.py:44 list_keystone_endpointspublic200500
GET/api/v1/contrib/domainsskyline_apiserver/api/v1/contrib.py:75 list_domainsauthenticated200401, 500
GET/api/v1/contrib/regionsskyline_apiserver/api/v1/contrib.py:97 list_regionspublic200500
GET/api/v1/policiesskyline_apiserver/api/v1/policy.py:96 list_policiesauthenticated200401, 500
POST/api/v1/policies/checkskyline_apiserver/api/v1/policy.py:157 check_policiesauthenticated200401, 403, 422
GET/api/v1/queryskyline_apiserver/api/v1/prometheus.py:112 prometheus_queryauthenticated200401, Prometheus status, 500
GET/api/v1/query_rangeskyline_apiserver/api/v1/prometheus.py:157 prometheus_query_rangeauthenticated200idem
GET/api/v1/settingsskyline_apiserver/api/v1/setting.py:112 list_settingsauthenticated200401
GET/api/v1/setting/{key}skyline_apiserver/api/v1/setting.py:52 show_settingauthenticated200401, 404
PUT/api/v1/settingskyline_apiserver/api/v1/setting.py:84 update_settingsystem admin200401, 403, 404, 422
DELETE/api/v1/setting/{key}skyline_apiserver/api/v1/setting.py:144 reset_settingsystem admin200401, 403, 404

All success statuses are 200 (no 201/204), including the creations, updates and deletions.


Session endpoints

The Profile object

Returned by login, TOTP login, profile, switch project and switch region (skyline_apiserver/schemas/login.py:92):

FieldContentSource
keystone_tokenthe raw project-scoped Keystone tokenskyline_apiserver/schemas/login.py:69
regioncurrent region—
exp, uuidexpiry (epoch s) and ID of the Skyline session (JWT)skyline_apiserver/core/security.py:81
project, user, rolesfrom Keystone token dataskyline_apiserver/core/security.py:76
keystone_token_expKeystone token expires_atskyline_apiserver/core/security.py:79
base_domainsopenstack.base_domains (default heat_user_domain)skyline_apiserver/config/openstack.py:98
endpointsmap service name → nginx path /<nginx_prefix>/<region lower>/<service> built from the catalog and service_mapping, plus Neutron extension aliases mapped by extension_mappingskyline_apiserver/client/openstack/system.py:55
projects{id: {name, enabled, domain_id, domain_name, description}} of the user's projectsskyline_apiserver/api/v1/login.py:363
default_project_iduser's default projectskyline_apiserver/api/v1/login.py:374
regionssorted region IDs in the user's catalogskyline_apiserver/api/v1/login.py:314
versioninstalled skyline-apiserver versionskyline_apiserver/version.py:22

Cookies set on success: session = HS256 JWT {keystone_token, region, exp, uuid} (skyline_apiserver/schemas/login.py:84) and time_expired = exp (skyline_apiserver/api/v1/login.py:171). Both are session cookies (no Max-Age), path /, SameSite=Lax, not HttpOnly, not Secure (Starlette defaults).

POST /api/v1/login

Functional description

  • Purpose: open a dashboard session with a user name and password, landing in the user's default project.
  • Actor: anyone; the Keystone user must have a role on at least one enabled project.
  • Preconditions: Keystone reachable with the system account (openstack.system_user_*), which is used to find the identity endpoint of default_region in the catalog (skyline_apiserver/api/v1/login.py:256).
  • Business rules:
    • The region is always openstack.default_region; the region field of the body is ignored (skyline_apiserver/api/v1/login.py:405).
    • Domain: body domain, else openstack.user_default_domain (skyline_apiserver/api/v1/login.py:406).
    • Only enabled projects count (skyline_apiserver/api/v1/login.py:300); none → 401 You are not authorized for any projects or domains. (skyline_apiserver/api/v1/login.py:303).
    • Landing project: the user's default_project_id if it is among the enabled ones, else the first project returned by GET /v3/auth/projects (skyline_apiserver/api/v1/login.py:155).
    • If Keystone answers that a second factor is required (receipt + a rule containing both password and totp), the answer is 401 {"detail": {"totp_required": true, "receipt": "<receipt>"}} (skyline_apiserver/api/v1/login.py:60, skyline_apiserver/api/v1/login.py:73). Any other multi-factor rule gives 401 "An error occurred authenticating. Please try again later.".
  • Flow (all synchronous; 16 outbound requests in the capture, see 03):
    1. Password authentication (unscoped token) → GET /v3/auth/projects with it.
    2. Validate the token and read the user (GET /v3/users/{id}) with the system session to get default_project_id (skyline_apiserver/api/v1/login.py:230).
    3. Re-scope to the landing project (POST /v3/auth/tokens, token method) (skyline_apiserver/client/openstack/system.py:30), validate it, build the profile.
    4. Enrich: regions, endpoints (with GET /v2.0/extensions on Neutron), projects of the user (GET /v3/users/{id}/projects), default project (skyline_apiserver/api/v1/login.py:330).
    5. Set the cookies, return the profile.
  • Outcome: 200 with the profile; nothing is written in the database.
  • Limitations: the Keystone error text is returned as is in detail (skyline_apiserver/api/v1/login.py:427); the console parses it (07 § 4).

Technical sheet

  • Request: JSON Credential (skyline_apiserver/schemas/login.py:26): username (str, required), password (str, required), domain (str, optional), region (str, optional, ignored). Header X-Openstack-Request-Id optional.
  • Response: 200 Profile; cookies session, time_expired.
  • Errors: 401 with detail = Keystone's message or the TOTP challenge; 422 on a missing field (FastAPI validation).

POST /api/v1/login/totp

Functional description

  • Purpose: second step of a password + TOTP login.
  • Actor: a user who received totp_required from /login.
  • Business rules: passcode must be 6 digits (skyline_apiserver/schemas/login.py:134); region = body region or default_region (unlike /login, the body region is used, skyline_apiserver/api/v1/login.py:458); the receipt is sent back to Keystone with the TOTP method (skyline_apiserver/api/v1/login.py:215).
  • Flow: TOTP + receipt authentication → unscoped token → same steps 1–5 as /login, through _finish_login (skyline_apiserver/api/v1/login.py:176).
  • Errors: 401 "receipt_expired" when Keystone says the receipt expired or asks again for methods without a receipt header; 401 "invalid_totp" on a Keystone 401; 401 "authentication_failed" on anything else (skyline_apiserver/api/v1/login.py:111). These three strings are matched by the console (07 § 4).

Technical sheet

  • Request: JSON TOTPCredential (skyline_apiserver/schemas/login.py:130): username, passcode (^\d{6}$), receipt required; domain, region optional.
  • Response: as /login.

GET /api/v1/config

  • Purpose: tell the login page which domain and region are the defaults.
  • Response: {"default_domain": <user_default_domain>, "default_region": <default_region>} (skyline_apiserver/api/v1/login.py:501). No outbound call.

GET /api/v1/sso

  • Purpose: tell the login page whether federated login is offered and where to send the browser.
  • Business rules (skyline_apiserver/api/v1/login.py:518): if openstack.sso_enabled is false → {"enable_sso": false, "protocols": []}. Otherwise the origin (return URL) is <http|https per default.ssl_enabled>://<Host of the request>[:port]/<nginx_prefix>/skyline/api/v1/websso; with sso_identity_providers each entry gives {protocol, label, url: <keystone_url>/auth/OS-FEDERATION/identity_providers/<name>/protocols/<protocol>/websso?origin=…}, else each of sso_protocols gives <keystone_url>/auth/OS-FEDERATION/websso/<protocol>?origin=….
  • Limitations: origin is not URL-encoded and is built from the Host header of the request.

POST /api/v1/websso

Functional description

  • Purpose: landing point of Keystone WebSSO: the browser posts the federated unscoped token and receives a dashboard session.
  • Actor: the browser, redirected by Keystone after the IdP.
  • Business rules: region openstack.sso_region (skyline_apiserver/api/v1/login.py:589); landing project as for /login.
  • Flow: same as /login from step 1 with the posted token; then 302 Location: /base/overview with the cookies (skyline_apiserver/api/v1/login.py:621).
  • Errors: any failure → 401 {"detail": "<text>"} (skyline_apiserver/api/v1/login.py:615); missing token form field → 422.

Technical sheet

  • Request: application/x-www-form-urlencoded, field token (required).
  • Response: 302 to the relative URL /base/overview (hard-coded, not under nginx_prefix).

GET /api/v1/profile

  • Purpose: return the current session profile, refreshed (projects, regions, endpoints).
  • Flow: profile from the middleware (validated token) → _patch_profile (skyline_apiserver/api/v1/login.py:647) → 200. 8 outbound requests (03).
  • Errors: 401 from the middleware; 401 {"detail": …} if enrichment fails (skyline_apiserver/api/v1/login.py:376).
  • Note: works with the X-Auth-Token header too; the console calls it after login with the cookie and the header (skyline-console:src/stores/root.js:210).

POST /api/v1/logout

Functional description

  • Purpose: end the session.
  • Flow (skyline_apiserver/api/v1/login.py:664): if a profile was built by the middleware, its JWT payload is re-parsed, the profile regenerated, a user session built, the Keystone token revoked (DELETE /v3/auth/tokens, skyline_apiserver/client/openstack/keystone.py:57) and the session uuid inserted into revoked_token with its exp (skyline_apiserver/db/api.py:56). Every error there is swallowed (logged at debug, skyline_apiserver/api/v1/login.py:682). The session cookie is deleted.
  • Outcome: always 200 {"message": "Logout OK", "code": 200, "title": "OK"} once past the middleware.
  • Limitations:
    • With only the X-Auth-Token header, the profile has a fresh random uuid, so the revocation row is useless, but the Keystone token is revoked.
    • The time_expired cookie is left in place.
    • Without any credential, the middleware answers 401 before the handler (the console ignores it).

POST /api/v1/switch_project/{project_id}

Functional description

  • Purpose: move the session to another project of the user.
  • Business rules: the check "project in profile.projects" (skyline_apiserver/api/v1/login.py:712) never fires, because the middleware's profile has no projects; Keystone decides (re-scoping fails with 401 if the user has no role there).
  • Flow: re-scope the current token to project_id (POST /v3/auth/tokens) → validate → enrich → new cookies (skyline_apiserver/api/v1/login.py:717).
  • Outcome: 200 with the new profile; the old Keystone token is not revoked and the old JWT is not added to revoked_token.
  • Technical sheet: no body is read (the console sends {project_id, project_domain_id} and ?project_domain_id=, both ignored, skyline-console:src/client/skyline/index.js:103).

POST /api/v1/switch_region/{region}

Functional description

  • Purpose: move the session to another region of the same Keystone.
  • Business rules: the region must be in profile.regions or, since the middleware's profile has none, in the regions of the user's catalog (skyline_apiserver/api/v1/login.py:768); else 401 "Region not accessible". If reading the catalog fails, the list is empty and every region is refused (skyline_apiserver/api/v1/login.py:326).
  • Flow: the same Keystone token is validated through the identity endpoint of the new region (no re-scoping, skyline_apiserver/api/v1/login.py:778) → enrich → new cookies carrying the new region.

Aggregated list endpoints (/extension/*)

These endpoints exist to give the console, in one call, lists that need several services: server lists with image names, volume lists with server names, etc. Common rules:

  • all_projects=true requires system admin or reader (skyline_apiserver/utils/roles.py:54) → 403 Not allowed to get <resources> for all projects.; when false, project_id/project_name filters are ignored (reset to None).
  • Two Keystone sessions are used: the user session (current token re-scoped to the current project, skyline_apiserver/client/utils.py:36) and the system session (the skyline service user, skyline_apiserver/client/utils.py:58). Which one is used for each call is in 03.
  • Lookups by ID lists are split in batches of 100 IDs (skyline_apiserver/types/constants.py:38) to keep URLs short.
  • Each item carries origin_data, the untouched resource as returned by the service (skyline_apiserver/api/wrapper/openstack.py:110), next to the flattened Skyline fields (skyline_apiserver/api/wrapper/skyline.py:56).
  • Errors of the service calls become 400/401/403/500 with the client library's message (02).

GET /api/v1/extension/servers

Functional description

  • Purpose: list instances with their image name and OS distribution, and their project name for administrators.
  • Actor: project member (own project) or system admin/reader (all_projects).
  • Business rules:
    • project_name (with all_projects) is resolved to an ID first with the system session; no match, or a mismatch with project_id, returns an empty list (skyline_apiserver/api/v1/extension.py:143).
    • Filters passed to Nova: name (Nova regex match), status, host, flavor, project_id, all_tenants, uuid, ip (skyline_apiserver/api/v1/extension.py:162).
    • Image of a volume-booted server: taken from the root volume's volume_image_metadata (skyline_apiserver/api/v1/extension.py:229); otherwise from Glance by image ID; otherwise null.
    • host is blanked unless all_projects (skyline_apiserver/api/v1/extension.py:242), even for an administrator; project_name only filled with all_projects (skyline_apiserver/api/v1/extension.py:254).
  • Flow: 1. GET /servers/detail (user session, microversion 2.79); 2. Glance GET /v2/images?id=in:<ids> by batches (system session); 3. Cinder GET /volumes/detail?id=[…]&all_tenants=True by batches (system session); 4. with all_projects, Keystone GET /v3/projects (user session) for names.
  • Outcome: {"servers": [...]} (skyline_apiserver/schemas/extension.py:275); the Nova page is returned as is (one page, limit/marker passed through).
  • Limitations: server and image IDs must be UUID v4 (skyline_apiserver/schemas/extension.py:206), else 500.

Technical sheet

  • Query: limit (int > 0), marker, sort_dirs (asc|desc), sort_keys (repeatable: uuid, display_name, vm_state, locked, created_at, host, project_id, skyline_apiserver/schemas/extension.py:139), all_projects (bool), project_id, project_name, name, status (enum skyline_apiserver/schemas/extension.py:28), host, flavor_id, uuid, ip.
  • Response item (skyline_apiserver/schemas/extension.py:205): id, name, project_id, project_name, host, hostname, image, image_name, image_os_distro, fixed_addresses, floating_addresses, flavor, flavor_info, status, locked, created_at, updated_at, task_state, vm_state, power_state, volumes_attached, root_device_name, metadata, origin_data.

GET /api/v1/extension/recycle_servers

Functional description

  • Purpose: list soft-deleted instances (recycle bin) with the time at which Nova will reclaim them.
  • Business rules: Nova only returns deleted servers to administrators, so the call always uses the system session with status=soft_deleted, deleted=True, all_tenants=True, and project_id=<current project> unless all_projects (skyline_apiserver/api/v1/extension.py:374). deleted_at = updated_at; reclaim_timestamp = updated_at + openstack.reclaim_instance_interval (default 7 days, must match Nova's own setting) (skyline_apiserver/api/v1/extension.py:456).
  • Outcome: {"recycle_servers": [...]}.
  • Limitations: 500 whenever the list is not empty (see 00 § Points of attention 1).
  • Query: as servers, without status, host, flavor_id; sort_keys among uuid, display_name, updated_at, project_id (skyline_apiserver/schemas/extension.py:152).

GET /api/v1/extension/volumes

Functional description

  • Purpose: list volumes with a total count and the names of the servers they are attached to, including soft-deleted servers.
  • Business rules:
    • With all_projects, a reader without admin role gets the system session (Cinder would ignore all_tenants for him) (skyline_apiserver/api/v1/extension.py:563).
    • With uuid (repeatable), the system session is used and, unless all_projects, restricted with project_id=<current project> (skyline_apiserver/api/v1/extension.py:586).
    • Sort: sort=<key>[:<dir>],… (skyline_apiserver/api/v1/extension.py:568); multiattach and bootable are sent as "True"/"False" strings.
    • For each attached server, two Nova calls (normal, then soft-deleted) are made per server (skyline_apiserver/api/v1/extension.py:624).
  • Outcome: {"count": <total from Cinder>, "volumes": [...]} (skyline_apiserver/schemas/extension.py:399).
  • Query: limit, marker, sort_dirs, sort_keys (id, name, size, status, bootable, created_at, skyline_apiserver/schemas/extension.py:162), all_projects, project_id, name, multiattach, status (skyline_apiserver/schemas/extension.py:54), bootable, uuid (list).

GET /api/v1/extension/volume_snapshots

Functional description

  • Purpose: list snapshots with their volume name, host (administrators), project name and the volumes created from each snapshot (child_volumes).
  • Business rules: with uuid and without all_projects, the snapshot is first read with the user session and an empty list is returned if it is not found or belongs to another project (skyline_apiserver/api/v1/extension.py:744); then the list is made with the system session and all_tenants=True. Volumes and child volumes are looked up by batches with the system session (skyline_apiserver/api/v1/extension.py:799).
  • Outcome: {"count": …, "volume_snapshots": [...]}; host only with all_projects (skyline_apiserver/api/v1/extension.py:850).
  • Query: limit, marker, sort_dirs, sort_keys, all_projects, project_id, name, status, volume_id, uuid.

GET /api/v1/extension/ports

Functional description

  • Purpose: list Neutron ports with the name of their network and of the server using them.
  • Business rules: without all_projects, project_id=<current project> is forced (skyline_apiserver/api/v1/extension.py:936); network_name is resolved to network IDs first (none → empty list); network_id must then be one of them (skyline_apiserver/api/v1/extension.py:948); sort directions default to asc per key.
  • Flow: Neutron GET /v2.0/ports (one page only: retrieve_all=False, skyline_apiserver/client/openstack/neutron.py:67) → shared networks + networks by ID batches → one Nova call per server (device_owner == compute:nova) (skyline_apiserver/api/v1/extension.py:1022). All with the user session.
  • Outcome: {"ports": [...]} (no count).
  • Query: limit, marker, sort_dirs, sort_keys (skyline_apiserver/schemas/extension.py:184), all_projects, project_id, name, status, network_name, network_id, device_id, device_owner (list, enum skyline_apiserver/schemas/extension.py:106), uuid (list).

GET /api/v1/extension/compute-services

  • Purpose: list Nova compute services (hypervisor overview) for administrators and readers.
  • Rules: system admin or reader, else 403 Not allowed to get compute services. (skyline_apiserver/api/v1/extension.py:1069); always the system session; filters binary, host.
  • Outcome: {"services": [...]} with id, binary, disabled_reason, host, state, status, updated_at, forced_down, zone (skyline_apiserver/api/wrapper/skyline.py:179), null fields omitted.

Catalog helpers (/contrib/*)

EndpointPurposeBehaviour
GET /contrib/keystone_endpoints (public)let the login page show one entry per regionfor each region of the system catalog, {"region_name", "url": <nginx path of keystone>} (skyline_apiserver/api/v1/contrib.py:44); one Neutron GET /v2.0/extensions per region; any error → 500
GET /contrib/domains (authenticated)list enabled domain namesGET /v3/domains?enabled=True with the system session (skyline_apiserver/client/openstack/system.py:109)
GET /contrib/regions (public)list region IDsfrom the system catalog, unordered (skyline_apiserver/client/openstack/system.py:123)

Policy endpoints

GET /api/v1/policies

Functional description

  • Purpose: tell the console, for every known rule of 16 services, whether the current user would be allowed, so that it hides what would fail.
  • Business rules:
    • Credentials: built from the user's re-scoped token (skyline_apiserver/policy/base.py:32): roles, project, domains, is_admin (= any system admin role), system_scope.
    • system_scope is all only if the user's token can be re-scoped to the system (skyline_apiserver/api/v1/policy.py:104): Skyline asks Keystone for a system-scoped token; a 401 or 500 there is ignored.
    • Target: the user's own IDs under every attribute name used by the services' rules (project_id, tenant, target.project.id, owner…), plus enforce_new_defaults (skyline_apiserver/api/v1/policy.py:40).
    • Rule source: for each service, <policy_file_path>/<service>_<policy_file_suffix> (default /etc/skyline/policy/nova_policy.yaml) overrides the embedded default, reloaded when the file changes (skyline_apiserver/policy/base.py:105); the embedded rules use basic_check_str when present (skyline_apiserver/policy/base.py:124).
    • A rule that cannot be evaluated is false; an unknown rule is false (skyline_apiserver/policy/base.py:135).
  • Outcome: {"policies": [{"rule": "<service>:<rule>", "allowed": bool}, …]}, ~1 860 entries.
  • Limitations: evaluation is only an approximation of each service's own policy: Skyline's copy may differ from the deployed service's policy.yaml unless the operator copies it into policy_file_path.

POST /api/v1/policies/check

  • Purpose: evaluate a chosen list of rules, optionally against a target object.
  • Request (skyline_apiserver/schemas/policy.py:31): {"rules": ["nova:os_compute_api:servers:create", …], "target": {"project_id": "…"}}; target entries override the default target.
  • Errors: an unknown service prefix or a rule without : → 403 {"detail": "<error text>"}, e.g. 'foo' (skyline_apiserver/api/v1/policy.py:193).

Prometheus proxy

GET /api/v1/query and GET /api/v1/query_range

  • Purpose: let the console draw monitoring charts without exposing Prometheus.
  • Flow: the query parameters present among query, time, timeout (instant) or query, start, end, step, timeout (range) are forwarded to <prometheus_endpoint>/api/v1/query[_range] with optional basic auth (skyline_apiserver/api/v1/prometheus.py:132). A non-200 Prometheus answer is returned with the same status and detail = Prometheus body text (skyline_apiserver/api/v1/prometheus.py:138).
  • Business rules: for users without a system admin or reader role, series without a project_id label equal to the current project are removed (skyline_apiserver/api/v1/prometheus.py:50).
  • Outcome: Prometheus's own shape: status, data.resultType, data.result[].metric, data.result[].value (for ranges, Prometheus values are renamed value, skyline_apiserver/api/v1/prometheus.py:79); warnings, errorType, error passed through; null fields omitted.

Settings

Settings are UI preferences (flavor families, GPU and USB models) whose defaults come from the setting section of skyline.yaml (skyline_apiserver/config/setting.py:23) and can be overridden at run time in the settings table. The allowed keys are setting.base_settings (default flavor_families, gpu_models, usb_models).

EndpointBehaviour
GET /settingsevery key of base_settings with the database value if any, else the configured value (skyline_apiserver/api/v1/setting.py:112)
GET /setting/{key}one key; unknown key → 404 "Not a valid setting key." (skyline_apiserver/api/v1/setting.py:32)
PUT /settingsystem admin only (403 "Not allowed to update settings."); body {"key", "value"} (skyline_apiserver/schemas/setting.py:30); upsert into settings (skyline_apiserver/db/api.py:93); returns the stored value
DELETE /setting/{key}system admin only (403 "Not allowed to reset settings."); deletes the override, returns the configured default (skyline_apiserver/api/v1/setting.py:144)

Response object: {"key", "value", "hidden", "restart_service"} (skyline_apiserver/schemas/setting.py:25); hidden and restart_service are always false on this branch (empty sets, skyline_apiserver/types/constants.py:40).

Lifecycle: Skyline session

The only stateful object owned by skyline-apiserver is the session (JWT); its states are observed through the cookie and the revoked_token table.

stateDiagram-v2
  [*] --> Active: POST /login, /login/totp, /websso (cookie session + time_expired)
  [*] --> TotpPending: POST /login → 401 totp_required (receipt held by the browser)
  TotpPending --> Active: POST /login/totp
  TotpPending --> [*]: receipt_expired / invalid_totp
  Active --> Active: any call with < access_token_renew left (middleware renews the cookie)
  Active --> Active: POST /switch_project, /switch_region (new JWT, old one not revoked)
  Active --> Revoked: POST /logout (uuid inserted in revoked_token, Keystone token revoked)
  Active --> Expired: exp reached (JWT signature check fails → 401)
  Active --> Invalid: Keystone token expired or revoked elsewhere (validation fails → 401)
  Revoked --> [*]: row purged by the next request once exp < now
  Expired --> [*]
  Invalid --> [*]

Source: reverse-spec-skyline-apiserver/01-rest-api_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.