Skip to content

Using the VALDI API

A public API is accessible to VALDI customers that enables programmatic provisioning, management, and termination of Virtual Machines.

Authentication

VALDI's API is authenticated with JSON Web Tokens (JWT). The team is in the process of implementing support for API keys, which when complete, will replace the authentication method described here.

Log in

First, log in and obtain a refresh token and access token. This is the programmatic equivalent to logging in to the VALDI dashboard at valdi.ai.

POST  https://api.valdi.ai/account/login

You must include the following payload:

{
  "email": "your.account.email@youremailprovider.com",
  "password": "your_password"
}

An example 200 OK response is:

{
    "access_token": "eyJhbGciOiJiUZI1NiIsInr5cCI6IkpXVCJ9.eyJkYXRhIjP7InVzZXJfaWQiOjgsImNyZWF0aW9uX3RpbWUiOiIymdIzLTEyLTA0IDIzOjU0OjU5LjczMzUwOCJ9LCJwdXJwb3NlIjoiYWNjZXNzX3Rva2VuIiwiZXhwIjoyMDIzMTIwNDIzNTk1Ox0.gTBdAOE1_SdAB7BHeUkOUiZfiiyJJBSJ89_ojRS7oFw",
    "token_type": "Bearer",
    "refresh_token": "eyjhbGciOiJIUzI1NiIsInR5cCI6IkpXVCj9.eyJkyxRhIjp7InVzZXJfawqiOjgsImNyZWF0aW9uX3RpbWUiOiIyMDIzLTEyLTA0IDIzOjU0OjU5LjczMzUwOCJ9LCJwdXJwb3NlIjoicmVmcmVzaC10b2tlbiIsimv4ccI6MjAyNDAxMDMyMzU0NTl9.g8uQyhC7yypj0jQPSK3LmS5MOYs90fD_k7NyfnYhi1m"
}

The access_token is what you will use to authenticate when calling other APIs. The access token expires every 5 minutes. (This is why we are implementing API keys.)

Refresh access token

To obtain a new access token, use the refresh_token from the /account/login call as follows.

POST https://api.valdi.ai/account/refresh_token

You must include the following payload:

{
  "token": "your_refresh_token"
}

An example 200 OK response is:

{
    "access_token": "your_new_access_token",
    "token_type": "Bearer"
}

Virtual Machines (VM)

Fetch available devices

To see the list of available VALDI devices and configurations across all of our different providers, use the following API call.

GET https://api.valdi.ai/v1/devices/available

This endpoint requires authentication. To authenticate, include the following header in the call:

'Authorization: Bearer {your_access_token}'

An example 200 OK response is:

{
  "devices_by_provider": [
    {
      "provider_id": "a45088e0-1681-446e-9a3e-3ed9dfc3d57e",
      "provider_devices": [
        {
          "flavor_id": "94d76997-44ec-4f1e-8291-231de42b6030",
          "gpu_type": "RTX A6000 48GB",
          "cpu_core_count": 6,
          "main_ram": 55,
          "storage": 500,
          "gpu_count": 1,
          "running_cost": "0.660000000000",
          "stopped_cost": "0.100000000000"
        },
        {
          "flavor_id": "b5917017-7ca4-403e-b08b-ba5cc31c7d2a",
          "gpu_type": "RTX A6000 48GB",
          "cpu_core_count": 12,
          "main_ram": 110,
          "storage": 1000,
          "gpu_count": 2,
          "running_cost": "1.320000000000",
          "stopped_cost": "0.200000000000"
        }
      ]
    },
    {
      "provider_id": "66730e5d-98d2-45f6-8aaa-6ad8cdcbc285",
      "provider_devices": [
        {
          "cpu_type": "AMD EPYC 7713P",
          "cpu_core_count": 128,
          "main_ram": 185,
          "gpu_enabled": true,
          "gpu_ram": 80,
          "gpu_type": "A100 80GB",
          "gpu_code": "a100-pcie-80gb",
          "gpu_count": 2,
          "location": "Exmouth, Devon, United Kingdom",
          "storage": 300,
          "hostnode": "b2ce5479-fa59-483f-86ba-0aa128051821",
          "available_port": [
            21299,
            21298,
            21297,
            21296,
            21295,
            21294
          ],
          "cpu_cost": 0.0035,
          "ram_cost": 0.0025,
          "storage_cost": 0.0002,
          "gpu_cost": "2.044000000000000",
          "upload_bw": 1000.0,
          "download_bw": 1000.0
        }
      ]
    }
  ]
}

Generate an SSH key

Some VALDI servers require SSH key authentication, while others require password authentication. This is a consequence of the requirements and configurations implemented by the actual host of the server.

For security purposes, and in line with best practices of major hyperscalers, VALDI does not allow externally generated SSH keys to be used for provisioning a VM.

NOTE: It is generally easier and faster to generate your SSH keys through the dashboard at valdi.ai.

To generate an SSH key, use the following endpoint.

POST https://api.valdi.ai/sshkeys/sshkey

This endpoint requires authentication. To authenticate, include the following header in the call:

'Authorization: Bearer {your_access_token}'

The format of the payload is very simple:

{
  "name": "whatever-you-want-to-name-the-key"
}

An example 201 Created response is:

{
  "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIJKAIBAAKCAgEAvVslMAKcIKk4blv182E2Fh7oJOzvOfzgoenual92VvOG6WPB\n30SHs4+MXBf17Nei+PRUsZ4aYGodt1GIjz3sX/nUdLEdN1oIEYIEqZuMSD899LAh\nme9wBRqydbhhYJOTksbufAkBJNjJT9QkZDArVpvMQ1hqOwCRqpBMY4QsYwxdK3wN\nYr/moffJEXiHvY3XnpaIAPfX+KzO/vp7N01K3L5mugQDisgxXlk7TQn3Py3fJkiD...",
  "ssh_key_id": "your-new-ssh-key-uuid"
}

Please save both the private key and the SSH key ID. This is the only time you will be able to retrieve the private key, and the SSH key ID is needed to provision VMs that require SSH key authentication.

Remove new lines from private key

After copying and pasting the private key into a plain text file, you will need to manually remove new line characters and replace them with actual new lines. One way to do this is by opening the file in vim (sorry emacs fans), entering command mode by pressing :, and then running the following command:

:%s/\\n/\r/g

If you generate the SSH key in the dashboard, you don't have to deal with this. :)

Modify permissions of private key

The private key file should have the following permissions, or ssh may refuse to connect you.

-rw-------

To assign such permissions, use the following command:

chmod 600 {private_key_filename}

Get SSH Keys

To get a list of the SSH keys under your account, use the following authenticated API.

GET https://api.valdi.ai/sshkeys

An example 200 OK response is as follows.

{
  "ssh_keys": [
    {
      "name": "api-docs-key",
      "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC9WyUwApwgqThuW/XzYTYWHugk7O85/OCh6e5qX3ZW84bpY8HfRIezj4xcF/Xs16L49FSxnhpgah23UYiPPexf+dR0sR03WggRggSpm4xIPz30sCGZ73AFGrJ1uGFgk5OSxu58CQEk2MlP1CRkMCtWm8xDWGo7AJGqkExjhCxjDF0rfA1iv+ah98kReIe9jdeelogA99f4rM7++ns3TUrcvma6BAOKyDFeWTtNCfc/Ld8mSIPWCyxC+D3bmrIOm7re87oW01lanp7SSk1xU3P3hXNBsZRf1SkCjNXJIWEq3xp5WJDQYL4yy+J0FnahZC6o6LxeCkc/fEWfTV7yC4Cp7lBstCn9fiZyh2YAnFJd5FjD1JNThiWTw7KUB8qePbhZdxPfNJhMt3qRknMj2wRM6aDAQ68OSKMjUvJjDoJnr3jmlo4ZVvpcU2qRO73vJdrJDtwTCRUheDTXrSd0091JrQGN7qEsjj8BtC4NsPMuQpNJpUAVvCftBgjTD3OH892Ey8geDctLgI8HXoGhkIZNuRsmdL4SmWeH2nyWKRtnKpjiMTUi32Y6KV+Fs7ttvJZ5nG8x+SZ+4G2XfkeFXp/tRypiU7bYnYm5AQINXhSloaHsG/sztCaWyettQje5khNmnrHT/29XQB8vfcSHI3nnY/xkA9I5SN+fB2Vxcav69w==",
      "ssh_key_id": "02a18443-93d1-4c86-92f9-65f90d129a74",
      "created_at": "2023-12-12T23:13:54.285477"
    },
    {
      "name": "valdi-test-key",
      "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCXPGUW6PkZjDtOjNf4o9mqtkTTl2w2UCLelcsVDXS2Hh/pS28D6Jyq9JFeLxgusvJrcMA+xUfW7Jc5PiWlvsSzkiaHHDd+Qp38TluxXPiCLz/grLURC6jwQt39z3DMh4Yqrc3ybUdVr8ScD54h6dEw/OtDchdgqmjkdAWTCdlGxmpdw4lPPFuR0MoTVR9ebbwtKxlB2eXk6+voNIYykLdlVxia71xpuVdaBBSI2c59fDK7KsGqkQw7xv31gE3RKc3Z663ieOB3sSClU9FFaixBplrqa2kU8Cb3c5zcmM7VXQiR2rVKSvIdFgIbHdIaEnSn0D9vauNxhJMsONxbVSY2+a6zx+tJ4FR25qsBWkCUwxe2vlMVeoR4kppk11XPiVQBVWPI+Pkygzh0PhrpCqrPz30m9Ue7Oug5SueCefJtrb3GBId2sCuAhiVxQETvUG0873D/TpMI+4mL4tmsNb/RgrxE7kAOAji+bJUaYORmLbJuY98VRJlECJsKHUn52VX9HpA2Vnpfo1RNcnUnMBUzDmh1kqBzzi8mx3c0xL/JQozqtNVuwDg9dAy79CNdU6+nwQdAqvYLU11v68x1i8XssA8b+rRJWzZ2W7QBXssIyPPvsvP1F8Na+8yBam9+XO1nyjL4i4XzmMCcddJJ/edb1xk0Lbrehh5J1LWTPHXVWQ==",
      "ssh_key_id": "708bffd3-bce7-4806-aa55-cbaa9a3740b9",
      "created_at": "2023-11-30T22:57:53.425409"
    }
  ]
}

Notice that the private keys are not returned. VALDI does not store your private key, so you need to make sure to save it when you first create it.

Provision a VM

POST https://api.valdi.ai/v1/vm/provision

This endpoint requires authentication. To authenticate, include the following header in the call:

'Authorization: Bearer {your_access_token}'

The format of the payload is as follows:

{
  "provider_id": "provider_id_from_available_devices_api",
  "name": "whatever-name-you-want",
  "details": PROVIDER-SPECIFIC OBJECT (SEE BELOW)
}

As noted in the above, the format of the details object varies depending on the provider.

Provider ID a45088e0

An example details object for this provider is shown below.

{
  "flavor_id": "94d76997-44ec-4f1e-8291-231de42b6030",
  "ssh_key_id": "02a18443-93d1-4c86-92f9-65f90d129a74"
}

The definitions of the two fields are:

  • flavor_id: The UUID of the desired server configuration as returned by GET /v1/devices/available
  • ssh_key_id: The UUID of the SSH key you would like to use to access this machine

A full example payload for provider ID a45088e0-1681-446e-9a3e-3ed9dfc3d57e is as follows.

{
  "provider_id": "a45088e0-1681-446e-9a3e-3ed9dfc3d57e",
  "name": "whatever-name-you-want",
  "details": {
    "flavor_id": "94d76997-44ec-4f1e-8291-231de42b6030",
    "ssh_key_id": "02a18443-93d1-4c86-92f9-65f90d129a74"
  }
}

These VMs have ports 80, 443, and 22 open by default. Other ports can be configured using ufw once logged in.

Provider ID 66730e5d

An example details object for this provider is shown below.

{
  "gpu_model": "a100-pcie-80gb",
  "operating_system": "Ubuntu 20.04 LTS",
  "external_ports": "{21299}",
  "internal_ports": "{22}",
  "gpu_count": 1,
  "password": "your-desired-password-to-access-the-vm",
  "hostnode": "b2ce5479-fa59-483f-86ba-0aa128051821",
  "storage": 256,
  "vcpus": 64,
  "ram": 128
}

The definitions of the various fields are:

  • gpu_model: The desired GPU model as returned by GET /v1/devices/available under gpu_code
  • operating_system: One of the following:
    • Ubuntu 20.04 LTS: Vanilla image of Ubuntu 20.04 LTS (min. 20 GB storage req'd)
    • Ubuntu 22.04 LTS: Vanilla image of Ubuntu 22.04 LTS (min. 20 GB storage req'd)
    • TensorML 20 TensorFlow: Ubuntu 20.04 with TensorFlow and Jupyter (min. 40 GB storage req'd)
    • TensorML 20 PyTorch: Ubuntu 20.04 with PyTorch and Jupyter (min. 40 GB storage req'd)
    • TensorML 20 Everything: Ubuntu 20.04 with TensorFlow, PyTorch, MxNet, and Jupyter (min. 60 GB storage req'd)
  • external_ports: A list of external ports you want to be exposed (e.g., {21297, 21298, 21299})
  • internal_ports: A list of corresponding internal ports to which the external ports will map (.e.g, {22, 80, 443})
    • This would map 21297 to 22, 21298 to 80, and 21299 to 443
  • gpu_count: The number of GPUs you want to attach to the VM
  • password: Your desired password
  • hostnode: The UUID of the hostnode as returned by GET /v1/devices/available
  • storage: The desired amount of storage in GB
  • vcpus: The desired number of vCPUs
  • ram: The desired amount of RAM in GB

A full example payload for provider ID 66730e5d-98d2-45f6-8aaa-6ad8cdcbc285 is as follows.

{
  "provider_id": "66730e5d-98d2-45f6-8aaa-6ad8cdcbc285",
  "name": "whatever-name-you-want",
  "details": {
    "gpu_model": "a100-pcie-80gb",
    "operating_system": "Ubuntu 20.04 LTS",
    "external_ports": "{21297, 21298, 21299}",
    "internal_ports": "{22, 80, 443}",
    "gpu_count": 1,
    "password": "your-desired-password-to-access-the-vm",
    "hostnode": "b2ce5479-fa59-483f-86ba-0aa128051821",
    "storage": 256,
    "vcpus": 64,
    "ram": 128
  }
}

This request is comparatively complex because this provider's VM specifications and networking are configurable.

NOTE: Virtual machines can take up to 15 minutes to provision, especially for large RAM deployments. If you are getting a "Connection refused" error from ssh, it is best to wait a bit longer and try again.

Get VM status

You can retrieve the full list of your virtual machines with the following authenticated call.

GET https://api.valdi.ai/v1/vm

An example 200 OK response is:

{
  "virtual_machines": [
    {
      "server": "5bebf874-05d9-49d7-a45f-801a702ac27c",
      "ip": "2a0b:d40:0:4c::1",
      "provider_id": "a45088e0-1681-446e-9a3e-3ed9dfc3d57e",
      "ssh_key_id": "02a18443-93d1-4c86-92f9-65f90d129a74",
      "name": "valdi-customer-Vlj!%k",
      "user_provided_name": "api-docs-test",
      "gpu_count": 1,
      "gpu_model": "RTXA6000.1GPU",
      "pretty_gpu_name": "RTX A6000 48GB",
      "vcpus": 6,
      "ram": 55,
      "storage": 500,
      "operating_system": "Ubuntu 20.04 LTS",
      "port_forwards": "{\"22\": \"22\", \"443\": \"443\", \"80\": \"80\"}",
      "all_inclusive": true,
      "cpu_cost": null,
      "ram_cost": null,
      "storage_cost": null,
      "gpu_cost": null,
      "all_inclusive_running_cost": "0.830000000000",
      "all_inclusive_stopped_cost": "0.130000000000",
      "last_stopped_time": null,
      "last_restarted_time": null,
      "created_at": "2023-12-12T23:41:05.232767",
      "status": "running"
    },
    {
      "server": "52703e5e-347a-47af-bdf4-1ac8fbb9942e",
      "ip": "217.79.242.232",
      "provider_id": "66730e5d-98d2-45f6-8aaa-6ad8cdcbc285",
      "ssh_key_id": null,
      "name": "valdi-customer-li03lg",
      "user_provided_name": "api-docs-test",
      "gpu_count": 1,
      "gpu_model": "v100-pcie-16gb",
      "pretty_gpu_name": "V100 16GB",
      "vcpus": 2,
      "ram": 4,
      "storage": 20,
      "operating_system": "Ubuntu 20.04 LTS",
      "port_forwards": "{\"60000\": \"22\"}",
      "all_inclusive": false,
      "cpu_cost": "0.003500000000",
      "ram_cost": "0.002500000000",
      "storage_cost": "0.000100000000",
      "gpu_cost": "0.292000000000",
      "all_inclusive_running_cost": null,
      "all_inclusive_stopped_cost": null,
      "last_stopped_time": null,
      "last_restarted_time": null,
      "created_at": "2023-12-12T23:40:28.217619",
      "status": "running"
    }
  ]
}

Note that different fields are null depending on the provider.

You can get the status of specific VMs by appending the VM ID to the path of the GET call. For example, GET /v1/vm/52703e5e-347a-47af-bdf4-1ac8fbb9942e would return only the second entry in the list above.

Stop a VM

To stop (i.e., temporarily turn off) your VM but retain your locally stored data, use the following authenticated endpoint.

POST https://api.valdi.ai/v1/vm/stop/{server_id}

Upon success, you will get the following 200 OK response:

{
    "message": "success"
}

For VMs from Provider ID a45088e0, the cost of the VM while stopped will be 13-16% of the cost while running. Your data will be retained, and you can restart the VM at any time.

For VMs from Provider ID 66730e5d, you will only pay for the amount of storage you assigned to the VM. Your data will be retained, but you are not guaranteed to be able to restart the VM at any time. (If all GPUs attached to your physical server are rented out while your VM is stopped, you will have to wait until at least one GPU becomes available again.)

Start a VM

To start a VM that had been stopped, use the following authenticated endpoint.

POST https://api.valdi.ai/v1/vm/start/{server_id}

Upon success, you will get the following 200 OK response:

{
  "message": "success"
}

It may take a few minutes for your VM to come online and be accessible again.

Terminate a VM

To permanently delete a VM, use the following command. The VM can be either in a running or stopped state.

POST https://api.valdi.ai/v1/vm/destroy/{server_id}

Upon success, you will get the following 200 OK response:

{
  "message": "success"
}

Storage

Create a volume

POST https://api.valdi.ai/volume

This endpoint requires authentication. To authenticate, include the following header in the call:

'Authorization: Bearer {your_access_token}'

Refer to Authentication for instructions on how to generate an access token.

The format of the payload is as follows:

{
  "bucket_name": "your_bucket_name"
}

There are no restrictions on allowable characters in bucket names.

Upon success, you will receive a 200 OK response:

{
    "volume_id": "76b892fb-7c2a-335c-9456-63b952e0014d",
    "bucket_name": "valid bucket name",
    "access_key_id": "jyasso4pxvwt2qv5k6nn626vs3cb",
    "secret_access_key": "jyzrh9lsssyj761l2kbbdefjkb6hijklxsgoif4p2sbzswn2qt32o",
    "created_at": "2024-05-01T21:31:26.671080",
    "deleted_at": null,
    "contents": null
}

The access_key_id and secret_access_key should be safely stored.

Create a subdirectory

PUT https://api.valdi.ai/volume/{volume_id}/folder/{encoded_path}

The encoded path is the Base64-encoding of the subdirectory you would like to create. As an example, you can generate a Base64-encoded string in Python as follows:

import base64
text_path = 'newsub1/newsub2'
encoded_path = base64.urlsafe_b64encode(text_path.encode()).decode()

In the above, encoded_path comes out as bmV3c3ViMS9uZXdzdWIy, and the query PUT /volume/{volume_id}/folder/bmV3c3ViMS9uZXdzdWIy creates the subdirectories newsub1/newsub2.

List all volumes

GET https://api.valdi.ai/volume

with the Authorization header, as usual.

Upon success, you will receive a 200 OK response with a list of your volumes similar to the following:

{
  "volumes": [
    {
      "volume_id": "f81bebd8-fe50-41bb-b146-10c7b950370e",
      "bucket_name": "valid bucket name",
      "access_key_id": null,
      "secret_access_key": null,
      "created_at": "2024-05-01T21:31:26.671080",
      "deleted_at": null,
      "contents": null
    },
    {
      "volume_id": "2345b858-fcbd-4e56-866a-0f2e0ead8fae",
      "bucket_name": "detachable-volume",
      "access_key_id": null,
      "secret_access_key": null,
      "created_at": "2024-01-22T21:55:33.166196",
      "deleted_at": null,
      "contents": null
    },
    {
      "volume_id": "3ef27d50-3f15-47c9-9788-6c55dfdd3b6a",
      "bucket_name": "mounting-test",
      "access_key_id": null,
      "secret_access_key": null,
      "created_at": "2024-01-20T00:35:18.015527",
      "deleted_at": null,
      "contents": null
    }
  ]
}

List specific volume

GET https://api.valdi.ai/volume/{volume_id}

A successful response looks like a single entry from the volumes list response of GET /volume as shown above.

List specific volume contents

Include the query string parameter include_contents=True as, e.g., GET /volume/{volume_id}?include_contents=True to also retrieve the bucket's contents in the response.

List subdirectory contents

To list the contents of a volume's subdirectory, include the query string parameter subdirs={subdir} as, e.g., GET /volume/{volume_id}?include_contents=True&subdirs={subdir} where subdir is the path to the target subdirectory.

Upload data

GET https://api.valdi.ai/volume/{volume_id}/upload/{encoded_filepath}

The first step to uploading data is to generate a PUT URL for the file you would like to upload. The encoded_filepath parameter is the Base64-encoded relative path (including the filename) of the file you are uploading. The Base64 encoding can be calculated as described above. Note that the target filename once uploaded does not have to match the local name.

The response of the above endpoint will include a PUT URL similar to the following:

{
    "upload_link": "https://gateway.valdi.ai/valdi/22/some-volume/newsub1/somefile.data?AWSAccessKeyId=jbsg1kn6to3c2yvzta3ulfq2fsmq&Signature=fD7vsrfxYvb%2bzhHtJHJyb6bMcgw%3D&Expires=1714697322"
}

To upload the file, make a PUT request to the above URL specifying the following headers:

Accept: */*
Content-Type: 

(Yes, the Content-Type header should be empty.) The file itself should be included in the PUT request as a binary stream.

Download data

GET https://api.valdi.ai/volume/{volume_id}/object/{encoded_filepath}

The first step to downloading data is to generate a download link with the above GET request. As before, the encoded_filepath parameter is the Base64-encoded relative filepath of the file you would like to download. See here for how to Base64 encode a string.

The response will be like the following:

{
    "download_link": "https://gateway.valdi.ai/valdi/22/some-volume/somefile.data?response-content-disposition=attachment&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=iwrglkm4235c3bacta3ulfq2fznq%2F20240503%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20240503T220110Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=2f4185b33dber553e45c74dbbab8680d4bebffb710e74d629006c720ba913c32"
}

Following download_link will automatically download the file.

Delete a file or subdirectory

To delete a subdirectory, use the following endpoint:

DELETE https://api.valdi.ai/volume/{volume_id}/object/{encoded_path}?path_type=PRE

To delete a file, use the same endpoint but with path_type=OBJ:

DELETE https://api.valdi.ai/volume/{volume_id}/object/{encoded_path}?path_type=OBJ

In both cases, the encoded_path parameter can be computed as shown here. Authentication header required as usual.

Upon successful completion of either of the above requests, you will receive 200 OK:

{
    "message": "Successfully deleted"
}

Note that this action is irreversible.

Delete a volume

DELETE https://api.valdi.ai/volume/{volume_id}

Authentication header required as usual. Upon success, you will receive 200 OK:

{
    "message": "Successfully deleted"
}

Note that this action is irreversible.