API concepts — registry, methods and kinds
This page explains how openremap.api is organised. It is developer-facing;
if you only need to call one method, its own page (e.g.
identify — API) is enough.
The registry — one source of truth
openremap.api.registry is the single source of truth for which methods
exist. Commands, composites and workflows all register here with metadata;
the transport (JSON-RPC) dispatches purely by name lookup and knows
nothing about the domain.
Each method carries:
{
"name": "identify", # the name you call it by
"kind": "command", # command | composite | workflow
"description": "Identify an ECU binary — manufacturer, family, confidence, VIN.",
"input_schema": {...}, # the typed parameter contract (below)
"steps": [...], # composites/workflows only: what they run
}
The important functions:
| Function | What it does |
|---|---|
register(name, kind=…, description=…, input_schema=…, steps=…) |
Decorator that adds a method to the registry |
dispatch(name, params) |
Look up, apply the schema (defaults + validation), run, return a JSON-safe dict |
call(name, params) |
In-process convenience wrapper around dispatch |
list_methods(kind=None) |
All method metadata (handlers excluded), sorted by name — the live catalogue |
get_method(name) |
One method's metadata |
unregister(name) |
Remove a method (used by tests/reloads) |
The input schema is the contract
Every method declares its parameters in its input_schema. Each parameter
spec can carry:
type—str,int,float,bool,bytes,dict,listrequired— bool; a required param with no value and no default is an errordefault— applied when the caller omits the parammin/max— numeric bounds, enforceddescription— what the parameter means
Defaults and validation live here, in the schema — not in the CLI or any client. The CLI, a GUI and an RPC client all read the same contract, so a call that validates in one place validates everywhere.
Tip
Run list_methods to see the live schema of every method — the
reference page mirrors the registry but may lag a release
behind. The registry is always the truth.
The three kinds
Methods come in three kinds:
kind |
Meaning | Defined as |
|---|---|---|
command |
Atomic — one cohesive operation, one domain | hand-written Python |
composite |
Composition — orchestrates independent operations or domains | hand-written Python |
workflow |
Declarative pipeline — linear, unconditional | a data file (TOML), no code |
When is a hand-written method a composite?
Its non-presentation core orchestrates multiple independent operations into one result. It qualifies if at least one of these holds:
- Cross-domain — it calls service functions from ≥ 2 distinct service domains (identify, maps, checksums, health, arch, recipes, entropy, convert).
- Command composition — it orchestrates ≥ 2 operations that are themselves exposed as separate API methods.
A method stays command (atomic) when its core is one cohesive
operation in one domain — even if it batches over a directory, has internal
sub-steps that are not exposed as separate methods, or uses
cross-cutting low-level primitives (entropy, convert/decode) as helpers.
The discriminator (quick test): "If I deleted this method, would its sub-parts still be independently useful — as separate methods, or as separate service domains?" — YES → composite; NO (the whole thing is one atomic "do X") → command.
Why composites are NOT workflows
Composites are hand-written because they have control flow (flags,
gates, fail-fast), cross-result coherence (identity ↔ checksum ↔ arch
agree), or one-pass efficiency that a linear declarative pipeline cannot
express. A workflow is reserved for simple, linear, unconditional
compositions — see the workflows page.
The classified set (verified 2026-09)
Composite (6):
| Method | What it composes |
|---|---|
analyze |
identify + VIN + layout + maps + checksums + health + coherence |
tune |
validate_before → patch → validate_after |
cook |
diff + map scan + annotation + region tags |
cook_volatile |
cook + volatile classification |
diff_maps |
scan ×2 + map matching + cell diff |
health |
identity + checksums + maps + layout + VIN |
Atomic (command): identify, scan, audit, merge, scan_maps,
convert, layout, scan_vins, routine, checksum, patch,
validate_before, validate_check, validate_after, plus the two staged
map-scan methods scan_map_axes / scan_map_tables, scan_classify, and
the meta commands ping / version / list_methods.
Note
audit and merge are atomic, not composite. Each is one service
function in the recipes domain (audit(), merge_recipes()) whose
sub-steps (provenance/fingerprint; validate-both/merge) are internal and
not separately exposed.
The current full catalogue with schemas is on the reference page.
Errors: typed in-process, coded over the wire
In-process callers get Python exceptions; RPC clients get JSON-RPC
error objects you can branch on by code. The API keeps these separate so
domain logic stays transport-agnostic.
| Code | Name | Meaning | Example message |
|---|---|---|---|
-32700 |
Parse error | The request line was not valid JSON | Parse error |
-32600 |
Invalid request | Not an object, or missing a method | Invalid request: missing method |
-32601 |
Method not found | The name is not registered | Unknown method: 'identifyx' |
-32602 |
Invalid params | Schema validation failed | identify: missing required param 'path' |
-32603 |
Internal error | Unexpected failure (never a traceback to the client) | Internal error |
-32000 |
Guard rejection | An expected business-rule rejection (empty file, bad image, non-unique anchors…) — not a bug | Binary file 'stock.bin' is empty. |
Python-side, these map to errors.ApiError subclasses:
UnknownMethodError (-32601), InvalidParamsError (-32602),
GuardError (-32000), InternalError (-32603).
Important
A guard rejection (-32000) is a clean, expected outcome — a file
that is empty or fails to decode, a recipe whose anchors are not unique,
a size mismatch. It is not an exception in the "something broke" sense;
clients should treat it as a normal business answer.
list_methods output shape
import openremap.api as api
catalogue = api.call("list_methods", {})
for m in catalogue["methods"]:
print(m["name"], m["kind"], m["input_schema"])
Each entry looks like:
{
"name": "identify",
"kind": "command",
"description": "Identify an ECU binary — manufacturer, family, confidence, VIN.",
"input_schema": {
"path": {"type": "str", "required": true, "description": "Path to the binary."}
},
"steps": []
}
See also
- Reference — the full method catalogue with schemas
- Workflows — the declarative
kind="workflow"pipelines - Transport (JSON-RPC) — how errors travel over the wire
- CLI, API & RPC — the three surfaces for humans