Calling it
In process
import openremap.api as api
result = api.call("identify", {"path": "original.bin"})
print(result["match_key"], result["software_version"])
identify returns 24 keys; the ones you will reach for first:
{
"manufacturer": "Bosch",
"match_key": "EDC17C66::1037541778126241V0",
"ecu_family": "EDC17",
"ecu_variant": "EDC17C66",
"software_version": "1037541778126241V0",
"hardware_number": None,
"detection_strength": "strong",
"detection_evidence": ["EXCLUSION_CLEAR", "DETECTION_SIGNATURE"],
"confidence": {...},
"vin": None,
"sha256": "00f727e8abf62d384acc4420b08fe8e5477f9d004c8d3a697bbaaa08fe2149f5",
"file_size": 4194304,
}
Fields a family does not store are None, not "" — test for None.
Direct functions
Two methods are also plain functions, without the dispatch overhead:
api.ping() # {'ok': True}
api.version() # {'version': '0.9.0', 'backend': 'rust'}
For everything else, api.call(name, params) is the interface. Importing a
command module directly (from openremap.api.commands.identify import ...) works
but bypasses the schema check, so don't.
JSON-RPC over stdio
openremap-api is a line-delimited JSON-RPC 2.0 server on stdin/stdout:
- one JSON object per line, one response per request;
- no batching (an array is rejected);
- optional
"jsonrpc": "2.0"— it is echoed back if present; "id"is echoed verbatim; a request without one gets"id": null;- diagnostics go to stderr, so stdout stays parseable.
Request:
{"jsonrpc": "2.0", "id": 1, "method": "version", "params": {}}
Response:
{"id": 1, "result": {"version": "0.9.0", "backend": "rust"}}
The server runs until stdin closes:
[openremap-api] INFO openremap-api started (pid=9)
[openremap-api] INFO stdin closed — shutting down.
Errors over the wire
Three bad lines in, three error objects out — the process stays alive:
{"jsonrpc":"2.0","id":3,"method":"no_such_method","params":{}}
{"jsonrpc":"2.0","id":4,"method":"identify","params":{}}
not json
{"id": 3, "error": {"code": -32601, "message": "Unknown method: 'no_such_method'"}}
{"id": 4, "error": {"code": -32602, "message": "identify: missing required param 'path'"}}
{"id": null, "error": {"code": -32700, "message": "Parse error"}}
A client in ten lines
import json
import subprocess
class Engine:
def __init__(self, cmd=("openremap-api",)):
self.proc = subprocess.Popen(cmd, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, text=True, bufsize=1)
self._id = 0
def call(self, method, **params):
self._id += 1
self.proc.stdin.write(json.dumps(
{"jsonrpc": "2.0", "id": self._id, "method": method, "params": params}) + "\n")
self.proc.stdin.flush()
reply = json.loads(self.proc.stdout.readline())
if "error" in reply:
raise RuntimeError(f"{reply['error']['code']}: {reply['error']['message']}")
return reply["result"]
engine = Engine()
print(engine.call("identify", path="original.bin")["ecu_family"])
One process, many calls: the model is load and keep, not spawn per call.
Binary payloads
No method takes or returns raw bytes across the wire. Where a binary comes out —
apply_strict produces a patched file — the result carries base64:
applied = api.call("apply_strict", {"path": "original.bin", "recipe_path": "stage1.remap"})
applied["applied"] # True
applied["patched_sha256"] # 'b737d34463cc9d25…'
len(applied["patched_b64"]) # 5592408 characters ≈ 4 MB file
import base64
open("patched.bin", "wb").write(base64.b64decode(applied["patched_b64"]))
Writing the file is the caller's job — the API never writes to a path you did not
name. A refusal returns applied: False and patched_b64: None.
Threads and responsiveness
The CPU-heavy work happens in the native extension, and every native entry point
releases the GIL for the duration (py.detach). Consequences for callers:
- a 4 MB
scan_map_axesholds the interpreter for ~46 ms of wall time, not 6.4 s; - other Python threads keep running while a scan is in progress;
- an event loop stays responsive if you offload the call with
run_in_executor, but even a plain call no longer freezes unrelated threads.
Memory: a 4 MB analysis is single-pass over the bytes; results are dicts of plain values, not views into the buffer.
When to use which
| You are… | Use |
|---|---|
| writing Python in the same process | api.call |
| integrating from another language | openremap-api over stdio |
| scripting a shell pipeline | openremap CLI (it is the same methods) |
| building a UI | the CLI's methods, or the API — the TUI itself calls the API |