How the API session works
What a conversation with the processor looks like, what to keep at each step, and what to do with a refusal.
Transport
Section titled “Transport”The compute_contract_cli processor starts with the --session flag. As its first line, it reports readiness: one JSON line on standard output. After that, the conversation runs line by line — you write one JSON request line to its input, and it answers with one JSON line on output. Requests are processed strictly in order: the next one is read only after the previous one’s response, so you can’t send them in a batch and match responses by number.
The conversation isn’t always one-directional. The processor has no TLS of its own, and for an operation that needs the network over https or a connection to Postgres or SQL Server, it writes you a counter-question — a line with a kind field and its own id — and stops until it gets a response with the same id. You’re the one who has to reach the network, answer, and watch the clock: the deadlineMs in its line is a deadline for you, not a limit the core gives up at on its own. Don’t answer, and it will wait indefinitely; close the input stream, and the operation ends in a refusal. This only happens inside such an operation — exactly when you’re already waiting for its response anyway; an ordinary workbook edit never asks a counter-question.
What kinds of counter-lines there are
Section titled “What kinds of counter-lines there are”There are three conversations in all, and every one starts with a line from the core and ends with your line carrying the same id.
| The core writes | What it’s asking for | You respond |
|---|---|---|
transport-request |
A single https request: the address, method, headers, deadlineMs |
transport-response with status, headers, and the body in bodyBase64 — or transport-error with the value timeout, tls, offline, or connection-reset |
socket-open, socket-send, socket-recv, socket-close, socket-cert-hash |
A long byte stream to a database: open, send, receive, close, show the certificate fingerprint | socket-opened with the channel number, socket-sent, socket-data (with an eof flag), socket-closed, socket-cert — or socket-error with the same list of causes |
credential-proof |
A proof of the database password: the core never holds the password itself or anything derived from it | credential-proof-response with proofBase64 and serverSignatureBase64 — or credential-proof-error |
The core reaches out over http on its own: only https is delegated outward.
const child = spawn(binary, ["--session"], { stdio: ["pipe", "pipe", "inherit"],});
child.stdin.write(JSON.stringify(request) + "\n");Lifecycle
Section titled “Lifecycle”| Step | What to keep | Why |
|---|---|---|
open_workbook_subset |
payload.subsetId |
Identifies the workbook in this process |
open_workbook_subset |
payload.activeSheet.path |
The sheet key: that’s what edit operations expect, not the visible name |
apply_cell_edits |
payload.workbookHistory.revision |
The new accepted revision |
| every response | ok / code / payload | Every refusal has its own code; you must never show success on ok: false |
save_workbook_subset |
destinationUri |
The full address of the saved file, starting with file:// |
What to do with a refusal
Section titled “What to do with a refusal”if (!response.ok) { throw new Error(response.code + ": " + JSON.stringify(response));}