AI Agent Guide
The Hamtrax CLI is built to be operated by AI agents as a first-class consumer, not just a humans-at-keyboards afterthought. This guide covers the three things an agent needs: how to inject credentials without an interactive prompt, how to consume structured output, and how to interpret exit codes deterministically. It ends with two sample LLM prompts you can use as system messages.
Why this matters
When a user asks an LLM to "log my POTA contact for K-1234," the LLM needs to issue real shell commands against a real API on the user's behalf. To do that safely, the integration surface must be:
- Stateless from the agent's perspective -- credentials come in via env var, not an interactive prompt the agent can't drive.
- Machine-readable -- both successful output and errors must be parseable without regex hacks.
- Stable -- exit codes and JSON shapes don't change across patch releases. SemVer applies.
The CLI has a small command surface, so you can paste hamtrax help --all into the agent's context and it can construct valid commands from that text alone. That's the bar.
Env-var auth
Set HAMTRAX_API_KEY before invoking the CLI. The CLI checks env first, then keychain, then the config file.
export HAMTRAX_API_KEY=htx_live_xxxxxxxxxxxxxxxxxxxxxxxx
hamtrax whoami --json
This is the only auth mechanism that works in headless, non-interactive contexts (CI, Docker, agent runtimes). See Auth and Keys → Resolution order.
In agent runtimes, store the key in your secret manager and inject it into the subprocess environment for each tool invocation -- never write it to disk.
--json output
Data commands support --json: whoami, auth status, folders list/show, contacts list/create/delete, and activations list/create. Auth storage commands such as auth login, auth set-key, auth logout, and auth panic-revoke are human-readable in 0.1.1.
- Mutations return a single object.
createreturns{ id, ... };deletereturns{ id, success: true }. Examples:
{ "id": "qso_92b1f4d2e8a0" }
{ "id": "fld_8a4f0c2b9e1d", "name": "N0CALL@K-1234-20260507", "autoFolderKey": "activity:v1:activation:pota:N0CALL%40K-1234-20260507", "created": true }
{ "id": "qso_92b1f4d2e8a0", "success": true }
Treat autoFolderKey as an opaque, versioned storage identity. It may use a
released legacy form for an adopted older folder; use name for display and do
not parse or construct the key yourself.
- Reads that return a list use
{ items, cursor }. Thecursorfield is omitted when there's no next page.
{
"items": [
{ "id": "fld_8a4f0c2b9e1d", "name": "N0CALL@K-1234-20260507" },
{ "id": "fld_2c1a3d4e5f60", "name": "N0CALL@K-7777-20260423" }
],
"cursor": "fld_2c1a3d4e5f60"
}
- Errors (any non-zero exit with
--json) write a CLI envelope to stderr. API errors include the server fields when present, but the CLI also addscommandandexitCode:
{
"command": "contacts delete",
"error": "tier_insufficient",
"message": "Requires 'elevated' tier.",
"exitCode": 5,
"status": 403,
"details": { "required": "elevated" },
"requestId": "f9c0a1d4-..."
}
--ndjson output
For list commands, --ndjson emits one JSON object per line with no wrapping object. This is the right choice when an agent is streaming a long list into a transformation pipeline.
hamtrax contacts list --folder fld_8a4f0c2b9e1d --ndjson
# {"id":"qso_92b1...","callsign":"W1AW","mode":"FT8",...}
# {"id":"qso_aa12...","callsign":"K5ZD","mode":"SSB",...}
NDJSON is list-only; use --json for mutations.
Exit-code table
Errors map to deterministic exit codes. An agent should branch on the exit code, not parse the error message string.
| Exit | Server error code | Meaning | Recovery |
|---|---|---|---|
0 | -- | Success | Continue. |
1 | -- | Generic / uncategorized failure | Retry once; surface to the user if it persists. |
2 | validation_error | Invalid flags/input or server folder/program hygiene rejection | Re-read help and the error. Ask the user to correct the destination or input; do NOT switch folders or retry blindly. |
3 | unauthorized | Missing, malformed, or expired key | Prompt the user for a new key. Do NOT retry. |
3 | key_revoked | Key was revoked server-side | Prompt the user for a fresh key from the web app. Do NOT retry. |
4 | rate_limited | Burst limit or sustained logbook safety budget | Inspect details.reason. Wait Retry-After for a resettable response; if it is lifetime_logbook_budget, preserve the command, stop, and surface the required support review. |
5 | tier_insufficient | Operation requires elevated; configured key is basic | Surface to the user; suggest generating an elevated key. |
6 | qso_cap_reached | Free-tier 200-QSO native cap hit | Surface the cap message and direct the user to their account page. Do NOT retry. |
7 | -- | Network / transport error (DNS, TLS, timeout) | Retry with exponential backoff up to 3 attempts; surface if all fail. |
8 | internal | Server-side 5xx | Retry once after a short delay; surface if it persists. |
Additional server error codes that map to specific exits:
| Server code | Maps to exit | Notes |
|---|---|---|
validation_error | 2 | Also returned when the destination is a container, the UTC month does not match, or automatic-folder program/reference identity is unsupported, ambiguous, or malformed. |
not_found | 1 | Treat as "doesn't exist or not yours" -- both collapse to 404 by design. |
The CLI also writes errors to stderr in a stable line format, suitable for log scraping when JSON isn't appropriate:
hamtrax: contacts delete: tier_insufficient: Requires 'elevated' tier.
Sample LLM prompts
These two prompts are battle-tested starting points. Drop them into your agent's system message, set HAMTRAX_API_KEY in the agent's environment, and you have a working integration.
Prompt 1: One-shot logger
You are a logging assistant for the Hamtrax CLI. The user will describe a
contact they just made; you will log it.
Tools available:
- shell: run a single command, return stdout, stderr, and exit code.
Rules:
1. Always call commands with --json. Parse the JSON; do NOT guess output shape.
2. If the user does not specify a folder, find the in-progress activation:
hamtrax activations list --in-progress --json
If there is exactly one, use it. If there are zero, ASK the user before
creating an activation. If there are more than one, ASK which one.
3. Required fields for `contacts create`: --folder, --callsign, --frequency
(MHz, decimal), --mode. --time-on is required server-side but the CLI
defaults it to "now" when omitted. In a valid POTA activation folder the
server auto-populates your own POTA fields (mySig, mySigInfo) once the
program/reference guard passes; sending values that contradict the folder is
rejected, never silently rewritten. Everywhere else activity identity is
yours to send: POST /v1/contacts accepts sig, sigInfo, mySig, and mySigInfo.
Filing into a monthly hunting folder, send the worked station's sig/sigInfo
-- a contact with no program identity is filed by date alone and duplicate
detection will not see it. Root/category folders reject contacts, and
monthly folders require the contact's UTC month.
4. Exit-code handling:
- 0: success. Confirm the contact by callsign, UTC date/time, band, and mode. Keep the internal id hidden unless the user explicitly asks for it.
- 3: tell the user the API key is invalid; do NOT retry.
- 5: tell the user this requires an elevated key; do NOT retry.
- 6: tell the user they hit the free-tier 200-QSO cap; do NOT retry.
- 4: inspect `details.reason`; retry once after `Retry-After` only when the response supplies it. For `lifetime_logbook_budget`, preserve the command and stop for support review.
- 2: surface the validation message and ASK for corrected input or folder;
never silently choose another destination or retry unchanged.
- any other non-zero: surface the message; do NOT retry blindly.
5. NEVER reveal HAMTRAX_API_KEY in any output to the user.
6. Be aware: if the operator has QRZ Auto-Push enabled in Hamtrax, every
contact you create is mirrored to their public QRZ logbook automatically,
edits update the QRZ copy, and deleting a contact removes it from QRZ too.
Prompt 2: Activation runner
You are an activation copilot. The user is portable at a POTA site and will
dictate contacts as they work them. You log each one.
Workflow:
1. At the start of the session, ensure the activation folder exists:
hamtrax activations create --reference $REFERENCE --callsign $MY_CALL --json
This is idempotent -- safe to call again. Capture the .id field.
2. For each contact:
hamtrax contacts create --folder <id> --callsign X --frequency F --mode M --json
Confirm the contact by callsign, UTC date/time, band, and mode. Keep the internal id hidden unless the user explicitly asks for it.
If exit 2 reports folder/program/reference hygiene, stop and ask the user;
never move the QSO to another folder automatically.
3. Every 10 QSOs, run `hamtrax whoami --json` and tell the user their
nativeQsoCount. Free-tier cap is 200 -- warn at 180.
4. If exit 6 (qso_cap_reached) is returned, STOP logging and tell the
user to subscribe at https://hamtrax.com/account.
Reference: hamtrax help --all is available; consult it for any flag you
are unsure about. Do not invent flags that aren't in the help output.
Versioning contract
- The CLI is independently versioned (SemVer) from the API (
/v1/). - A patch release will not change exit codes or JSON shapes.
- A minor release may add new commands, flags, or output fields, but never remove or rename existing ones.
- A breaking change requires a major release AND an
/v2/API. hamtrax --help-jsonincludes bothcli_versionandapi_versionso an agent can pin its understanding to a specific release.
Related pages
- Command Reference -- every flag
- Auth and Keys -- env-var resolution order
- Examples -- worked end-to-end flows
- Troubleshooting -- common failure modes
- Security -- threat model and key hygiene