All the communications between LXD and its clients happen using a RESTful API over http which is then encapsulated over either SSL for remote operations or a unix socket for local operations.

API versioning#

The list of supported major API versions can be retrieved using GET /.

The reason for a major API bump is if the API breaks backward compatibility.

Feature additions done without breaking backward compatibility only result in addition to api_extensions which can be used by the client to check if a given feature is supported by the server.

Return values#

There are three standard return types:

  • Standard return value

  • Background operation

  • Error

Standard return value#

For a standard synchronous operation, the following dict is returned:

    "type": "sync",
    "status": "Success",
    "status_code": 200,
    "metadata": {}                          // Extra resource/action specific metadata

HTTP code must be 200.

Background operation#

When a request results in a background operation, the HTTP code is set to 202 (Accepted) and the Location HTTP header is set to the operation URL.

The body is a dict with the following structure:

    "type": "async",
    "status": "OK",
    "status_code": 100,
    "operation": "/1.0/instances/<id>",                     // URL to the background operation
    "metadata": {}                                          // Operation metadata (see below)

The operation metadata structure looks like:

    "id": "a40f5541-5e98-454f-b3b6-8a51ef5dbd3c",           // UUID of the operation
    "class": "websocket",                                   // Class of the operation (task, websocket or token)
    "created_at": "2015-11-17T22:32:02.226176091-05:00",    // When the operation was created
    "updated_at": "2015-11-17T22:32:02.226176091-05:00",    // Last time the operation was updated
    "status": "Running",                                    // String version of the operation's status
    "status_code": 103,                                     // Integer version of the operation's status (use this rather than status)
    "resources": {                                          // Dictionary of resource types (container, snapshots, images) and affected resources
      "containers": [
    "metadata": {                                           // Metadata specific to the operation in question (in this case, exec)
      "fds": {
        "0": "2a4a97af81529f6608dca31f03a7b7e47acc0b8dc6514496eb25e325f9e4fa6a",
        "control": "5b64c661ef313b423b5317ba9cb6410e40b705806c28255f601c0ef603f079a7"
    "may_cancel": false,                                    // Whether the operation can be canceled (DELETE over REST)
    "err": ""                                               // The error string should the operation have failed

The body is mostly provided as a user friendly way of seeing what’s going on without having to pull the target operation, all information in the body can also be retrieved from the background operation URL.


There are various situations in which something may immediately go wrong, in those cases, the following return value is used:

    "type": "error",
    "error": "Failure",
    "error_code": 400,
    "metadata": {}                      // More details about the error

HTTP code must be one of of 400, 401, 403, 404, 409, 412 or 500.

Status codes#

The LXD REST API often has to return status information, be that the reason for an error, the current state of an operation or the state of the various resources it exports.

To make it simple to debug, all of those are always doubled. There is a numeric representation of the state which is guaranteed never to change and can be relied on by API clients. Then there is a text version meant to make it easier for people manually using the API to figure out what’s happening.

In most cases, those will be called status and status_code, the former being the user-friendly string representation and the latter the fixed numeric value.

The codes are always 3 digits, with the following ranges:

  • 100 to 199: resource state (started, stopped, ready, …)

  • 200 to 399: positive action result

  • 400 to 599: negative action result

  • 600 to 999: future use

List of current status codes#




Operation created
































To optimize queries of large lists, recursion is implemented for collections. A recursion argument can be passed to a GET query against a collection.

The default value is 0 which means that collection member URLs are returned. Setting it to 1 will have those URLs be replaced by the object they point to (typically a dict).

Recursion is implemented by simply replacing any pointer to an job (URL) by the object itself.


To filter your results on certain values, filter is implemented for collections. A filter argument can be passed to a GET query against a collection.

Filtering is available for the instance and image endpoints.

There is no default value for filter which means that all results found will be returned. The following is the language used for the filter argument:

?filter=field_name eq desired_field_assignment

The language follows the OData conventions for structuring REST API filtering logic. Logical operators are also supported for filtering: not(not), equals(eq), not equals(ne), and(and), or(or). Filters are evaluated with left associativity. Values with spaces can be surrounded with quotes. Nesting filtering is also supported. For instance, to filter on a field in a config you would pass:

?filter=config.field_name eq desired_field_assignment

For filtering on device attributes you would pass:

?filter=devices.device_name.field_name eq desired_field_assignment

Here are a few GET query examples of the different filtering methods mentioned above:

containers?filter=name eq “my container” and status eq Running

containers?filter=config.image.os eq ubuntu or devices.eth0.nictype eq bridged

images?filter=Properties.os eq Centos and not UpdateSource.Protocol eq simplestreams

Async operations#

Any operation which may take more than a second to be done must be done in the background, returning a background operation ID to the client.

The client will then be able to either poll for a status update or wait for a notification using the long-poll API.


A websocket based API is available for notifications, different notification types exist to limit the traffic going to the client.

It’s recommended that the client always subscribes to the operations notification type before triggering remote operations so that it doesn’t have to then poll for their status.


The LXD API supports both PUT and PATCH to modify existing objects.

PUT replaces the entire object with a new definition, it’s typically called after the current object state was retrieved through GET.

To avoid race conditions, the Etag header should be read from the GET response and sent as If-Match for the PUT request. This will cause LXD to fail the request if the object was modified between GET and PUT.

PATCH can be used to modify a single field inside an object by only specifying the property that you want to change. To unset a key, setting it to empty will usually do the trick, but there are cases where PATCH won’t work and PUT needs to be used instead.

Instances, containers and virtual-machines#

This documentation will always show paths such as /1.0/instances/.... Those are fairly new, introduced with LXD 3.19 when virtual-machine support.

Older releases that only supported containers will instead use the exact same API at /1.0/containers/....

For backward compatibility reasons, LXD does still expose and support that /1.0/containers API, though for the sake of brevity, we decided not to double-document everything below.

An additional endpoint at /1.0/virtual-machines is also present and much like /1.0/containers will only show you instances of that type.

API structure#

LXD has an auto-generated Swagger specification describing its API endpoints. The YAML version of this API specification can be found in rest-api.yaml. See Main API specification for a convenient web rendering of it.