API Reference

Every OcaltQL script runs through one endpoint: POST https://ql.ocalt.com/api, authenticated with your identity/password and a q field containing the script. Full basics are on OcaltQL API & Client. This page goes further — every calling convention, and how to pass real request data through to a script running outside Site Mode.

identity is who you are. 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 Endpoint

Field Required Description
identityYesYour Ocalt account email
passwordYesYour Ocalt account password
qYesThe OcaltQL script to execute
getNoJSON-encoded object — populates !GET inside the script
postNoJSON-encoded object — populates !POST inside the script
cookieNoJSON-encoded object — populates !COOKIE inside the script
pathNoString — populates !REQUEST('path')
methodNoString — populates !REQUEST('method')

Calling the API — Every Language

curl
curl -X POST https://ql.ocalt.com/api \
  -d "identity=your_identity" \
  -d "password=your_api_key" \
  -d 'q=EMIT "Hello World"'
curl — Session
# -c saves the session cookie, -b sends it back.
curl -s -c /tmp/jar -X POST https://ql.ocalt.com/api \
  -d "identity=your_identity" -d "password=your_api_key" \
  --data-urlencode $'q=START SESSION\nAFTER SET "who" AS "Kea" OF !SESSION'

curl -s -b /tmp/jar -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\')'
JavaScript (Browser)
const fd = new URLSearchParams();
fd.append('identity', 'your_identity');
fd.append('password', 'your_api_key');
fd.append('q', 'EMIT "Hello World"');

// credentials:'include' lets the browser carry OQLSESSID across calls.
fetch('https://ql.ocalt.com/api', { method: 'POST', body: fd, credentials: 'include' })
  .then(res => res.text())
  .then(output => console.log(output));
JavaScript (Browser) — Session
// credentials:'include' carries OQLSESSID across calls automatically.
const call = (q) => fetch('https://ql.ocalt.com/api', {
  method: 'POST', credentials: 'include',
  body: new URLSearchParams({ identity: 'your_identity', password: 'your_api_key', q })
}).then(r => r.text());

await call('START SESSION\nAFTER SET "who" AS "Kea" OF !SESSION');
console.log(await call("START SESSION\nAFTER EMIT !SESSION('who')")); // Kea
JavaScript (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());
JavaScript (Node.js) — Session
// Node fetch doesn't persist cookies — capture Set-Cookie, resend it.
const API = 'https://ql.ocalt.com/api';
const creds = { identity: 'your_identity', password: 'your_api_key' };
const call = (q, cookie) => fetch(API, {
  method: 'POST', headers: cookie ? { cookie } : {},
  body: new URLSearchParams({ ...creds, q })
});

const r1 = await call('START SESSION\nAFTER SET "who" AS "Kea" OF !SESSION');
const jar = r1.headers.get('set-cookie')?.split(';')[0];
const r2 = await call("START SESSION\nAFTER EMIT !SESSION('who')", jar);
console.log(await r2.text()); // 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 — Session
<?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, CURLOPT_COOKIEFILE => $jar,
        CURLOPT_POSTFIELDS => http_build_query([
            'identity' => 'your_identity', 'password' => 'your_api_key', 'q' => $q,
        ]),
    ]);
    $out = curl_exec($ch); curl_close($ch); return $out;
}
ql("START SESSION\nAFTER SET \"who\" AS \"Kea\" OF !SESSION", $jar);
echo ql("START SESSION\nAFTER EMIT !SESSION('who')", $jar); // Kea
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)
Python — Session
import requests
s = requests.Session()   # persists OQLSESSID automatically
creds = {'identity': 'your_identity', 'password': 'your_api_key'}

s.post('https://ql.ocalt.com/api',
       data={**creds, 'q': 'START SESSION\nAFTER SET \"who\" AS \"Kea\" OF !SESSION'})
r = s.post('https://ql.ocalt.com/api',
       data={**creds, 'q': "START SESSION\nAFTER EMIT !SESSION('who')"})
print(r.text)  # Kea
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());
C# — Session
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 \"Kea\" OF !SESSION" }));
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))
Go — Session
jar, _ := cookiejar.New(nil)
client := &http.Client{Jar: jar}
api := "https://ql.ocalt.com/api"
creds := url.Values{"identity": {"your_identity"}, "password": {"your_api_key"}}

v1 := url.Values{"q": {"START SESSION\nAFTER SET \"who\" AS \"Kea\" OF !SESSION"}}
for k, x := range creds { v1[k] = x }
client.PostForm(api, v1)

v2 := url.Values{"q": {"START SESSION\nAFTER EMIT !SESSION('who')"}}
for k, x := range creds { v2[k] = x }
resp, _ := client.PostForm(api, v2)
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
Ruby — Session
require 'net/http'
uri = URI('https://ql.ocalt.com/api')
http = Net::HTTP.new(uri.host, uri.port); http.use_ssl = true
creds = { 'identity' => 'your_identity', 'password' => 'your_api_key' }

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

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, the credentialed API carries no GET, POST, or request data at all — documented as a real boundary on Session & Cookies and Header & Globals. That boundary describes the default: nothing supplied, nothing available. It is not an absolute technical wall — any caller can opt in by supplying the same extra fields Site Mode's own router already sends internally: get, post, cookie, path, method, each JSON-encoded. Supply them, and !GET/!POST/!REQUEST/!COOKIE read from them exactly as if the call had come through Site Mode.

The Script Being Called
EMIT "Name: " & !POST('name')
AFTER EMIT "Search: " & !GET('q')
AFTER EMIT "Session cookie: " & !COOKIE('session')
Node.js — Proxying a Real End-User's Request
// Inside your own Express route handler, forwarding the incoming request
const fd = new URLSearchParams();
fd.append('identity', 'your_identity');
fd.append('password', 'your_api_key');
fd.append('q', savedScript);
fd.append('get', JSON.stringify(req.query));
fd.append('post', JSON.stringify(req.body));
fd.append('cookie', JSON.stringify(req.cookies));
fd.append('path', req.path);
fd.append('method', req.method);

const res = await fetch('https://ql.ocalt.com/api', { method: 'POST', body: fd });
console.log(await res.text());
Python — Proxying a Flask Request
import requests, json

response = requests.post('https://ql.ocalt.com/api', data={
    'identity': 'your_identity',
    'password': 'your_api_key',
    'q':        saved_script,
    'get':      json.dumps(request.args.to_dict()),
    'post':     json.dumps(request.form.to_dict()),
    'cookie':   json.dumps(request.cookies.to_dict()),
    'path':     request.path,
    'method':   request.method,
})
print(response.text)
curl — Proxying a Request
curl -s -b /tmp/jar -X POST https://ql.ocalt.com/api \
  -d "identity=your_identity" -d "password=your_api_key" \
  --data-urlencode "q=$saved_script" \
  --data-urlencode 'get={"q":"search term"}' \
  --data-urlencode 'post={"name":"Kea"}' \
  --data-urlencode 'cookie={"session":"abc123"}' \
  --data-urlencode "path=/profile" \
  --data-urlencode "method=POST"
JavaScript (Browser) — Proxying a Request
// A browser proxies its own live request context into the script.
const fd = new URLSearchParams({
  identity: 'your_identity', password: 'your_api_key', q: savedScript,
  get:    JSON.stringify(Object.fromEntries(new URLSearchParams(location.search))),
  cookie: JSON.stringify(Object.fromEntries(
            document.cookie.split('; ').map(c => c.split('=')))),
  path:   location.pathname,
  method: 'GET',
});
const res = await fetch('https://ql.ocalt.com/api', {
  method: 'POST', body: fd, credentials: 'include' });
console.log(await res.text());
JavaScript (Node.js) — Proxying an Express Request
// Inside your Express route handler, forwarding the incoming request.
const fd = new URLSearchParams({
  identity: 'your_identity', password: 'your_api_key', q: savedScript,
  get:    JSON.stringify(req.query),
  post:   JSON.stringify(req.body),
  cookie: JSON.stringify(req.cookies),
  path:   req.path,
  method: req.method,
});
const res = await fetch('https://ql.ocalt.com/api', { method: 'POST', body: fd });
res.text().then(out => console.log(out));
PHP — Proxying a Request
<?php
// Forward the end-user's incoming request to a saved OcaltQL script.
$ch = curl_init('https://ql.ocalt.com/api');
curl_setopt_array($ch, [
    CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true,
    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);
C# — Proxying a Request
// Inside an ASP.NET controller, forwarding HttpContext.Request.
using var client = new HttpClient();
var payload = new FormUrlEncodedContent(new Dictionary<string,string> {
    ["identity"] = "your_identity",
    ["password"] = "your_api_key",
    ["q"]        = savedScript,
    ["get"]      = JsonSerializer.Serialize(Request.Query
                     .ToDictionary(k => k.Key, v => v.Value.ToString())),
    ["cookie"]   = JsonSerializer.Serialize(Request.Cookies
                     .ToDictionary(k => k.Key, v => v.Value)),
    ["path"]     = Request.Path,
    ["method"]   = Request.Method,
});
var res = await client.PostAsync("https://ql.ocalt.com/api", payload);
Console.WriteLine(await res.Content.ReadAsStringAsync());
Go — Proxying a Request
// Inside an http.HandlerFunc, forwarding the inbound *http.Request r.
getJSON, _ := json.Marshal(r.URL.Query())
resp, _ := http.PostForm("https://ql.ocalt.com/api", url.Values{
    "identity": {"your_identity"},
    "password": {"your_api_key"},
    "q":        {savedScript},
    "get":      {string(getJSON)},
    "path":     {r.URL.Path},
    "method":   {r.Method},
})
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
Ruby — Proxying a Rack Request
require 'net/http'
require 'json'
# Inside a Rack/Rails action, forwarding the incoming request.
uri = URI('https://ql.ocalt.com/api')
res = Net::HTTP.post_form(uri,
  'identity' => 'your_identity',
  'password' => 'your_api_key',
  'q'        => saved_script,
  'get'      => JSON.generate(request.GET),
  'post'     => JSON.generate(request.POST),
  'cookie'   => JSON.generate(request.cookies),
  'path'     => request.path,
  'method'   => request.request_method
)
puts res.body
This is opt-in, not automatic. A plain call with only identity/password/q still has empty !GET/!POST/!COOKIE, exactly as documented elsewhere. Supplying these extra fields is the caller's own explicit choice — typically a backend proxying real requests on behalf of its own end-users, one script execution per incoming request.