Skip to main content

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:

# Set this once; every example below reuses it.
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
}

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.

Automating agents and onboarding workflows?

The admin API for managing agents, dispatching jobs, and orchestrating onboarding workflows is documented below under Agent API and Workflow API.

Disk Partition Details

This API call retrieves all disk details like name, partitions, model and size.

GET /zfs/disks

  • AUTHENTICATIONS: headers

  • EXAMPLE:

    # 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())

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:

    # 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())

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"]
    }
    NameTypeDescription
    name
    required
    String
    Name of pool to be created.
    device
    required
    ListList of device names.
  • EXAMPLE:

    # 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())

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"
    }
    NameTypeDescription
    name
    required
    StringName of pool to be destroyed.
  • EXAMPLE:

    # 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())

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"
    }
    NameTypeDescription
    name
    required
    StringThe name of the pool to show status on.
  • EXAMPLE:

    # 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())

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"
    }
    NameTypeDescription
    name
    required
    StringThe name of the pool to run the history command on.
  • EXAMPLE:

    # 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())

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"
    }
    NameTypeDescription
    name
    required
    String
    The name of the pool to be registered.
  • EXAMPLE:

    # 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())

Source Routes

Show all sources

This API call will return all sources present under the specified pool

GET /source/show

  • AUTHENTICATIONS: headers

  • REQUEST BODY SCHEMA:

      {
    "name" : "vme-pool"
    }
    NameTypeDescription
    pool_name
    optional
    StringName of pool, default is vme-pool.
  • EXAMPLE:

    # 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())

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
      }
      }
    NameTypeDescription
    name
    required
    StringName of source to be created.
    pool_name
    required
    StringName of pool that contains a source.
    dbtype
    optional
    StringDatabase type, such as mysql, mariadb, mssql, mongodb etc., used to label a source, default is unknown.
    quota
    optional
    StringA limit on the amount of disk space a dataset can consume, for example, 10G for ten GB.
    compression
    optional
    StringName 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 is False.
    database_method
    optional
    Stringcontainer (default) or network. Use network to expose the source over NFS or iSCSI so an external host can mount it.
    network_info
    optional
    DictionaryRequired when database_method is network. Holds the network settings below (at minimum type).
    network_info.type
    required
    Stringnfs or iscsi. Required inside network_info.
    network_info.auto_grow
    optional
    BooleaniSCSI only. When true, the volume is thin-provisioned and grows on demand up to auto_grow_max_gb. Default false.
    network_info.auto_grow_max_gb
    optional
    IntegeriSCSI only. Maximum size (GB) the volume can grow to when auto_grow is enabled. Default 16384.
    network_info.vsize
    optional
    IntegeriSCSI only. Fixed volume size (GB). Used when auto_grow is false; if omitted or 0, the volume auto-grows.
    note

    For iSCSI, creating a source with database_method: network provisions the iSCSI target and LUN automatically as part of source creation - no separate call is needed to expose it. For NFS, use /db/network/create after creating the source to publish the share and set the client allow-list (client_address).

  • EXAMPLE:

    # 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())

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",
    "source_name" : "foo",
    "property" : {
    "database_method" :"network",
    "quota":"5G",
    "dbtype":"mysql",
    "compression":"gzip"
    }
    }
    NameTypeDescription
    name
    required
    StringName of source to be modified.
    pool_name
    required
    StringName of pool that contains a source.
    property
    required
    DictionaryThe properties of the source to be modified.
  • EXAMPLE:

    # Download the requests library from https://docs.python-requests.org/en/latest/
    import requests
    body = {
    "pool_name" : "vme-pool",
    "name" : "foo",
    "property" : {
    "database_method" : "network",
    "quota" : "5G",
    "dbtype" : "mysql",
    "compression" : "gzip",
    "client_address" : "9.87.65.43"
    }
    }
    response = requests.post(f"{base_url}/source/modify", json=body, headers=headers)
    if response.status_code < 500:
    print(response.json())

Rename a source

PUT /source/rename

  • AUTHENTICATIONS: headers

  • REQUEST BODY SCHEMA:

    {
    "pool_name" : "vme-pool",
    "currentname" : "foo",
    "newname" : "foofoo"
    }
    NameTypeDescription
    pool_name
    required
    StringName of pool that contains a source.
    currentname
    required
    StringName of source to be renamed.
    newname
    required
    StringNew source name.
  • EXAMPLE:

    # 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())

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"
    }
    NameTypeDescription
    pool_name
    required
    StringName of pool that contains a source.
    source_name
    required
    StringName of source to be destroyed.
    recursive
    optional
    BooleanThe recursive option, default is False.
  • EXAMPLE:

    # 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())

Snapshot Routes

Show all snapshots

GET /snapshot/show

This API call is used to list all snapshots taken under the particular source.

  • AUTHENTICATIONS: headers

  • REQUEST BODY SCHEMA:

      {
    "name" : "vme-pool",
    "source_name" : "foo"
    }
    NameTypeDescription
    pool_name
    optional
    StringName of pool.
    source_name
    optional
    StringName of source/clone.
  • EXAMPLE:

    # 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 < 200:
    print(response.json())

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"
    }
    NameTypeDescription
    pool_name
    required
    StringName of pool.
    source_name
    required
    StringName of source/clone.
    name
    required
    StringName of snapshot/bookmark to be created.
    recursive
    optional
    BooleanRecursive option, default is False.
  • RESPONSE: On success the response now includes a stopped_containers array listing any containers that were stopped to take a crash-consistent snapshot (empty if none).

    {
    "status": 1,
    "message": "snapshot created successfully",
    "stopped_containers": ["container-a", "container-b"]
    }
    Crash-consistent snapshots

    If 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:

    # 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())

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
    }
    NameTypeDescription
    pool_name
    required
    StringName of pool.
    source_name
    required
    StringName of source/clone.
    snapshot_name
    required
    StringName of snapshot/bookmark to be created.
    force
    optional
    BooleanForce option to rollback, default is False.
  • EXAMPLE:

    # 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())

Rename a snapshot

PUT /snapshot/rename

  • AUTHENTICATIONS: headers

  • REQUEST BODY SCHEMA:

    {
    "pool_name" : "vme-pool",
    "source_name" : "foo",
    "currentname" : "snap",
    "newname" : "snap-new"
    }
    NameTypeDescription
    pool_name
    required
    StringName of pool.
    source_name
    required
    StringName of source/clone.
    currentname
    required
    StringName of snapshot/bookmark to be renamed.
    newname
    required
    StringNew name of snapshot/bookmark.
  • EXAMPLE:

    # 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())

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"
    }
    NameTypeDescription
    pool_name
    required
    StringName of pool.
    source_name
    required
    StringName of source/clone.
    snapshot_name
    required
    StringName of snapshot/bookmark to be destroyed.
    recursive
    optional
    BooleanThe recursive option, default is False.
  • EXAMPLE:

    # 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())

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"
    }
    NameTypeDescription
    pool_name
    optional
    StringName of pool.
    source_name
    optional
    StringName of source.
    snapshot_name
    optional
    StringName of snapshot.
  • EXAMPLE:

    # 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())

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"
      }
      }
    NameTypeDescription
    pool_name
    required
    StringName of pool.
    source_name
    required
    StringName of source.
    snapshot_name
    required
    StringName of snapshot.
    name
    required
    StringName of clone to be created.
    dbtype
    optional
    StringDatabase type, such as mysql, mariadb, mssql, mongodb etc., used to label a source, default is unknown.
    size
    optional
    IntegerNumber of clones (clonefarm) to be created, default is 1.
    database_method
    optional
    Stringcontainer (default) or network. Use network to expose the clone over NFS or iSCSI so an external host can mount it.
    network_info
    optional
    DictionaryRequired when database_method is network. Must contain type (nfs or iscsi). A clone inherits its size from the snapshot, so no vsize is needed.
    network_info.type
    required
    Stringnfs or iscsi. Required inside network_info.
    note

    For 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/create after the clone is created to publish the share and set the client allow-list.

  • EXAMPLE:

    # 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())

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" : {
    "database_method" : "network",
    "quota" : "5G",
    "dbtype" : "mysql",
    "compression" : "gzip"
    }
    }
    NameTypeDescription
    name
    required
    StringName of clone to be modified.
    pool_name
    required
    StringName of pool that contains a source.
    property
    required
    DictionaryThe properties of the source to be modified.
  • EXAMPLE:

    # Download the requests library from https://docs.python-requests.org/en/latest/
    import requests
    body = {
    "pool_name" : "vme-pool",
    "name" : "foo-clone",
    "property" : {
    "database_method" : "network",
    "quota" : "5G",
    "dbtype" : "mysql",
    "compression" : "gzip",
    "client_address" : "9.87.65.43"
    }
    }
    response = requests.post(f"{base_url}/clone/modify", json=body, headers=headers)
    if response.status_code < 500:
    print(response.json())

Rename a clone

PUT /clone/rename

  • AUTHENTICATIONS: headers

  • REQUEST BODY SCHEMA:

    {
    "pool_name" : "vme-pool",
    "currentname" : "foo-clone",
    "newname" : "foofoo-clone"
    }
    NameTypeDescription
    pool_name
    required
    StringName of pool.
    currentname
    required
    StringName of clone to be renamed.
    newname
    required
    StringNew name of clone.
  • EXAMPLE:

    # 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())

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"
    }
    NameTypeDescription
    pool_name
    required
    StringName of pool.
    clone_name
    required
    StringName of clone to be destroyed.
    recursive
    optional
    BooleanThe recursive option, default is False.
  • EXAMPLE:

    # 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())

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":""}
    }
    NameTypeDescription
    repository
    required
    StringName of image repository to be pulled.
    tag
    optional
    StringName of tag, default is latest.
    dbtype
    optional
    StringDatabase type, such as mysql, mariadb, mssql, mongodb etc., used to label a source, default is same as repository name.
    credential
    optional
    DictionaryInformation used to verify repository accession, default is None.
  • EXAMPLE:

    # 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())

Register an image

POST /docker/image/register

  • AUTHENTICATIONS: headers

  • REQUEST BODY SCHEMA:

    {
    "name" : "mysql:latest",
    "dbtype" : "mysql"
    }
    NameTypeDescription
    name
    required
    StringName of image name:tag to be registered.
    dbtype
    optional
    StringDatabase type, such as mysql, mariadb, mssql, mongodb etc., used to label a source, default is same as repository name.
  • EXAMPLE:

    # 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())

List all images

Similar to the docker images command.

GET /docker/image/list

  • AUTHENTICATIONS: headers

  • EXAMPLE:

    # 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())

Save an image as .tar

POST /docker/image/save

  • AUTHENTICATIONS: headers

  • REQUEST BODY SCHEMA:

    {
    "image" : "mysql:latest",
    "filename" : "myimage.tar"
    }
    NameTypeDescription
    image
    required
    StringName of image name:tag to be saved.
    filename
    required
    StringFilename of saved image, the tar file will be saved at /home/vme/uploads.
  • EXAMPLE:

    # 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())

Load an image from .tar

POST /docker/image/load

  • AUTHENTICATIONS: headers

  • REQUEST BODY SCHEMA:

    {
    "filename" : "myimage.tar"
    }
    NameTypeDescription
    filename
    required
    StringFilename of image to be loaded.
  • EXAMPLE:

    # 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())

Remove an image

DELETE /docker/image/remove

  • AUTHENTICATIONS: headers

  • REQUEST BODY SCHEMA:

      {
    "image" : "mysql:latest"
    }
    NameTypeDescription
    image
    required
    StringName of image name:tag to be removed.
  • EXAMPLE:

    # 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())

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"]
    }
    NameTypeDescription
    image
    required
    StringName of image.
    name
    required
    StringName of container to be created.
    detach
    optional
    BooleanBoolean for running container in the background, default is True.
    command
    optional
    StringCommand to run in the container, default is None.
    environment
    optional
    List or DictionaryEnvironment variables to set inside the container, as a dictionary or a list of strings in the format ["SOMEVARIABLE=xxx"], default is None.
    volumes
    optional
    List or DictionaryConfigure volumes mounted inside the container, default is [].
    ports
    optional
    StringPorts to bind inside the container in the format "a:b,c:d,...", for example 8080:80 means map port 8080 on the host to TCP port 80 in the container, default is None.
  • EXAMPLE:

    # 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())

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 (status is 1), the result field is a list of container objects. Each object includes an env array of strings in KEY=value format (the same values shown in the vME UI under Connection Information for a container).

    NameTypeDescription
    nameStringContainer name.
    idStringFull Docker container ID.
    short_idStringShort Docker container ID.
    imageStringImage reference (for example postgres:17).
    statusStringContainer state from Docker Engine (for example running, paused).
    portObjectDocker port bindings (NetworkSettings.Ports).
    mountedListBind mounts as host_path:container_path strings.
    dbtypeStringDatabase type from the registered image (for example postgres).
    createdStringHuman-readable time since the container was created.
    envListEnvironment variables as KEY=value strings.

    Example excerpt from result for 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:

    # 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())

Start a container

Similar to the docker start command.

POST /docker/container/start

  • AUTHENTICATIONS: headers

  • REQUEST BODY SCHEMA:

    { 
    "name" : "foo-mysql"
    }
    NameTypeDescription
    name
    required
    StringName of container to be started.
  • EXAMPLE:

    # 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())

Stop a container

Similar to the docker stop command.

POST /docker/container/stop

  • AUTHENTICATIONS: headers

  • REQUEST BODY SCHEMA:

    { 
    "name" : "foo-mysql"
    }
    NameTypeDescription
    name
    required
    StringName of container to be stoped.
  • EXAMPLE:

    # 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())

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;\""
    }
    NameTypeDescription
    name
    required
    StringName of container to be executed.
    command
    required
    String or ListCommands to be executed.
  • EXAMPLE:

    # 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())

Remove a container

Delete /docker/container/remove

  • AUTHENTICATIONS: headers

  • REQUEST BODY SCHEMA:

      {
    "name" : "foo-mysql"
    }
    NameTypeDescription
    name
    required
    StringName of container to be removed.
  • EXAMPLE:

    # 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())

Get a container log

GET /docker/container/log

  • AUTHENTICATIONS: headers

  • REQUEST BODY SCHEMA:

    { 
    "name" : "foo-mysql"
    }
    NameTypeDescription
    name
    required
    StringName of container to get the log.
  • EXAMPLE:

    # 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())

Get a container detail

GET /docker/container/detail

  • AUTHENTICATIONS: headers

  • REQUEST BODY SCHEMA:

    { 
    "name" : "foo-mysql"
    }
    NameTypeDescription
    name
    required
    StringName of container to get the detail.
  • EXAMPLE:

    # 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())

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:

    # 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())

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"
    }
    NameTypeDescription
    pool_name
    required
    StringName of pool.
    dataset_name
    required
    StringName of dataset to be restored.
    dbtype
    optional
    StringDatabase type, such as mysql, mariadb, mssql, mongodb etc., used to label a source, default is unknown.
    src
    required
    StringPath name of received backup file in .vmebk format, 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 returned task_id.

    {
    "task_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "status": "queued"
    }
  • EXAMPLE:

    # 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')

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
      {
      "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 authentication

      For a sys2sys transfer, provide the receiver's address, username, and admin api-key. The sending appliance uses the api-key to establish SSH trust with the receiver automatically, registering its own managed public key on the target.

    NameTypeDescription
    type
    required
    StringSending type, pool2pool or sys2sys.
    dataset
    required
    StringName of dataset to be send.
    receiver
    required
    DictionaryInformation of receiver. For pool2pool contains pool_name and dataset_name. For sys2sys also contains address, username, and api-key (the receiver's admin API key, used to automatically establish SSH trust).
    receiver.ssh_address
    optional
    Stringsys2sys only. Override the host used for the SSH/ZFS transfer when it differs from the receiver's API address (e.g. a separate data network). Defaults to the host in address.
    overwrite
    optional
    BooleanIf true, destroy an existing destination dataset on the receiver before sending. Default is false (a name clash fails the transfer).
    keep_snapshot
    optional
    BooleanIf true, keep the temporary snapshot created for the send operation. Default is false (snapshot is deleted after send completes).
  • RESPONSE: Returns HTTP 202 with a task_id that can be used to track the transfer status.

    {
    "task_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "status": "queued"
    }
  • EXAMPLE:

    # 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')

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_id or dataset, not both)

    NameTypeDescription
    task_id
    optional
    StringThe task ID returned from the /db/send endpoint. Returns status for a specific transfer.
    dataset
    optional
    StringDataset 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 start
    • in_progress - Transfer is currently running
    • completed - Transfer completed successfully
    • failed - Transfer failed (see error field for details)
  • EXAMPLE:

    # 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())

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"
    }
    NameTypeDescription
    task_id
    required
    StringThe task_id returned by /db/send or /db/receive. May also be supplied as a query parameter (?task_id=).
  • RESPONSE: 200 on success. A transfer that has already finished (completed, failed, or already cancelled) is returned as-is with 200. An unknown task_id returns 404.

  • EXAMPLE:

    # 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())

Backup database

Get all backup filenames.

GET /db/backup

  • AUTHENTICATIONS: headers

  • EXAMPLE:

    # 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())

Create backup file.

POST /db/backup

  • AUTHENTICATIONS: headers

  • REQUEST BODY SCHEMA:

    { 
    "dataset" : "vme-pool/sources/foo"
    }
    NameTypeDescription
    dataset
    required
    StringName of dataset to be backup.
  • EXAMPLE:

    # 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())

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:

    {
    "full_name" : "vme-pool/sources/foo",
    "client_address" : ["10.0.0.21", "10.0.0.22"]
    }
    NameTypeDescription
    full_name
    required
    StringFully-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
    String or ListOne IPv4 address, or a list of IPv4 addresses, permitted to access the share.
    client_username
    optional
    StringClient username for the share. Default admin.
    client_password
    optional
    StringClient password for the share. Default admin.
    anonuid
    optional
    NumberAnonymous UID for NFS mapping. Default 117.
    anongid
    optional
    NumberAnonymous GID for NFS mapping. Default 122.
  • RESPONSE: { "status": 1, "message": "Network create success : <full_name>" }

  • EXAMPLE:

    # 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())

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"
    }
    NameTypeDescription
    full_name
    required
    StringFully-qualified dataset (<pool>/sources/<source> or <pool>/clones/<clone>) whose share should be removed.
  • EXAMPLE:

    # 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())

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: headers

  • PATH PARAMETERS:

    NameTypeDescription
    task_id
    required
    StringThe task ID returned by an asynchronous endpoint.
  • RESPONSE: A state of PENDING, STARTED, SUCCESS, or FAILURE. On SUCCESS the payload is returned in result; otherwise a human-readable status describes the current state or error.

    { "state": "SUCCESS", "result": { "status": 1, "message": "done" } }
    tip

    This is the generic Celery task poll used after db and pool operations. 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:

    # 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())

User Management Routes

Add User

This API call adds a new user. Requires an administrator account.

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"
    }
    NameTypeDescription
    username
    required
    StringLogin name for the new user.
    name
    required
    StringFull/display name of the user.
    mail
    required
    StringEmail address for the user.
    group_id
    optional
    NumberID of the group to assign the user to. If omitted, a new group is created and the user is assigned to it (used for first-time setup).
    auth_type
    required
    StringAuthentication type (e.g. ldap or local).
    password
    optional
    StringRequired when auth_type is local (or omitted). Initial password for the new local user. Not used for ldap.
  • EXAMPLE:

    # 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())

Modify User

This API call modifies an existing user. You can update email, full name, auth type, and (for local users) password.

POST /user/modify

  • AUTHENTICATIONS: headers

  • REQUEST BODY SCHEMA:

    {
    "username": "jane",
    "current_password": "OldPass123!",
    "password": {
    "current": "OldPass123!",
    "new": "NewPass456!",
    "confirm": "NewPass456!"
    }
    }
    NameTypeDescription
    username
    required
    StringLogin name of the user to modify (must be your own account; cannot be changed).
    current_password
    required
    StringYour current password, required to authorise any modification.
    password
    optional
    ObjectTo change the password, pass an object with current, new, and confirm keys (all three required). Omit to leave the password unchanged.
    name
    optional
    StringUpdated full/display name.
    mail
    optional
    StringUpdated email address.
    auth_type
    optional
    StringAuthentication type (e.g. local or ldap).
    rotate_api_key
    optional
    BooleanIf true, issue a new API key for the user; the new key is returned in updated.api_key.
    note

    /user/modify requires current_password, and a password change must be sent as a structured object (password: {current, new, confirm}). A user can only modify their own account.

  • RESPONSE: 200 with an updated object listing the fields that changed (a changed password shows as ***updated***; a rotated key appears as api_key).

    { "status": 1, "message": "user modify success", "updated": { "password": "***updated***" } }
  • EXAMPLE:

    # 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())

Delete User

This API call deletes a user.

DELETE /user/delete

  • AUTHENTICATIONS: headers

  • REQUEST BODY SCHEMA:

    {
    "username": "ubuntu123",
    "password": "MyPassword123!",
    "confirm_text": "DELETE ubuntu123"
    }
    NameTypeDescription
    username
    required
    StringLogin name of the user to delete.
    password
    required
    StringYour password, required to authorise the deletion.
    confirm_text
    required
    StringConfirmation phrase; must be the literal string DELETE <username> (e.g. DELETE ubuntu123).
    method
    optional
    StringSign-in method used to verify the password. Default is password.
    note

    /user/delete requires your password and a confirm_text value of exactly DELETE <username> in addition to username.

  • EXAMPLE:

    # 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())

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!"
    }
    NameTypeDescription
    username
    required
    StringYour login name. Must match the authenticated user; changing another user's password returns 403.
    password
    required
    StringYour current password.
    new_password
    required
    StringThe new password to set.
  • RESPONSE: { "status": 1, "message": "password changed successfully" }

  • EXAMPLE:

    # 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())

Get All Users

This API call returns all users. Requires an administrator account.

GET /admin/user/list

  • AUTHENTICATIONS: headers - Admin only

  • EXAMPLE:

    # 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())

LDAP Configuration Routes

Admin only

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:

    # 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())

Delete LDAP Configuration

This API call removes the LDAP configuration.

DELETE /user/ldap

  • AUTHENTICATIONS: headers

  • EXAMPLE:

    # 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())

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"}
    }
    NameTypeDescription
    server
    required
    StringHostname or IP address of the LDAP server.
    port
    required
    NumberPort on which the LDAP server listens (e.g. 389 for LDAP, 636 for LDAPS).
    bind_dn
    required
    StringDistinguished Name (DN) used to bind to the LDAP server.
    bind_password
    required
    StringPassword for the bind_dn account.
    user_search_base
    required
    StringBase DN under which user entries are searched.
    user_filter
    required
    StringLDAP search filter for users; placeholder (e.g. {adminuser}) is replaced by the login name.
    vme_attrs
    required
    ObjectMapping of VME user fields (username, name, mail) to LDAP attribute names.
  • EXAMPLE:

    # 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())

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"}
    }
    NameTypeDescription
    server
    required
    StringHostname or IP address of the LDAP server.
    port
    required
    NumberPort on which the LDAP server listens (e.g. 389 for LDAP, 636 for LDAPS).
    bind_dn
    required
    StringDistinguished Name (DN) used to bind to the LDAP server.
    bind_password
    required
    StringPassword for the bind_dn account.
    user_search_base
    required
    StringBase DN under which user entries are searched.
    user_filter
    required
    StringLDAP search filter for users; placeholder (e.g. {adminuser}) is replaced by the login name.
    vme_attrs
    required
    ObjectMapping of VME user fields (username, name, mail) to LDAP attribute names.
  • EXAMPLE:

    # 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())

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.

Admin only

Every endpoint in the Agent API and Workflow API sections requires an administrator account.

How it works:

  1. An administrator mints a one-time enrollment token (POST /agent-token) and gives it to whoever installs the agent.
  2. The agent enrols itself with that token, receives a long-lived agent key, and runs as a background service on the host.
  3. The agent polls vME for assigned jobs, runs each one locally, and reports progress and results back.
  4. 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
    }
    NameTypeDescription
    label
    optional
    StringFriendly name for the agent that will enrol with this token.
    environment
    optional
    StringEnvironment the host belongs to (e.g. prod, dev, test).
    ttl_minutes
    optional
    NumberHow long the token stays valid. Defaults to a short-lived window suitable for one install.
  • RESPONSE: The raw token is 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) - when true, return only tokens that are still valid and unused.
  • RESPONSE: { "result": [ ... ] } (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 includes its ID, label, environment, version, enabled flag, and an effective status (online / inactive / disabled).
  • 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" }
    NameTypeDescription
    agent_id
    required
    StringThe agent to act on.
    action
    required
    StringOne of enable, disable, rotate, or touch. rotate issues a new agent key (returned once in the response); touch refreshes the agent's last-seen timestamp.

Delete an agent

DELETE /agent

  • AUTHENTICATIONS: headers - Admin only
  • REQUEST BODY SCHEMA: { "agent_id": "..." }
Agent-side routes

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": "..." }
    }
    NameTypeDescription
    agent_id
    required
    StringThe agent that should run the job. The agent must be online.
    phase
    required
    StringThe workflow phase to run: DiscoverHost, ConnectTargets, SourcePrep, SourcePrepOnly, SourceMigrateOnly, CloneMount, or CloneUnmount.
    config
    optional
    ObjectNon-secret configuration for the phase (the same fields used by the connector's config.json).
    secrets
    optional
    ObjectSecret values for the phase (e.g. CHAP credentials). Stored encrypted and never returned in job listings.
    workflow_id
    optional
    StringAssociate the job with an onboarding workflow.
  • RESPONSE: { "result": { ... } } - the created job (without secret values).

List agent jobs

GET /agent-job

  • AUTHENTICATIONS: headers - Admin only
  • QUERY PARAMETERS: job_id for a single job, or filters such as agent_id, state, workflow_id, job_type, role, and limit. Add include_events=1 to include the job's progress events.

Cancel an agent job

PATCH /agent-job

  • AUTHENTICATIONS: headers - Admin only
  • REQUEST BODY SCHEMA: { "job_id": "...", "action": "cancel" }
note

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 across two appliances to clone a production database onto another vME (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.

Run the whole flow via the API

The Onboarding (API) folder in the vME Postman collection walks the entire Onboard Application sequence - create workflow, provision the iSCSI sources, discover, prep, snapshot, send to the target, clone, and mount - as ordered, chained requests. Run it 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": [ ... ] }.

Show a workflow

GET /workflow/<workflow_id>

  • AUTHENTICATIONS: headers - Admin only
  • RESPONSE: The workflow with its steps and the agent jobs linked to each step.

Create a workflow

POST /workflow

  • AUTHENTICATIONS: headers - Admin only
  • REQUEST BODY SCHEMA: { "name": "...", "workflow_type": "...", "template_id": "...", "config": { ... } }. A new workflow starts in the draft state. The web wizard drives most workflow creation; call this directly only when scripting your own automation.

Update a workflow

PATCH /workflow/<workflow_id>

  • AUTHENTICATIONS: headers - Admin only
  • REQUEST BODY SCHEMA: any of name, state, current_step, config, or secrets. Use config_mode: "merge" (default) or "replace" to control how config is applied. State and step values are validated against the allowed workflow states.

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, the side (local/remote), its kind (source / snapshot / clone), and a display label. 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:
    NameDescription
    (none)Soft-cancel: the workflow is marked cancelled; its record and the ZFS objects it created are kept.
    purge=1Hard-delete the workflow record. Required before cleanup or force below have any effect.
    purge=1&cleanup=1Also destroy the source / snapshot / clone objects the workflow created on both appliances.
    purge=1&force=1Delete the record even if some cleanup steps failed.
  • REQUEST BODY SCHEMA (optional): { "objects": ["key1", "key2"] } - restrict a cleanup delete to a subset of the cleanup candidates.
  • RESPONSE: { "message": "...", "cleanup": { ... } }. If a cleanup step fails and force is 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"

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:
    {
    "address" : "https://10.0.0.20",
    "admin_username" : "admin",
    "admin_api_key" : "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    }
  • 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": "..." }.

API Examples (Postman Collection)

Click here to download the Postman collection with example requests for various vME API requests.

note

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.