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.
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.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"
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.
EMIT "Hello World"
Hello World
Connecting from your application
curl
curl -X POST https://ql.ocalt.com/api \
-d "identity=your_identity" \
-d "password=your_api_key" \
-d 'q=EMIT "Hello World"'
# 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
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.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));
// 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
$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
$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
// 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
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)
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
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 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#)
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());
// 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
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))
// 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
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
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.
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:
- Your Ocalt credentials — username, email, or phone and password
- A machine alias — the ID used to target this machine in DIRECTIVE
DIRECTIVE
DIRECTIVE sends commands to a registered remote machine. The agent executes shell commands directly — there is no OPEN/CLOSE block form.
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 |
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:
- Click More info in the dialog.
- 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:
chmod +x ocaltql-client.AppImage
./ocaltql-client.AppImage
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.