vME APIs
Overview
vME's API allows you to programatically manage the vME instances and Docker engine.
Base URL
The API is served over HTTPS and reverse-proxied under the /api path on the appliance. Every endpoint documented here is relative to that base:
https://<your-vme-host>/api
For example, the Show all pools route documented as /pool/show is called at https://<your-vme-host>/api/pool/show.
To keep the examples below concise, they assume a base_url (Python) / BASE_URL (curl) variable that you set once to your appliance's API base:
- Python
- Curl
# Set this once; every example below reuses it.
base_url = "https://<your-vme-host>/api"
# Set this once; every example below reuses it.
export BASE_URL="https://<your-vme-host>/api"
Authentication
All endpoints are authenticated according to the following headers schema:
{
"username" : "xxxxxx", # your username
"api-key" : "xxxxxx" # your api-key
}
A request that leaves out either header, or whose username and api-key do not match a user, is refused with a JSON body carrying "status": 0 and a message. The status code is 401, or 400 on the routes that check for a missing header before checking the key.
The examples assume these are defined once as headers (Python) or passed as -H flags (curl). Some routes additionally require an administrator account; those are marked Admin only in this reference. A valid username and api-key that belong to a non-administrator account are refused on those routes with 403.
Getting an API key
Sign in with POST /login to get your api-key. The key is tied to your account and stays the same on every sign-in until it is rotated (see Modify User and Modify a User (admin)), so you can sign in once and reuse it.
POST /login
-
AUTHENTICATIONS: none (username and password in the body)
-
REQUEST BODY SCHEMA:
{
"username" : "jane",
"password" : "MyPassword123!"
}Name Type Description username requiredString Your login name. A value containing @is matched against the account's email address instead.password requiredString Your password. For an LDAP account, this is your directory password. method optionalString password(local account) orldap. When given, it must match how the account is set up, or the sign-in is refused with401. When omitted, vME uses the account's own sign-in type.Both local and LDAP accounts sign in through this route. An account set up as
ldapis checked against the configured LDAP directory. A username that vME does not know yet is also tried against LDAP (unlessmethodispassword); when the LDAP configuration hasauto_provisionswitched on, a successful sign-in creates the vME account as a non-administrator. -
RESPONSE:
200on success.api-keyis the value to send in theapi-keyheader;is_admintells you whether the account can call the Admin only routes. Wrong credentials answer401; a missing username or password answers400.{
"action" : "sign in",
"status" : 1,
"message" : "log in success",
"username" : "jane",
"api-key" : "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"group_id" : 1,
"is_admin" : false
} -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"username" : "jane",
"password" : "MyPassword123!"
}
response = requests.post(f"{base_url}/login", json=body)
login = response.json()
if login.get("status") == 1:
# Reuse these headers for every other call in this reference.
headers = {
"username" : login["username"],
"api-key" : login["api-key"]
}
else:
print(login)curl -X POST \
-H "Content-Type: application/json" \
-d '{"username": "jane", "password": "MyPassword123!"}' \
"$BASE_URL/login"
Show your own account
GET /user returns the profile of the account named in the headers, including its api-key, group and sign-in type. Use it to check that a username and api-key pair is valid.
GET /user
-
AUTHENTICATIONS:
headers -
RESPONSE:
{
"action" : "get user info",
"status" : 1,
"message" : "log in success",
"username" : "jane",
"name" : "Jane Doe",
"mail" : "jane@company.com",
"api-key" : "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"group_id" : 1,
"is_admin" : false,
"auth_type" : "local"
} -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
response = requests.get(f"{base_url}/user", headers=headers)
if response.status_code < 500:
print(response.json())curl -X GET \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/user"
The admin API for managing agents, dispatching jobs, and orchestrating onboarding workflows is documented below under Agent API and Workflow API. The appliances a workflow can send to are registered through the Appliance Registry.
Disk Partition Details
This API call retrieves all disk details like name, partitions, model and size.
GET /zfs/disks
-
AUTHENTICATIONS:
headers -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
response = requests.get(f"{base_url}/zfs/disks", headers=headers)
if response.status_code < 500:
print(response.json())curl -X GET \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/zfs/disks"
Pool Routes
Show all pools
This API Call is used to show all the current pools that are existing.
GET /pool/show
-
AUTHENTICATIONS:
headers -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
response = requests.get(f"{base_url}/pool/show", headers=headers)
if response.status_code < 500:
print(response.json())curl -X GET \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/pool/show"
Create a pool
Here, we are creating a pool from this API call and the respective body schema
POST /pool/create
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"name" : "vme-pool",
"device" : ["test1","test2"]
}Name Type Description name requiredString Name of pool to be created.device requiredList List of device names. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"name" : "vme-pool",
"device" : ["nvme0n2", "nvme0n3"]
}
response = requests.post(f"{base_url}/pool/create", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'name': 'vme-pool', 'device': ['nvme0n2', 'nvme0n3']}" \
"$BASE_URL/pool/create"
Destroy a pool
This API call is responsible for destroying a pool that already exists
DELETE /pool/destroy
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"name" : "vme-pool"
}Name Type Description name requiredString Name of pool to be destroyed. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"name" : "vme-pool"
}
response = requests.delete(f"{base_url}/pool/destroy", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X DELETE \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'name': 'vme-pool'}" \
"$BASE_URL/pool/destroy"
Show current status of a pool
This API call will let us know what the current status is for a specific pool, which could be active,creating or destroyed.
GET /pool/status
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"name" : "vme-pool"
}Name Type Description name requiredString The name of the pool to show status on. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
params = {
"name" : "vme-pool"
}
response = requests.get(f"{base_url}/pool/status", params=params, headers=headers)
if response.status_code < 500:
print(response.json())curl -X GET \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/pool/status?name=vme-pool"
Show history of a pool
Here, we can check all the operations that have been performed on that pool, from when it was created, till it has been destroyed.
GET /pool/history
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"name" : "vme-pool"
}Name Type Description name requiredString The name of the pool to run the history command on. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
params = {
"name" : "vme-pool"
}
response = requests.get(f"{base_url}/pool/history", params=params, headers=headers)
if response.status_code < 500:
print(response.json())curl -X GET \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/pool/history?name=vme-pool"
Register a pool
Here we can register the pool for creation of sources and other operations like cloning and snapshotting.
POST /pool/register
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"name" : "vme-pool"
}Name Type Description name requiredString The name of the pool to be registered. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {"name" : "vme-pool"}
response = requests.post(f"{base_url}/pool/register", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'name': 'vme-pool'}" \
"$BASE_URL/pool/register"
Source Routes
Show all sources
This API call returns the sources in one pool, or in every pool.
GET /source/show
-
AUTHENTICATIONS:
headers -
QUERY PARAMETERS:
Name Type Description pool_name optionalString Name of the pool to list. If omitted, the sources in all pools are listed. source_name optionalString List only the sources nested under this source. With pool_name, give the source's short name (foo); without it, give the full name (vme-pool/sources/foo).properties optionalString ZFS properties to include for each source, comma separated or repeated (for example properties=used,quota). An unknown property is refused with400. -
RESPONSE:
resultis the list of sources andsource_namestheir full names. Each source carries, beside its ZFS properties, itsstatusanddbtypeand the fields described in What a source or clone listing carries. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
params = {
"pool_name" : "vme-pool"
}
response = requests.get(f"{base_url}/source/show", params=params, headers=headers)
if response.status_code < 500:
print(response.json())curl -X GET \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/source/show?pool_name=vme-pool"
What a source or clone listing carries
Each row returned by Show all sources and Show all clones carries these fields beside its ZFS properties:
| Field | Description |
|---|---|
| guid | The dataset's ZFS guid. It identifies the dataset whatever it is called, and does not change when the dataset is renamed. |
| iqn_name | For a dataset exported over iSCSI, the target name hosts connect to, otherwise null. It is built from the guid, as iqn.<yyyy-mm>.com.enov8:g-<guid>, so it stays the same when the dataset is renamed. |
| iqn_scheme | guid when iqn_name is built from the guid; name when it is built from the dataset's name. |
| attached_to | Where the dataset is in use. See the fields below. null for a dataset that is still being created. |
| used_by | The unfinished onboarding workflows using the dataset, each as { "workflow_id": "...", "name": "..." }. Empty when none is. |
attached_to holds:
| Field | Description |
|---|---|
| type | How the dataset is reached: iscsi, nfs, or container. |
| name | Who has it: the host a workflow mounted it on, or for iSCSI the host whose initiator is connected; the NFS clients it is exported to; or the container bound to it. |
| detail | Where: the folder on the host a workflow mounted it at, or the dataset's mountpoint for a container. |
| live | true when that is so right now, false when it is not, null when vME cannot tell (NFS). |
| state | A word for the screen: connected or not connected (iSCSI), exported (NFS), running or stopped (container), recorded, or not attached. |
{
"name" : "vme-pool/clones/app01-test",
"guid" : "8214416316530251783",
"iqn_name" : "iqn.2026-09.com.enov8:g-8214416316530251783",
"iqn_scheme" : "guid",
"attached_to" : {
"type" : "iscsi",
"name" : "SQL-B",
"detail" : "C:\\vME\\clones\\8214416316530251783",
"live" : true,
"state" : "connected"
},
"used_by" : []
}
Create a source
We can create a source under a particular pool name along with other properties like size/quote,compression type, database method and so on.
POST /source/create
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
- database method:
container{
"pool_name" : "vme-pool",
"name" : "foo",
"quota" : "1G",
"compression" : "lzjb"
} - database method
network, NFS{
"pool_name" : "vme-pool",
"name" : "foo",
"compression" : "lzjb",
"database_method" : "network",
"network_info" : {
"type" : "nfs"
}
} - database method
network, iSCSI{
"pool_name" : "vme-pool",
"name" : "foo",
"compression" : "lzjb",
"database_method" : "network",
"network_info" : {
"type" : "iscsi",
"auto_grow" : true,
"auto_grow_max_gb" : 16384
}
}
Name Type Description name requiredString Name of source to be created. pool_name requiredString Name of pool that contains a source. dbtype optionalString Database type, such as mysql, mariadb, mssql, mongodb etc., used to label a source, default is unknown.quota optionalString A limit on the amount of disk space a dataset can consume, for example, 10Gfor ten GB.compression optionalString Name of process where data is stored using less disk space, the name can be one of on,off,lzjb,gzip,gzip-1,gzip-2,gzip-3,gzip-4,gzip-5,gzip-6,gzip-7,gzip-8,gzip-9,zle,lz4,False, default isFalse.database_method optionalString container(default) ornetwork. Usenetworkto expose the source over NFS or iSCSI so an external host can mount it.network_info optionalDictionary Required when database_methodisnetwork. Holds the network settings below (at minimumtype).network_info.type requiredString nfsoriscsi. Required insidenetwork_info.network_info.auto_grow optionalBoolean iSCSI only. When true, the volume is thin-provisioned and grows on demand up toauto_grow_max_gb. Defaultfalse.network_info.auto_grow_max_gb optionalInteger iSCSI only. Maximum size (GB) the volume can grow to when auto_growis enabled. Default16384.network_info.vsize optionalInteger iSCSI only. Fixed volume size (GB). Used when auto_growisfalse; if omitted or0, the volume auto-grows.workflow_id optionalString The onboarding workflow this source is created for. If that workflow has been cancelled, the call is refused with 409. See Linking calls to a workflow. wizard_step, role optionalString Accepted so a script can send the same linkage fields to every call. This call does not mark a workflow step complete; the sourcestep is recorded when you publish the source with/db/network/createcarryingworkflow_idandrole.noteFor iSCSI, creating a source with
database_method: networkprovisions the iSCSI target and LUN automatically as part of source creation - no separate call is needed to expose it. For NFS, use/db/network/createafter creating the source to publish the share and set the client allow-list (client_address). - database method:
-
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"pool_name" : "vme-pool",
"name" : "foo"
}
response = requests.post(f"{base_url}/source/create", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d '{"pool_name": "vme-pool", "name": "foo"}' \
"$BASE_URL/source/create"
Modify a source
This API call is used to understand and modify the source properties as per use case.
POST /source/modify
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"pool_name" : "vme-pool",
"name" : "foo",
"property" : {
"quota":"5G",
"dbtype":"mysql",
"compression":"gzip"
}
}Name Type Description name requiredString Name of source to be modified. pool_name requiredString Name of pool that contains a source. property requiredDictionary The properties of the source to be modified, for example quota,compression,dbtype(the source's label) orclient_address.notedatabase_methodis set when the source is created and cannot be changed afterwards; adatabase_methodinsidepropertyis ignored. The source must be in the available state; a source that is still being created or changed is refused with400. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"pool_name" : "vme-pool",
"name" : "foo",
"property" : {
"quota" : "5G",
"dbtype" : "mysql",
"compression" : "gzip"
}
}
response = requests.post(f"{base_url}/source/modify", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d '{"pool_name": "vme-pool", "name": "foo", "property" : {"quota" : "5G", "dbtype" : "mysql", "compression" : "gzip"}}' \
"$BASE_URL/source/modify"
Rename a source
PUT /source/rename
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"pool_name" : "vme-pool",
"currentname" : "foo",
"newname" : "foofoo"
}Name Type Description pool_name requiredString Name of pool that contains a source. currentname requiredString Name of source to be renamed. newname requiredString New source name. -
RESPONSE: On success,
source_nameholds the new full name andresultdescribes the export:{
"status" : 1,
"message" : "Source [vme-pool/sources/foo -> vme-pool/sources/foofoo] rename success",
"source_name" : "vme-pool/sources/foofoo",
"result" : {
"iscsi" : true,
"iqn_name" : "iqn.2026-09.com.enov8:g-8214416316530251783",
"backstore_name" : "g-8214416316530251783",
"warning" : null,
"workflows_holding_the_previous_name" : []
}
}workflows_holding_the_previous_namelists finished onboarding workflows that recorded the old name. If the appliance could not save its iSCSI target configuration after the rename,warningsays so and is added tomessage. -
NOTES:
- A source exported over iSCSI keeps its target name and backstore when it is renamed, and a host that has it connected keeps its session. The appliance updates its saved target configuration so the export is restored after a restart.
- A source exported over NFS cannot be renamed: its export path is the dataset's mountpoint. The call is refused with 400.
- A source an unfinished onboarding workflow is using is refused with 409.
messagenames the workflows, andused_bylists them as{ "workflow_id", "name" }.
-
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"pool_name" : "vme-pool",
"currentname" : "foo",
"newname" : "foofoo"
}
response = requests.put(f"{base_url}/source/rename", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X PUT \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'pool_name': 'vme-pool', 'currentname': 'foo', 'newname': 'foofoo'}" \
"$BASE_URL/source/rename"
Destroy a source
This is used to remove a source listed under that specific pool.
DELETE /source/destroy
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"pool_name" : "vme-pool",
"source_name" : "foo"
}Name Type Description pool_name requiredString Name of pool that contains a source. source_name requiredString Name of source to be destroyed. recursive optionalBoolean The recursive option, default is False. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"pool_name" : "vme-pool",
"source_name" : "foo"
}
response = requests.delete(f"{base_url}/source/destroy", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X DELETE \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'pool_name': 'vme-pool', 'source_name': 'foo'}" \
"$BASE_URL/source/destroy"
Snapshot Routes
Show all snapshots
GET /snapshot/show
This API call is used to list all snapshots taken under the particular source or clone, under one pool, or across all pools.
-
AUTHENTICATIONS:
headers -
QUERY PARAMETERS:
Name Type Description pool_name optionalString Name of pool. If omitted, the snapshots in all pools are listed and source_nameis ignored.source_name optionalString List only the snapshots of this source or clone. A short name ( foo) is a source; to name a clone, give its full name (vme-pool/clones/foo-clone).properties optionalString ZFS properties to include for each snapshot, comma separated or repeated. sort optionalBoolean When true,snapshot_namesis in the order the snapshots were created. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
params = {
"pool_name" : "vme-pool"
}
response = requests.get(f"{base_url}/snapshot/show", params=params, headers=headers)
if response.status_code < 500:
print(response.json())curl -X GET \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/snapshot/show?pool_name=vme-pool"
Create a snapshot
POST /snapshot/create
This is used to create a snapshot under the particular source name which can be used for cloning.
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"pool_name" : "vme-pool",
"source_name" : "foo",
"name" : "snap"
}Name Type Description pool_name requiredString Name of pool. source_name requiredString Name of source/clone. name requiredString Name of snapshot/bookmark to be created. snapof optionalString Whether source_namenames asourceor aclone. Default issource. Send"snapof": "clone"to snapshot a clone. Not needed whensource_nameis a full name such asvme-pool/clones/foo-clone, which already says which it is.recursive optionalBoolean Recursive option, default is False.skip_host optionalBoolean Take the snapshot without asking the host that has the source mounted to quiesce it first. Default is false. See the note below.workflow_id optionalString The onboarding workflow this snapshot is taken for. On success, the workflow's step is marked complete. See Linking calls to a workflow. wizard_step optionalString The workflow step to mark complete. Default is snapshot.role optionalString The dataset role this snapshot belongs to, for example data. -
RESPONSE: On success the response includes a
stopped_containersarray listing any containers that were stopped to take a crash-consistent snapshot (empty if none).{
"status": 1,
"message": "Snapshot [vme-pool/sources/foo@snap] create success",
"pool_name": "vme-pool",
"source_name": "vme-pool/sources/foo",
"snapshot_name": "vme-pool/sources/foo@snap",
"recursive": false,
"stopped_containers": ["container-a", "container-b"]
}A source or clone a host has mountedIf a SQL Server host has this dataset attached over iSCSI, vME does not change it under the host. It asks the host's agent to take the databases on the volume offline and the disk with them (Quiesce), makes the snapshot, and brings them back (Resume). Resume runs even when the snapshot fails. The answer tells you which of these happened:
Status When Body 200 No host has the dataset attached, or skip_hostistrue.The snapshot is done, as before. 202 A host has it attached and its agent is online. {"task_id": "...", "plan": {"dataset", "host", "agent_id", "targets", "mount_path"}}. Follow it with Follow a coordinated snapshot or rollback.409 A host has it attached and no online agent can reach it; another coordinated operation is already running on that host; or vME could not work out whether a host has it. messagesays which, and for an unreachable host lists the steps to do it by hand. Nothing was changed.If the host's Quiesce fails, the snapshot is not made. Set
skip_hosttotrueonly when you have taken the databases on that volume offline yourself.Crash-consistent snapshotsIf the source has a container mount, vME finds any running containers bind-mounted on that source and stops them before taking the snapshot, so the captured data is consistent. The containers that were stopped are returned in
stopped_containers. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"pool_name" : "vme-pool",
"source_name" : "foo",
"name" : "snap"
}
response = requests.post(f"{base_url}/snapshot/create", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'pool_name': 'vme-pool', 'source_name': 'foo', 'name': 'snap'}" \
"$BASE_URL/snapshot/create"
Rollback a snapshot
POST /snapshot/rollback
This functionality is used to go back to the last saved instance of the snapshot previously captured
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"pool_name" : "vme-pool",
"source_name" : "foo",
"snapshot_name" : "snap",
"force" : true
}Name Type Description pool_name requiredString Name of pool. source_name requiredString Name of source/clone. snapshot_name requiredString Name of the snapshot/bookmark to roll back to. snapof optionalString Whether source_namenames asourceor aclone. Default issource. Send"snapof": "clone"to roll back a clone. Not needed whensource_name(orsnapshot_name) is a full name such asvme-pool/clones/foo-clone@snap.force optionalBoolean Force option to rollback, default is False.skip_host optionalBoolean Roll back without asking the host that has the dataset mounted to quiesce it first. Default is false.A source or clone a host has mountedIf a SQL Server host has this dataset attached over iSCSI, vME does not change it under the host. It asks the host's agent to take the databases on the volume offline and the disk with them (Quiesce), makes the rollback, and brings them back (Resume). Resume runs even when the rollback fails. The answer tells you which of these happened:
Status When Body 200 No host has the dataset attached, or skip_hostistrue.The rollback is done, as before. 202 A host has it attached and its agent is online. {"task_id": "...", "plan": {"dataset", "host", "agent_id", "targets", "mount_path"}}. Follow it with Follow a coordinated snapshot or rollback.409 A host has it attached and no online agent can reach it; another coordinated operation is already running on that host; or vME could not work out whether a host has it. messagesays which, and for an unreachable host lists the steps to do it by hand. Nothing was changed.If the host's Quiesce fails, the rollback is not made. Set
skip_hosttotrueonly when you have taken the databases on that volume offline yourself. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"pool_name" : "vme-pool",
"source_name" : "foo",
"snapshot_name" : "snap",
"force" : True
}
response = requests.post(f"{base_url}/snapshot/rollback", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'pool_name': 'vme-pool', 'source_name': 'foo', 'snapshot_name': 'snap', 'force': true}" \
"$BASE_URL/snapshot/rollback"
Follow a coordinated snapshot or rollback
GET /snapshot/task/<task_id>
How a snapshot or rollback that returned 202 is getting on.
- AUTHENTICATIONS:
headers - RESPONSE:
{
"status": 1,
"task_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"state": "PROGRESS",
"plan": { "dataset": "vme-pool/sources/foo", "host": "SQL-01", "agent_id": "...", "targets": ["iqn..."], "mount_path": "C:\\vME\\sources\\..." },
"steps": [
{ "key": "quiesce", "label": "Quiesce databases on SQL-01", "state": "completed", "message": "...", "job_id": "..." },
{ "key": "zfs", "label": "Bookmark", "state": "running", "message": "", "job_id": null },
{ "key": "resume", "label": "Resume databases on SQL-01", "state": "pending", "message": "", "job_id": null }
],
"result": null
}Field Description state PENDING(not started, or no longer known),PROGRESS,SUCCESSorFAILURE.steps Always the three steps, in order. The middle step is labelled Bookmarkfor a snapshot andRollbackfor a rollback. Eachstateispending,queued,running,completed,failedorskipped;job_idis the agent job for the host steps.result Once finished, what happened, with a message. - NOTES: Poll until
stateisSUCCESSorFAILURE. A failure names the step that failed; when Quiesce fails the ZFS step isskipped, and Resume still runs.
Rename a snapshot
PUT /snapshot/rename
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"pool_name" : "vme-pool",
"source_name" : "foo",
"currentname" : "snap",
"newname" : "snap-new"
}Name Type Description pool_name requiredString Name of pool. source_name requiredString Name of source/clone. currentname requiredString Name of snapshot/bookmark to be renamed. newname requiredString New name of snapshot/bookmark. snapof optionalString Whether source_namenames asourceor aclone. Default issource. Send"snapof": "clone"to rename a clone's snapshot. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"pool_name" : "vme-pool",
"source_name" : "foo",
"currentname" : "snap",
"newname" : "snap-new"
}
response = requests.put(f"{base_url}/snapshot/rename", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X PUT \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'pool_name': 'vme-pool', 'source_name': 'foo', 'currentname': 'snap', 'newname': 'snap-new'}" \
"$BASE_URL/snapshot/rename"
Destroy a snapshot
DELETE /snapshot/destroy
Helps remove a previously existing snapshot in the source list.
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"pool_name" : "vme-pool",
"source_name" : "foo",
"snapshot_name" : "snap"
}Name Type Description pool_name requiredString Name of pool. source_name requiredString Name of source/clone. snapshot_name requiredString Name of snapshot/bookmark to be destroyed. snapof optionalString Whether source_namenames asourceor aclone. Default issource. Send"snapof": "clone"to destroy a clone's snapshot, or give the full name (vme-pool/clones/foo-clone@snap). -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"pool_name" : "vme-pool",
"source_name" : "foo",
"snapshot_name" : "snap"
}
response = requests.delete(f"{base_url}/snapshot/destroy", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X DELETE \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'pool_name': 'vme-pool', 'source_name': 'foo', 'snapshot_name': 'snap'}" \
"$BASE_URL/snapshot/destroy"
Clone Routes
Show all clones
GET /clone/show
Lists down all available clones listed under that specific source
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"pool_name" : "vme-pool",
"source_name" : "foo",
"snapshot_name" : "snap"
}Name Type Description pool_name optionalString Name of pool. source_name optionalString Name of source. snapshot_name optionalString Name of snapshot. -
RESPONSE: Each clone carries, beside its ZFS properties and its
sourceandsnapshot, the fields described in What a source or clone listing carries. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
params = {
'pool_name' : 'vme-pool'
}
response = requests.get(f"{base_url}/clone/show", params=params, headers=headers)
if response.status_code < 500:
print(response.json())curl -X GET \
-H "username: xxxxx" \
-H 'api-key: xxxx' \
"$BASE_URL/clone/show?pool_name=vme-pool"
Create a clone
POST /clone/create
This API call assistsis responsible in creating a copy from the snapshot that has been captured.
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
- database method
container(default){
"pool_name" : "vme-pool",
"source_name" : "foo",
"snapshot_name" : "snap",
"name" : "foo-clone"
} - database method
network, iSCSI{
"pool_name" : "vme-pool",
"source_name" : "foo",
"snapshot_name" : "snap",
"name" : "foo-clone",
"database_method" : "network",
"network_info" : {
"type" : "iscsi"
}
}
Name Type Description pool_name requiredString Name of pool. source_name requiredString Name of the source (or clone) the snapshot was taken of. snapshot_name requiredString Name of snapshot. A full name such as vme-pool/sources/foo@snapcan be given instead ofpool_name,source_nameandsnapshot_name.snapof optionalString Whether source_namenames asourceor aclone. Default issource. Send"snapof": "clone"to clone a snapshot of a clone.name optionalString Name of clone to be created. If omitted, the clone is named <snapshot>.<unix time>, for examplesnap.1760918400.dbtype optionalString Database type, such as mysql, mariadb, mssql, mongodb etc., used to label a source, default is unknown.size optionalInteger Number of clones (clonefarm) to be created, default is 1.database_method optionalString container(default) ornetwork. Usenetworkto expose the clone over NFS or iSCSI so an external host can mount it.network_info optionalDictionary Required when database_methodisnetwork. Must containtype(nfsoriscsi). A clone inherits its size from the snapshot, so novsizeis needed.network_info.type requiredString nfsoriscsi. Required insidenetwork_info.workflow_id optionalString The onboarding workflow this clone is created for. On success, the workflow's step is marked complete. See Linking calls to a workflow. wizard_step optionalString The workflow step to mark complete. Default is clone.role optionalString The dataset role this clone belongs to, for example data. - database method
-
RESPONSE: The clone is created in the background; the call returns once it is queued.
clone_nameis the full name of the new clone; poll Show all clones for its status.{
"status": 1,
"message": "Clone [vme-pool/clones/foo-clone] create success",
"pool_name": "vme-pool",
"source_name": "vme-pool/sources/foo",
"snapshot_name": "vme-pool/sources/foo@snap",
"clone_name": "vme-pool/clones/foo-clone"
}noteFor an iSCSI clone, the iSCSI target and LUN are created automatically as part of clone creation - no separate call is needed to expose it. For NFS, use
/db/network/createafter the clone is created to publish the share and set the client allow-list. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"pool_name" : "vme-pool",
"source_name" : "foo",
"snapshot_name" : "snap",
"name" : "foo-clone"
}
response = requests.post(f"{base_url}/clone/create", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'pool_name': 'vme-pool', 'source_name': 'foo', 'snapshot_name': 'snap', 'name': 'foo-clone'}" \
"$BASE_URL/clone/create"
Modify a clone
POST /clone/modify
Here we can modify the properties of the clone based on the compression type, db type, quota and so on.
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"pool_name" : "vme-pool",
"name" : "foo-clone",
"property" : {
"quota" : "5G",
"dbtype" : "mysql",
"compression" : "gzip"
}
}Name Type Description name requiredString Name of clone to be modified. pool_name requiredString Name of pool that contains the clone. property requiredDictionary The properties of the clone to be modified, for example quota,compression,dbtype(the clone's label) orclient_address.notedatabase_methodis set when the clone is created and cannot be changed afterwards; adatabase_methodinsidepropertyis ignored. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"pool_name" : "vme-pool",
"name" : "foo-clone",
"property" : {
"quota" : "5G",
"dbtype" : "mysql",
"compression" : "gzip"
}
}
response = requests.post(f"{base_url}/clone/modify", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d '{"pool_name": "vme-pool", "name": "foo-clone", "property" : {"quota" : "5G", "dbtype" : "mysql", "compression" : "gzip"}}' \
"$BASE_URL/clone/modify"
Rename a clone
PUT /clone/rename
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"pool_name" : "vme-pool",
"currentname" : "foo-clone",
"newname" : "foofoo-clone"
}Name Type Description pool_name requiredString Name of pool. currentname requiredString Name of clone to be renamed. newname requiredString New name of clone. -
RESPONSE: On success,
clone_nameholds the new full name andresultdescribes the export, as for Rename a source. -
NOTES:
- A clone exported over iSCSI keeps its target name and backstore when it is renamed, and a host that has it connected keeps its session. The appliance updates its saved target configuration so the export is restored after a restart.
- A clone exported over NFS cannot be renamed: its export path is the dataset's mountpoint. The call is refused with 400.
- A clone an unfinished onboarding workflow is using is refused with 409.
messagenames the workflows, andused_bylists them as{ "workflow_id", "name" }.
-
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"pool_name" : "vme-pool",
"currentname" : "foo-clone",
"newname" : "foofoo-clone"
}
response = requests.put(f"{base_url}/clone/rename", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X PUT \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'pool_name': 'vme-pool', 'currentname': 'foo-clone', 'newname': 'foofoo-clone'}" \
"$BASE_URL/clone/rename"
Destroy a clone
DELETE /clone/destroy
Removes an existing clone under the specified pool
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"pool_name" : "vme-pool",
"clone_name" : "foo-clone"
}Name Type Description pool_name requiredString Name of pool. clone_name requiredString Name of clone to be destroyed. recursive optionalBoolean The recursive option, default is False. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"pool_name" : "vme-pool",
"clone_name" : "foo-clone"
}
response = requests.delete(f"{base_url}/clone/destroy", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X DELETE \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'pool_name': 'vme-pool', 'clone_name': 'foo-clone'}" \
"$BASE_URL/clone/destroy"
Object Routes
Destroy several objects at once
Destroy a selection of sources, snapshots and clones in one request, with one result per object.
DELETE /object/destroy
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"objects" : [
{ "kind": "clone", "pool_name": "vme-pool", "name": "vme-pool/clones/app01-test" },
{ "kind": "snapshot", "pool_name": "vme-pool", "name": "vme-pool/sources/app01@nightly" },
{ "kind": "source", "pool_name": "vme-pool", "name": "app01" }
],
"force" : false
}Name Type Description objects requiredList Between 1 and 100 objects. objects[].pool_name requiredString The pool the object is in. objects[].name requiredString The object's full name ( vme-pool/sources/app01,vme-pool/clones/app01-test), or just the name of a source or clone (app01). A snapshot is always named in full, as<dataset>@<snapshot>.objects[].kind optionalString source,snapshotorclone. When left out, a name with@is a snapshot, a name with/clones/is a clone, and anything else is a source.force optionalBoolean Destroy objects an unfinished onboarding workflow is still using. Default false. -
ORDER: Clones are destroyed first, then snapshots, then sources, whatever order you list them in. A snapshot that still has a clone, or a source that still has snapshots, is not destroyed unless those are in the same selection.
-
RESPONSE: 200 with a
resultentry per object, in the order you sent them, andcounts:{
"status" : 1,
"result" : [
{ "key": "clone:vme-pool/clones/app01-test", "kind": "clone", "name": "vme-pool/clones/app01-test",
"ok": true, "pending": false, "message": "destroyed" },
{ "key": "snapshot:vme-pool/sources/app01@nightly", "kind": "snapshot", "name": "vme-pool/sources/app01@nightly",
"ok": true, "pending": false, "message": "destroyed" },
{ "key": "source:vme-pool/sources/app01", "kind": "source", "name": "vme-pool/sources/app01",
"ok": false, "pending": false,
"message": "has 2 snapshot(s) (vme-pool/sources/app01@weekly, vme-pool/sources/app01@monthly); delete them first, or select them with it",
"snapshots": ["vme-pool/sources/app01@weekly", "vme-pool/sources/app01@monthly"] }
],
"counts" : { "destroyed": 2, "in_progress": 0, "refused": 0, "failed": 1 }
}Field Description ok, pending ok: true, pending: false- destroyed.ok: true, pending: true- the destroy of a source or clone has started but had not finished when the answer was sent; check it with Show all sources or Show all clones.ok: false- not destroyed;messagesays why.message destroyed,destroy in progress, or the reason it was not destroyed, for examplehas 2 snapshot(s) (...); delete them first, or select them with it,has 1 clone(s) (...); delete them first, or select them with it, orin use by an unfinished onboarding workflow (<workflow names>), so it was not destroyed. When something earlier in the same selection was still being destroyed, the message says so andwaiting_onnames its kind: run the request again once it has gone.refused, used_by Set on an object an unfinished onboarding workflow is using: used_bylists those workflows as{ "workflow_id", "name" }. Sendforce: trueto destroy it anyway.snapshots, clones The snapshots or clones that kept a source or snapshot from being destroyed. warning Set when a workflow had mounted the object on a host, for example was mounted on SQL-B; check the host afterwards. -
ERRORS: 400 when
objectsis missing or empty, holds more than 100 entries, has an entry withoutpool_nameandname, or names a snapshot without<dataset>@. Nothing is destroyed. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"objects" : [
{ "pool_name": "vme-pool", "name": "vme-pool/clones/app01-test" },
{ "pool_name": "vme-pool", "name": "vme-pool/sources/app01@nightly" }
]
}
response = requests.delete(f"{base_url}/object/destroy", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X DELETE \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d '{"objects": [{"pool_name": "vme-pool", "name": "vme-pool/clones/app01-test"}, {"pool_name": "vme-pool", "name": "vme-pool/sources/app01@nightly"}]}' \
"$BASE_URL/object/destroy"
Docker Image Routes
Pull an image
Similar to the docker pull command.
POST /docker/image/pull
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"repository" : "mysql",
"tag" : "8.4.0",
"credential" : {"username":"","password":""}
}Name Type Description repository requiredString Name of image repository to be pulled. tag optionalString Name of tag, default is latest.dbtype optionalString Database type, such as mysql, mariadb, mssql, mongodb etc., used to label a source, default is same as repository name. credential optionalDictionary Information used to verify repository accession, default is None. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"repository" : "mysql",
}
response = requests.post(f"{base_url}/docker/image/pull", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'repository': 'mysql'}" \
"$BASE_URL/docker/image/pull"
Register an image
POST /docker/image/register
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"name" : "mysql:latest",
"dbtype" : "mysql"
}Name Type Description name requiredString Name of image name:tagto be registered.dbtype optionalString Database type, such as mysql, mariadb, mssql, mongodb etc., used to label a source, default is same as repository name. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"name" : "mysql:latest",
"dbtype" : "mysql"
}
response = requests.post(f"{base_url}/docker/image/register", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'name': 'mysql:latest', 'dbtype': 'mysql'}" \
"$BASE_URL/docker/image/register"
List all images
Similar to the docker images command.
GET /docker/image/list
-
AUTHENTICATIONS:
headers -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
response = requests.get(f"{base_url}/docker/image/list", headers=headers)
if response.status_code < 500:
print(response.json())curl -X GET \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/docker/image/list"
Save an image as .tar
POST /docker/image/save
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"image" : "mysql:latest",
"filename" : "myimage.tar"
}Name Type Description image requiredString Name of image name:tagto be saved.filename requiredString Filename of saved image, the tar file will be saved at /home/vme/uploads. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"image" : "mysql:latest",
"filename" : "myimage.tar"
}
response = requests.post(f"{base_url}/docker/image/save", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'image': 'mysql:latest', 'filename': 'myimage.tar'}" \
"$BASE_URL/docker/image/save"
Load an image from .tar
POST /docker/image/load
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"filename" : "myimage.tar"
}Name Type Description filename requiredString Filename of image to be loaded. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"filename" : "myimage.tar"
}
response = requests.post(f"{base_url}/docker/image/load", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'filename': 'myimage.tar'}" \
"$BASE_URL/docker/image/load"
Remove an image
DELETE /docker/image/remove
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"image" : "mysql:latest"
}Name Type Description image requiredString Name of image name:tagto be removed. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"image" : "mysql:latest"
}
response = requests.delete(f"{base_url}/docker/image/remove", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X DELETE \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'image': 'mysql:latest'}" \
"$BASE_URL/docker/image/remove"
Docker Container Routes
Create a container
Similar to the docker run command.
POST /docker/container/create
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"image" : "mysql:latest",
"name" : "foo-mysql",
"volumes" : ["/vme-pool/sources/foo:/var/lib/mysql"],
"ports" : "8888:3306",
"environment" : ["MYSQL_ROOT_PASSWORD=secret_password"]
}Name Type Description image requiredString Name of image. name requiredString Name of container to be created. detach optionalBoolean Boolean for running container in the background, default is True.command optionalString Command to run in the container, default is None.environment optionalList or Dictionary Environment variables to set inside the container, as a dictionary or a list of strings in the format ["SOMEVARIABLE=xxx"], default isNone.volumes optionalList or Dictionary Configure volumes mounted inside the container, default is [].ports optionalString Ports to bind inside the container in the format "a:b,c:d,...", for example8080:80means map port8080on the host to TCP port80in the container, default isNone. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"image" : "mysql:latest",
"name" : "foo-mysql",
"volumes" : ["/vme-pool/sources/foo:/var/lib/mysql"],
"ports" : "8888:3306",
"environment" : ["MYSQL_ROOT_PASSWORD=secret_password"]
}
response = requests.post(f"{base_url}/docker/container/create", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'image': 'mysql:latest', 'name': 'foo-mysql', 'volumes': ['/vme-pool/sources/foo:/var/lib/mysql'], 'ports': '8888:3306', 'environment': ['MYSQL_ROOT_PASSWORD=secret_password']}" \
"$BASE_URL/docker/container/create"
List all containers
Similar to the docker ps -a command. This endpoint is also used to retrieve container connection information, including each container's environment variables (for example database credentials such as POSTGRES_PASSWORD, POSTGRES_USER, and POSTGRES_DB), port mappings, mount paths, and database type.
GET /docker/container/list
-
AUTHENTICATIONS:
headers -
RESPONSE SCHEMA: On success (
statusis1), theresultfield is a list of container objects. Each object includes anenvarray of strings inKEY=valueformat (the same values shown in the vME UI under Connection Information for a container).Name Type Description name String Container name. id String Full Docker container ID. short_id String Short Docker container ID. image String Image reference (for example postgres:17).status String Container state from Docker Engine (for example running,paused).port Object Docker port bindings ( NetworkSettings.Ports).mounted List Bind mounts as host_path:container_pathstrings.dbtype String Database type from the registered image (for example postgres).created String Human-readable time since the container was created. env List Environment variables as KEY=valuestrings.Example excerpt from
resultfor a single container:{
"name": "posttest",
"id": "205ce1989e609ecf7ffbaa616df26a9bf14cf34600e79d94cd0592b7a2bd2d91",
"short_id": "205ce1989e60",
"image": "postgres:17",
"status": "running",
"dbtype": "postgres",
"created": "12 minutes ago",
"port": {},
"mounted": ["/testing/sources/postgressource:/var/lib/postgresql/data"],
"env": [
"POSTGRES_PASSWORD=Password123",
"POSTGRES_USER=VME",
"POSTGRES_DB=VME",
"PATH=/usr/local/sbin:/usr/local/bin:...",
"GOSU_VERSION=1.19",
"LANG=en_US.utf8",
"PG_MAJOR=17",
"PG_VERSION=17.10-1.pgdg13+1",
"PGDATA=/var/lib/postgresql/data"
]
} -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
response = requests.get(f"{base_url}/docker/container/list", headers=headers)
if response.status_code < 500:
print(response.json())curl -X GET \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/docker/container/list"
Start a container
Similar to the docker start command.
POST /docker/container/start
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"name" : "foo-mysql"
}Name Type Description name requiredString Name of container to be started. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"name" : "foo-mysql"
}
response = requests.post(f"{base_url}/docker/container/start", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'name': 'foo-mysql'}" \
"$BASE_URL/docker/container/start"
Stop a container
Similar to the docker stop command.
POST /docker/container/stop
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"name" : "foo-mysql"
}Name Type Description name requiredString Name of container to be stoped. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"name" : "foo-mysql"
}
response = requests.post(f"{base_url}/docker/container/stop", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'name': 'foo-mysql'}" \
"$BASE_URL/docker/container/stop"
Execute command inside a container
Similar to the docker exec command.
POST /docker/container/exec
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"name" : "foo-mysql",
"command" : "mysql --user=root --password=\"secret_password\" -e \"show databases;\""
}Name Type Description name requiredString Name of container to be executed. command requiredString or List Commands to be executed. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"name" : "foo-mysql",
"command" : "mysql --user=root --password=\"secret_password\" -e \"show databases;\""
}
response = requests.post(f"{base_url}/docker/container/exec", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'name': 'foo-mysql', 'command': 'mysql --user=root --password=\"secret_password\" -e \"show databases;\"'}" \
"$BASE_URL/docker/container/exec"
Remove a container
Delete /docker/container/remove
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"name" : "foo-mysql"
}Name Type Description name requiredString Name of container to be removed. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"name" : "foo-mysql"
}
response = requests.delete(f"{base_url}/docker/container/remove", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X DELETE \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'name': 'foo-mysql'}" \
"$BASE_URL/docker/container/remove"
Get a container log
GET /docker/container/log
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"name" : "foo-mysql"
}Name Type Description name requiredString Name of container to get the log. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
params = {
"name" : "foo-con"
}
response = requests.get(f"{base_url}/docker/container/log", params=params, headers=headers)
if response.status_code < 500:
print(response.json())curl -X GET \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/docker/container/log?name=foo-con"
Get a container detail
GET /docker/container/detail
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"name" : "foo-mysql"
}Name Type Description name requiredString Name of container to get the detail. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
params = {
"name" : "foo-con"
}
response = requests.get(f"{base_url}/docker/container/detail", params=params, headers=headers)
if response.status_code < 500:
print(response.json())curl -X GET \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/docker/container/detail?name=foo-con"
Database Routes
Connect to database
POST /db/connect
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"dbtype" : "mysql",
"host" : "localhost",
"user" : "root",
"password" : "secret_password",
"port" : 8888
}The body is the required credential information, the general formatting is
{"dbtype": "<database_type>", "host": "<host>", "user": "<user>", "password": "<password>", "port": "<port>"}. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
credential = {
"dbtype" : "mysql",
"host" : "localhost",
"user" : "root",
"password" : "secret_password",
"port" : 8888
}
response = requests.post(f"{base_url}/db/connect", json=credential, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'dbtype': 'mysql', 'host': 'localhost', 'user': 'root', 'password': 'secret_password', 'port': 8888}" \
"$BASE_URL/db/connect"
Receive database
Receive backup and restore dataset.
POST /db/receive
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"pool_name" : "vme-pool",
"dataset_name" : "restored-sender-from-backup",
"dbtype" : "mysql",
"src" : "demo-pool-sources-sender@now-20230829183552.vmebk"
}Name Type Description pool_name requiredString Name of pool. dataset_name requiredString Name of dataset to be restored. dbtype optionalString Database type, such as mysql, mariadb, mssql, mongodb etc., used to label a source, default is unknown.src requiredString Path name of received backup file in .vmebkformat, the default absolute path is/enov8/vme/data/<filename>when only the filename is provided. -
RESPONSE: Returns HTTP 202 with a
task_id. The restore runs as a background task; poll its progress with/tasks/<task_id>using the returnedtask_id.{
"task_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "queued"
} -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"pool_name" : "vme-pool",
"dataset_name" : "restored-sender-from-backup",
"src" : "demo-pool-sources-sender@now-20230829183552.vmebk"
}
response = requests.post(f"{base_url}/db/receive", json=body, headers=headers)
if response.status_code < 500:
result = response.json()
print(result)
# Use the task_id to track the restore
task_id = result.get('task_id')curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'pool_name': 'vme-pool', 'dataset_name': 'restored-sender-from-backup', 'src': 'demo-pool-sources-sender@now-20230829183552.vmebk'}" \
"$BASE_URL/db/receive"
Send database
POST /db/send
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
- pool2pool
{
"type" : "pool2pool",
"dataset" : "demo-pool/sources/sender@now",
"receiver" : {
"pool_name" : "vme-pool",
"dataset_name" : "receiver-snap"
}
} - sys2sys, to an appliance registered under Appliance Registry Routes
{
"type" : "sys2sys",
"dataset" : "demo-pool/sources/sender@now",
"receiver" : {
"peer_id" : "c8d4f2e3b0a54c1c93e10a780a88b373",
"pool_name" : "vme-pool",
"dataset_name" : "receiver-sys2sys-snap"
}
} - sys2sys, naming the receiver directly
{
"type" : "sys2sys",
"dataset" : "demo-pool/sources/sender@now",
"receiver" : {
"address" : "https://9.87.654.321",
"username" : "admin",
"api-key" : "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"pool_name" : "vme-pool",
"dataset_name" : "receiver-sys2sys-snap"
}
}sys2sys authenticationFor a
sys2systransfer, name a registered appliance withpeer_id: the address, admin username, api-key and transfer address stored when it was registered are used, and any of them you also send takes precedence. Otherwise provide the receiver'saddress,username, and adminapi-key. The sending appliance uses theapi-keyto establish SSH trust with the receiver automatically, registering its own managed public key on the target.
Name Type Description type requiredString Sending type, pool2poolorsys2sys.dataset requiredString Name of dataset to be send. receiver requiredDictionary Information of receiver. For pool2poolcontainspool_nameanddataset_name. Forsys2sysalso containsaddress,username, andapi-key(the receiver's admin API key, used to automatically establish SSH trust).receiver.peer_id optionalString sys2sysonly. A registered appliance, from List appliances, in place ofaddress,usernameandapi-key. Apeer_idthat names no registered appliance is refused with 404; asys2syssend with neither is refused with 400.receiver.ssh_address optionalString sys2sysonly. Override the host used for the SSH/ZFS transfer when it differs from the receiver's APIaddress(e.g. a separate data network). Defaults to the host inaddress.overwrite optionalBoolean If true, destroy an existing destination dataset on the receiver before sending. Default isfalse(a name clash fails the transfer). A destination that has clones on the receiver is never overwritten: the transfer fails naming the clones, and nothing on the receiver is changed.keep_snapshot optionalBoolean If true, keep the temporary snapshot created for the send operation. Default isfalse(snapshot is deleted after send completes).workflow_id optionalString The onboarding workflow this send belongs to. When the transfer completes, the workflow's step is marked complete. See Linking calls to a workflow. wizard_step optionalString The workflow step to mark complete. Default is send.role optionalString The dataset role being sent, for example data. - pool2pool
-
RESPONSE: Returns HTTP 202 with a
task_idthat can be used to track the transfer status.{
"task_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "queued"
} -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"type" : "pool2pool",
"dataset" : "demo-pool/sources/sender@now",
"receiver" : {
"pool_name" : "vme-pool",
"dataset_name" : "receiver-snap"
}
}
response = requests.post(f"{base_url}/db/send", json=body, headers=headers)
if response.status_code < 500:
result = response.json()
print(result)
# Use the task_id to check status later
task_id = result.get('task_id')curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'type': 'pool2pool', 'dataset': 'demo-pool/sources/sender@now', 'receiver': {'pool_name': 'vme-pool', 'dataset_name': 'receiver-snap'}}" \
"$BASE_URL/db/send"
Get send status
Check the status of a database send operation by task ID or get all transfer history for a specific dataset.
GET /db/send/status
-
AUTHENTICATIONS:
headers -
QUERY PARAMETERS: (mutually exclusive - provide either
task_idordataset, not both)Name Type Description task_id optionalString The task ID returned from the /db/sendendpoint. Returns status for a specific transfer.dataset optionalString Dataset name (e.g., vme-pool/sources/foo). Returns all transfer history for this dataset. -
RESPONSE SCHEMA:
- Query by task_id: Returns information about a single transfer
{
"task_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "completed",
"progress_percentage": 100,
"message": "Transfer completed successfully",
"error": null,
"dataset": "demo-pool/sources/sender@now",
"dataset_type": "snapshot",
"send_type": "pool2pool",
"destination": {
"pool_name": "vme-pool",
"dataset_name": "receiver-snap",
"address": null
},
"started_at": "2024-01-15T10:30:00Z",
"completed_at": "2024-01-15T10:35:00Z",
"initiated_by": "admin"
} - Query by dataset: Returns all transfers for the dataset
{
"dataset": "vme-pool/sources/foo",
"total_transfers": 2,
"transfers": [
{
"task_id": "...",
"status": "completed",
"progress_percentage": 100,
...
},
{
"task_id": "...",
"status": "in_progress",
"progress_percentage": 45,
...
}
]
}
Status values:
queued- Transfer is waiting to startin_progress- Transfer is currently runningcompleted- Transfer completed successfullyfailed- Transfer failed (seeerrorfield for details)cancelled- Transfer was stopped with Cancel a send
- Query by task_id: Returns information about a single transfer
-
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
# Check status by task_id
params = {
"task_id" : "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
response = requests.get(f"{base_url}/db/send/status", params=params, headers=headers)
if response.status_code < 500:
print(response.json())
# Or check all transfers for a dataset
params = {
"dataset" : "vme-pool/sources/foo"
}
response = requests.get(f"{base_url}/db/send/status", params=params, headers=headers)
if response.status_code < 500:
print(response.json())# Check status by task_id
curl -X GET \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/db/send/status?task_id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# Or check all transfers for a dataset
curl -X GET \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/db/send/status?dataset=vme-pool/sources/foo"
Cancel a send
Cancel an in-flight database send (or receive) by its task ID. The running transfer task is signalled to stop and its transfer-history record is marked cancelled.
POST /db/send/cancel
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"task_id" : "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}Name Type Description task_id requiredString The task_idreturned by/db/sendor/db/receive. May also be supplied as a query parameter (?task_id=). -
RESPONSE:
200on success. A transfer that has already finished (completed,failed, or alreadycancelled) is returned as-is with200. An unknowntask_idreturns404. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = { "task_id" : "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }
response = requests.post(f"{base_url}/db/send/cancel", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'task_id': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'}" \
"$BASE_URL/db/send/cancel"
Backup database
Get all backup filenames.
GET /db/backup
-
AUTHENTICATIONS:
headers -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
response = requests.get(f"{base_url}/db/backup", headers=headers)
if response.status_code < 500:
print(response.json())curl -X GET \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/db/backup"
Create backup file.
POST /db/backup
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"dataset" : "vme-pool/sources/foo"
}Name Type Description dataset requiredString Name of dataset to be backup. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"dataset" : "vme-pool/sources/foo"
}
response = requests.post(f"{base_url}/db/backup", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d "{'dataset' : 'vme-pool/sources/foo'}" \
"$BASE_URL/db/backup"
Create a network share
Expose a source or clone dataset over the network (NFS or iSCSI, depending on how the dataset's network type is configured) so a database client can mount it. Use this to serve a restored/cloned database to an external host.
POST /db/network/create
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
- NFS
{
"full_name" : "vme-pool/sources/foo",
"client_address" : ["10.0.0.21", "10.0.0.22"]
} - iSCSI
{
"full_name" : "vme-pool/sources/foo"
}
Name Type Description full_name requiredString Fully-qualified dataset, in the form <pool>/sources/<source>or<pool>/clones/<clone>. The dataset must already have a supported network type configured.client_address required for NFSString or List NFS only. One IPv4 address, or a list of IPv4 addresses, permitted to mount the share. Leave it out for iSCSI - see the note below. client_username optionalString Client username for the share. Default admin.client_password optionalString Client password for the share. Default admin.anonuid optionalNumber NFS only. Anonymous UID for NFS mapping. Default 117.anongid optionalNumber NFS only. Anonymous GID for NFS mapping. Default 122.workflow_id optionalString The onboarding workflow this source belongs to. When the share is created (or was already configured), the workflow's step is marked complete; for the sourcestep, the dataset's name and iSCSI target name are also recorded on the workflow. See Linking calls to a workflow.wizard_step optionalString The workflow step to mark complete. Default is source.role optionalString The dataset role of full_name, for exampledata. Send it so the workflow records the dataset under that role.iSCSI exportsEvery iSCSI export on a vME is served by one portal listening on
0.0.0.0:3260, on every interface of the appliance, so an iSCSI export takes noclient_address,anonuidoranongid. Send onlyfull_name. Aclient_addressother than0.0.0.0is refused with 400:an iSCSI export listens on 0.0.0.0:3260, on every interface of this vME, so it cannot also be bound to <address>. Leave client_address out. - NFS
-
RESPONSE:
{ "status": 1, "message": "Network create success : <full_name>" }. A dataset that is already exported answers 200 withNetwork already configured : <full_name>. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"full_name" : "vme-pool/sources/foo",
"client_address" : ["10.0.0.21", "10.0.0.22"]
}
response = requests.post(f"{base_url}/db/network/create", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d '{"full_name": "vme-pool/sources/foo", "client_address": ["10.0.0.21", "10.0.0.22"]}' \
"$BASE_URL/db/network/create"
Delete a network share
Tear down an existing network share for a dataset.
DELETE /db/network/delete
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"full_name" : "vme-pool/sources/foo"
}Name Type Description full_name requiredString Fully-qualified dataset ( <pool>/sources/<source>or<pool>/clones/<clone>) whose share should be removed. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = { "full_name" : "vme-pool/sources/foo" }
response = requests.delete(f"{base_url}/db/network/delete", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X DELETE \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d '{"full_name": "vme-pool/sources/foo"}' \
"$BASE_URL/db/network/delete"
Poll a background task
Several operations run asynchronously and return a task_id (for example /db/receive and /db/send). Use this endpoint to poll a task's state by its ID.
GET /tasks/<task_id>
-
AUTHENTICATIONS: none. The route does not check the
usernameandapi-keyheaders; sending them does no harm. -
PATH PARAMETERS:
Name Type Description task_id requiredString The task ID returned by an asynchronous endpoint. -
RESPONSE: A
stateofPENDING,STARTED,PROGRESS,SUCCESS, orFAILURE. OnSUCCESSthe payload is returned inresult; otherwisestatusdescribes the current state or error. While a long-running task reports how far it has got,stateisPROGRESSandstatuscarries its progress details. An unknown task ID also answersPENDING.{ "state": "SUCCESS", "result": { "status": 1, "message": "done" } }tipThis is the generic Celery task poll used after
dbandpooloperations. It is not the same as the setup wizard's/setup/task/<id>poll. For database send transfers you can alternatively use/db/send/status, which returns richer transfer history. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
task_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
response = requests.get(f"{base_url}/tasks/" + task_id, headers=headers)
if response.status_code < 500:
print(response.json())curl -X GET \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/tasks/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
iSCSI CHAP Routes
These routes control which host may log in to an iSCSI target, and with what credentials. They add or remove an access entry for one host's initiator on one target, with CHAP and mutual CHAP credentials.
Set CHAP credentials
Allow one initiator to log in to a target, and set its CHAP and mutual CHAP credentials.
POST /iscsi/chap/set
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"target_iqn" : "iqn.2026-09.com.enov8:g-8214416316530251783",
"initiator_iqn" : "iqn.1991-05.com.microsoft:sql-b",
"chap_user" : "sqlb",
"chap_password" : "TargetSecret01",
"mutual_chap_password" : "InitiatorSecret1"
}Name Type Description target_iqn requiredString The target to protect: the iqn_nameof a source or clone, from What a source or clone listing carries.initiator_iqn requiredString The host's iSCSI initiator name. For a host with an agent, this is host_facts.initiator_iqnin List agents. It is also used as the mutual CHAP user name.mutual_chap_password requiredString The secret the target uses to prove itself to the host (mutual CHAP). chap_user optionalString The CHAP user name the host logs in with. Default is admin.chap_password optionalString The CHAP secret the host logs in with. If omitted, a random 16-character secret is generated and returned in result.chap_secret. -
RESPONSE: The credentials to configure on the host.
{
"action" : "set_chap",
"status" : 1,
"message" : "Mutual CHAP configured for target_iqn: iqn.2026-09.com.enov8:g-8214416316530251783 , initiator_iqn: iqn.1991-05.com.microsoft:sql-b",
"result" : {
"status" : "success",
"chap_user" : "sqlb",
"chap_secret" : "TargetSecret01",
"mutual_user" : "iqn.1991-05.com.microsoft:sql-b",
"target_iqn" : "iqn.2026-09.com.enov8:g-8214416316530251783",
"initiator_iqn" : "iqn.1991-05.com.microsoft:sql-b"
}
}The call is refused with 400 when either IQN is missing, or when the target does not exist or already has an entry for that initiator; remove the entry first to change its credentials.
-
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"target_iqn" : "iqn.2026-09.com.enov8:g-8214416316530251783",
"initiator_iqn" : "iqn.1991-05.com.microsoft:sql-b",
"chap_user" : "sqlb",
"mutual_chap_password" : "InitiatorSecret1"
}
response = requests.post(f"{base_url}/iscsi/chap/set", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d '{"target_iqn": "iqn.2026-09.com.enov8:g-8214416316530251783", "initiator_iqn": "iqn.1991-05.com.microsoft:sql-b", "chap_user": "sqlb", "mutual_chap_password": "InitiatorSecret1"}' \
"$BASE_URL/iscsi/chap/set"
Remove CHAP credentials
Clear an initiator's CHAP and mutual CHAP credentials on a target and remove its access entry.
DELETE /iscsi/chap/remove
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"target_iqn" : "iqn.2026-09.com.enov8:g-8214416316530251783",
"initiator_iqn" : "iqn.1991-05.com.microsoft:sql-b"
}Name Type Description target_iqn requiredString The target the entry is on. initiator_iqn requiredString The host's iSCSI initiator name. -
RESPONSE:
{ "action": "remove_chap", "status": 1, "message": "CHAP and Mutual CHAP removed for <initiator_iqn>" }. If the credentials could not be cleared (for example, there is no entry for that initiator), the call answers 400 with the reason inmessage. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"target_iqn" : "iqn.2026-09.com.enov8:g-8214416316530251783",
"initiator_iqn" : "iqn.1991-05.com.microsoft:sql-b"
}
response = requests.delete(f"{base_url}/iscsi/chap/remove", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X DELETE \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d '{"target_iqn": "iqn.2026-09.com.enov8:g-8214416316530251783", "initiator_iqn": "iqn.1991-05.com.microsoft:sql-b"}' \
"$BASE_URL/iscsi/chap/remove"
User Management Routes
Add User
This API call adds a new user. Requires an administrator account. POST /admin/user/add takes the same body and behaves the same way.
POST /user/add
-
AUTHENTICATIONS:
headers- Admin only -
REQUEST BODY SCHEMA:
{
"username": "jane",
"name": "Jane Doe",
"mail": "jane@company.com",
"group_id": 1,
"auth_type": "ldap"
}Name Type Description username requiredString Login name for the new user. name requiredString Full/display name of the user. mail requiredString Email address for the user. group_id optionalNumber ID of the group to assign the user to: 0is the administrator group,1is the standard user group. The group must already exist. If omitted, the user is added to the administrator group (0).auth_type optionalString localorldap(passwordis accepted as another name forlocal). If omitted, the user is alocaluser.password optionalString Required when auth_typeislocal(or omitted). Initial password for the new local user. Not used forldap.Set group_id for a non-administratorA user added without
group_idbecomes an administrator. To add a standard user, send"group_id": 1(or the ID of another non-administrator group). -
RESPONSE:
{ "action": "add_user", "status": 1, "message": "user add success", "user": "admin", "username": "jane" }(useris the administrator who made the call). An email address that is already in use, or agroup_idthat does not exist, answers400. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"username": "jane",
"name": "Jane Doe",
"mail": "jane@company.com",
"group_id": 1,
"auth_type": "ldap"
}
response = requests.post(f"{base_url}/user/add", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d '{"username": "jane", "name": "Jane Doe", "mail": "jane@company.com", "group_id": 1, "auth_type": "ldap"}' \
"$BASE_URL/user/add"
Modify User
This API call changes your own account: your full name, email address, password (local accounts only), or api-key. It does not change your group or sign-in type; an administrator does that with Modify a User (admin).
POST /user/modify
-
AUTHENTICATIONS: the account's own
usernameandcurrent_passwordin the body. Theusernameandapi-keyheaders are not checked on this route. -
REQUEST BODY SCHEMA:
{
"username": "jane",
"current_password": "OldPass123!",
"password": {
"current": "OldPass123!",
"new": "NewPass456!",
"confirm": "NewPass456!"
}
}Name Type Description username requiredString Login name of the account to change. The login name itself cannot be changed. current_password requiredString The account's current password, which authorises the change. For an LDAP account, this is the directory password. password optionalObject To change the password, pass an object with current,new, andconfirmkeys (all three required;newandconfirmmust match). Omit to leave the password unchanged. Refused for LDAP accounts.name optionalString Updated full/display name. mail optionalString Updated email address. rotate_api_key optionalBoolean If true, issue a new api-key for the account; the new key is returned inupdated.api_keyand the old key stops working.note/user/modifyis authorised by the account's own password, so it only ever changes the account whose password you supply. A password change must be sent as a structured object (password: {current, new, confirm}). To change another user's group, sign-in type or password, use Modify a User (admin). -
RESPONSE:
200with anupdatedobject listing the fields that changed (a changed password shows as***updated***; a rotated key appears asapi_key).{ "status": 1, "message": "user modify success", "updated": { "password": "***updated***" } } -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"username": "jane",
"current_password": "OldPass123!",
"password": {
"current": "OldPass123!",
"new": "NewPass456!",
"confirm": "NewPass456!"
}
}
response = requests.post(f"{base_url}/user/modify", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d '{"username": "jane", "current_password": "OldPass123!", "password": {"current": "OldPass123!", "new": "NewPass456!", "confirm": "NewPass456!"}}' \
"$BASE_URL/user/modify"
Delete User
This API call deletes your own account. It is authorised by the account's own password, so in practice a user can only delete themselves. Administrator accounts cannot be deleted through this route; an administrator removes users with Delete a User (admin).
DELETE /user/delete
-
AUTHENTICATIONS: the account's own
usernameandpasswordin the body. Theusernameandapi-keyheaders are not checked on this route. -
REQUEST BODY SCHEMA:
{
"username": "ubuntu123",
"password": "MyPassword123!",
"confirm_text": "DELETE ubuntu123"
}Name Type Description username requiredString Login name of the account to delete. password requiredString The password of the account being deleted. For an LDAP account, this is the directory password. confirm_text requiredString Confirmation phrase; must be the literal string DELETE <username>(e.g.DELETE ubuntu123).note/user/deleteneeds the account's ownpasswordand aconfirm_textvalue of exactlyDELETE <username>. An account in the administrator group is refused with400. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"username": "ubuntu123",
"password": "MyPassword123!",
"confirm_text": "DELETE ubuntu123"
}
response = requests.delete(f"{base_url}/user/delete", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X DELETE \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d '{"username": "ubuntu123", "password": "MyPassword123!", "confirm_text": "DELETE ubuntu123"}' \
"$BASE_URL/user/delete"
Change Password
Change your own password using your current password. Authenticated users can only change their own password.
POST /user/changepass
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"username": "jane",
"password": "OldPass123!",
"new_password": "NewPass456!"
}Name Type Description username requiredString Your login name. Must match the authenticated user; changing another user's password returns 403.password requiredString Your current password. new_password requiredString The new password to set. -
RESPONSE:
{ "status": 1, "message": "password changed successfully" } -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"username": "jane",
"password": "OldPass123!",
"new_password": "NewPass456!"
}
response = requests.post(f"{base_url}/user/changepass", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d '{"username": "jane", "password": "OldPass123!", "new_password": "NewPass456!"}' \
"$BASE_URL/user/changepass"
Get All Users
This API call returns all users. Requires an administrator account. GET /user/list is another name for the same route and returns the same result.
GET /admin/user/list
-
AUTHENTICATIONS:
headers- Admin only -
RESPONSE:
resultis a list of users. Passwords and api-keys are not included.{
"action" : "admin_user_list",
"status" : 1,
"message" : "success",
"result" : [
{ "username": "jane", "name": "Jane Doe", "mail": "jane@company.com", "group_id": 1, "auth_type": "local" }
]
} -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
response = requests.get(f"{base_url}/admin/user/list", headers=headers)
if response.status_code < 500:
print(response.json())curl -X GET \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/admin/user/list"
Add a User (admin)
POST /admin/user/add adds a user. It takes the same body, has the same defaults and returns the same result as Add User, including that a user added without group_id becomes an administrator. Set group_id to 1 (or another non-administrator group) for a standard user.
POST /admin/user/add
-
AUTHENTICATIONS:
headers- Admin only -
REQUEST BODY SCHEMA: as for Add User.
-
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"username": "sam",
"name": "Sam Lee",
"mail": "sam@company.com",
"password": "MyPassword123!",
"group_id": 1
}
response = requests.post(f"{base_url}/admin/user/add", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d '{"username": "sam", "name": "Sam Lee", "mail": "sam@company.com", "password": "MyPassword123!", "group_id": 1}' \
"$BASE_URL/admin/user/add"
Modify a User (admin)
An administrator changes any user's details, group, sign-in type or password, or rotates their api-key. No current password is needed.
PUT /admin/user/modify
-
AUTHENTICATIONS:
headers- Admin only -
REQUEST BODY SCHEMA:
{
"username" : "sam",
"group_id" : 0
}Name Type Description username requiredString Login name of the user to change. name optionalString Updated full/display name. mail optionalString Updated email address. group_id optionalNumber Move the user to this group ( 0administrator,1standard user). The group must exist. The last remaining administrator cannot be moved out of group0.auth_type optionalString localorldap. Switching tolocalneedspassword. Switching toldapclears the stored password.password optionalString New password for a local user. Must be sent together with auth_type(send"auth_type": "local"to reset a local user's password).rotate_api_key optionalBoolean If true, issue a new api-key for the user; the new key is returned inupdated.api_keyand the old key stops working.At least one field besides
usernamemust be sent. -
RESPONSE:
200with anupdatedobject listing what changed.{ "action": "admin_modify_user", "status": 1, "message": "user modify success", "username": "sam", "updated": { "group_id": 0 } } -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"username" : "sam",
"auth_type" : "local",
"password" : "NewPass456!"
}
response = requests.put(f"{base_url}/admin/user/modify", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X PUT \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d '{"username": "sam", "auth_type": "local", "password": "NewPass456!"}' \
"$BASE_URL/admin/user/modify"
Delete a User (admin)
An administrator deletes any user, including another administrator. The last remaining administrator cannot be deleted.
DELETE /admin/user/delete
-
AUTHENTICATIONS:
headers- Admin only -
REQUEST BODY SCHEMA:
{
"username" : "sam"
}Name Type Description username requiredString Login name of the user to delete. -
RESPONSE:
{ "action": "admin_delete_user", "status": 1, "message": "account sam was deleted", "username": "sam" }. An unknown username answers400. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {"username" : "sam"}
response = requests.delete(f"{base_url}/admin/user/delete", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X DELETE \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d '{"username": "sam"}' \
"$BASE_URL/admin/user/delete"
LDAP Configuration Routes
All LDAP configuration endpoints (GET/POST/PUT/DELETE on /user/ldap) require an administrator account. A separate POST /user/ldap/test endpoint (also admin-only) validates a connection without saving it.
Get LDAP Configuration
This API call retrieves the current LDAP configuration.
GET /user/ldap
-
AUTHENTICATIONS:
headers -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
response = requests.get(f"{base_url}/user/ldap", headers=headers)
if response.status_code < 500:
print(response.json())curl -X GET \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/user/ldap"
Delete LDAP Configuration
This API call removes the LDAP configuration.
DELETE /user/ldap
-
AUTHENTICATIONS:
headers -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
response = requests.delete(f"{base_url}/user/ldap", headers=headers)
if response.status_code < 500:
print(response.json())curl -X DELETE \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
"$BASE_URL/user/ldap"
Create LDAP Configuration
This API call creates the LDAP server.
POST /user/ldap
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"server": "sample.com",
"port": 389,
"bind_dn": "cn=readonly,dc=company,dc=com",
"bind_password": "admin",
"user_search_base": "ou=users,dc=company,dc=com",
"user_filter": "(uid={adminuser})",
"vme_attrs": {"username": "uid", "name": "cn", "mail": "mail"}
}Name Type Description server requiredString Hostname or IP address of the LDAP server. port requiredNumber Port on which the LDAP server listens (e.g. 389 for LDAP, 636 for LDAPS). bind_dn requiredString Distinguished Name (DN) used to bind to the LDAP server. bind_password requiredString Password for the bind_dn account. user_search_base requiredString Base DN under which user entries are searched. user_filter requiredString LDAP search filter for users; placeholder (e.g. {adminuser}) is replaced by the login name.vme_attrs requiredObject Mapping of VME user fields ( username,name,mail) to LDAP attribute names. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"server": "sample.com",
"port": 389,
"bind_dn": "cn=readonly,dc=company,dc=com",
"bind_password": "admin",
"user_search_base": "ou=users,dc=company,dc=com",
"user_filter": "(uid={adminuser})",
"vme_attrs": {"username": "uid", "name": "cn", "mail": "mail"}
}
response = requests.post(f"{base_url}/user/ldap", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d '{"server": "sample.com", "port": 389, "bind_dn": "cn=readonly,dc=company,dc=com", "bind_password": "admin", "user_search_base": "ou=users,dc=company,dc=com", "user_filter": "(uid={adminuser})", "vme_attrs": {"username": "uid", "name": "cn", "mail": "mail"}}' \
"$BASE_URL/user/ldap"
Update LDAP Configuration
This API call updates the existing LDAP configuration.
PUT /user/ldap
-
AUTHENTICATIONS:
headers -
REQUEST BODY SCHEMA:
{
"server": "sample.com",
"port": 389,
"bind_dn": "cn=readonly,dc=company,dc=com",
"bind_password": "admin",
"user_search_base": "ou=users,dc=company,dc=com",
"user_filter": "(uid={adminuser})",
"vme_attrs": {"username": "uid", "name": "cn", "mail": "mail"}
}Name Type Description server requiredString Hostname or IP address of the LDAP server. port requiredNumber Port on which the LDAP server listens (e.g. 389 for LDAP, 636 for LDAPS). bind_dn requiredString Distinguished Name (DN) used to bind to the LDAP server. bind_password requiredString Password for the bind_dn account. user_search_base requiredString Base DN under which user entries are searched. user_filter requiredString LDAP search filter for users; placeholder (e.g. {adminuser}) is replaced by the login name.vme_attrs requiredObject Mapping of VME user fields ( username,name,mail) to LDAP attribute names. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
body = {
"server": "sample.com",
"port": 389,
"bind_dn": "cn=readonly,dc=company,dc=com",
"bind_password": "admin",
"user_search_base": "ou=users,dc=company,dc=com",
"user_filter": "(uid={adminuser})",
"vme_attrs": {"username": "uid", "name": "cn", "mail": "mail"}
}
response = requests.put(f"{base_url}/user/ldap", json=body, headers=headers)
if response.status_code < 500:
print(response.json())curl -X PUT \
-H "Content-Type: application/json" \
-H "username: xxxxx" \
-H "api-key: xxxxx" \
-d '{"server": "sample.com", "port": 389, "bind_dn": "cn=readonly,dc=company,dc=com", "bind_password": "admin", "user_search_base": "ou=users,dc=company,dc=com", "user_filter": "(uid={adminuser})", "vme_attrs": {"username": "uid", "name": "cn", "mail": "mail"}}' \
"$BASE_URL/user/ldap"
Setup Routes
Show first-time setup state
Where the appliance's first-time setup wizard stands. The web interface reads this before anyone logs in, to decide whether to show the wizard and which step to resume at.
GET /setup/state
-
AUTHENTICATIONS: None.
-
RESPONSE:
{
"is_complete" : false,
"current_step" : 2,
"steps_done" : ["hostname", "network", "ntp"],
"step_summary" : { "hostname": { "hostname": "vme-prod-01" } },
"steps_config" : [ { "name": "hostname", "ui_index": 1, "required": false } ],
"defaults" : { "ntp_server": "time.example.com" },
"network_preconfigured" : true,
"current" : {
"hostname" : "vme-prod-01",
"ipv4" : "10.20.30.41/24",
"ipv4_method" : "static",
"gateway" : "10.20.30.1",
"dns" : ["10.20.30.2"]
}
}Field Description is_complete trueonce setup has finished; the wizard is then closed.current_step The last step saved, 0when setup has not started.steps_done, step_summary The steps recorded so far, and what each one set, leaving out anything secret. steps_config The wizard's steps, the page each is on, and whether it is required. defaults Starting values for wizard fields, such as the appliance's current NTP server. network_preconfigured truewhen the hostname, NTP and network were already set and confirmed in the appliance's console setup, so the wizard skips the page that asks for them.falseon an appliance that was not set up from its console, such as a cloud image; the wizard then asks.current The appliance's hostname and network as they are right now: ipv4in CIDR form (ornot configured),ipv4_method(dhcp,staticorunknown),gateway(ornone) anddns. -
EXAMPLE:
- Python
- Curl
# Download the requests library from https://docs.python-requests.org/en/latest/
import requests
response = requests.get(f"{base_url}/setup/state")
if response.status_code < 500:
print(response.json())curl -X GET "$BASE_URL/setup/state"
Agent API
vME can drive database onboarding and clone workflows on remote hosts through enrolled agents (for example, the Windows iSCSI Agent). The endpoints in this section, together with the Workflow API, are the admin-facing surface for that automation.
This section documents the endpoints. For how to sequence them into a working clone cycle - job states, polling, timeouts, and error handling - see Agent Jobs. For the phases and config fields of a specific connector, see that connector's API reference, such as the iSCSI API reference.
Every endpoint in the Agent API and Workflow API sections requires an administrator account.
How it works:
- An administrator mints a one-time enrollment token (
POST /agent-token) and gives it to whoever installs the agent. - The agent enrols itself with that token, receives a long-lived agent key, and runs as a background service on the host.
- The agent polls vME for assigned jobs, runs each one locally, and reports progress and results back.
- An administrator dispatches jobs (
POST /agent-job) and monitors them, or drives a higher-level onboarding workflow that sequences jobs across two appliances.
Enrollment, polling, and progress reporting are performed by the agent itself using its own credentials (x-agent-id / x-agent-key). Those routes are part of the agent's protocol and are not called by hand.
Create an enrollment token
An enrollment token is a single-use, time-limited secret that lets a host register one agent.
POST /agent-token
- AUTHENTICATIONS:
headers- Admin only - REQUEST BODY SCHEMA:
{
"label" : "prod-sql-01",
"environment" : "prod",
"ttl_minutes" : 60
}Name Type Description label optionalString Friendly name for the agent that will enrol with this token. environment optionalString Environment the host belongs to (e.g. prod,dev,test).ttl_minutes optionalNumber How long the token stays valid. Defaults to a short-lived window suitable for one install. - RESPONSE: The raw
tokenis returned once - store it immediately, it cannot be retrieved again.{ "result": { "token_id": "...", "token": "...", "environment": "prod", "expires_at": "..." } } - EXAMPLE:
curl -X POST \
-H "Content-Type: application/json" \
-H "username: xxxxx" -H "api-key: xxxxx" \
-d '{"label": "prod-sql-01", "environment": "prod", "ttl_minutes": 60}' \
"$BASE_URL/agent-token"
List enrollment tokens
GET /agent-token
- AUTHENTICATIONS:
headers- Admin only - QUERY PARAMETERS:
active_only(optional, on by default): only tokens that are still valid, unused and not revoked are returned. Sendactive_only=0(orfalse) to list every token, including used, expired and revoked ones. - RESPONSE:
{ "result": [ ... ] }, newest first. Each token carriestoken_id,label,environment,created_by,created_at,expires_at,consumed_at,consumed_by_agent_idandrevoked. Raw token values are never returned again.
Revoke an enrollment token
DELETE /agent-token
- AUTHENTICATIONS:
headers- Admin only - REQUEST BODY SCHEMA:
{ "token_id": "..." }
List agents
GET /agent
-
AUTHENTICATIONS:
headers- Admin only -
QUERY PARAMETERS (all optional filters):
environment,status,enabled,version. -
RESPONSE:
{ "result": [ ... ] }. Each agent carries:Field Description agent_id The agent's ID. host_name, display_name The host the agent runs on. label The agent's name in vME: the label an administrator set with Manage an agent, otherwise the label of the enrollment token it enrolled with, otherwise its host name. environment The environment it belongs to. tags The tags an administrator set, as a list. Empty when there are none. version The agent's version. status The effective status: disabledwhen the agent is disabled,inactivewhen its heartbeat has gone quiet, otherwise the status the agent reported (normallyonline).enabled Whether the agent may take jobs. enrolled_by, enrolled_at, last_seen_at Who enrolled it, when, and its last heartbeat. message The agent's last status message. attributes What the agent reports about itself and its host with each heartbeat, plus the labelandtagsset in vME. A heartbeat never overwriteslabelortags.The Windows iSCSI Agent reports these
attributes:Field Description capabilities What this agent can do, by name: folder_mount(sources and clones mount as folders under the host's vME folder),quiesce_resume(theQuiesceandResumephases),forget_named_targetandforget_named_portal(CloneUnmountremoves only the targets and portals it is given),attach_as(CloneMountattaches the clone's one database under the name given inattach.attachAs). A name that is absent means the agent cannot do that.host_facts Collected when the agent starts and every 15 minutes after. Absent from the first heartbeat after a start. host_facts.os caption,version,buildandarchitectureof Windows.host_facts.cpu_logical, host_facts.memory_gb Logical processors and physical memory. host_facts.domain, host_facts.part_of_domain The host's domain or workgroup. host_facts.ip_addresses Its IPv4 addresses, without loopback and link-local. host_facts.initiator_iqn The host's iSCSI initiator name. host_facts.vme_root The host's vME folder, which sources and clones mount under. Empty when it is not set. host_facts.sql_instances One entry per SQL Server instance: name,service_name,status,database_count,product_name,product_version,major_version,product_level,update_level,edition,collation,is_clustered,is_hadr_enabled. The version fields are absent when the instance could not be reached, for example when it is stopped.host_facts.warnings, host_facts.collected_at What could not be read, and when the facts were collected (UTC). -
EXAMPLE:
curl -X GET -H "username: xxxxx" -H "api-key: xxxxx" \
"$BASE_URL/agent?environment=prod&status=online"
Manage an agent
Perform a lifecycle action on an enrolled agent.
PATCH /agent
- AUTHENTICATIONS:
headers- Admin only - REQUEST BODY SCHEMA:
{ "agent_id": "...", "action": "disable" }Name Type Description agent_id requiredString The agent to act on. action requiredString One of enable,disable,rotate,touch, orupdate.rotateissues a new agent key (returned once in the response);touchrefreshes the agent's last-seen timestamp;updatechanges the agent's label, environment and tags.label optionalString updateonly. The agent's name in vME, 1 to 128 characters.environment optionalString updateonly. Up to 64 characters: letters, digits, spaces, dots, underscores and hyphens, starting with a letter or digit.tags optionalList updateonly. Replaces the agent's tags. Up to 20 tags, each up to 32 characters, with the same characters allowed asenvironment. Tags are compared without regard to case, and a repeat is dropped, keeping the first spelling. Blank entries are ignored. Send[]to remove all tags. - RESPONSE:
enable,disableandtouchanswer{ "action": "patch_agent", "status": 1, "message": "..." }.rotateaddsresult: { "agent_id", "agent_key" }; the newagent_keyis shown only once. - UPDATE: Send only the fields you want to change;
updateneeds at least one oflabel,environmentandtags.The response returns the updated agent in{
"agent_id" : "...",
"action" : "update",
"label" : "SQL-B (test)",
"environment" : "test",
"tags" : ["sql2022", "nightly-refresh"]
}result, as List agents shows it:A value that breaks a rule is refused with 400 and a{
"action" : "patch_agent",
"status" : 1,
"message" : "agent updated",
"result" : {
"agent_id" : "...",
"label" : "SQL-B (test)",
"environment" : "test",
"tags" : ["sql2022", "nightly-refresh"],
"status" : "online",
"enabled" : true
}
}messagenaming the rule, for examplelabel must not be empty,an agent can have at most 20 tags, ornothing to update: send label, environment or tags.
Delete an agent
DELETE /agent
- AUTHENTICATIONS:
headers- Admin only - REQUEST BODY SCHEMA:
{ "agent_id": "..." }
POST /agent (enrolment) and PATCH /agent sent with x-agent-* headers (heartbeat) are used by the agent itself during install and while running. They are not part of the admin workflow and are not documented here.
Create an agent job
A job is a single unit of work assigned to an agent - one workflow phase run against a target host.
POST /agent-job
-
AUTHENTICATIONS:
headers- Admin only -
REQUEST BODY SCHEMA:
{
"agent_id" : "...",
"phase" : "SourcePrep",
"config" : { "portal": "10.0.0.10", "manualIQN": "data,log" },
"secrets" : { "chapPassword": "..." }
}Name Type Description agent_id requiredString The agent that should run the job. The agent must be online. phase requiredString The workflow phase to run. The Windows iSCSI Agent runs DiscoverHost,ConnectTargets,SourcePrep,SourcePrepOnly,SourceMigrateOnly,CloneMount,CloneUnmount,QuiesceandResume; see the iSCSI API reference. The appliance also acceptsPreflight,NfsMount,DataCopy,SourceUnmount,OnboardMove,OnboardCopy,SnapshotBegin,SnapshotEndandCloneAttach, which are reserved for the Oracle connector and are not available in this release. Any other value is refused with 400.config optionalObject Non-secret configuration for the phase. Which fields are required depends on the phase - see the connector's API reference, for example iSCSI config fields. Mandatory fields are validated before the agent touches the host. secrets optionalObject Secret values for the phase (e.g. CHAP credentials). Stored encrypted and never returned in job listings. workflow_id optionalString Associate the job with an onboarding workflow (up to 64 characters). See Linking calls to a workflow. wizard_step optionalString The workflow step this job carries out, one of the wizard steps (for example source_preporclone_mount). When the job completes, that step is marked complete on the workflow. Up to 32 characters.role optionalString The dataset role the job works on, for example data(see the template'srolesunder List workflow templates). Up to 16 characters.job_type optionalString A label of your own to group jobs by, up to 32 characters. vME itself uses coordinationfor the jobs of a coordinated snapshot or rollback, andcleanupfor the jobs a workflow cleanup queues.ingest_path optionalString A label of your own for how the job's data is loaded, up to 255 characters. vME stores it on the job so you can filter by it; it does not change what the job does. workflow_id,wizard_step,role,job_typeandingest_pathare stored on the job and also copied intoconfig._meta, so the agent sees them. A value longer than its limit is refused with 400. The job is refused with 400 if the agent is disabled, and with 409 if the agent is not online (no heartbeat yet, or its heartbeat has gone stale). -
RESPONSE:
{ "result": { ... } }- the created job (without secret values), in thequeuedstate. A job carriesjob_id,agent_id,phase,state,requested_by,config,result,message,workflow_id,wizard_step,job_type,role,ingest_path, and the timestampscreated_at,claimed_at,started_at,completed_atandupdated_at.
List agent jobs
GET /agent-job
- AUTHENTICATIONS:
headers- Admin only - QUERY PARAMETERS:
Name Description job_id Return that one job instead of a list (404 if it does not exist). Add include_events=1to include its progress events.agent_id, state, workflow_id, wizard_step, job_type, role, ingest_path Filters; each matches the value exactly. A job's stateis one ofqueued,claimed,running,completed,failed,cancelledortimed_out.limit How many jobs to return, newest first. Default 100, at most500. - RESPONSE:
{ "result": [ ... ] }, or{ "result": { ... } }withjob_id.
Cancel an agent job
PATCH /agent-job
- AUTHENTICATIONS:
headers- Admin only - REQUEST BODY SCHEMA:
{ "job_id": "...", "action": "cancel" }
Agents claim and update their own jobs via GET /agent-job?next=1 and PATCH /agent-job using their agent credentials. As with the agent lifecycle routes, those calls are part of the agent protocol and are not made by an administrator.
Workflow API
An onboarding workflow is a higher-level orchestrator that sequences agent jobs and ZFS operations to clone a database onto a SQL Server host (the "Onboard Application" wizard). The endpoints below let you list, inspect, and tear down workflows, and validate a target appliance. All require an administrator account.
Parts: what a workflow does
A workflow runs some or all of five parts, in this order:
| Part | What it does |
|---|---|
ingest | Creates the source datasets on this appliance and loads the database into them. |
snapshot | Takes a point-in-time snapshot of the source. |
send | Replicates the snapshot to a second appliance. |
clone | Creates a writable clone from the snapshot. |
mount | Attaches the clone to a SQL Server host. |
Each part consumes what the part before it produced. Omit parts and all five run, which spans two appliances and takes ten steps. Select a subset and the run is shorter - and whatever it needs but does not do, it starts from an object that already exists on the appliance, named in start_from.
Two combinations cannot run and are refused with 400:
mountwithoutclone- there is nothing to mount, and no way to start from an existing clone.cloneorsendwithoutsnapshotwhileingestis on - withingeston, the source does not exist yet, so there is no snapshot to start from either. Either addsnapshot, or dropingestand start from a snapshot that already exists.
Selecting clone + mount gives the most common short run: clone a snapshot you already have and mount it on a host. Repeat it for each host you want a copy on; nothing is ingested or transferred again.
The Onboarding (API) folder in the vME Postman collection has two ordered, chained walk-throughs: Main flow runs all five parts across two appliances, and Clone and mount an existing snapshot runs the short clone + mount version. Run either top to bottom, or use Postman's Code button to copy each call into your own scripts.
List workflows
GET /workflow
- AUTHENTICATIONS:
headers- Admin only - QUERY PARAMETERS (optional filters):
state,created_by,workflow_type,source_mode,target_mode,transport_mode. - RESPONSE:
{ "result": [ ... ] }. Each workflow carriesworkflow_id,name,created_by,state,current_step,template_id,workflow_type,source_mode,target_mode,transport_mode,config,roles,host_agent_available,parts(a{part: boolean}map),live_steps(the steps this run actually has, in order),last_error,has_secrets,created_atandupdated_at, so you do not have to derive the shape of a run yourself.
Show a workflow
GET /workflow/<workflow_id>
-
AUTHENTICATIONS:
headers- Admin only -
RESPONSE: The workflow with every field List workflows returns, plus:
Field Description steps The agent jobs linked to the workflow, grouped by wizard step (or by phase for a job with no step). Each carries job_id,agent_id,phase,state,role,message,result,created_atandupdated_at.dataset_iqns For each dataset the workflow names, its iSCSI target name, as { "<dataset>": { "iqn_name", "guid", "iqn_scheme", "status" } }, or{ "status", "message" }when its export failed. A dataset with no export yet is left out. A dataset on the appliance the run sends to is looked up there.secrets The workflow's stored secrets (for example CHAP credentials), decrypted. Answers 404 if the workflow does not exist.
This response contains secretsGET /workflow/<workflow_id>returns the workflow'ssecretsin clear text. Call it only from a trusted script, do not log or store the response, and use List workflows (which carries onlyhas_secrets) when you just need a workflow's state.
Create a workflow
POST /workflow
- AUTHENTICATIONS:
headers- Admin only - REQUEST BODY SCHEMA:
Name Type Description name requiredString A friendly name, 1-128 characters. You can resume a workflow by name later. template_id optionalString Which flow to run - see List workflow templates. Leave it out and the appliance chooses from the run's choices; for SQL Server that is sql_server_iscsi_one_volume.parts optionalArray or Object Which parts this run does. An array of names ( ["clone","mount"]) or a map ({"ingest": false}). Omit it and all five parts run.start_from optionalObject The objects an existing-object run begins from: { "source": { ... } }and/or{ "snapshot": { ... } }. Required in practice whenever a part that produces an input is off.config optionalObject Free-form workflow configuration. workflow_type, source_mode, target_mode, transport_mode optionalString Flow metadata; sensible defaults are applied. - RESPONSE: The new workflow, in the
draftstate, opened at the first step its parts include.live_stepstells you which steps that is. - NOTES: Refused with 400 if the
partscombination cannot run, with the reason and which tick to change. The web wizard drives most workflow creation; call this directly only when scripting your own automation. - ERRORS: Besides the combinations under Parts, these are refused with 400, with this
message:Message When unknown parts: <names>partsnames something that is not one of the five parts.parts must be an object or a list of part namespartsis neither an array of names nor a{part: boolean}map.at least one part must be selectedEvery part is off. <template name> is switched off on this vMEThe template_idnames a template that is switched off on this appliance. - EXAMPLE - clone a snapshot you already have and mount it on a host, with no ingest and no transfer:
Each dataset a run uses is keyed by its role. A SQL Server run holds one dataset per database, role
curl -X POST -H "username: xxxxx" -H "api-key: xxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Clone app01 onto SQL-B",
"parts": ["clone", "mount"],
"start_from": {
"source": {
"pool_name": "vme-pool",
"source_name": "app01",
"full_name": "vme-pool/sources/app01",
"data_full_name": "vme-pool/sources/app01",
"data": { "name": "app01", "full_name": "vme-pool/sources/app01", "role": "data" },
"picked": true, "completed": true
},
"snapshot": {
"snapshot_name": "nightly-20260910",
"data": { "name": "nightly-20260910", "source_name": "app01",
"source_full_name": "vme-pool/sources/app01",
"full_snapshot_name": "vme-pool/sources/app01@nightly-20260910" },
"picked": true, "completed": true
}
}
}' "$BASE_URL/workflow"data. A run created before one volume per database holds a<base>_dataand a<base>_logdataset, rolesdataandlog, and only a snapshot name taken on both can be cloned.GET /workflow-templateslists the roles of each template.
Update a workflow
PATCH /workflow/<workflow_id>
- AUTHENTICATIONS:
headers- Admin only - REQUEST BODY SCHEMA: any of
name,state,current_step,config, orsecrets. Useconfig_mode: "merge"(default) or"replace"to control howconfigis applied. Sendconfig.partsto change which parts the run does; the same validation, and the same 400 messages, apply as on create. - NOTES:
current_stepis validated against the steps this run has. A step whose part is off is refused with 400, naming the parts that are off and the steps the run does have.- A cancelled workflow cannot be changed: the call is refused with 409.
state: "completed"is refused with 409 while aclone_mountjob is unfinished, or after a step has failed.- Pass
"force": trueto override any of the three refusals above. - Changing
template_idto a template that is switched off on this appliance is refused with 400:<template name> is switched off on this vME.forcedoes not override this. A workflow already on that template is not affected by this check and stays readable. - As a run moves forward its
statefollows the step being worked, and skips any state whose part is off - a clone-and-mount run never reports itself assource_provisioning.
- WORKFLOW STATES:
draft,source_selection,source_provisioning,source_prep,snapshot_selection,snapshot,sending,cloning,clone_mounting,completed,failed,cancelled. - WIZARD STEPS:
basics,pick_source,source,prod_agent,discover_prod,source_prep,pick_snapshot,snapshot,send,clone,nonprod_agent,discover_nonprod,clone_mount,complete.pick_sourceandpick_snapshotreplace the steps that would have produced those objects, and appear only when their part is off.
Linking calls to a workflow
A workflow does not run the work itself. Its steps are marked complete, and its state moves forward, when the calls that do the work name the workflow. When you script a workflow, send these fields on each of those calls:
| Field | Description |
|---|---|
| workflow_id | The workflow the call belongs to, from Create a workflow. |
| wizard_step | The wizard step the call completes. Each call below has a default, so send it only to record the work under a different step. A value that is not a wizard step is ignored. |
| role | The dataset role the call works on, from the template's roles (for example data). The source, snapshot, send and clone steps run once per role, and are complete only when every role the template declares has been recorded. |
| Call | Default step | Recorded when |
|---|---|---|
POST /db/network/create | source | The share is created, or was already configured. The dataset's full name and iSCSI target name are recorded under its role. |
POST /snapshot/create | snapshot | The snapshot is taken, including when it runs as a coordinated task. The snapshot name is recorded under its role. |
POST /db/send | send | The transfer completes. |
POST /clone/create | clone | The clone is queued for creation. |
POST /agent-job | none (send wizard_step) | The agent reports the job completed. |
POST /source/create | not recorded | This call only checks that the workflow has not been cancelled. |
- A step is only ever marked complete, never cleared, and the workflow's
stateonly moves forward. - A step this workflow does not run (its part is off) is ignored.
- A
workflow_idthat does not exist on the appliance you are calling is ignored. That is the normal case for a job queued on the appliance a run sends to. - If an agent job that carries a
workflow_idfails, or the agent reports it cancelled, the workflow'sstatebecomesfailedandlast_errorholds the step and the job's message (a workflow that is alreadycompletedorcancelledis left as it is).CloneUnmountjobs, and the jobs of a coordinated snapshot or a workflow cleanup, do not change the workflow's state. - Any of these calls naming a cancelled workflow is refused with 409 (see below).
List cleanup candidates
GET /workflow/<workflow_id>/cleanup-candidates
- AUTHENTICATIONS:
headers- Admin only - RESPONSE: The objects a cleanup delete would destroy, across both appliances - each with a
key, theside(local/remote), itskind(source / snapshot / clone), and a displaylabel. Use this to preview, or to build a subset for a selective delete.
Cancel or delete a workflow
DELETE /workflow/<workflow_id>
- AUTHENTICATIONS:
headers- Admin only - QUERY PARAMETERS:
Name Description (none) Soft-cancel: the workflow is marked cancelled; its record and the ZFS objects it created are kept.purge=1 Hard-delete the workflow record. Required before cleanuporforcebelow have any effect.purge=1&cleanup=1 Also destroy the source / snapshot / clone objects the workflow created on both appliances. purge=1&force=1 Delete the record even if some cleanup steps failed. - REQUEST BODY SCHEMA (optional):
{ "objects": ["key1", "key2"] }- restrict acleanupdelete to a subset of the cleanup candidates. - RESPONSE:
{ "message": "...", "cleanup": { ... } }. If a cleanup step fails andforceis not set, the call returns 409 Conflict with a report of what could not be removed. - EXAMPLE - hard-delete and clean up everything on both sides:
curl -X DELETE -H "username: xxxxx" -H "api-key: xxxxx" \
"$BASE_URL/workflow/WORKFLOW_ID?purge=1&cleanup=1"
Once a workflow is cancelled, any call that carries its workflow_id and would do work on its behalf is refused with 409 - POST /agent-job, /source/create, /db/network/create, /snapshot/create, /clone/create and /db/send, as well as PATCH /workflow/<id>. Create a new workflow to run those steps again.
The CloneUnmount phase is the exception: a cancelled run can have left a clone mounted on a host, so tearing it down stays available. A workflow_id that does not resolve on the appliance you are calling is not affected either - that is the normal case for a job queued against a target appliance.
Validate a target appliance (remote preview)
Confirm that a target appliance is reachable, list its pools, and establish SSH trust with it (so the clone transfer can run).
POST /workflow/<workflow_id>/remote-preview
- AUTHENTICATIONS:
headers- Admin only - REQUEST BODY SCHEMA - either address the target directly:
or name one you have registered, and its address, username and api-key are read from the appliance registry:
{
"address" : "https://10.0.0.20",
"admin_username" : "admin",
"admin_api_key" : "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}Anything you send explicitly wins over the stored value.{ "peer_id" : "c8d4f2e3b0a54c1c93e10a780a88b373" } - RESPONSE:
{ "result": { "host": "...", "pools": [ ... ], "peer_trust": { "added": true, "fingerprint": "..." } } }.
List workflow templates
GET /workflow-templates
- AUTHENTICATIONS:
headers- Admin only - QUERY PARAMETERS:
enabled_only(optional) - return only enabled templates. - RESPONSE:
{ "result": [ ... ], "default_template_id": "..." }. Each descriptor carries:Field Description id The value to send as template_idon create.name, description The template's display name and a one-line description. enabled Whether a new workflow can use this template. A workflow on a template that is switched off is refused on create with 400. roles The dataset roles a source is made of, as a list of objects: key(the value to send asrole),label(its display name),suffix(appended to the dataset name for that role; empty when the dataset is the whole database) andrequired. SQL Server over iSCSI keeps a database's data and log files on one volume, so it has one role; the earlier SQL Server template kept them on two volumes and has two (_dataand_log). Oracle over dNFS has one.transport How the datasets are exported - iscsiordnfs.agent_action_set Which host-agent behaviour drives the run. Opaque to the appliance. host_agent_available Whether the host agent implements that action set. When this is false, the vME-side steps still run, but the steps that need an agent on the database host have nothing behind them. - TEMPLATES:
sql_server_iscsi_one_volume(one volume per database over iSCSI, host agent available; the default),sql_server_iscsi(separate data and log volumes, kept for runs created before one volume per database;enabled: false, so not offered for new runs), andoracle_dnfs(a single dataset over dNFS; switched off in this release:enabled: falseandhost_agent_available: false, soPOST /workflowrefuses it with 400).
Appliance Registry Routes
Register the other vME appliances this one sends snapshots to. Credentials are stored once here - the admin api-key encrypted at rest and never returned - so a send names an appliance instead of carrying its api-key on every call. All routes require an administrator account.
The registry is managed from the vME appliances tab on Agent Manager in the web interface; these routes are the same surface for scripting.
List appliances
GET /peer
- AUTHENTICATIONS:
headers- Admin only - RESPONSE:
{ "result": [ ... ] }. Each row carries:Field Description peer_id The id to use in the routes below. name The name you gave it. Unique across the registry. base_url Its address. ssh_address Set only when SSH data transfer uses a different address than base_url.admin_username The account used to call it. has_api_key Whether an api-key is stored. The key itself is never returned. trust trustedonce this appliance's SSH key has been installed on it, otherwisenone.status online,unreachable, orunknown- what the last test found.remote_version, remote_hostname Read from the appliance during a test. pools Its pool names, from the last test. last_seen_at, last_error When it last answered, and why it last did not.
Add an appliance
POST /peer
- AUTHENTICATIONS:
headers- Admin only - REQUEST BODY SCHEMA:
Name Type Description name requiredString A friendly name, up to 128 characters. Must be unique. base_url requiredString Its address. A bare host is accepted and https://is assumed; a trailing/apiis trimmed.admin_api_key requiredString An administrator api-key on that appliance. Stored encrypted. admin_username optionalString The account that api-key belongs to. Defaults to admin.ssh_address optionalString Set only if SSH data transfer uses a different address than base_url.verify_tls optionalBoolean Defaults to false, since appliances ship a self-signed certificate. Settrueonly if you have pointed the appliance at a CA bundle. - RESPONSE: 201 with the new row. A duplicate name is refused with 409.
- EXAMPLE:
curl -X POST -H "username: xxxxx" -H "api-key: xxxxx" \
-H "Content-Type: application/json" \
-d '{ "name": "vme-nonprod-01", "base_url": "https://10.0.0.20",
"admin_username": "admin", "admin_api_key": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }' \
"$BASE_URL/peer"
Show an appliance
GET /peer/<peer_id>
- AUTHENTICATIONS:
headers- Admin only - RESPONSE: The row, in the same shape as List appliances.
Amend an appliance
PATCH /peer/<peer_id>
- AUTHENTICATIONS:
headers- Admin only - REQUEST BODY SCHEMA: any of
name,base_url,admin_username,admin_api_key,ssh_address,verify_tls. Sendadmin_api_keyonly when you want to replace the stored key. A name already used by another row is refused with 409.
Remove an appliance
DELETE /peer/<peer_id>
- AUTHENTICATIONS:
headers- Admin only - NOTES: Removes the registry entry. Workflows that send to that appliance will need it registered again. Nothing on the appliance itself is changed - including the SSH key this one installed on it.
Establish trust
Installs this appliance's managed SSH public key on the other one, so snapshot transfers to it need no SSH setup by hand.
POST /peer/<peer_id>/trust
- AUTHENTICATIONS:
headers- Admin only - RESPONSE:
{ "result": { "installed": true, "fingerprint": "SHA256:..." } }, and the row'strustbecomestrusted. - NOTES: Uses the stored credentials to call the other appliance's own trust route. 502 if it cannot be reached or refuses the key.
Test an appliance
POST /peer/<peer_id>/test
- AUTHENTICATIONS:
headers- Admin only - RESPONSE:
{ "result": { "reachable": true, "pools": [ ... ], "agents_count": 2, "remote_version": "1.10.0", "warnings": [ ... ] } }. - NOTES: Records
status,last_seen_at,last_errorand the pool names on the row either way. 502 with the sameresultshape when the appliance cannot be reached.
List an appliance's pools
GET /peer/<peer_id>/pools
- AUTHENTICATIONS:
headers- Admin only - RESPONSE: The other appliance's pools, fetched with the stored credentials. Use this to choose a destination pool for a send without holding that appliance's api-key yourself.
List an appliance's agents
GET /peer/<peer_id>/agents
- AUTHENTICATIONS:
headers- Admin only - RESPONSE: The other appliance's enrolled agents, fetched with the stored credentials.
Call an appliance through this one
Carries one API call to a registered appliance, using the credentials stored for it, and hands back its answer. This is how the web interface works with a second appliance during a run without the browser holding that appliance's api-key.
POST /peer/<peer_id>/proxy
- AUTHENTICATIONS:
headers- Admin only - REQUEST BODY SCHEMA:
{
"method" : "GET",
"path" : "/agent-job?workflow_id=WORKFLOW_ID"
}Name Type Description path requiredString The API path on the other appliance, including any query string. Only paths starting with /agent(which covers/agent-joband/agent-token),/pool/,/source/,/snapshot/,/clone/,/db/network/or/db/sendare carried.method optionalString One of GET,POST,PUT,PATCHorDELETE. Defaults toGET.body optionalObject or Array The JSON body to send with the call. - RESPONSE: The other appliance's JSON body and HTTP status, unchanged. The call waits up to 60 seconds for it to answer.
- ERRORS:
Status Message When 400 method not allowedmethodis not one of the five above.403 path not carried by the appliance proxypathdoes not start with one of the prefixes above.404 appliance not foundNo registered appliance has that peer_id.502 <name> unreachable: ...The appliance could not be reached. - EXAMPLE - list a run's agent jobs on the appliance it sent to:
curl -X POST -H "username: xxxxx" -H "api-key: xxxxx" \
-H "Content-Type: application/json" \
-d '{ "method": "GET", "path": "/agent-job?workflow_id=WORKFLOW_ID" }' \
"$BASE_URL/peer/PEER_ID/proxy"
Peer identity and incoming trust
These two routes are how appliances pair with each other. In normal use they are called for you by Establish trust rather than by hand.
GET /peer/identity
- AUTHENTICATIONS:
headers- Admin only - RESPONSE:
{ "pubkey": "ssh-ed25519 ...", "fingerprint": "SHA256:..." }- this appliance's own managed public key, generated on first call. Use it to trust this appliance from somewhere else.
POST /peer/trust
- AUTHENTICATIONS:
headers- Admin only - REQUEST BODY SCHEMA:
{ "pubkey": "ssh-ed25519 ...", "label": "vme:10.0.0.10" } - RESPONSE:
{ "added": true, "fingerprint": "SHA256:..." }. Answered by an appliance when another one pairs with it.
API Examples (Postman Collection)
Click here to download the Postman collection with example requests for various vME API requests.
If you're unsure how to import the collection into Postman, please refer to the Postman documentation for a step-by-step guide on importing collection.