MFX Hub
Getting started

Your first terminal

Generated from mfx-kb/guides/your-first-terminal.md·rebuilt on every release

A terminal is one the terminal session that MFX Hub runs for you: it logs in to your broker with your credential, and everything else — quotes, account state, orders — happens through it. This guide takes you from an API key to a terminal that is ACTIVE, and then retires it.

Before you start

  • An API key: a key id and the private key shown once when the key was issued. MFX Hub keeps only the public half, so a lost private key is rotated, never recovered.
  • The ids of the farm, broker and server you were given.
  • Your broker login and password. They travel once, over TLS, straight into the platform’s vault; no API ever returns them.

The five steps

  1. Create a client. Every request is signed with your private key (Ed25519); you never write the headers yourself.
  2. Request the terminal. The answer is immediate and the state is REQUESTED: placing it on a host is asynchronous. An idempotency_key is required — retry with the same key and you get the same terminal back, never a second one.
  3. Submit the credential. It is stored by reference; the terminal can log in from now on.
  4. Wait for ACTIVE. It passes through PROVISIONING (capacity is preparing it) and LOGGING_IN (the broker is answering). conditions say why if it stalls — never guess from the state alone.
  5. Retire it when you are done. It drains (DRAINING) and then it is RETIRED.

The whole flow is one runnable file: samples/first_terminal.py.

What each state means to you

State What is happening What you do
REQUESTED Accepted; waiting for capacity with room Nothing
PROVISIONING A host is preparing the terminal Submit the credential if you have not
LOGGING_IN The terminal is logging in to your broker Nothing; read conditions if it takes long
ACTIVE Logged in and healthy Use it
OFFLINE The host running your terminal is down Nothing — MFX brings it back or moves it
DRAINING Stopping, at your request or for maintenance Nothing
RETIRED Gone; its slot is free Request a new one if you need it
FAILED The broker refused the login, or it did not finish in time Read conditions, then submit the credential again

The runnable sample: samples/first_terminal.py

Executed in CI against MFX Hub on every change to the knowledge base.

"""Your first terminal: request it, give it a credential, wait for ACTIVE, retire it.

The runnable half of guides/your-first-terminal.md, against the SDK as it shipped.

It was written against the surface *proposed* for  and drifted from the one that landed: a
`SigningKey.from_base64` that does not exist, the required `server` missing from
`submit_credentials`, and `conditions` read as a plain mapping. All three were type errors, and none
could fail the samples job, which only checks that each file parses.

    MFX_KEY_ID=... MFX_PRIVATE_KEY=... MFX_FARM_ID=... MFX_BROKER_ID=... MFX_SERVER_ID=... \\
    MFX_SERVER=... MFX_LOGIN=... python samples/first_terminal.py

The broker password is typed at a prompt. It is never an argument, an environment variable or a
file, so it reaches no shell history, no process list and no CI log.
"""

from __future__ import annotations

import asyncio
import getpass
import os
import uuid

from mfx import MfxClient, SigningKey

BASE_URL = os.environ.get("MFX_BASE_URL", "https://api.dev.mfxhub.com/v1")
SETTLED = {"ACTIVE", "FAILED", "RETIRED"}


async def main() -> None:
    client = MfxClient(
        BASE_URL,
        os.environ["MFX_KEY_ID"],
        SigningKey(os.environ["MFX_PRIVATE_KEY"]),
    )

    # The same key with the same body returns the same terminal, never a second one.
    terminal = await client.terminals.create(
        farm_id=os.environ["MFX_FARM_ID"],
        broker_id=os.environ["MFX_BROKER_ID"],
        server_id=os.environ["MFX_SERVER_ID"],
        streaming=True,
        idempotency_key=f"first-terminal-{uuid.uuid4()}",
    )
    print(terminal.id, terminal.state)  # REQUESTED

    await client.terminals.submit_credentials(
        terminal.id,
        login=int(os.environ["MFX_LOGIN"]),
        password=getpass.getpass("Broker password: "),
        server=os.environ["MFX_SERVER"],
        idempotency_key=f"first-credential-{uuid.uuid4()}",
    )

    while terminal.state not in SETTLED:
        await asyncio.sleep(5)
        terminal = await client.terminals.get(terminal.id)
        seen = terminal.conditions.to_dict() if terminal.conditions else {}
        print(terminal.state, {name: c.get("status", "?") for name, c in seen.items()})

    if terminal.state == "ACTIVE":
        await client.terminals.retire(terminal.id, idempotency_key=f"retire-{uuid.uuid4()}")


if __name__ == "__main__":
    asyncio.run(main())