Reference

HTTP API

Post rows to Evlat from any language: the socket, fields and answers.

evlat signal is a thin client over a local endpoint. Anything that speaks HTTP over a unix socket can post rows directly.

Endpoint

Socket~/.config/evlat/run/evlat.sock
RequestPOST /signal, with Host: 127.0.0.1
BodyJSON, up to 1 MiB

Evlat opens no network port for this: every request goes through the socket. The socket’s folder can be entered by you alone, so no key is needed. The examples still write http://127.0.0.1:48151/signal, but only to fill the Host header; nothing connects to that port. The Host must be 127.0.0.1, localhost or [::1], and a request that carries an Origin header is refused, so web pages cannot reach it.

curl

curl 7.40 or newer, for --unix-socket:

curl -s --unix-socket "$HOME/.config/evlat/run/evlat.sock" \
  http://127.0.0.1:48151/signal -H 'Content-Type: application/json' \
  -d '{"id":"render","ttl":900,"phase":"working","label":"Render","progress":0.4}'

Python

import http.client, json, os, socket

SOCK = os.path.expanduser("~/.config/evlat/run/evlat.sock")

class EvlatConnection(http.client.HTTPConnection):
    def __init__(self):
        super().__init__("127.0.0.1", timeout=2)

    def connect(self):
        self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        self.sock.settimeout(self.timeout)
        self.sock.connect(SOCK)

def signal(**row):
    try:
        conn = EvlatConnection()
        conn.request("POST", "/signal", json.dumps(row), {"Content-Type": "application/json"})
        conn.getresponse().read()
        conn.close()
    except OSError:
        pass  # Evlat isn't running: carry on

signal(id="train", ttl=900, phase="working", label="Training", progress=0.25)

Node

import { request } from 'node:http';
import { homedir } from 'node:os';

const socketPath = `${homedir()}/.config/evlat/run/evlat.sock`;

export function signal(row) {
  return new Promise((resolve) => {
    const req = request({
      socketPath, path: '/signal', method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      timeout: 2000,
    }, (res) => { res.resume(); res.on('end', resolve); });
    req.on('timeout', () => req.destroy());
    req.on('error', resolve); // Evlat isn't running: carry on
    req.end(JSON.stringify(row));
  });
}

await signal({ id: 'export', ttl: 900, phase: 'working', label: 'Export', progress: 0.5 });

Fields

FieldRules
idRequired. Letters, digits, . _ -; 1–64 characters.
ttlRequired. Whole seconds, 0–86400. 0 removes the row and nothing else is read. It is how long a working or waiting row lives; a done or failed row stays until it has been seen, up to 12 hours.
phaseRequired unless ttl is 0: working, waiting, done or failed.
progressOptional number from 0 to 1.
labelOptional, up to 80 characters. Defaults to the id.
detailOptional, up to 200 characters.
senderOptional, up to 24 characters.

Longer text is cut, not refused. Line breaks and tabs become spaces. null counts as absent. Each request replaces the whole row, as with evlat signal.

Answers

StatusMeans
200 {}Taken.
400A field is wrong. error.code names it: invalidId, invalidTtl, invalidPhase, invalidProgress, invalidLabel, invalidDetail, invalidSender. A body that isn’t a JSON object is badRequest.
403A request from a browser (it has an Origin header), or a Host that isn’t 127.0.0.1, localhost or [::1].
404No such route: check the path, or the Evlat that answers is older than this page.
{"error":{"code":"invalidProgress","message":"progress must be a number from 0 to 1"}}

An X-Evlat-Key header, which older versions asked for, is ignored.

Another socket

If Evlat was started with EVLAT_SOCKET, post to that path instead; with EVLAT_HOME, the socket is under that home. evlat signal and evlat watch follow the same variables. See Limits & environment.

The sandbox port

While Watch sandboxes is on, Evlat also listens on 127.0.0.1:48152, for the hooks of Claude Code in Docker sandboxes. It takes hooks only: /signal answers 404 there, as do the routes that wait for an answer on a card. Post your rows to the socket.