Navin Manager SDK and CLI
This guide shows how to call the API of the Navin Manager (navin-manager), which installs and operates the
infrastructure of the platform: hosts, clusters, services, high availability, backups, restores and disaster
recovery. It covers the manager client of each SDK and the enterprise-ai manager command line. The API is
described by navin-manager/docs/api/manager-openapi.json,
written from the server by tools/manager/openapi.py.
Version française : manager.fr.md
Overview
The manager clients are generated from the OpenAPI document by tools/manager/gen_sdks.py: one method per
operation under /api/v1, grouped by tag (269 operations in 31 groups), plus contract aliases such as
hosts.preflight, services.rollback, ha.test_failover, backups.create_plan, restores.execute and
dr.request_failover. A coverage test in each SDK fails as soon as an operation of the document has no method.
| SDK | Entry point | Generated table | Coverage test |
|---|---|---|---|
| Python | enterprise_ai.manager.ManagerClient, AsyncManagerClient | sdks/python/src/enterprise_ai/manager/_generated.py | sdks/python/tests/test_manager.py |
| TypeScript | ManagerClient of @enterprise-ai/sdk | sdks/typescript/packages/sdk/src/manager/generated/operations.ts | sdks/typescript/packages/sdk/test/manager.test.ts |
| Go | package manager of the Go SDK module | sdks/go/manager/operations_gen.go | sdks/go/manager/manager_test.go |
| Java | io.enterpriseai.manager.ManagerClient | sdks/java/enterprise-ai-java/src/main/java/io/enterpriseai/manager/ManagerGroups.java | the Java test suite |
The manager client is not the platform_manager sub-client of the Control Plane SDK: that one reaches the Platform
Manager space of the console through the Control Plane, for a platform_admin.
Requirements and compatibility
The manager client ships inside each SDK and has the same requirements: Python 3.10+, Node.js 18+, Go 1.26+, Java
17+ (SDK overview). It targets the manager API v1 (/api/v1). Install the SDK as described in its
guide; no extra package is needed.
Authentication and delegation
- Create a manager API token. In the manager console, Profile > API tokens, create a token with the
smallest permission list. The token looks like
nmt_<12 hex>_<secret>; every SDK refuses another format before any call. - Store the token in a file readable by its owner only (mode 0600) and give its path (
NM_MANAGER_TOKEN_FILE,token_file,tokenFile), or pass it from a secret store. No SDK and no CLI command prints it. - Act for a user when a service calls on behalf of a person. The
on_behalf_ofoption (onBehalfOfin TypeScript and Java,WithOnBehalfOfin Go) sendsX-NM-On-Behalf-Of: <user>. The manager accepts it only from a token whose explicit permission list holdsauth:delegate: the request then holds the intersection of the token and user permissions, and every audit event records the actor (the service token) plusdetails.on_behalf_ofanddetails.delegation. Any other caller gets HTTP 403on_behalf_of_forbidden; an unknown or disabled user gets 403on_behalf_of_unknown.
The Control Plane credentials (EAI_*) are never sent to the manager: the manager clients read only their own
settings.
Client configuration
| Setting | Python (ManagerConfig) | TypeScript (ManagerClientOptions) | Go (manager.With...) | Java (ManagerClient.Builder) | Environment variable |
|---|---|---|---|---|---|
| Manager URL (https, or http on localhost) | base_url: str | baseUrl: string | WithBaseURL(string) | baseUrl(String) | NM_MANAGER_URL |
| Token | token: str | None | token: string | () => string | Promise<string> | WithToken(string) | token(String) | NM_MANAGER_TOKEN |
| Token file | token_file: str | None | none | none | tokenFile(Path) | NM_MANAGER_TOKEN_FILE |
| CA bundle (PEM) | ca_bundle: str | None | runtime trust (NODE_EXTRA_CA_CERTS) | WithCABundle([]byte) | caBundle(Path) | NM_MANAGER_CA_BUNDLE |
| Delegation | on_behalf_of: str | None | onBehalfOf: string | WithOnBehalfOf(string) | onBehalfOf(String) | NM_MANAGER_ON_BEHALF_OF |
Locale (en, fr) | locale: str | None | locale | WithLocale(string) | locale(Locale) | NM_MANAGER_LOCALE |
| Timeout | timeout: float = 30.0 | timeoutMs (default 60000) | WithHTTPClient(...) | timeout(Duration) (30 s), connectTimeout(Duration) (10 s) | none |
| Retries | retries: int = 2 | retries (default 2) | WithRetries(int) (2), WithRetryBaseDelay (250 ms) | retries(int) (2), retryBaseDelay(Duration) (250 ms) | none |
| Extra headers | headers: dict[str, str] | headers | WithHeader(name, value) | header(name, value) | none |
| HTTP client | http_client= | fetch | WithHTTPClient(*http.Client) | httpClient(HttpClient) | none |
The environment variables are read by ManagerConfig.from_env() (Python, also used when no config is given) and
ManagerClient.Builder.fromEnv(System.getenv()) or ManagerClient.fromEnv() (Java). TypeScript and Go take the
values from your code.
Quickstart and resource groups
Python
from enterprise_ai.manager import ManagerClient
with ManagerClient("<manager-url>", token=TOKEN, ca_bundle=None, http_client=HTTP) as nm:
check = nm.hosts.preflight({"cluster_id": "c1", "target": {"host": "10.0.0.5"}, "private_key": "ref",
"fqdn": "<host-fqdn>", "expected_ip": "10.0.0.5"})
op = nm.services.action("c1", "kafka", {"action": "restart"})
status = nm.operations.get_operation(op["operation_id"])
for item in nm.operations.list_operations("c1", limit=50):
print(item["id"])
as_alice = nm.on_behalf_of("alice")
as_alice.restores.plan({"artifact_id": "a1", "environment": "staging"})
What happens: the client checks the token format, opens one connection pool, runs a preflight of a new host,
restarts a service, reads the resulting operation, walks every page of the operations of the cluster, then plans a
restore on behalf of alice. AsyncManagerClient has the same methods, each returning an awaitable.
nm.operation("<operationId>") returns the bound method of any operation id of the document.
TypeScript
import { ManagerClient, collect, paginate } from "@enterprise-ai/sdk";
const nm = new ManagerClient({ baseUrl: "<manager-url>", token, onBehalfOf: "alice" });
const op = await nm.services.action("c1", "kafka", { action: "restart" });
const ops = await collect(paginate((q) => nm.operations.listOperations("c1", q), { limit: 50 }));
Go
nm, err := manager.New(manager.WithBaseURL("<manager-url>"), manager.WithToken(token),
manager.WithCABundle(pem), manager.WithOnBehalfOf("alice"))
raw, err := nm.Services.Action(ctx, "c1", "kafka", map[string]any{"action": "restart"})
Java
ManagerClient nm = ManagerClient.builder()
.baseUrl("<manager-url>")
.tokenFile(Path.of("/run/secrets/manager.token"))
.caBundle(Path.of("/etc/pki/manager-ca.pem"))
.build();
JsonNode op = nm.services().action("c1", "kafka", Map.of("action", "restart"));
for (JsonNode item : nm.operations().listOperations("c1").autoPaging()) {
System.out.println(item.path("id").asText());
}
ManagerClient asAlice = nm.onBehalfOf("alice");
Resource groups
Every group exists in the four SDKs (Python nm.hosts, TypeScript nm.hosts, Go nm.Hosts, Java nm.hosts()).
The number is the count of operations of the document in the group.
| Group | Operations | Group | Operations | Group | Operations |
|---|---|---|---|---|---|
alerts | 10 | configs | 12 | node_security | 22 |
audit | 3 | connections | 7 | operations | 7 |
auth | 2 | consoles | 4 | pki | 3 |
auth_config | 11 | hosts | 13 | services | 22 |
availability | 6 | hub | 5 | settings | 2 |
backup | 31 | kubernetes | 19 | stack | 3 |
blueprint | 3 | lifecycle | 29 | system | 3 |
clusters | 10 | mcp | 12 | telemetry | 1 |
me | 3 | mcp_runtime | 4 | users | 10 |
meta | 1 | mirror | 2 | vault | 7 |
network | 2 |
The contract groups backups, restores, ha and dr hold only aliases of the operations above (for example
restores.plan, ha.test_failover). Every method, with its parameters and route, is in the generated reference:
Python, TypeScript,
Go, Java.
Errors
Errors keep the manager envelope {"error": {"code", "message", "message_i18n", "details"}} and are the typed
errors of each SDK: Python EaiError subclasses (messages holds message_i18n), TypeScript EaiError subclasses
(messageI18n), Go *eai.APIError (MessageI18n), Java EaiException subclasses. The codes specific to the
manager include on_behalf_of_forbidden, on_behalf_of_unknown (403) and the approval, lock and conflict codes of
the operations (409, 423, 428).
Retries and timeouts
The manager does not deduplicate POST calls by Idempotency-Key, so the clients never retry an operation that may
have run:
| SDK | Retried calls | Retried failures |
|---|---|---|
| Python | GET, PUT, DELETE; any method on HTTP 429 | network errors, 429, 500, 502, 503, 504 |
| TypeScript | GET, PUT, DELETE (other methods use noRetry) | network errors, timeouts, 429, 5xx |
| Go | GET, PUT, DELETE | network errors, 408, 429, 502, 503, 504 |
| Java | GET, PUT, DELETE | I/O errors, 408, 429, 502, 503, 504 |
Pagination and long operations
List operations take limit and cursor and return the {items, next_cursor} page of each SDK: Python Page,
TypeScript Page<Doc> (walk it with paginate and collect), Go json.RawMessage (with eai.WithLimit and
eai.WithCursor), Java Page<JsonNode> (autoPaging()). Actions on services, hosts and backups answer with an
operation: read it with operations.get_operation(id) (getOperation, GetOperation), and its tasks and logs with
the other methods of the operations group.
Exempt operations
The browser and MFA flows are not wrapped: they need the session cookie and the CSRF token of a person, never a
service token. The list with its reasons is EXEMPT (Python), MANAGER_EXEMPT (TypeScript), manager.Exempt (Go)
and ManagerOperations.EXEMPT (Java): auth/login, auth/logout, auth/setup, auth/password/change,
auth/oidc/{provider_id}/start and callback, auth/mfa/verify, auth/mfa/enroll/start and confirm,
auth/mfa/setup, auth/mfa/confirm, DELETE auth/mfa, POST me/tokens (a token cannot mint tokens) and
PATCH me (profile edited from the session).
Testing
Each manager client accepts an injected HTTP layer, exactly like the Control Plane client: Python
http_client=httpx.Client(transport=httpx.MockTransport(handler)), TypeScript fetch, Go WithHTTPClient with an
httptest server, Java httpClient or a local server. The examples of this page are run by
sdks/python/tests/test_manager.py against a mocked manager.
CLI reference
Configuration, highest priority first: options (--manager-url, --ca-bundle, --token-file, --on-behalf-of,
--manager-config), environment variables (NM_MANAGER_URL, NM_MANAGER_CA_BUNDLE, NM_MANAGER_TOKEN_FILE or
NM_MANAGER_TOKEN, NM_MANAGER_ON_BEHALF_OF, NM_MANAGER_CONFIG), then the JSON file
~/.config/enterprise-ai/manager.json with the keys url, ca_bundle, token_file, on_behalf_of, locale. A
token key in that file is refused (exit 7). --json prints the raw JSON response; --body FILE (JSON or YAML, -
for stdin) gives the request body, the options are merged on top. Help works offline: enterprise-ai manager --help.
| Command | Manager route |
|---|---|
host add --body F [--cluster C] | POST /api/v1/hosts/bootstrap/plan (plan of the addition, nothing executed) |
host preflight --body F | POST /api/v1/hosts/preflight |
host bootstrap --body F | POST /api/v1/hosts/bootstrap (strict host key, temporary SSH) |
host enroll --cluster C --fqdn N --fingerprint SHA256 [--ttl S] | POST /api/v1/hosts/enroll |
host list [--cluster C] | GET /api/v1/hosts |
host show H | GET /api/v1/hosts/{host_id} |
host revoke H [--reason R] | POST /api/v1/hosts/{host_id}/revoke |
host diagnose H | GET /api/v1/agents/nodes/{host_id}, GET /api/v1/hosts/{host_id}/inventory, GET /api/v1/hosts/{host_id}/preflight |
cluster import C --api-server URL --kubeconfig F [--namespace N] | POST /api/v1/clusters/{cluster_id}/kubernetes |
cluster list | GET /api/v1/clusters |
cluster nodes C | GET /api/v1/clusters/{cluster_id}/hosts |
cluster pools C | GET /api/v1/clusters/{cluster_id}/topology, hosts grouped by rack (fault domain); the manager has no node pool route |
service install C S [--body F] [--no-start] | POST /api/v1/clusters/{cluster_id}/services |
service configure C S --body F [--note N] | PUT /api/v1/clusters/{cluster_id}/services/{service}/config |
service start|stop|restart C S [--approval A] | POST /api/v1/clusters/{cluster_id}/services/{service}/actions |
service upgrade C S [--approval A] [--canary-host H] | POST /api/v1/clusters/{cluster_id}/services/{service}/upgrade |
service rollback C UPGRADE [--approval A] | POST /api/v1/clusters/{cluster_id}/upgrades/{upgrade_id}/rollback |
service status C S | GET /api/v1/clusters/{cluster_id}/services/{service} |
ha create --body F | POST /api/v1/ha-groups |
ha status G | GET /api/v1/ha-groups/{group_id} |
ha test-failover G | POST /api/v1/ha-groups/{group_id}/test-failover |
backup plan --body F | POST /api/v1/backup-plans |
backup run P --location L [--kind K] | POST /api/v1/backup-plans/{plan_id}/backups |
backup verify B | POST /api/v1/backups/{backup_id}/verify |
backup list | GET /api/v1/backups |
restore plan --artifact A --environment E | POST /api/v1/restores/plan |
restore approve P | POST /api/v1/restores/{plan_id}/approve (separation of duties enforced by the manager) |
restore execute P | POST /api/v1/restores/{plan_id}/execute |
restore verify P | GET /api/v1/restores/{plan_id} (state, approvers, verification) |
dr plan --body F | POST /api/v1/dr-plans |
dr exercise --plan P | POST /api/v1/dr-exercises |
dr failover-request --plan P | POST /api/v1/dr-failovers/request (request only: approval required) |
Exit codes: 0 success; 1 invalid input (local check, HTTP 400 or 422); 2 usage error; 3 authentication or permission refused (HTTP 401 or 403); 4 not found (HTTP 404); 5 conflict, lock or approval required (HTTP 409, 423 or 428); 6 server or network error; 7 configuration error.
Examples
These lines are run by sdks/python/tests/test_manager.py against a mocked manager.
export NM_MANAGER_URL=<manager-url> NM_MANAGER_TOKEN_FILE=~/.config/enterprise-ai/manager.token
enterprise-ai manager host preflight --cluster c1 --body host.json --json
enterprise-ai manager host bootstrap --cluster c1 --body host.json
enterprise-ai manager host enroll --cluster c1 --fqdn <host-fqdn> --fingerprint SHA256:abc
enterprise-ai manager host list --cluster c1
enterprise-ai manager host diagnose h1 --json
enterprise-ai manager host revoke h1 --reason "key compromised"
enterprise-ai manager cluster import c1 --api-server <api-server-url> --kubeconfig kubeconfig
enterprise-ai manager cluster pools c1 --json
enterprise-ai manager service install c1 kafka
enterprise-ai manager service configure c1 kafka --body kafka.json --note "retention 7 days"
enterprise-ai manager service restart c1 kafka
enterprise-ai manager service upgrade c1 kafka --approval ap1
enterprise-ai manager service rollback c1 up1 --approval ap2
enterprise-ai manager service status c1 kafka --json
enterprise-ai manager ha create --body ha.json
enterprise-ai manager ha test-failover g1
enterprise-ai manager backup plan --body backup-plan.json
enterprise-ai manager backup run p1 --location s3://backups/pg/2026-10-30
enterprise-ai manager backup verify b1
enterprise-ai manager backup list --json
enterprise-ai manager restore plan --artifact a1 --environment staging
enterprise-ai manager restore approve r1 --on-behalf-of alice
enterprise-ai manager restore execute r1
enterprise-ai manager restore verify r1 --json
enterprise-ai manager dr plan --body dr-plan.json
enterprise-ai manager dr exercise --plan d1
enterprise-ai manager dr failover-request --plan d1
Reference
- Generated reference of the manager clients: Python, TypeScript, Go, Java.
- CLI guide and CLI reference.
- SDK overview.