Simulates the Xen Orchestra REST API for testing and development. At startup it loads fixtures, enriches them from the Swagger schema, then registers the OpenAPI routes under /rest/v0/ plus custom handlers for endpoints that need special logic.
Swagger UI is available at /docs/. The raw spec is at /swagger.json.
npm install
npm startThe server listens on http://localhost:3001 by default. Override with PORT. Override fixture loading with FIXTURES_DIR.
The new Vue.js frontend lives at @xen-orchestra/web in the
xen-orchestra monorepo. It talks to
the backend entirely over the REST API (/rest/v0/*): collections are streamed
as NDJSON, and live updates come over a Server-Sent Events channel at
/rest/v0/events. The simulator implements all of this, so you can develop the
UI without a real XO instance.
# in xo-api-sim
npm install
npm start # http://localhost:3001The frontend's Vite dev server proxies /rest (and the /api console
websocket) to VITE_XO_REST_HOST.
# in xen-orchestra/@xen-orchestra/web
yarn
cp .env.dist .envSet the host in .env to wherever the simulator is listening:
VITE_XO_REST_HOST=localhost:3001Then start it:
yarn dev # http://localhost:5173The frontend gates authenticated requests on a token cookie. Open
http://localhost:5173/#/dev/token and paste
any non-empty value — in the simulator's default accept-any auth mode the
token is not validated, and /rest/v0/users/me resolves to the default admin
fixture user. The page stores the cookie and reloads; the UI then lists the
fixture VMs, hosts, pools, SRs, networks, and so on.
Keep
MOCK_AUTH_ENFORCEunset (the default) when working with the web UI. Enforcement would reject the opaque dev token, and because the SSE handshake must succeed before a watched collection loads, nothing would appear.
| Concern | Endpoint(s) | Notes |
|---|---|---|
| Collections | GET /rest/v0/{resource}?fields=…&ndjson=true |
One JSON object per line (application/x-ndjson). |
| Live updates | GET /rest/v0/events (SSE) |
Emits init (carries the SSE id), then periodic ping. |
| Subscriptions | POST /rest/v0/events/:sseId/subscriptions → { id }, DELETE …/:subscriptionId |
The UI subscribes per collection; the handshake must complete before the initial NDJSON fetch runs. |
| Current user | GET /rest/v0/users/me |
Returns the default admin in accept-any mode. |
| Dashboards | GET /rest/v0/dashboard, …/pools/:id/dashboard, …/vms/:id/dashboard |
Return an empty object (the simulator has no aggregation layer); the UI renders an empty dashboard. |
Because fixtures are static, the simulator never pushes add/update/remove
events — the SSE channel exists to unblock the subscription handshake and report
liveness. Sub-collections without fixture-backed relationships (e.g. a VM's
alarms) return an empty NDJSON list rather than an error, so detail pages render
cleanly.
Generic Swagger CRUD is still used for most resources:
GET /rest/v0/{resource} list
GET /rest/v0/{resource}/{id} get by ID
POST /rest/v0/{resource} create
PUT /rest/v0/{resource}/{id} update
PATCH /rest/v0/{resource}/{id} update
DELETE /rest/v0/{resource}/{id} delete
List endpoints accept ?fields=a,b,c to project fields, ?filter= (complex-matcher
syntax) to filter, and ?limit= to cap results. With no fields, a list returns
an array of resource URIs. Add ?ndjson=true to stream the records as
newline-delimited JSON (application/x-ndjson) — this is the form the XO 6 web UI
uses.
Custom handlers currently exist for:
GET /rest/v0/eventsplusPOST/DELETE /rest/v0/events/:sseId/subscriptions/…(SSE channel used by the XO 6 web UI)GET /rest/v0/dashboard,GET /rest/v0/pools/:id/dashboard,GET /rest/v0/vms/:id/dashboardPOST /rest/v0/users/me/authentication_tokens(login) and related token endpointsPOST /rest/v0/vdisPOST /rest/v0/vifsPOST /rest/v0/vbdsPOST /rest/v0/pools/:id/actions/create_vmPOST /rest/v0/vms/:id/actions/:actionDELETE /rest/v0/vm-templates/:id(deletes the template's disks; default templates are refused with409)GET /rest/v0/vm-templates/:id.:format(fakexva/ovaexport)POST /rest/v0/vbds/:id/actions/:actionPOST /rest/v0/pbds/:id/actions/:actionPOST /rest/v0/srs/:id/actions/:action- tag add/remove endpoints for taggable resources
GET /rest/v0/:resource/:id/tasks
The simulator implements XO's token auth so clients can run their real login flow against it.
POST /rest/v0/users/me/authentication_tokens HTTP Basic login -> 201 { token: { id, ... } }
GET /rest/v0/users/me current user for the presented token
GET /rest/v0/users/me/authentication_tokens tokens owned by the current user
DELETE /rest/v0/users/me/authentication_tokens/:id revoke a token (logout; simulator extension)
Log in with an Authorization: Basic base64(user:pass) header; the returned
token.id is then sent back as an authenticationToken (or token) cookie.
As on a real XO, every /rest/v0/* request authenticates with either a token
cookie or HTTP Basic credentials:
Authorization: Bearer <token>is not supported (it is decoded as basic credentials and fails with401 invalid credentials);- sending a cookie and an
Authorizationheader together answers400; - an
Authorizationheader with no payload answers400 Malformed Authorization header.
By default any username/password is accepted (dev-friendly) and requests are not required to be authenticated. Configure via env vars:
| Variable | Default | Effect |
|---|---|---|
MOCK_AUTH_CREDENTIALS |
— | Comma-separated user:pass pairs to accept, e.g. admin:admin,operator:secret. Setting it turns off accept-any. |
MOCK_AUTH_ANY |
true unless credentials set |
Force accept-any on/off. |
MOCK_AUTH_ENFORCE |
false |
Require a valid token cookie or basic credentials on every /rest/v0/* request (login stays open). |
MOCK_AUTH_TOKEN_TTL |
604800 |
Token lifetime in seconds; non-positive or invalid values fall back to the default. |
AUTH_TOKEN |
test-token |
Static token always accepted as a cookie, bound to the default admin. Set it to a secret when enforcing auth. |
Users come from src/fixtures/users.json (a default admin and operator);
issued tokens bind to the matching user, or to the first admin in accept-any
mode.
Fixtures are loaded from src/fixtures/*.json and merged by collection name. enrichFixtures() fills in defaults from the OpenAPI schema and computed fields used by the handlers.
To add static fixtures:
- Add a JSON file in
src/fixtures/named after the collection, for examplehosts.json. - Use an array of objects, or an object whose array values will be merged by key.
- Include at least
idandtypewhere required by the resource.
When an endpoint needs custom behavior, follow the existing pattern in src/handlers/:
- Mirror the real Xen Orchestra controller types in
src/types.tsusingParameters<Xapi['METHOD_NAME']>. - Add the handler in
src/handlers/<resource>.ts. - Register it from
src/handlers/index.tsso it runs before the generic Swagger router. - Match the existing validation style:
400for missing required fields,404for missing referenced resources,201for successful creates,202for async task actions, and consistent{ error, data }payloads.
npm run docker:build
docker run -p 3001:3001 xo-api-simulator:latestOr:
docker run -p 8080:3001 -e PORT=8080 xo-api-simulator:latestThis project is licensed under the GNU Affero General Public License v3.
Copyright (C) 2026 VATES SAS