OcaltQL API & Client

OcaltQL scripts are executed via a single HTTP endpoint. The OcaltQL Client extends this by letting you run scripts on remote machines using the DIRECTIVE verb.

The identity field is your account identifier. Your Ocalt username or the email address on your account — either one works, they identify the same namespace. It is not a display name and not something you choose per request; it is the account whose files, databases, machines and quota the script will run against.
The password field takes your API key. Not your account password. The key is on your account dashboard — the field is still named password so existing clients keep working, but the value should be the key. It authenticates identically, carries no sign-in rights, and rotating it invalidates the old one immediately without touching the password you sign in with. A leaked key costs one rotation; a leaked password costs the account.

The API

Every OcaltQL script is sent as a POST request to:

POST https://ql.ocalt.com/api

The request body is application/x-www-form-urlencoded — standard form POST. The response body is raw output — exactly what your script emits, nothing more.

Request Format

POST https://ql.ocalt.com/api
Content-Type: application/x-www-form-urlencoded

identity=your_identity&password=your_api_key&q=EMIT+"Hello+World"
Every request requires credentials. There is no credential-free path. Browser requests are no exception — see the Browser JavaScript section below for why this matters.

Response

The API returns raw output. No JSON wrapper. No envelope. Whatever your script emits is what comes back in the response body. Errors return as plain text in the body alongside the appropriate HTTP status code.

Example
EMIT "Hello World"
Response
Hello World

Connecting from your application

curl

Example
curl -X POST https://ql.ocalt.com/api \
  -d "identity=your_identity" \
  -d "password=your_api_key" \
  -d 'q=EMIT "Hello World"'
curl — Session + request data
# Call 1 saves the session cookie to a jar; call 2 sends it back.
curl -s -c /tmp/oqljar -X POST https://ql.ocalt.com/api \
  -d "identity=your_identity" -d "password=your_api_key" \
  --data-urlencode 'get={"q":"search"}' \
  --data-urlencode 'post={"name":"Kea"}' \
  --data-urlencode $'q=START SESSION\nAFTER SET "who" AS !POST(\'name\') OF !SESSION'

curl -s -b /tmp/oqljar -X POST https://ql.ocalt.com/api \
  -d "identity=your_identity" -d "password=your_api_key" \
  --data-urlencode $'q=START SESSION\nAFTER EMIT !SESSION(\'who\')'

Browser JavaScript

Testing only — not safe for production. Any credential embedded in client-side JavaScript is visible to anyone who views page source or opens the browser’s dev tools, regardless of the domain it runs on. Use this pattern for local testing — on localhost, or even a plain unhosted HTML file — never on a public production site. For production, call this API from your own backend (see the PHP, Python, Node.js, and C# examples below), where the credential never reaches the browser.
Example
const fd = new URLSearchParams();
fd.append('identity', 'your_identity');
fd.append('password', 'your_api_key');
fd.append('q', 'EMIT "Hello World"');

fetch('https://ql.ocalt.com/api', {
  method: 'POST',
  body: fd
})
.then(res => res.text())
.then(output => console.log(output));
Browser JavaScript — Session + request data
// credentials:'include' makes the browser carry OQLSESSID across calls,
// exactly as the Ocalt console does. Request data goes in the get/post fields.
async function ql(q, extra = {}) {
  const fd = new URLSearchParams({
    identity: 'your_identity',
    password: 'your_api_key',
    q, ...extra
  });
  const res = await fetch('https://ql.ocalt.com/api', {
    method: 'POST', body: fd, credentials: 'include'
  });
  return res.text();
}

await ql('START SESSION\nAFTER SET "who" AS !POST(\'name\') OF !SESSION',
         { post: JSON.stringify({ name: 'Kea' }) });
console.log(await ql("START SESSION\nAFTER EMIT !SESSION('who')"));  // Kea

PHP

Example
$response = file_get_contents('https://ql.ocalt.com/api', false, stream_context_create([
  'http' => [
    'method'  => 'POST',
    'header'  => 'Content-Type: application/x-www-form-urlencoded',
    'content' => http_build_query([
      'identity' => 'your_identity',
      'password' => 'your_api_key',
      'q'        => 'EMIT "Hello World"'
    ])
  ]
]));
echo $response;
PHP — Session (cURL, carries OQLSESSID)
<?php
$jar = tempnam(sys_get_temp_dir(), 'oql');

function ql($q, $jar) {
    $ch = curl_init('https://ql.ocalt.com/api');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_COOKIEJAR      => $jar,   // save the session cookie
        CURLOPT_COOKIEFILE     => $jar,   // send it back on the next call
        CURLOPT_POSTFIELDS     => http_build_query([
            'identity' => 'your_identity',
            'password' => 'your_api_key',
            'q'        => $q,
        ]),
    ]);
    $out = curl_exec($ch);
    curl_close($ch);
    return $out;
}

// Call 1 — start a session and write a value into it.
ql("START SESSION\nAFTER SET \"who\" AS \"Kea\" OF !SESSION", $jar);

// Call 2 — same jar, so the session persists and the value reads back.
echo ql("START SESSION\nAFTER EMIT !SESSION('who')", $jar);   // Kea
PHP — Proxying a real request (session + !GET / !POST / !COOKIE)
<?php
// Inside your own backend, forwarding the end-user's incoming request to a
// saved OcaltQL script — session cookie carried, request data passed through.
$jar = $_SESSION['oql_jar'] ??= tempnam(sys_get_temp_dir(), 'oql');

$ch = curl_init('https://ql.ocalt.com/api');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_COOKIEJAR      => $jar,
    CURLOPT_COOKIEFILE     => $jar,
    CURLOPT_POSTFIELDS     => http_build_query([
        'identity' => 'your_identity',
        'password' => 'your_api_key',
        'q'        => $savedScript,
        'get'      => json_encode($_GET),
        'post'     => json_encode($_POST),
        'cookie'   => json_encode($_COOKIE),
        'path'     => $_SERVER['REQUEST_URI'] ?? '/',
        'method'   => $_SERVER['REQUEST_METHOD'] ?? 'GET',
    ]),
]);
echo curl_exec($ch);
curl_close($ch);

Python

Example
import requests

response = requests.post('https://ql.ocalt.com/api', data={
    'identity': 'your_identity',
    'password': 'your_api_key',
    'q':        'EMIT "Hello World"'
})
print(response.text)
Python — Session + request data
import requests, json

# requests.Session() persists OQLSESSID automatically across calls.
s = requests.Session()
creds = {'identity': 'your_identity', 'password': 'your_api_key'}

s.post('https://ql.ocalt.com/api', data={
    **creds,
    'q':    'START SESSION\nAFTER SET "who" AS !POST(\'name\') OF !SESSION',
    'post': json.dumps({'name': 'Kea'}),
})

r = s.post('https://ql.ocalt.com/api', data={
    **creds,
    'q': "START SESSION\nAFTER EMIT !SESSION('who')",
})
print(r.text)  # Kea

Node.js

Example
const fd = new URLSearchParams({
  identity: 'your_identity',
  password: 'your_api_key',
  q:        'EMIT "Hello World"'
});
const res = await fetch('https://ql.ocalt.com/api', {
  method: 'POST',
  body: fd
});
console.log(await res.text());
Node.js — Session + request data
// Node fetch does not persist cookies on its own — capture Set-Cookie
// from call 1 and send it back on call 2.
const API = 'https://ql.ocalt.com/api';
const creds = { identity: 'your_identity', password: 'your_api_key' };

async function call(fields, cookie) {
  const res = await fetch(API, {
    method: 'POST',
    headers: cookie ? { cookie } : {},
    body: new URLSearchParams({ ...creds, ...fields }),
  });
  return { body: await res.text(), setCookie: res.headers.get('set-cookie') };
}

const first = await call({
  q:    'START SESSION\nAFTER SET "who" AS !POST(\'name\') OF !SESSION',
  post: JSON.stringify({ name: 'Kea' }),
});
const jar = first.setCookie?.split(';')[0];  // OQLSESSID=...

const second = await call({ q: "START SESSION\nAFTER EMIT !SESSION('who')" }, jar);
console.log(second.body);  // Kea

ASP.NET (C#)

Example
using var client = new HttpClient();
var payload = new FormUrlEncodedContent(new[] {
    new KeyValuePair<string,string>("identity", "your_identity"),
    new KeyValuePair<string,string>("password", "your_api_key"),
    new KeyValuePair<string,string>("q", "EMIT \"Hello World\"")
});
var response = await client.PostAsync("https://ql.ocalt.com/api", payload);
Console.WriteLine(await response.Content.ReadAsStringAsync());
C# — Session + request data
// A CookieContainer on the handler carries OQLSESSID across calls.
using System.Net;

var handler = new HttpClientHandler { CookieContainer = new CookieContainer() };
using var client = new HttpClient(handler);
var creds = new Dictionary<string,string> {
    ["identity"] = "your_identity", ["password"] = "your_api_key"
};

await client.PostAsync("https://ql.ocalt.com/api", new FormUrlEncodedContent(
    new Dictionary<string,string>(creds) {
        ["q"]    = "START SESSION\nAFTER SET \"who\" AS !POST('name') OF !SESSION",
        ["post"] = "{\"name\":\"Kea\"}",
    }));

var res = await client.PostAsync("https://ql.ocalt.com/api", new FormUrlEncodedContent(
    new Dictionary<string,string>(creds) {
        ["q"] = "START SESSION\nAFTER EMIT !SESSION('who')",
    }));
Console.WriteLine(await res.Content.ReadAsStringAsync());  // Kea

Go

Example
resp, _ := http.PostForm("https://ql.ocalt.com/api", url.Values{
    "identity": {"your_identity"},
    "password": {"your_api_key"},
    "q":        {`EMIT "Hello World"`},
})
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
Go — Session + request data
// A cookiejar on the client carries OQLSESSID across calls.
jar, _ := cookiejar.New(nil)
client := &http.Client{Jar: jar}
api := "https://ql.ocalt.com/api"

client.PostForm(api, url.Values{
    "identity": {"your_identity"}, "password": {"your_api_key"},
    "q":    {"START SESSION\nAFTER SET \"who\" AS !POST('name') OF !SESSION"},
    "post": {`{"name":"Kea"}`},
})

resp, _ := client.PostForm(api, url.Values{
    "identity": {"your_identity"}, "password": {"your_api_key"},
    "q": {"START SESSION\nAFTER EMIT !SESSION('who')"},
})
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))  // Kea

Ruby

Example
require 'net/http'

uri = URI('https://ql.ocalt.com/api')
res = Net::HTTP.post_form(uri,
  'identity' => 'your_identity',
  'password' => 'your_api_key',
  'q'        => 'EMIT "Hello World"'
)
puts res.body
Ruby — Session + request data
require 'net/http'
require 'json'

uri = URI('https://ql.ocalt.com/api')
creds = { 'identity' => 'your_identity', 'password' => 'your_api_key' }

http = Net::HTTP.new(uri.host, uri.port); http.use_ssl = true

r1 = http.post(uri.path, URI.encode_www_form(creds.merge(
  'q'    => %Q(START SESSION\nAFTER SET "who" AS !POST('name') OF !SESSION),
  'post' => JSON.generate({ 'name' => 'Kea' }),
)))
cookie = r1['set-cookie']&.split(';')&.first   # OQLSESSID=...

r2 = http.post(uri.path, URI.encode_www_form(creds.merge(
  'q' => "START SESSION\nAFTER EMIT !SESSION('who')",
)), { 'Cookie' => cookie })
puts r2.body   # Kea

Passing Request Data Through the API

By default a credentialed API call carries no !GET, !POST, !COOKIE, or request metadata — a plain identity/password/q call sees them all empty. Any caller can opt in by supplying extra fields alongside the script: get, post, cookie (each JSON-encoded), plus path and method. Supply them and !GET/!POST/!COOKIE/!REQUEST read from them inside the script exactly as if the call had arrived through Site Mode. The per-language Session + request data examples above each show this in practice; the API Reference documents every field.

The Script Being Called
EMIT "Name: " & !POST('name')
AFTER EMIT "Search: " & !GET('q')
AFTER EMIT "Session cookie: " & !COOKIE('session')

OcaltQL Client

The OcaltQL Client is a lightweight cross-platform application. Install it on any machine, provide your Ocalt credentials and a machine alias, and that machine becomes addressable from any OcaltQL script via the DIRECTIVE verb.

Setup

On first launch the client asks for:

DIRECTIVE

DIRECTIVE sends commands to a registered remote machine. The agent executes shell commands directly — there is no OPEN/CLOSE block form.

Example
DIRECTIVE "machineid" EXEC "echo Hello from the remote machine" SET ?result
AFTER EMIT ?result

Download

OS Download
Windows ocaltql-client.exe
macOS Not yet available
Linux — Debian / Ubuntu ocaltql-client.deb
Linux — RedHat / Fedora ocaltql-client.rpm
Linux — Universal ocaltql-client.AppImage
FreeBSD ocaltql-client-freebsd
Android ocaltql-client.apk
ChromeOS Enable the Linux environment in ChromeOS settings, then install the .deb
The Android build is the full client. It runs the agent as a foreground service and opens the console beside it, so a phone is both a machine a script can address and somewhere to drive your namespace from. Screen capture, input, tunnels, file transfer and local serving all work the same as on desktop.

Installing an unsigned build

The client is shipped unsigned. Code-signing certificates are issued per-vendor and tie a binary to a commercial identity; the agent is deliberately distributed without one, so every operating system will warn you the first time you run it. The warnings are about the absence of a certificate, not about the file. Each is cleared in a few seconds.

Windows — SmartScreen

Windows Defender SmartScreen blocks unrecognised applications with a blue dialog reading “Windows protected your PC”. There is no Run button visible — it is behind a link:

  1. Click More info in the dialog.
  2. Click Run anyway, which appears once More info is expanded.

If the browser blocked the download itself rather than the launch, open the browser’s downloads list and choose Keep on the file. Where the file has already been saved, right-click it, choose Properties, tick Unblock at the bottom of the General tab, and click OK — that clears the mark-of-the-web and SmartScreen will not ask again.

Linux — making the binary executable

Downloading a file does not make it runnable. The .deb and .rpm packages set their own permissions when installed, but the universal AppImage and the raw binaries have to be marked executable first:

AppImage
chmod +x ocaltql-client.AppImage
./ocaltql-client.AppImage
Package installs
sudo dpkg -i ocaltql-client.deb        # Debian, Ubuntu
sudo rpm -i ocaltql-client.rpm        # RedHat, Fedora
chmod +x adds the execute bit to a file you own. Without it the shell reports Permission denied even though the file downloaded correctly. The same applies to the FreeBSD binary: chmod +x ocaltql-client-freebsd before running it.

macOS — Gatekeeper

macOS refuses to open an unsigned application on first launch, reporting that it cannot be checked for malicious software. Open System Settings → Privacy & Security, scroll to the Security section, and click Open Anyway beside the blocked application. Confirm at the prompt. Subsequent launches open normally.