APIShardX Launcher APIAutomation

Automation

List automation projects

GEThttp://127.0.0.1:40325/automation/projects

Every project saved in the launcher, with its blocks and run settings. These are the same projects the desktop Automation section edits.

Authorizations
bearerAuth
Authorizationstringrequired

Permanent JWT from Settings → Automation API.

Responses
200Project list.application/json
idstring · uuidoptional
namestringoptional
notesstringoptional
blocksobject · AutomationBlock[]optional
Show propertiesHide properties
idstringoptional
kindstringoptional

A built-in palette kind (goto, click, type, profile.create, var.set, ...) or module:<module id>:<block>.

On a mobile profile, common actions use touch input: click taps, rightClick performs a long press, and scroll and swipe use touch gestures. hover is unavailable because mobile devices have no pointer. The following mobile-only steps are rejected on desktop profiles:

| kind | params | action | | --- | --- | --- | | touch.tap | selector or x+y, tapCount (1-3), timeout | Tap the target. | | touch.longPress | selector or x+y, timeout | Press and hold. | | touch.swipe | selector or x+y, dx, dy, timeout | Swipe. | | touch.drag | selector, to, holdMs, timeout | Drag an element. | | touch.pinch | scale, selector or x+y | Pinch to zoom. Values above 1 zoom out. | | screen.rotate | angle (0/90/180/270), turnMs, into | Rotate the device. into stores the new screen.orientation.type. |

labelstringoptional
paramsobjectoptional

The step's own fields, as the palette defines them.

enabledboolean· default: trueoptional
xnumberoptional

Canvas position. Layout only; the run follows the branches.

ynumberoptional
on_donestring · enum | objectoptional

Where the run goes after a step. A plain string for the simple cases, an object for the ones carrying a value.

on_failany · AutomationBranchoptional

Defaults to stop; carrying on after a click that found nothing is how a run types into the wrong page.

secretsstring[]optional

Parameter names emptied on export.

runobject · AutomationRunSettingsoptional
Show propertiesHide properties
threadsinteger· default: 1optional

Browsers running at once; clamped to 1-64.

loopsinteger· default: 1optional

Passes over the blocks. 0 means run until hours is up.

hoursnumberoptional

Only meaningful when loops is 0.

startstringoptional

Block id to start at; empty means the first.

profilesstring[]optional

Kept for projects saved before profiles became blocks; one thread each when set.

created_atintegeroptional

Unix seconds.

updated_atintegeroptional

Unix seconds.

401Missing or invalid Bearer token.
GET/automation/projects
GET /automation/projects HTTP/1.1
Host: 127.0.0.1:40325
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
curl -X GET 'http://127.0.0.1:40325/automation/projects' \
  -H 'Authorization: Bearer YOUR_SECRET_TOKEN'
import requests

# Permanent JWT from Settings → Automation API.
token = "YOUR_SECRET_TOKEN"

url = "http://127.0.0.1:40325/automation/projects"
headers = {
    "Authorization": f"Bearer {token}",
}

response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
print(response.status_code)
print(response.text)
// Permanent JWT from Settings → Automation API.
const token = "YOUR_SECRET_TOKEN";

const url = "http://127.0.0.1:40325/automation/projects";

const response = await fetch(url, {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${token}`,
  },
});

console.log(response.status);
console.log(await response.text());
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	// Permanent JWT from Settings → Automation API.
	token := "YOUR_SECRET_TOKEN"

	url := "http://127.0.0.1:40325/automation/projects"

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	out, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
	fmt.Println(string(out))
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }, tokio = { version = "1", features = ["full"] }

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Permanent JWT from Settings → Automation API.
    let token = "YOUR_SECRET_TOKEN";

    let client = reqwest::Client::new();
    let response = client
        .get("http://127.0.0.1:40325/automation/projects")
        .header("Authorization", format!("Bearer {token}"))
        .send()
        .await?;

    println!("{}", response.status());
    println!("{}", response.text().await?);
    Ok(())
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Permanent JWT from Settings → Automation API.
        var token = "YOUR_SECRET_TOKEN";

        using var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

        var request = new HttpRequestMessage(HttpMethod.Get, "http://127.0.0.1:40325/automation/projects");

        var response = await client.SendAsync(request);
        Console.WriteLine((int)response.StatusCode);
        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }
}
// build: c++ -std=c++17 main.cpp -lcurl
#include <curl/curl.h>

#include <iostream>
#include <string>

static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
    static_cast<std::string *>(userdata)->append(ptr, size * nmemb);
    return size * nmemb;
}

int main() {
    // Permanent JWT from Settings → Automation API.
    const std::string token = "YOUR_SECRET_TOKEN";

    curl_global_init(CURL_GLOBAL_DEFAULT);
    CURL *curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl_easy_init failed\n";
        curl_global_cleanup();
        return 1;
    }

    struct curl_slist *headers = nullptr;
    headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str());

    std::string response;
    curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:40325/automation/projects");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    const CURLcode rc = curl_easy_perform(curl);
    if (rc != CURLE_OK) {
        std::cerr << curl_easy_strerror(rc) << "\n";
    } else {
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
        std::cout << status << "\n" << response << "\n";
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    curl_global_cleanup();
    return rc == CURLE_OK ? 0 : 1;
}
200Project list.
[
  {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "name": "text",
    "notes": "text",
    "blocks": [
      {
        "id": "text",
        "kind": "click",
        "label": "text",
        "params": {},
        "enabled": true,
        "x": 1,
        "y": 1,
        "on_done": "next",
        "on_fail": null,
        "secrets": [
          "text"
        ]
      }
    ],
    "run": {
      "threads": 1,
      "loops": 1,
      "hours": 1,
      "start": "text",
      "profiles": [
        "text"
      ]
    },
    "created_at": 1,
    "updated_at": 1
  }
]

Create project

POSThttp://127.0.0.1:40325/automation/projects
Authorizations
bearerAuth
Authorizationstringrequired

Permanent JWT from Settings → Automation API.

Body
application/json
namestringoptional

Uses Untitled when empty.

Responses
200Created project.application/json
idstring · uuidoptional
namestringoptional
notesstringoptional
blocksobject · AutomationBlock[]optional
Show propertiesHide properties
idstringoptional
kindstringoptional

A built-in palette kind (goto, click, type, profile.create, var.set, ...) or module:<module id>:<block>.

On a mobile profile, common actions use touch input: click taps, rightClick performs a long press, and scroll and swipe use touch gestures. hover is unavailable because mobile devices have no pointer. The following mobile-only steps are rejected on desktop profiles:

| kind | params | action | | --- | --- | --- | | touch.tap | selector or x+y, tapCount (1-3), timeout | Tap the target. | | touch.longPress | selector or x+y, timeout | Press and hold. | | touch.swipe | selector or x+y, dx, dy, timeout | Swipe. | | touch.drag | selector, to, holdMs, timeout | Drag an element. | | touch.pinch | scale, selector or x+y | Pinch to zoom. Values above 1 zoom out. | | screen.rotate | angle (0/90/180/270), turnMs, into | Rotate the device. into stores the new screen.orientation.type. |

labelstringoptional
paramsobjectoptional

The step's own fields, as the palette defines them.

enabledboolean· default: trueoptional
xnumberoptional

Canvas position. Layout only; the run follows the branches.

ynumberoptional
on_donestring · enum | objectoptional

Where the run goes after a step. A plain string for the simple cases, an object for the ones carrying a value.

on_failany · AutomationBranchoptional

Defaults to stop; carrying on after a click that found nothing is how a run types into the wrong page.

secretsstring[]optional

Parameter names emptied on export.

runobject · AutomationRunSettingsoptional
Show propertiesHide properties
threadsinteger· default: 1optional

Browsers running at once; clamped to 1-64.

loopsinteger· default: 1optional

Passes over the blocks. 0 means run until hours is up.

hoursnumberoptional

Only meaningful when loops is 0.

startstringoptional

Block id to start at; empty means the first.

profilesstring[]optional

Kept for projects saved before profiles became blocks; one thread each when set.

created_atintegeroptional

Unix seconds.

updated_atintegeroptional

Unix seconds.

401Missing or invalid Bearer token.
POST/automation/projects
POST /automation/projects HTTP/1.1
Host: 127.0.0.1:40325
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*

{
  "name": "text"
}
curl -X POST 'http://127.0.0.1:40325/automation/projects' \
  -H 'Authorization: Bearer YOUR_SECRET_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"name":"text"}'
import requests

# Permanent JWT from Settings → Automation API.
token = "YOUR_SECRET_TOKEN"

url = "http://127.0.0.1:40325/automation/projects"
headers = {
    "Authorization": f"Bearer {token}",
}
payload = {
    "name": "text",
}

response = requests.post(url, headers=headers, json=payload, timeout=30)
response.raise_for_status()
print(response.status_code)
print(response.text)
// Permanent JWT from Settings → Automation API.
const token = "YOUR_SECRET_TOKEN";

const url = "http://127.0.0.1:40325/automation/projects";
const payload = {
  "name": "text"
};

const response = await fetch(url, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(payload),
});

console.log(response.status);
console.log(await response.text());
package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	// Permanent JWT from Settings → Automation API.
	token := "YOUR_SECRET_TOKEN"

	url := "http://127.0.0.1:40325/automation/projects"
	body := bytes.NewBufferString(`{
  "name": "text"
}`)

	req, err := http.NewRequest("POST", url, body)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	out, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
	fmt.Println(string(out))
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }, tokio = { version = "1", features = ["full"] }, serde_json = "1"

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Permanent JWT from Settings → Automation API.
    let token = "YOUR_SECRET_TOKEN";

    let client = reqwest::Client::new();
    let response = client
        .post("http://127.0.0.1:40325/automation/projects")
        .header("Authorization", format!("Bearer {token}"))
        .json(&serde_json::json!({
          "name": "text"
        }))
        .send()
        .await?;

    println!("{}", response.status());
    println!("{}", response.text().await?);
    Ok(())
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Permanent JWT from Settings → Automation API.
        var token = "YOUR_SECRET_TOKEN";

        using var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

        var request = new HttpRequestMessage(HttpMethod.Post, "http://127.0.0.1:40325/automation/projects");
        request.Content = new StringContent("""
            {
              "name": "text"
            }
            """, Encoding.UTF8, "application/json");

        var response = await client.SendAsync(request);
        Console.WriteLine((int)response.StatusCode);
        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }
}
// build: c++ -std=c++17 main.cpp -lcurl
#include <curl/curl.h>

#include <iostream>
#include <string>

static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
    static_cast<std::string *>(userdata)->append(ptr, size * nmemb);
    return size * nmemb;
}

int main() {
    // Permanent JWT from Settings → Automation API.
    const std::string token = "YOUR_SECRET_TOKEN";

    curl_global_init(CURL_GLOBAL_DEFAULT);
    CURL *curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl_easy_init failed\n";
        curl_global_cleanup();
        return 1;
    }

    struct curl_slist *headers = nullptr;
    headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str());
    headers = curl_slist_append(headers, "Content-Type: application/json");

    const std::string body = R"json({
  "name": "text"
})json";

    std::string response;
    curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:40325/automation/projects");
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast<long>(body.size()));
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    const CURLcode rc = curl_easy_perform(curl);
    if (rc != CURLE_OK) {
        std::cerr << curl_easy_strerror(rc) << "\n";
    } else {
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
        std::cout << status << "\n" << response << "\n";
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    curl_global_cleanup();
    return rc == CURLE_OK ? 0 : 1;
}
200Created project.
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "text",
  "notes": "text",
  "blocks": [
    {
      "id": "text",
      "kind": "click",
      "label": "text",
      "params": {},
      "enabled": true,
      "x": 1,
      "y": 1,
      "on_done": "next",
      "on_fail": null,
      "secrets": [
        "text"
      ]
    }
  ],
  "run": {
    "threads": 1,
    "loops": 1,
    "hours": 1,
    "start": "text",
    "profiles": [
      "text"
    ]
  },
  "created_at": 1,
  "updated_at": 1
}

Get project

GEThttp://127.0.0.1:40325/automation/projects/{id}
Authorizations
bearerAuth
Authorizationstringrequired

Permanent JWT from Settings → Automation API.

Path parameters
idstring · uuidrequired
Responses
200Project details.application/json
idstring · uuidoptional
namestringoptional
notesstringoptional
blocksobject · AutomationBlock[]optional
Show propertiesHide properties
idstringoptional
kindstringoptional

A built-in palette kind (goto, click, type, profile.create, var.set, ...) or module:<module id>:<block>.

On a mobile profile, common actions use touch input: click taps, rightClick performs a long press, and scroll and swipe use touch gestures. hover is unavailable because mobile devices have no pointer. The following mobile-only steps are rejected on desktop profiles:

| kind | params | action | | --- | --- | --- | | touch.tap | selector or x+y, tapCount (1-3), timeout | Tap the target. | | touch.longPress | selector or x+y, timeout | Press and hold. | | touch.swipe | selector or x+y, dx, dy, timeout | Swipe. | | touch.drag | selector, to, holdMs, timeout | Drag an element. | | touch.pinch | scale, selector or x+y | Pinch to zoom. Values above 1 zoom out. | | screen.rotate | angle (0/90/180/270), turnMs, into | Rotate the device. into stores the new screen.orientation.type. |

labelstringoptional
paramsobjectoptional

The step's own fields, as the palette defines them.

enabledboolean· default: trueoptional
xnumberoptional

Canvas position. Layout only; the run follows the branches.

ynumberoptional
on_donestring · enum | objectoptional

Where the run goes after a step. A plain string for the simple cases, an object for the ones carrying a value.

on_failany · AutomationBranchoptional

Defaults to stop; carrying on after a click that found nothing is how a run types into the wrong page.

secretsstring[]optional

Parameter names emptied on export.

runobject · AutomationRunSettingsoptional
Show propertiesHide properties
threadsinteger· default: 1optional

Browsers running at once; clamped to 1-64.

loopsinteger· default: 1optional

Passes over the blocks. 0 means run until hours is up.

hoursnumberoptional

Only meaningful when loops is 0.

startstringoptional

Block id to start at; empty means the first.

profilesstring[]optional

Kept for projects saved before profiles became blocks; one thread each when set.

created_atintegeroptional

Unix seconds.

updated_atintegeroptional

Unix seconds.

401Missing or invalid Bearer token.
404Project not found.application/json
errorstringrequired
GET/automation/projects/123e4567-e89b-12d3-a456-426614174000
GET /automation/projects/123e4567-e89b-12d3-a456-426614174000 HTTP/1.1
Host: 127.0.0.1:40325
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
curl -X GET 'http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000' \
  -H 'Authorization: Bearer YOUR_SECRET_TOKEN'
import requests

# Permanent JWT from Settings → Automation API.
token = "YOUR_SECRET_TOKEN"

url = "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000"
headers = {
    "Authorization": f"Bearer {token}",
}

response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
print(response.status_code)
print(response.text)
// Permanent JWT from Settings → Automation API.
const token = "YOUR_SECRET_TOKEN";

const url = "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000";

const response = await fetch(url, {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${token}`,
  },
});

console.log(response.status);
console.log(await response.text());
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	// Permanent JWT from Settings → Automation API.
	token := "YOUR_SECRET_TOKEN"

	url := "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000"

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	out, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
	fmt.Println(string(out))
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }, tokio = { version = "1", features = ["full"] }

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Permanent JWT from Settings → Automation API.
    let token = "YOUR_SECRET_TOKEN";

    let client = reqwest::Client::new();
    let response = client
        .get("http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000")
        .header("Authorization", format!("Bearer {token}"))
        .send()
        .await?;

    println!("{}", response.status());
    println!("{}", response.text().await?);
    Ok(())
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Permanent JWT from Settings → Automation API.
        var token = "YOUR_SECRET_TOKEN";

        using var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

        var request = new HttpRequestMessage(HttpMethod.Get, "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000");

        var response = await client.SendAsync(request);
        Console.WriteLine((int)response.StatusCode);
        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }
}
// build: c++ -std=c++17 main.cpp -lcurl
#include <curl/curl.h>

#include <iostream>
#include <string>

static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
    static_cast<std::string *>(userdata)->append(ptr, size * nmemb);
    return size * nmemb;
}

int main() {
    // Permanent JWT from Settings → Automation API.
    const std::string token = "YOUR_SECRET_TOKEN";

    curl_global_init(CURL_GLOBAL_DEFAULT);
    CURL *curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl_easy_init failed\n";
        curl_global_cleanup();
        return 1;
    }

    struct curl_slist *headers = nullptr;
    headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str());

    std::string response;
    curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    const CURLcode rc = curl_easy_perform(curl);
    if (rc != CURLE_OK) {
        std::cerr << curl_easy_strerror(rc) << "\n";
    } else {
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
        std::cout << status << "\n" << response << "\n";
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    curl_global_cleanup();
    return rc == CURLE_OK ? 0 : 1;
}
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "text",
  "notes": "text",
  "blocks": [
    {
      "id": "text",
      "kind": "click",
      "label": "text",
      "params": {},
      "enabled": true,
      "x": 1,
      "y": 1,
      "on_done": "next",
      "on_fail": null,
      "secrets": [
        "text"
      ]
    }
  ],
  "run": {
    "threads": 1,
    "loops": 1,
    "hours": 1,
    "start": "text",
    "profiles": [
      "text"
    ]
  },
  "created_at": 1,
  "updated_at": 1
}
{
  "error": "text"
}

Replace project

PUThttp://127.0.0.1:40325/automation/projects/{id}

Replaces the entire project. Send the object returned by GET. The path ID takes precedence over the body ID. A missing project returns 404; use POST /automation/projects to create one.

Authorizations
bearerAuth
Authorizationstringrequired

Permanent JWT from Settings → Automation API.

Path parameters
idstring · uuidrequired
Body
application/json
idstring · uuidoptional
namestringoptional
notesstringoptional
blocksobject · AutomationBlock[]optional
Show propertiesHide properties
idstringoptional
kindstringoptional

A built-in palette kind (goto, click, type, profile.create, var.set, ...) or module:<module id>:<block>.

On a mobile profile, common actions use touch input: click taps, rightClick performs a long press, and scroll and swipe use touch gestures. hover is unavailable because mobile devices have no pointer. The following mobile-only steps are rejected on desktop profiles:

| kind | params | action | | --- | --- | --- | | touch.tap | selector or x+y, tapCount (1-3), timeout | Tap the target. | | touch.longPress | selector or x+y, timeout | Press and hold. | | touch.swipe | selector or x+y, dx, dy, timeout | Swipe. | | touch.drag | selector, to, holdMs, timeout | Drag an element. | | touch.pinch | scale, selector or x+y | Pinch to zoom. Values above 1 zoom out. | | screen.rotate | angle (0/90/180/270), turnMs, into | Rotate the device. into stores the new screen.orientation.type. |

labelstringoptional
paramsobjectoptional

The step's own fields, as the palette defines them.

enabledboolean· default: trueoptional
xnumberoptional

Canvas position. Layout only; the run follows the branches.

ynumberoptional
on_donestring · enum | objectoptional

Where the run goes after a step. A plain string for the simple cases, an object for the ones carrying a value.

on_failany · AutomationBranchoptional

Defaults to stop; carrying on after a click that found nothing is how a run types into the wrong page.

secretsstring[]optional

Parameter names emptied on export.

runobject · AutomationRunSettingsoptional
Show propertiesHide properties
threadsinteger· default: 1optional

Browsers running at once; clamped to 1-64.

loopsinteger· default: 1optional

Passes over the blocks. 0 means run until hours is up.

hoursnumberoptional

Only meaningful when loops is 0.

startstringoptional

Block id to start at; empty means the first.

profilesstring[]optional

Kept for projects saved before profiles became blocks; one thread each when set.

created_atintegeroptional

Unix seconds.

updated_atintegeroptional

Unix seconds.

Responses
200Saved project.application/json
idstring · uuidoptional
namestringoptional
notesstringoptional
blocksobject · AutomationBlock[]optional
Show propertiesHide properties
idstringoptional
kindstringoptional

A built-in palette kind (goto, click, type, profile.create, var.set, ...) or module:<module id>:<block>.

On a mobile profile, common actions use touch input: click taps, rightClick performs a long press, and scroll and swipe use touch gestures. hover is unavailable because mobile devices have no pointer. The following mobile-only steps are rejected on desktop profiles:

| kind | params | action | | --- | --- | --- | | touch.tap | selector or x+y, tapCount (1-3), timeout | Tap the target. | | touch.longPress | selector or x+y, timeout | Press and hold. | | touch.swipe | selector or x+y, dx, dy, timeout | Swipe. | | touch.drag | selector, to, holdMs, timeout | Drag an element. | | touch.pinch | scale, selector or x+y | Pinch to zoom. Values above 1 zoom out. | | screen.rotate | angle (0/90/180/270), turnMs, into | Rotate the device. into stores the new screen.orientation.type. |

labelstringoptional
paramsobjectoptional

The step's own fields, as the palette defines them.

enabledboolean· default: trueoptional
xnumberoptional

Canvas position. Layout only; the run follows the branches.

ynumberoptional
on_donestring · enum | objectoptional

Where the run goes after a step. A plain string for the simple cases, an object for the ones carrying a value.

on_failany · AutomationBranchoptional

Defaults to stop; carrying on after a click that found nothing is how a run types into the wrong page.

secretsstring[]optional

Parameter names emptied on export.

runobject · AutomationRunSettingsoptional
Show propertiesHide properties
threadsinteger· default: 1optional

Browsers running at once; clamped to 1-64.

loopsinteger· default: 1optional

Passes over the blocks. 0 means run until hours is up.

hoursnumberoptional

Only meaningful when loops is 0.

startstringoptional

Block id to start at; empty means the first.

profilesstring[]optional

Kept for projects saved before profiles became blocks; one thread each when set.

created_atintegeroptional

Unix seconds.

updated_atintegeroptional

Unix seconds.

400Invalid project object.application/json
errorstringrequired
401Missing or invalid Bearer token.
404Project not found.application/json
errorstringrequired
PUT/automation/projects/123e4567-e89b-12d3-a456-426614174000
PUT /automation/projects/123e4567-e89b-12d3-a456-426614174000 HTTP/1.1
Host: 127.0.0.1:40325
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*

{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "text",
  "notes": "text",
  "blocks": [
    {
      "id": "text",
      "kind": "click",
      "label": "text",
      "params": {},
      "enabled": true,
      "x": 1,
      "y": 1,
      "on_done": "next",
      "on_fail": null,
      "secrets": [
        "text"
      ]
    }
  ],
  "run": {
    "threads": 1,
    "loops": 1,
    "hours": 1,
    "start": "text",
    "profiles": [
      "text"
    ]
  },
  "created_at": 1,
  "updated_at": 1
}
curl -X PUT 'http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000' \
  -H 'Authorization: Bearer YOUR_SECRET_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"id":"123e4567-e89b-12d3-a456-426614174000","name":"text","notes":"text","blocks":[{"id":"text","kind":"click","label":"text","params":{},"enabled":true,"x":1,"y":1,"on_done":"next","on_fail":null,"secrets":["text"]}],"run":{"threads":1,"loops":1,"hours":1,"start":"text","profiles":["text"]},"created_at":1,"updated_at":1}'
import requests

# Permanent JWT from Settings → Automation API.
token = "YOUR_SECRET_TOKEN"

url = "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000"
headers = {
    "Authorization": f"Bearer {token}",
}
payload = {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "name": "text",
    "notes": "text",
    "blocks": [
        {
            "id": "text",
            "kind": "click",
            "label": "text",
            "params": {},
            "enabled": True,
            "x": 1,
            "y": 1,
            "on_done": "next",
            "on_fail": None,
            "secrets": [
                "text",
            ],
        },
    ],
    "run": {
        "threads": 1,
        "loops": 1,
        "hours": 1,
        "start": "text",
        "profiles": [
            "text",
        ],
    },
    "created_at": 1,
    "updated_at": 1,
}

response = requests.put(url, headers=headers, json=payload, timeout=30)
response.raise_for_status()
print(response.status_code)
print(response.text)
// Permanent JWT from Settings → Automation API.
const token = "YOUR_SECRET_TOKEN";

const url = "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000";
const payload = {
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "text",
  "notes": "text",
  "blocks": [
    {
      "id": "text",
      "kind": "click",
      "label": "text",
      "params": {},
      "enabled": true,
      "x": 1,
      "y": 1,
      "on_done": "next",
      "on_fail": null,
      "secrets": [
        "text"
      ]
    }
  ],
  "run": {
    "threads": 1,
    "loops": 1,
    "hours": 1,
    "start": "text",
    "profiles": [
      "text"
    ]
  },
  "created_at": 1,
  "updated_at": 1
};

const response = await fetch(url, {
  method: "PUT",
  headers: {
    "Authorization": `Bearer ${token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(payload),
});

console.log(response.status);
console.log(await response.text());
package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	// Permanent JWT from Settings → Automation API.
	token := "YOUR_SECRET_TOKEN"

	url := "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000"
	body := bytes.NewBufferString(`{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "text",
  "notes": "text",
  "blocks": [
    {
      "id": "text",
      "kind": "click",
      "label": "text",
      "params": {},
      "enabled": true,
      "x": 1,
      "y": 1,
      "on_done": "next",
      "on_fail": null,
      "secrets": [
        "text"
      ]
    }
  ],
  "run": {
    "threads": 1,
    "loops": 1,
    "hours": 1,
    "start": "text",
    "profiles": [
      "text"
    ]
  },
  "created_at": 1,
  "updated_at": 1
}`)

	req, err := http.NewRequest("PUT", url, body)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	out, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
	fmt.Println(string(out))
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }, tokio = { version = "1", features = ["full"] }, serde_json = "1"

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Permanent JWT from Settings → Automation API.
    let token = "YOUR_SECRET_TOKEN";

    let client = reqwest::Client::new();
    let response = client
        .put("http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000")
        .header("Authorization", format!("Bearer {token}"))
        .json(&serde_json::json!({
          "id": "123e4567-e89b-12d3-a456-426614174000",
          "name": "text",
          "notes": "text",
          "blocks": [
            {
              "id": "text",
              "kind": "click",
              "label": "text",
              "params": {},
              "enabled": true,
              "x": 1,
              "y": 1,
              "on_done": "next",
              "on_fail": null,
              "secrets": [
                "text"
              ]
            }
          ],
          "run": {
            "threads": 1,
            "loops": 1,
            "hours": 1,
            "start": "text",
            "profiles": [
              "text"
            ]
          },
          "created_at": 1,
          "updated_at": 1
        }))
        .send()
        .await?;

    println!("{}", response.status());
    println!("{}", response.text().await?);
    Ok(())
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Permanent JWT from Settings → Automation API.
        var token = "YOUR_SECRET_TOKEN";

        using var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

        var request = new HttpRequestMessage(HttpMethod.Put, "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000");
        request.Content = new StringContent("""
            {
              "id": "123e4567-e89b-12d3-a456-426614174000",
              "name": "text",
              "notes": "text",
              "blocks": [
                {
                  "id": "text",
                  "kind": "click",
                  "label": "text",
                  "params": {},
                  "enabled": true,
                  "x": 1,
                  "y": 1,
                  "on_done": "next",
                  "on_fail": null,
                  "secrets": [
                    "text"
                  ]
                }
              ],
              "run": {
                "threads": 1,
                "loops": 1,
                "hours": 1,
                "start": "text",
                "profiles": [
                  "text"
                ]
              },
              "created_at": 1,
              "updated_at": 1
            }
            """, Encoding.UTF8, "application/json");

        var response = await client.SendAsync(request);
        Console.WriteLine((int)response.StatusCode);
        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }
}
// build: c++ -std=c++17 main.cpp -lcurl
#include <curl/curl.h>

#include <iostream>
#include <string>

static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
    static_cast<std::string *>(userdata)->append(ptr, size * nmemb);
    return size * nmemb;
}

int main() {
    // Permanent JWT from Settings → Automation API.
    const std::string token = "YOUR_SECRET_TOKEN";

    curl_global_init(CURL_GLOBAL_DEFAULT);
    CURL *curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl_easy_init failed\n";
        curl_global_cleanup();
        return 1;
    }

    struct curl_slist *headers = nullptr;
    headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str());
    headers = curl_slist_append(headers, "Content-Type: application/json");

    const std::string body = R"json({
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "text",
  "notes": "text",
  "blocks": [
    {
      "id": "text",
      "kind": "click",
      "label": "text",
      "params": {},
      "enabled": true,
      "x": 1,
      "y": 1,
      "on_done": "next",
      "on_fail": null,
      "secrets": [
        "text"
      ]
    }
  ],
  "run": {
    "threads": 1,
    "loops": 1,
    "hours": 1,
    "start": "text",
    "profiles": [
      "text"
    ]
  },
  "created_at": 1,
  "updated_at": 1
})json";

    std::string response;
    curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000");
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast<long>(body.size()));
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    const CURLcode rc = curl_easy_perform(curl);
    if (rc != CURLE_OK) {
        std::cerr << curl_easy_strerror(rc) << "\n";
    } else {
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
        std::cout << status << "\n" << response << "\n";
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    curl_global_cleanup();
    return rc == CURLE_OK ? 0 : 1;
}
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "text",
  "notes": "text",
  "blocks": [
    {
      "id": "text",
      "kind": "click",
      "label": "text",
      "params": {},
      "enabled": true,
      "x": 1,
      "y": 1,
      "on_done": "next",
      "on_fail": null,
      "secrets": [
        "text"
      ]
    }
  ],
  "run": {
    "threads": 1,
    "loops": 1,
    "hours": 1,
    "start": "text",
    "profiles": [
      "text"
    ]
  },
  "created_at": 1,
  "updated_at": 1
}
{
  "error": "text"
}
{
  "error": "text"
}

Delete a project

DELhttp://127.0.0.1:40325/automation/projects/{id}
Authorizations
bearerAuth
Authorizationstringrequired

Permanent JWT from Settings → Automation API.

Path parameters
idstring · uuidrequired
Responses
200Deleted.application/json
deletedbooleanoptional
idstring · uuidoptional
401Missing or invalid Bearer token.
404Project not found.application/json
errorstringrequired
DEL/automation/projects/123e4567-e89b-12d3-a456-426614174000
DELETE /automation/projects/123e4567-e89b-12d3-a456-426614174000 HTTP/1.1
Host: 127.0.0.1:40325
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
curl -X DELETE 'http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000' \
  -H 'Authorization: Bearer YOUR_SECRET_TOKEN'
import requests

# Permanent JWT from Settings → Automation API.
token = "YOUR_SECRET_TOKEN"

url = "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000"
headers = {
    "Authorization": f"Bearer {token}",
}

response = requests.delete(url, headers=headers, timeout=30)
response.raise_for_status()
print(response.status_code)
print(response.text)
// Permanent JWT from Settings → Automation API.
const token = "YOUR_SECRET_TOKEN";

const url = "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000";

const response = await fetch(url, {
  method: "DELETE",
  headers: {
    "Authorization": `Bearer ${token}`,
  },
});

console.log(response.status);
console.log(await response.text());
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	// Permanent JWT from Settings → Automation API.
	token := "YOUR_SECRET_TOKEN"

	url := "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000"

	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	out, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
	fmt.Println(string(out))
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }, tokio = { version = "1", features = ["full"] }

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Permanent JWT from Settings → Automation API.
    let token = "YOUR_SECRET_TOKEN";

    let client = reqwest::Client::new();
    let response = client
        .delete("http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000")
        .header("Authorization", format!("Bearer {token}"))
        .send()
        .await?;

    println!("{}", response.status());
    println!("{}", response.text().await?);
    Ok(())
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Permanent JWT from Settings → Automation API.
        var token = "YOUR_SECRET_TOKEN";

        using var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

        var request = new HttpRequestMessage(HttpMethod.Delete, "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000");

        var response = await client.SendAsync(request);
        Console.WriteLine((int)response.StatusCode);
        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }
}
// build: c++ -std=c++17 main.cpp -lcurl
#include <curl/curl.h>

#include <iostream>
#include <string>

static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
    static_cast<std::string *>(userdata)->append(ptr, size * nmemb);
    return size * nmemb;
}

int main() {
    // Permanent JWT from Settings → Automation API.
    const std::string token = "YOUR_SECRET_TOKEN";

    curl_global_init(CURL_GLOBAL_DEFAULT);
    CURL *curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl_easy_init failed\n";
        curl_global_cleanup();
        return 1;
    }

    struct curl_slist *headers = nullptr;
    headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str());

    std::string response;
    curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000");
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    const CURLcode rc = curl_easy_perform(curl);
    if (rc != CURLE_OK) {
        std::cerr << curl_easy_strerror(rc) << "\n";
    } else {
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
        std::cout << status << "\n" << response << "\n";
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    curl_global_cleanup();
    return rc == CURLE_OK ? 0 : 1;
}
{
  "deleted": true,
  "id": "123e4567-e89b-12d3-a456-426614174000"
}
{
  "error": "text"
}

Duplicate project

POSThttp://127.0.0.1:40325/automation/projects/{id}/duplicate
Authorizations
bearerAuth
Authorizationstringrequired

Permanent JWT from Settings → Automation API.

Path parameters
idstring · uuidrequired
Responses
200Duplicated project.application/json
idstring · uuidoptional
namestringoptional
notesstringoptional
blocksobject · AutomationBlock[]optional
Show propertiesHide properties
idstringoptional
kindstringoptional

A built-in palette kind (goto, click, type, profile.create, var.set, ...) or module:<module id>:<block>.

On a mobile profile, common actions use touch input: click taps, rightClick performs a long press, and scroll and swipe use touch gestures. hover is unavailable because mobile devices have no pointer. The following mobile-only steps are rejected on desktop profiles:

| kind | params | action | | --- | --- | --- | | touch.tap | selector or x+y, tapCount (1-3), timeout | Tap the target. | | touch.longPress | selector or x+y, timeout | Press and hold. | | touch.swipe | selector or x+y, dx, dy, timeout | Swipe. | | touch.drag | selector, to, holdMs, timeout | Drag an element. | | touch.pinch | scale, selector or x+y | Pinch to zoom. Values above 1 zoom out. | | screen.rotate | angle (0/90/180/270), turnMs, into | Rotate the device. into stores the new screen.orientation.type. |

labelstringoptional
paramsobjectoptional

The step's own fields, as the palette defines them.

enabledboolean· default: trueoptional
xnumberoptional

Canvas position. Layout only; the run follows the branches.

ynumberoptional
on_donestring · enum | objectoptional

Where the run goes after a step. A plain string for the simple cases, an object for the ones carrying a value.

on_failany · AutomationBranchoptional

Defaults to stop; carrying on after a click that found nothing is how a run types into the wrong page.

secretsstring[]optional

Parameter names emptied on export.

runobject · AutomationRunSettingsoptional
Show propertiesHide properties
threadsinteger· default: 1optional

Browsers running at once; clamped to 1-64.

loopsinteger· default: 1optional

Passes over the blocks. 0 means run until hours is up.

hoursnumberoptional

Only meaningful when loops is 0.

startstringoptional

Block id to start at; empty means the first.

profilesstring[]optional

Kept for projects saved before profiles became blocks; one thread each when set.

created_atintegeroptional

Unix seconds.

updated_atintegeroptional

Unix seconds.

401Missing or invalid Bearer token.
404Project not found.application/json
errorstringrequired
POST/automation/projects/123e4567-e89b-12d3-a456-426614174000/duplicate
POST /automation/projects/123e4567-e89b-12d3-a456-426614174000/duplicate HTTP/1.1
Host: 127.0.0.1:40325
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
curl -X POST 'http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/duplicate' \
  -H 'Authorization: Bearer YOUR_SECRET_TOKEN'
import requests

# Permanent JWT from Settings → Automation API.
token = "YOUR_SECRET_TOKEN"

url = "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/duplicate"
headers = {
    "Authorization": f"Bearer {token}",
}

response = requests.post(url, headers=headers, timeout=30)
response.raise_for_status()
print(response.status_code)
print(response.text)
// Permanent JWT from Settings → Automation API.
const token = "YOUR_SECRET_TOKEN";

const url = "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/duplicate";

const response = await fetch(url, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${token}`,
  },
});

console.log(response.status);
console.log(await response.text());
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	// Permanent JWT from Settings → Automation API.
	token := "YOUR_SECRET_TOKEN"

	url := "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/duplicate"

	req, err := http.NewRequest("POST", url, nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	out, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
	fmt.Println(string(out))
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }, tokio = { version = "1", features = ["full"] }

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Permanent JWT from Settings → Automation API.
    let token = "YOUR_SECRET_TOKEN";

    let client = reqwest::Client::new();
    let response = client
        .post("http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/duplicate")
        .header("Authorization", format!("Bearer {token}"))
        .send()
        .await?;

    println!("{}", response.status());
    println!("{}", response.text().await?);
    Ok(())
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Permanent JWT from Settings → Automation API.
        var token = "YOUR_SECRET_TOKEN";

        using var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

        var request = new HttpRequestMessage(HttpMethod.Post, "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/duplicate");

        var response = await client.SendAsync(request);
        Console.WriteLine((int)response.StatusCode);
        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }
}
// build: c++ -std=c++17 main.cpp -lcurl
#include <curl/curl.h>

#include <iostream>
#include <string>

static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
    static_cast<std::string *>(userdata)->append(ptr, size * nmemb);
    return size * nmemb;
}

int main() {
    // Permanent JWT from Settings → Automation API.
    const std::string token = "YOUR_SECRET_TOKEN";

    curl_global_init(CURL_GLOBAL_DEFAULT);
    CURL *curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl_easy_init failed\n";
        curl_global_cleanup();
        return 1;
    }

    struct curl_slist *headers = nullptr;
    headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str());

    std::string response;
    curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/duplicate");
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    const CURLcode rc = curl_easy_perform(curl);
    if (rc != CURLE_OK) {
        std::cerr << curl_easy_strerror(rc) << "\n";
    } else {
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
        std::cout << status << "\n" << response << "\n";
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    curl_global_cleanup();
    return rc == CURLE_OK ? 0 : 1;
}
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "text",
  "notes": "text",
  "blocks": [
    {
      "id": "text",
      "kind": "click",
      "label": "text",
      "params": {},
      "enabled": true,
      "x": 1,
      "y": 1,
      "on_done": "next",
      "on_fail": null,
      "secrets": [
        "text"
      ]
    }
  ],
  "run": {
    "threads": 1,
    "loops": 1,
    "hours": 1,
    "start": "text",
    "profiles": [
      "text"
    ]
  },
  "created_at": 1,
  "updated_at": 1
}
{
  "error": "text"
}

Export project bundle

GEThttp://127.0.0.1:40325/automation/projects/{id}/export

Parameters the project marked secret are emptied on the way out, so a bundle can be handed on without taking a password with it. The import side asks for them again.

Authorizations
bearerAuth
Authorizationstringrequired

Permanent JWT from Settings → Automation API.

Path parameters
idstring · uuidrequired
Responses
200Export bundle.application/json

A project on its way to another machine.

formatintegeroptional

An import refuses a number it does not know.

exported_atintegeroptional

Unix seconds.

projectobject · AutomationProjectoptional
Show propertiesHide properties
idstring · uuidoptional
namestringoptional
notesstringoptional
blocksobject · AutomationBlock[]optional
Show propertiesHide properties
idstringoptional
kindstringoptional

A built-in palette kind (goto, click, type, profile.create, var.set, ...) or module:<module id>:<block>.

On a mobile profile, common actions use touch input: click taps, rightClick performs a long press, and scroll and swipe use touch gestures. hover is unavailable because mobile devices have no pointer. The following mobile-only steps are rejected on desktop profiles:

| kind | params | action | | --- | --- | --- | | touch.tap | selector or x+y, tapCount (1-3), timeout | Tap the target. | | touch.longPress | selector or x+y, timeout | Press and hold. | | touch.swipe | selector or x+y, dx, dy, timeout | Swipe. | | touch.drag | selector, to, holdMs, timeout | Drag an element. | | touch.pinch | scale, selector or x+y | Pinch to zoom. Values above 1 zoom out. | | screen.rotate | angle (0/90/180/270), turnMs, into | Rotate the device. into stores the new screen.orientation.type. |

labelstringoptional
paramsobjectoptional

The step's own fields, as the palette defines them.

enabledboolean· default: trueoptional
xnumberoptional

Canvas position. Layout only; the run follows the branches.

ynumberoptional
on_donestring · enum | objectoptional

Where the run goes after a step. A plain string for the simple cases, an object for the ones carrying a value.

on_failany · AutomationBranchoptional

Defaults to stop; carrying on after a click that found nothing is how a run types into the wrong page.

secretsstring[]optional

Parameter names emptied on export.

runobject · AutomationRunSettingsoptional
Show propertiesHide properties
threadsinteger· default: 1optional

Browsers running at once; clamped to 1-64.

loopsinteger· default: 1optional

Passes over the blocks. 0 means run until hours is up.

hoursnumberoptional

Only meaningful when loops is 0.

startstringoptional

Block id to start at; empty means the first.

profilesstring[]optional

Kept for projects saved before profiles became blocks; one thread each when set.

created_atintegeroptional

Unix seconds.

updated_atintegeroptional

Unix seconds.

modulesstring[]optional

Module ids the steps need.

module_filesobject[]optional

Those modules' files, carried inside the bundle so an imported project can run where it lands. A module missing from disk at export time is left out here but still named in modules. On import, a module already installed under the same id is kept as it is rather than overwritten.

Show propertiesHide properties
idstringoptional
wasmstringoptional

The .wasm, gzipped then base64.

needsobject[]optional

What export blanked, so an import can ask for exactly that.

Show propertiesHide properties
block_idstringoptional
labelstringoptional
paramsstring[]optional
401Missing or invalid Bearer token.
404Project not found.application/json
errorstringrequired
GET/automation/projects/123e4567-e89b-12d3-a456-426614174000/export
GET /automation/projects/123e4567-e89b-12d3-a456-426614174000/export HTTP/1.1
Host: 127.0.0.1:40325
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
curl -X GET 'http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/export' \
  -H 'Authorization: Bearer YOUR_SECRET_TOKEN'
import requests

# Permanent JWT from Settings → Automation API.
token = "YOUR_SECRET_TOKEN"

url = "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/export"
headers = {
    "Authorization": f"Bearer {token}",
}

response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
print(response.status_code)
print(response.text)
// Permanent JWT from Settings → Automation API.
const token = "YOUR_SECRET_TOKEN";

const url = "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/export";

const response = await fetch(url, {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${token}`,
  },
});

console.log(response.status);
console.log(await response.text());
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	// Permanent JWT from Settings → Automation API.
	token := "YOUR_SECRET_TOKEN"

	url := "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/export"

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	out, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
	fmt.Println(string(out))
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }, tokio = { version = "1", features = ["full"] }

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Permanent JWT from Settings → Automation API.
    let token = "YOUR_SECRET_TOKEN";

    let client = reqwest::Client::new();
    let response = client
        .get("http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/export")
        .header("Authorization", format!("Bearer {token}"))
        .send()
        .await?;

    println!("{}", response.status());
    println!("{}", response.text().await?);
    Ok(())
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Permanent JWT from Settings → Automation API.
        var token = "YOUR_SECRET_TOKEN";

        using var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

        var request = new HttpRequestMessage(HttpMethod.Get, "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/export");

        var response = await client.SendAsync(request);
        Console.WriteLine((int)response.StatusCode);
        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }
}
// build: c++ -std=c++17 main.cpp -lcurl
#include <curl/curl.h>

#include <iostream>
#include <string>

static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
    static_cast<std::string *>(userdata)->append(ptr, size * nmemb);
    return size * nmemb;
}

int main() {
    // Permanent JWT from Settings → Automation API.
    const std::string token = "YOUR_SECRET_TOKEN";

    curl_global_init(CURL_GLOBAL_DEFAULT);
    CURL *curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl_easy_init failed\n";
        curl_global_cleanup();
        return 1;
    }

    struct curl_slist *headers = nullptr;
    headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str());

    std::string response;
    curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/export");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    const CURLcode rc = curl_easy_perform(curl);
    if (rc != CURLE_OK) {
        std::cerr << curl_easy_strerror(rc) << "\n";
    } else {
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
        std::cout << status << "\n" << response << "\n";
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    curl_global_cleanup();
    return rc == CURLE_OK ? 0 : 1;
}
{
  "format": 1,
  "exported_at": 1,
  "project": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "name": "text",
    "notes": "text",
    "blocks": [
      {
        "id": "text",
        "kind": "click",
        "label": "text",
        "params": {},
        "enabled": true,
        "x": 1,
        "y": 1,
        "on_done": "next",
        "on_fail": null,
        "secrets": [
          "text"
        ]
      }
    ],
    "run": {
      "threads": 1,
      "loops": 1,
      "hours": 1,
      "start": "text",
      "profiles": [
        "text"
      ]
    },
    "created_at": 1,
    "updated_at": 1
  },
  "modules": [
    "text"
  ],
  "module_files": [
    {
      "id": "text",
      "wasm": "text"
    }
  ],
  "needs": [
    {
      "block_id": "text",
      "label": "text",
      "params": [
        "text"
      ]
    }
  ]
}
{
  "error": "text"
}

Import project bundle

POSThttp://127.0.0.1:40325/automation/import

The bundle arrives as a new project with a new id.

Authorizations
bearerAuth
Authorizationstringrequired

Permanent JWT from Settings → Automation API.

Body
application/json
formatintegeroptional

An import refuses a number it does not know.

exported_atintegeroptional

Unix seconds.

projectobject · AutomationProjectoptional
Show propertiesHide properties
idstring · uuidoptional
namestringoptional
notesstringoptional
blocksobject · AutomationBlock[]optional
Show propertiesHide properties
idstringoptional
kindstringoptional

A built-in palette kind (goto, click, type, profile.create, var.set, ...) or module:<module id>:<block>.

On a mobile profile, common actions use touch input: click taps, rightClick performs a long press, and scroll and swipe use touch gestures. hover is unavailable because mobile devices have no pointer. The following mobile-only steps are rejected on desktop profiles:

| kind | params | action | | --- | --- | --- | | touch.tap | selector or x+y, tapCount (1-3), timeout | Tap the target. | | touch.longPress | selector or x+y, timeout | Press and hold. | | touch.swipe | selector or x+y, dx, dy, timeout | Swipe. | | touch.drag | selector, to, holdMs, timeout | Drag an element. | | touch.pinch | scale, selector or x+y | Pinch to zoom. Values above 1 zoom out. | | screen.rotate | angle (0/90/180/270), turnMs, into | Rotate the device. into stores the new screen.orientation.type. |

labelstringoptional
paramsobjectoptional

The step's own fields, as the palette defines them.

enabledboolean· default: trueoptional
xnumberoptional

Canvas position. Layout only; the run follows the branches.

ynumberoptional
on_donestring · enum | objectoptional

Where the run goes after a step. A plain string for the simple cases, an object for the ones carrying a value.

on_failany · AutomationBranchoptional

Defaults to stop; carrying on after a click that found nothing is how a run types into the wrong page.

secretsstring[]optional

Parameter names emptied on export.

runobject · AutomationRunSettingsoptional
Show propertiesHide properties
threadsinteger· default: 1optional

Browsers running at once; clamped to 1-64.

loopsinteger· default: 1optional

Passes over the blocks. 0 means run until hours is up.

hoursnumberoptional

Only meaningful when loops is 0.

startstringoptional

Block id to start at; empty means the first.

profilesstring[]optional

Kept for projects saved before profiles became blocks; one thread each when set.

created_atintegeroptional

Unix seconds.

updated_atintegeroptional

Unix seconds.

modulesstring[]optional

Module ids the steps need.

module_filesobject[]optional

Those modules' files, carried inside the bundle so an imported project can run where it lands. A module missing from disk at export time is left out here but still named in modules. On import, a module already installed under the same id is kept as it is rather than overwritten.

Show propertiesHide properties
idstringoptional
wasmstringoptional

The .wasm, gzipped then base64.

needsobject[]optional

What export blanked, so an import can ask for exactly that.

Show propertiesHide properties
block_idstringoptional
labelstringoptional
paramsstring[]optional
Responses
200Imported project.application/json
idstring · uuidoptional
namestringoptional
notesstringoptional
blocksobject · AutomationBlock[]optional
Show propertiesHide properties
idstringoptional
kindstringoptional

A built-in palette kind (goto, click, type, profile.create, var.set, ...) or module:<module id>:<block>.

On a mobile profile, common actions use touch input: click taps, rightClick performs a long press, and scroll and swipe use touch gestures. hover is unavailable because mobile devices have no pointer. The following mobile-only steps are rejected on desktop profiles:

| kind | params | action | | --- | --- | --- | | touch.tap | selector or x+y, tapCount (1-3), timeout | Tap the target. | | touch.longPress | selector or x+y, timeout | Press and hold. | | touch.swipe | selector or x+y, dx, dy, timeout | Swipe. | | touch.drag | selector, to, holdMs, timeout | Drag an element. | | touch.pinch | scale, selector or x+y | Pinch to zoom. Values above 1 zoom out. | | screen.rotate | angle (0/90/180/270), turnMs, into | Rotate the device. into stores the new screen.orientation.type. |

labelstringoptional
paramsobjectoptional

The step's own fields, as the palette defines them.

enabledboolean· default: trueoptional
xnumberoptional

Canvas position. Layout only; the run follows the branches.

ynumberoptional
on_donestring · enum | objectoptional

Where the run goes after a step. A plain string for the simple cases, an object for the ones carrying a value.

on_failany · AutomationBranchoptional

Defaults to stop; carrying on after a click that found nothing is how a run types into the wrong page.

secretsstring[]optional

Parameter names emptied on export.

runobject · AutomationRunSettingsoptional
Show propertiesHide properties
threadsinteger· default: 1optional

Browsers running at once; clamped to 1-64.

loopsinteger· default: 1optional

Passes over the blocks. 0 means run until hours is up.

hoursnumberoptional

Only meaningful when loops is 0.

startstringoptional

Block id to start at; empty means the first.

profilesstring[]optional

Kept for projects saved before profiles became blocks; one thread each when set.

created_atintegeroptional

Unix seconds.

updated_atintegeroptional

Unix seconds.

400Invalid bundle or unsupported format version.application/json
errorstringrequired
401Missing or invalid Bearer token.
POST/automation/import
POST /automation/import HTTP/1.1
Host: 127.0.0.1:40325
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*

{
  "format": 1,
  "exported_at": 1,
  "project": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "name": "text",
    "notes": "text",
    "blocks": [
      {
        "id": "text",
        "kind": "click",
        "label": "text",
        "params": {},
        "enabled": true,
        "x": 1,
        "y": 1,
        "on_done": "next",
        "on_fail": null,
        "secrets": [
          "text"
        ]
      }
    ],
    "run": {
      "threads": 1,
      "loops": 1,
      "hours": 1,
      "start": "text",
      "profiles": [
        "text"
      ]
    },
    "created_at": 1,
    "updated_at": 1
  },
  "modules": [
    "text"
  ],
  "module_files": [
    {
      "id": "text",
      "wasm": "text"
    }
  ],
  "needs": [
    {
      "block_id": "text",
      "label": "text",
      "params": [
        "text"
      ]
    }
  ]
}
curl -X POST 'http://127.0.0.1:40325/automation/import' \
  -H 'Authorization: Bearer YOUR_SECRET_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"format":1,"exported_at":1,"project":{"id":"123e4567-e89b-12d3-a456-426614174000","name":"text","notes":"text","blocks":[{"id":"text","kind":"click","label":"text","params":{},"enabled":true,"x":1,"y":1,"on_done":"next","on_fail":null,"secrets":["text"]}],"run":{"threads":1,"loops":1,"hours":1,"start":"text","profiles":["text"]},"created_at":1,"updated_at":1},"modules":["text"],"module_files":[{"id":"text","wasm":"text"}],"needs":[{"block_id":"text","label":"text","params":["text"]}]}'
import requests

# Permanent JWT from Settings → Automation API.
token = "YOUR_SECRET_TOKEN"

url = "http://127.0.0.1:40325/automation/import"
headers = {
    "Authorization": f"Bearer {token}",
}
payload = {
    "format": 1,
    "exported_at": 1,
    "project": {
        "id": "123e4567-e89b-12d3-a456-426614174000",
        "name": "text",
        "notes": "text",
        "blocks": [
            {
                "id": "text",
                "kind": "click",
                "label": "text",
                "params": {},
                "enabled": True,
                "x": 1,
                "y": 1,
                "on_done": "next",
                "on_fail": None,
                "secrets": [
                    "text",
                ],
            },
        ],
        "run": {
            "threads": 1,
            "loops": 1,
            "hours": 1,
            "start": "text",
            "profiles": [
                "text",
            ],
        },
        "created_at": 1,
        "updated_at": 1,
    },
    "modules": [
        "text",
    ],
    "module_files": [
        {
            "id": "text",
            "wasm": "text",
        },
    ],
    "needs": [
        {
            "block_id": "text",
            "label": "text",
            "params": [
                "text",
            ],
        },
    ],
}

response = requests.post(url, headers=headers, json=payload, timeout=30)
response.raise_for_status()
print(response.status_code)
print(response.text)
// Permanent JWT from Settings → Automation API.
const token = "YOUR_SECRET_TOKEN";

const url = "http://127.0.0.1:40325/automation/import";
const payload = {
  "format": 1,
  "exported_at": 1,
  "project": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "name": "text",
    "notes": "text",
    "blocks": [
      {
        "id": "text",
        "kind": "click",
        "label": "text",
        "params": {},
        "enabled": true,
        "x": 1,
        "y": 1,
        "on_done": "next",
        "on_fail": null,
        "secrets": [
          "text"
        ]
      }
    ],
    "run": {
      "threads": 1,
      "loops": 1,
      "hours": 1,
      "start": "text",
      "profiles": [
        "text"
      ]
    },
    "created_at": 1,
    "updated_at": 1
  },
  "modules": [
    "text"
  ],
  "module_files": [
    {
      "id": "text",
      "wasm": "text"
    }
  ],
  "needs": [
    {
      "block_id": "text",
      "label": "text",
      "params": [
        "text"
      ]
    }
  ]
};

const response = await fetch(url, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(payload),
});

console.log(response.status);
console.log(await response.text());
package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	// Permanent JWT from Settings → Automation API.
	token := "YOUR_SECRET_TOKEN"

	url := "http://127.0.0.1:40325/automation/import"
	body := bytes.NewBufferString(`{
  "format": 1,
  "exported_at": 1,
  "project": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "name": "text",
    "notes": "text",
    "blocks": [
      {
        "id": "text",
        "kind": "click",
        "label": "text",
        "params": {},
        "enabled": true,
        "x": 1,
        "y": 1,
        "on_done": "next",
        "on_fail": null,
        "secrets": [
          "text"
        ]
      }
    ],
    "run": {
      "threads": 1,
      "loops": 1,
      "hours": 1,
      "start": "text",
      "profiles": [
        "text"
      ]
    },
    "created_at": 1,
    "updated_at": 1
  },
  "modules": [
    "text"
  ],
  "module_files": [
    {
      "id": "text",
      "wasm": "text"
    }
  ],
  "needs": [
    {
      "block_id": "text",
      "label": "text",
      "params": [
        "text"
      ]
    }
  ]
}`)

	req, err := http.NewRequest("POST", url, body)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	out, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
	fmt.Println(string(out))
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }, tokio = { version = "1", features = ["full"] }, serde_json = "1"

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Permanent JWT from Settings → Automation API.
    let token = "YOUR_SECRET_TOKEN";

    let client = reqwest::Client::new();
    let response = client
        .post("http://127.0.0.1:40325/automation/import")
        .header("Authorization", format!("Bearer {token}"))
        .json(&serde_json::json!({
          "format": 1,
          "exported_at": 1,
          "project": {
            "id": "123e4567-e89b-12d3-a456-426614174000",
            "name": "text",
            "notes": "text",
            "blocks": [
              {
                "id": "text",
                "kind": "click",
                "label": "text",
                "params": {},
                "enabled": true,
                "x": 1,
                "y": 1,
                "on_done": "next",
                "on_fail": null,
                "secrets": [
                  "text"
                ]
              }
            ],
            "run": {
              "threads": 1,
              "loops": 1,
              "hours": 1,
              "start": "text",
              "profiles": [
                "text"
              ]
            },
            "created_at": 1,
            "updated_at": 1
          },
          "modules": [
            "text"
          ],
          "module_files": [
            {
              "id": "text",
              "wasm": "text"
            }
          ],
          "needs": [
            {
              "block_id": "text",
              "label": "text",
              "params": [
                "text"
              ]
            }
          ]
        }))
        .send()
        .await?;

    println!("{}", response.status());
    println!("{}", response.text().await?);
    Ok(())
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Permanent JWT from Settings → Automation API.
        var token = "YOUR_SECRET_TOKEN";

        using var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

        var request = new HttpRequestMessage(HttpMethod.Post, "http://127.0.0.1:40325/automation/import");
        request.Content = new StringContent("""
            {
              "format": 1,
              "exported_at": 1,
              "project": {
                "id": "123e4567-e89b-12d3-a456-426614174000",
                "name": "text",
                "notes": "text",
                "blocks": [
                  {
                    "id": "text",
                    "kind": "click",
                    "label": "text",
                    "params": {},
                    "enabled": true,
                    "x": 1,
                    "y": 1,
                    "on_done": "next",
                    "on_fail": null,
                    "secrets": [
                      "text"
                    ]
                  }
                ],
                "run": {
                  "threads": 1,
                  "loops": 1,
                  "hours": 1,
                  "start": "text",
                  "profiles": [
                    "text"
                  ]
                },
                "created_at": 1,
                "updated_at": 1
              },
              "modules": [
                "text"
              ],
              "module_files": [
                {
                  "id": "text",
                  "wasm": "text"
                }
              ],
              "needs": [
                {
                  "block_id": "text",
                  "label": "text",
                  "params": [
                    "text"
                  ]
                }
              ]
            }
            """, Encoding.UTF8, "application/json");

        var response = await client.SendAsync(request);
        Console.WriteLine((int)response.StatusCode);
        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }
}
// build: c++ -std=c++17 main.cpp -lcurl
#include <curl/curl.h>

#include <iostream>
#include <string>

static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
    static_cast<std::string *>(userdata)->append(ptr, size * nmemb);
    return size * nmemb;
}

int main() {
    // Permanent JWT from Settings → Automation API.
    const std::string token = "YOUR_SECRET_TOKEN";

    curl_global_init(CURL_GLOBAL_DEFAULT);
    CURL *curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl_easy_init failed\n";
        curl_global_cleanup();
        return 1;
    }

    struct curl_slist *headers = nullptr;
    headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str());
    headers = curl_slist_append(headers, "Content-Type: application/json");

    const std::string body = R"json({
  "format": 1,
  "exported_at": 1,
  "project": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "name": "text",
    "notes": "text",
    "blocks": [
      {
        "id": "text",
        "kind": "click",
        "label": "text",
        "params": {},
        "enabled": true,
        "x": 1,
        "y": 1,
        "on_done": "next",
        "on_fail": null,
        "secrets": [
          "text"
        ]
      }
    ],
    "run": {
      "threads": 1,
      "loops": 1,
      "hours": 1,
      "start": "text",
      "profiles": [
        "text"
      ]
    },
    "created_at": 1,
    "updated_at": 1
  },
  "modules": [
    "text"
  ],
  "module_files": [
    {
      "id": "text",
      "wasm": "text"
    }
  ],
  "needs": [
    {
      "block_id": "text",
      "label": "text",
      "params": [
        "text"
      ]
    }
  ]
})json";

    std::string response;
    curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:40325/automation/import");
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast<long>(body.size()));
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    const CURLcode rc = curl_easy_perform(curl);
    if (rc != CURLE_OK) {
        std::cerr << curl_easy_strerror(rc) << "\n";
    } else {
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
        std::cout << status << "\n" << response << "\n";
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    curl_global_cleanup();
    return rc == CURLE_OK ? 0 : 1;
}
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "text",
  "notes": "text",
  "blocks": [
    {
      "id": "text",
      "kind": "click",
      "label": "text",
      "params": {},
      "enabled": true,
      "x": 1,
      "y": 1,
      "on_done": "next",
      "on_fail": null,
      "secrets": [
        "text"
      ]
    }
  ],
  "run": {
    "threads": 1,
    "loops": 1,
    "hours": 1,
    "start": "text",
    "profiles": [
      "text"
    ]
  },
  "created_at": 1,
  "updated_at": 1
}
{
  "error": "text"
}

Start project

POSThttp://127.0.0.1:40325/automation/projects/{id}/run

Starts the project and returns immediately. Project blocks start the required browsers. Use /status to monitor progress.

Returns 400 if the project is already running or has no enabled steps.

Authorizations
bearerAuth
Authorizationstringrequired

Permanent JWT from Settings → Automation API.

Path parameters
idstring · uuidrequired
Responses
200Project started.application/json
project_idstring · uuidoptional
runningbooleanoptional
400Project is already running or has no enabled steps.application/json
errorstringrequired
401Missing or invalid Bearer token.
404Project not found.application/json
errorstringrequired
501This build does not support automation.application/json
errorstringrequired
POST/automation/projects/123e4567-e89b-12d3-a456-426614174000/run
POST /automation/projects/123e4567-e89b-12d3-a456-426614174000/run HTTP/1.1
Host: 127.0.0.1:40325
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
curl -X POST 'http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/run' \
  -H 'Authorization: Bearer YOUR_SECRET_TOKEN'
import requests

# Permanent JWT from Settings → Automation API.
token = "YOUR_SECRET_TOKEN"

url = "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/run"
headers = {
    "Authorization": f"Bearer {token}",
}

response = requests.post(url, headers=headers, timeout=30)
response.raise_for_status()
print(response.status_code)
print(response.text)
// Permanent JWT from Settings → Automation API.
const token = "YOUR_SECRET_TOKEN";

const url = "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/run";

const response = await fetch(url, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${token}`,
  },
});

console.log(response.status);
console.log(await response.text());
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	// Permanent JWT from Settings → Automation API.
	token := "YOUR_SECRET_TOKEN"

	url := "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/run"

	req, err := http.NewRequest("POST", url, nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	out, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
	fmt.Println(string(out))
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }, tokio = { version = "1", features = ["full"] }

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Permanent JWT from Settings → Automation API.
    let token = "YOUR_SECRET_TOKEN";

    let client = reqwest::Client::new();
    let response = client
        .post("http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/run")
        .header("Authorization", format!("Bearer {token}"))
        .send()
        .await?;

    println!("{}", response.status());
    println!("{}", response.text().await?);
    Ok(())
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Permanent JWT from Settings → Automation API.
        var token = "YOUR_SECRET_TOKEN";

        using var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

        var request = new HttpRequestMessage(HttpMethod.Post, "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/run");

        var response = await client.SendAsync(request);
        Console.WriteLine((int)response.StatusCode);
        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }
}
// build: c++ -std=c++17 main.cpp -lcurl
#include <curl/curl.h>

#include <iostream>
#include <string>

static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
    static_cast<std::string *>(userdata)->append(ptr, size * nmemb);
    return size * nmemb;
}

int main() {
    // Permanent JWT from Settings → Automation API.
    const std::string token = "YOUR_SECRET_TOKEN";

    curl_global_init(CURL_GLOBAL_DEFAULT);
    CURL *curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl_easy_init failed\n";
        curl_global_cleanup();
        return 1;
    }

    struct curl_slist *headers = nullptr;
    headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str());

    std::string response;
    curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/run");
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    const CURLcode rc = curl_easy_perform(curl);
    if (rc != CURLE_OK) {
        std::cerr << curl_easy_strerror(rc) << "\n";
    } else {
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
        std::cout << status << "\n" << response << "\n";
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    curl_global_cleanup();
    return rc == CURLE_OK ? 0 : 1;
}
{
  "project_id": "123e4567-e89b-12d3-a456-426614174000",
  "running": true
}
{
  "error": "text"
}
{
  "error": "text"
}
{
  "error": "text"
}

Stop project

POSThttp://127.0.0.1:40325/automation/projects/{id}/stop

Requests that the run stop. Each browser finishes its current step before closing. Use /status to monitor the running field.

Authorizations
bearerAuth
Authorizationstringrequired

Permanent JWT from Settings → Automation API.

Path parameters
idstring · uuidrequired
Responses
200Stop requested.application/json
project_idstring · uuidoptional
stoppingbooleanoptional
401Missing or invalid Bearer token.
501This build does not support automation.application/json
errorstringrequired
POST/automation/projects/123e4567-e89b-12d3-a456-426614174000/stop
POST /automation/projects/123e4567-e89b-12d3-a456-426614174000/stop HTTP/1.1
Host: 127.0.0.1:40325
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
curl -X POST 'http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/stop' \
  -H 'Authorization: Bearer YOUR_SECRET_TOKEN'
import requests

# Permanent JWT from Settings → Automation API.
token = "YOUR_SECRET_TOKEN"

url = "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/stop"
headers = {
    "Authorization": f"Bearer {token}",
}

response = requests.post(url, headers=headers, timeout=30)
response.raise_for_status()
print(response.status_code)
print(response.text)
// Permanent JWT from Settings → Automation API.
const token = "YOUR_SECRET_TOKEN";

const url = "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/stop";

const response = await fetch(url, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${token}`,
  },
});

console.log(response.status);
console.log(await response.text());
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	// Permanent JWT from Settings → Automation API.
	token := "YOUR_SECRET_TOKEN"

	url := "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/stop"

	req, err := http.NewRequest("POST", url, nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	out, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
	fmt.Println(string(out))
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }, tokio = { version = "1", features = ["full"] }

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Permanent JWT from Settings → Automation API.
    let token = "YOUR_SECRET_TOKEN";

    let client = reqwest::Client::new();
    let response = client
        .post("http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/stop")
        .header("Authorization", format!("Bearer {token}"))
        .send()
        .await?;

    println!("{}", response.status());
    println!("{}", response.text().await?);
    Ok(())
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Permanent JWT from Settings → Automation API.
        var token = "YOUR_SECRET_TOKEN";

        using var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

        var request = new HttpRequestMessage(HttpMethod.Post, "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/stop");

        var response = await client.SendAsync(request);
        Console.WriteLine((int)response.StatusCode);
        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }
}
// build: c++ -std=c++17 main.cpp -lcurl
#include <curl/curl.h>

#include <iostream>
#include <string>

static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
    static_cast<std::string *>(userdata)->append(ptr, size * nmemb);
    return size * nmemb;
}

int main() {
    // Permanent JWT from Settings → Automation API.
    const std::string token = "YOUR_SECRET_TOKEN";

    curl_global_init(CURL_GLOBAL_DEFAULT);
    CURL *curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl_easy_init failed\n";
        curl_global_cleanup();
        return 1;
    }

    struct curl_slist *headers = nullptr;
    headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str());

    std::string response;
    curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/stop");
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    const CURLcode rc = curl_easy_perform(curl);
    if (rc != CURLE_OK) {
        std::cerr << curl_easy_strerror(rc) << "\n";
    } else {
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
        std::cout << status << "\n" << response << "\n";
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    curl_global_cleanup();
    return rc == CURLE_OK ? 0 : 1;
}
{
  "project_id": "123e4567-e89b-12d3-a456-426614174000",
  "stopping": true
}
{
  "error": "text"
}

Get project run status

GEThttp://127.0.0.1:40325/automation/projects/{id}/status

Returns null if the project is not running and has not run since launcher startup. log contains the recent entries shown in the fleet window. The full log is stored under automation-runs/.

Authorizations
bearerAuth
Authorizationstringrequired

Permanent JWT from Settings → Automation API.

Path parameters
idstring · uuidrequired
Responses
200Run status or `null`.application/json
401Missing or invalid Bearer token.
501This build does not support automation.application/json
errorstringrequired
GET/automation/projects/123e4567-e89b-12d3-a456-426614174000/status
GET /automation/projects/123e4567-e89b-12d3-a456-426614174000/status HTTP/1.1
Host: 127.0.0.1:40325
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
curl -X GET 'http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/status' \
  -H 'Authorization: Bearer YOUR_SECRET_TOKEN'
import requests

# Permanent JWT from Settings → Automation API.
token = "YOUR_SECRET_TOKEN"

url = "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/status"
headers = {
    "Authorization": f"Bearer {token}",
}

response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
print(response.status_code)
print(response.text)
// Permanent JWT from Settings → Automation API.
const token = "YOUR_SECRET_TOKEN";

const url = "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/status";

const response = await fetch(url, {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${token}`,
  },
});

console.log(response.status);
console.log(await response.text());
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	// Permanent JWT from Settings → Automation API.
	token := "YOUR_SECRET_TOKEN"

	url := "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/status"

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	out, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
	fmt.Println(string(out))
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }, tokio = { version = "1", features = ["full"] }

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Permanent JWT from Settings → Automation API.
    let token = "YOUR_SECRET_TOKEN";

    let client = reqwest::Client::new();
    let response = client
        .get("http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/status")
        .header("Authorization", format!("Bearer {token}"))
        .send()
        .await?;

    println!("{}", response.status());
    println!("{}", response.text().await?);
    Ok(())
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Permanent JWT from Settings → Automation API.
        var token = "YOUR_SECRET_TOKEN";

        using var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

        var request = new HttpRequestMessage(HttpMethod.Get, "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/status");

        var response = await client.SendAsync(request);
        Console.WriteLine((int)response.StatusCode);
        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }
}
// build: c++ -std=c++17 main.cpp -lcurl
#include <curl/curl.h>

#include <iostream>
#include <string>

static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
    static_cast<std::string *>(userdata)->append(ptr, size * nmemb);
    return size * nmemb;
}

int main() {
    // Permanent JWT from Settings → Automation API.
    const std::string token = "YOUR_SECRET_TOKEN";

    curl_global_init(CURL_GLOBAL_DEFAULT);
    CURL *curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl_easy_init failed\n";
        curl_global_cleanup();
        return 1;
    }

    struct curl_slist *headers = nullptr;
    headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str());

    std::string response;
    curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:40325/automation/projects/123e4567-e89b-12d3-a456-426614174000/status");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    const CURLcode rc = curl_easy_perform(curl);
    if (rc != CURLE_OK) {
        std::cerr << curl_easy_strerror(rc) << "\n";
    } else {
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
        std::cout << status << "\n" << response << "\n";
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    curl_global_cleanup();
    return rc == CURLE_OK ? 0 : 1;
}
{
  "project_id": "123e4567-e89b-12d3-a456-426614174000",
  "project_name": "text",
  "started_at": 1,
  "running": true,
  "workers": [
    {
      "profile_id": "text",
      "profile_name": "text",
      "pass": 1,
      "step": 1,
      "steps_total": 1,
      "status": "queued",
      "note": "text"
    }
  ],
  "log": [
    "text"
  ]
}
{
  "error": "text"
}

List active runs

GEThttp://127.0.0.1:40325/automation/runs
Authorizations
bearerAuth
Authorizationstringrequired

Permanent JWT from Settings → Automation API.

Responses
200Active run list.application/json
project_idstring · uuidoptional
project_namestringoptional
started_atintegeroptional

Unix seconds.

runningbooleanoptional
workersobject · AutomationWorker[]optional
Show propertiesHide properties
profile_idstringoptional
profile_namestringoptional
passintegeroptional

1-based; 0 before the first pass starts.

stepintegeroptional
steps_totalintegeroptional
statusstring · enumoptional
Possible values:queuedstartingrunningdonefailedstopped
notestringoptional
logstring[]optional

The tail the fleet window shows; the whole log is on disk under automation-runs/.

401Missing or invalid Bearer token.
501This build does not support automation.application/json
errorstringrequired
GET/automation/runs
GET /automation/runs HTTP/1.1
Host: 127.0.0.1:40325
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
curl -X GET 'http://127.0.0.1:40325/automation/runs' \
  -H 'Authorization: Bearer YOUR_SECRET_TOKEN'
import requests

# Permanent JWT from Settings → Automation API.
token = "YOUR_SECRET_TOKEN"

url = "http://127.0.0.1:40325/automation/runs"
headers = {
    "Authorization": f"Bearer {token}",
}

response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
print(response.status_code)
print(response.text)
// Permanent JWT from Settings → Automation API.
const token = "YOUR_SECRET_TOKEN";

const url = "http://127.0.0.1:40325/automation/runs";

const response = await fetch(url, {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${token}`,
  },
});

console.log(response.status);
console.log(await response.text());
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	// Permanent JWT from Settings → Automation API.
	token := "YOUR_SECRET_TOKEN"

	url := "http://127.0.0.1:40325/automation/runs"

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	out, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
	fmt.Println(string(out))
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }, tokio = { version = "1", features = ["full"] }

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Permanent JWT from Settings → Automation API.
    let token = "YOUR_SECRET_TOKEN";

    let client = reqwest::Client::new();
    let response = client
        .get("http://127.0.0.1:40325/automation/runs")
        .header("Authorization", format!("Bearer {token}"))
        .send()
        .await?;

    println!("{}", response.status());
    println!("{}", response.text().await?);
    Ok(())
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Permanent JWT from Settings → Automation API.
        var token = "YOUR_SECRET_TOKEN";

        using var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

        var request = new HttpRequestMessage(HttpMethod.Get, "http://127.0.0.1:40325/automation/runs");

        var response = await client.SendAsync(request);
        Console.WriteLine((int)response.StatusCode);
        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }
}
// build: c++ -std=c++17 main.cpp -lcurl
#include <curl/curl.h>

#include <iostream>
#include <string>

static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
    static_cast<std::string *>(userdata)->append(ptr, size * nmemb);
    return size * nmemb;
}

int main() {
    // Permanent JWT from Settings → Automation API.
    const std::string token = "YOUR_SECRET_TOKEN";

    curl_global_init(CURL_GLOBAL_DEFAULT);
    CURL *curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl_easy_init failed\n";
        curl_global_cleanup();
        return 1;
    }

    struct curl_slist *headers = nullptr;
    headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str());

    std::string response;
    curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:40325/automation/runs");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    const CURLcode rc = curl_easy_perform(curl);
    if (rc != CURLE_OK) {
        std::cerr << curl_easy_strerror(rc) << "\n";
    } else {
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
        std::cout << status << "\n" << response << "\n";
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    curl_global_cleanup();
    return rc == CURLE_OK ? 0 : 1;
}
[
  {
    "project_id": "123e4567-e89b-12d3-a456-426614174000",
    "project_name": "text",
    "started_at": 1,
    "running": true,
    "workers": [
      {
        "profile_id": "text",
        "profile_name": "text",
        "pass": 1,
        "step": 1,
        "steps_total": 1,
        "status": "queued",
        "note": "text"
      }
    ],
    "log": [
      "text"
    ]
  }
]
{
  "error": "text"
}

List installed modules

GEThttp://127.0.0.1:40325/automation/modules

Returns WebAssembly modules installed by the operator. Each module adds blocks with the kind module:<module id>:<block>. Modules return actions for the runner and do not access the page directly.

Authorizations
bearerAuth
Authorizationstringrequired

Permanent JWT from Settings → Automation API.

Responses
200Module list.application/json
idstringoptional

File stem; also the prefix every one of its block kinds carries.

namestringoptional
pathstringoptional
blocksobject[]optional

Block descriptors, in the same shape as the built-in palette.

errorstringoptional

Empty when the module loaded; otherwise why it did not.

401Missing or invalid Bearer token.
501This build does not support automation.application/json
errorstringrequired
GET/automation/modules
GET /automation/modules HTTP/1.1
Host: 127.0.0.1:40325
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
curl -X GET 'http://127.0.0.1:40325/automation/modules' \
  -H 'Authorization: Bearer YOUR_SECRET_TOKEN'
import requests

# Permanent JWT from Settings → Automation API.
token = "YOUR_SECRET_TOKEN"

url = "http://127.0.0.1:40325/automation/modules"
headers = {
    "Authorization": f"Bearer {token}",
}

response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
print(response.status_code)
print(response.text)
// Permanent JWT from Settings → Automation API.
const token = "YOUR_SECRET_TOKEN";

const url = "http://127.0.0.1:40325/automation/modules";

const response = await fetch(url, {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${token}`,
  },
});

console.log(response.status);
console.log(await response.text());
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	// Permanent JWT from Settings → Automation API.
	token := "YOUR_SECRET_TOKEN"

	url := "http://127.0.0.1:40325/automation/modules"

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	out, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
	fmt.Println(string(out))
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }, tokio = { version = "1", features = ["full"] }

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Permanent JWT from Settings → Automation API.
    let token = "YOUR_SECRET_TOKEN";

    let client = reqwest::Client::new();
    let response = client
        .get("http://127.0.0.1:40325/automation/modules")
        .header("Authorization", format!("Bearer {token}"))
        .send()
        .await?;

    println!("{}", response.status());
    println!("{}", response.text().await?);
    Ok(())
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Permanent JWT from Settings → Automation API.
        var token = "YOUR_SECRET_TOKEN";

        using var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

        var request = new HttpRequestMessage(HttpMethod.Get, "http://127.0.0.1:40325/automation/modules");

        var response = await client.SendAsync(request);
        Console.WriteLine((int)response.StatusCode);
        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }
}
// build: c++ -std=c++17 main.cpp -lcurl
#include <curl/curl.h>

#include <iostream>
#include <string>

static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
    static_cast<std::string *>(userdata)->append(ptr, size * nmemb);
    return size * nmemb;
}

int main() {
    // Permanent JWT from Settings → Automation API.
    const std::string token = "YOUR_SECRET_TOKEN";

    curl_global_init(CURL_GLOBAL_DEFAULT);
    CURL *curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl_easy_init failed\n";
        curl_global_cleanup();
        return 1;
    }

    struct curl_slist *headers = nullptr;
    headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str());

    std::string response;
    curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:40325/automation/modules");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    const CURLcode rc = curl_easy_perform(curl);
    if (rc != CURLE_OK) {
        std::cerr << curl_easy_strerror(rc) << "\n";
    } else {
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
        std::cout << status << "\n" << response << "\n";
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    curl_global_cleanup();
    return rc == CURLE_OK ? 0 : 1;
}
[
  {
    "id": "text",
    "name": "text",
    "path": "text",
    "blocks": [
      {}
    ],
    "error": "text"
  }
]
{
  "error": "text"
}

Install module

POSThttp://127.0.0.1:40325/automation/modules

path must point to a local .wasm file on the launcher host. Modules contain native code created by the operator.

Authorizations
bearerAuth
Authorizationstringrequired

Permanent JWT from Settings → Automation API.

Body
application/json
pathstringrequired
Responses
200The installed module.application/json
idstringoptional

File stem; also the prefix every one of its block kinds carries.

namestringoptional
pathstringoptional
blocksobject[]optional

Block descriptors, in the same shape as the built-in palette.

errorstringoptional

Empty when the module loaded; otherwise why it did not.

400Missing path or unsupported module file.application/json
errorstringrequired
401Missing or invalid Bearer token.
501This build does not support automation.application/json
errorstringrequired
POST/automation/modules
POST /automation/modules HTTP/1.1
Host: 127.0.0.1:40325
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*

{
  "path": "/home/me/module.wasm"
}
curl -X POST 'http://127.0.0.1:40325/automation/modules' \
  -H 'Authorization: Bearer YOUR_SECRET_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"path":"/home/me/module.wasm"}'
import requests

# Permanent JWT from Settings → Automation API.
token = "YOUR_SECRET_TOKEN"

url = "http://127.0.0.1:40325/automation/modules"
headers = {
    "Authorization": f"Bearer {token}",
}
payload = {
    "path": "/home/me/module.wasm",
}

response = requests.post(url, headers=headers, json=payload, timeout=30)
response.raise_for_status()
print(response.status_code)
print(response.text)
// Permanent JWT from Settings → Automation API.
const token = "YOUR_SECRET_TOKEN";

const url = "http://127.0.0.1:40325/automation/modules";
const payload = {
  "path": "/home/me/module.wasm"
};

const response = await fetch(url, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(payload),
});

console.log(response.status);
console.log(await response.text());
package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	// Permanent JWT from Settings → Automation API.
	token := "YOUR_SECRET_TOKEN"

	url := "http://127.0.0.1:40325/automation/modules"
	body := bytes.NewBufferString(`{
  "path": "/home/me/module.wasm"
}`)

	req, err := http.NewRequest("POST", url, body)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	out, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
	fmt.Println(string(out))
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }, tokio = { version = "1", features = ["full"] }, serde_json = "1"

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Permanent JWT from Settings → Automation API.
    let token = "YOUR_SECRET_TOKEN";

    let client = reqwest::Client::new();
    let response = client
        .post("http://127.0.0.1:40325/automation/modules")
        .header("Authorization", format!("Bearer {token}"))
        .json(&serde_json::json!({
          "path": "/home/me/module.wasm"
        }))
        .send()
        .await?;

    println!("{}", response.status());
    println!("{}", response.text().await?);
    Ok(())
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Permanent JWT from Settings → Automation API.
        var token = "YOUR_SECRET_TOKEN";

        using var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

        var request = new HttpRequestMessage(HttpMethod.Post, "http://127.0.0.1:40325/automation/modules");
        request.Content = new StringContent("""
            {
              "path": "/home/me/module.wasm"
            }
            """, Encoding.UTF8, "application/json");

        var response = await client.SendAsync(request);
        Console.WriteLine((int)response.StatusCode);
        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }
}
// build: c++ -std=c++17 main.cpp -lcurl
#include <curl/curl.h>

#include <iostream>
#include <string>

static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
    static_cast<std::string *>(userdata)->append(ptr, size * nmemb);
    return size * nmemb;
}

int main() {
    // Permanent JWT from Settings → Automation API.
    const std::string token = "YOUR_SECRET_TOKEN";

    curl_global_init(CURL_GLOBAL_DEFAULT);
    CURL *curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl_easy_init failed\n";
        curl_global_cleanup();
        return 1;
    }

    struct curl_slist *headers = nullptr;
    headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str());
    headers = curl_slist_append(headers, "Content-Type: application/json");

    const std::string body = R"json({
  "path": "/home/me/module.wasm"
})json";

    std::string response;
    curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:40325/automation/modules");
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast<long>(body.size()));
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    const CURLcode rc = curl_easy_perform(curl);
    if (rc != CURLE_OK) {
        std::cerr << curl_easy_strerror(rc) << "\n";
    } else {
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
        std::cout << status << "\n" << response << "\n";
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    curl_global_cleanup();
    return rc == CURLE_OK ? 0 : 1;
}
{
  "id": "text",
  "name": "text",
  "path": "text",
  "blocks": [
    {}
  ],
  "error": "text"
}
{
  "error": "text"
}
{
  "error": "text"
}

Remove module

DELhttp://127.0.0.1:40325/automation/modules/{id}

Projects that use the module stop working. Existing project definitions are not modified.

Authorizations
bearerAuth
Authorizationstringrequired

Permanent JWT from Settings → Automation API.

Path parameters
idstringrequired
Responses
200Removed.application/json
deletedbooleanoptional
idstringoptional
401Missing or invalid Bearer token.
404Module not found.application/json
errorstringrequired
501This build does not support automation.application/json
errorstringrequired
DEL/automation/modules/text
DELETE /automation/modules/text HTTP/1.1
Host: 127.0.0.1:40325
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
curl -X DELETE 'http://127.0.0.1:40325/automation/modules/text' \
  -H 'Authorization: Bearer YOUR_SECRET_TOKEN'
import requests

# Permanent JWT from Settings → Automation API.
token = "YOUR_SECRET_TOKEN"

url = "http://127.0.0.1:40325/automation/modules/text"
headers = {
    "Authorization": f"Bearer {token}",
}

response = requests.delete(url, headers=headers, timeout=30)
response.raise_for_status()
print(response.status_code)
print(response.text)
// Permanent JWT from Settings → Automation API.
const token = "YOUR_SECRET_TOKEN";

const url = "http://127.0.0.1:40325/automation/modules/text";

const response = await fetch(url, {
  method: "DELETE",
  headers: {
    "Authorization": `Bearer ${token}`,
  },
});

console.log(response.status);
console.log(await response.text());
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	// Permanent JWT from Settings → Automation API.
	token := "YOUR_SECRET_TOKEN"

	url := "http://127.0.0.1:40325/automation/modules/text"

	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	out, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
	fmt.Println(string(out))
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }, tokio = { version = "1", features = ["full"] }

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Permanent JWT from Settings → Automation API.
    let token = "YOUR_SECRET_TOKEN";

    let client = reqwest::Client::new();
    let response = client
        .delete("http://127.0.0.1:40325/automation/modules/text")
        .header("Authorization", format!("Bearer {token}"))
        .send()
        .await?;

    println!("{}", response.status());
    println!("{}", response.text().await?);
    Ok(())
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Permanent JWT from Settings → Automation API.
        var token = "YOUR_SECRET_TOKEN";

        using var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

        var request = new HttpRequestMessage(HttpMethod.Delete, "http://127.0.0.1:40325/automation/modules/text");

        var response = await client.SendAsync(request);
        Console.WriteLine((int)response.StatusCode);
        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }
}
// build: c++ -std=c++17 main.cpp -lcurl
#include <curl/curl.h>

#include <iostream>
#include <string>

static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
    static_cast<std::string *>(userdata)->append(ptr, size * nmemb);
    return size * nmemb;
}

int main() {
    // Permanent JWT from Settings → Automation API.
    const std::string token = "YOUR_SECRET_TOKEN";

    curl_global_init(CURL_GLOBAL_DEFAULT);
    CURL *curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl_easy_init failed\n";
        curl_global_cleanup();
        return 1;
    }

    struct curl_slist *headers = nullptr;
    headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str());

    std::string response;
    curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:40325/automation/modules/text");
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    const CURLcode rc = curl_easy_perform(curl);
    if (rc != CURLE_OK) {
        std::cerr << curl_easy_strerror(rc) << "\n";
    } else {
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
        std::cout << status << "\n" << response << "\n";
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    curl_global_cleanup();
    return rc == CURLE_OK ? 0 : 1;
}
{
  "deleted": true,
  "id": "text"
}
{
  "error": "text"
}
{
  "error": "text"
}

Last updated on

On this page