Icom CI-V Explorer

Searchable API reference for the Icom CI-V protocol across multiple radios.

Top-level radio info: default CI-V transceiver address and command count. Click a row for capabilities.

IDModelCI-V AddressCommandsCapabilities

Browse a radio's commands by code. Pick a radio to see its command codes, then click a code to drill into its sub-commands.

RadioCmdSub cmdDataDescription

Spotted a wrong or missing command or capability? Submit a correction for review. The API is read-only; this is the only write path and submissions never modify the reference data directly.

Download all submitted feedback (JSONL)

API Documentation

The full OpenAPI specification is at /openapi.json and an interactive Swagger UI at /docs. Point any OpenAPI-compatible client or AI agent at the spec — it is self-describing and includes every parameter constraint (hex patterns, length caps, enums).

Use this API from an AI agent

The easiest way to give an AI assistant or coding agent access to this reference is to feed it the OpenAPI spec. In your agent's prompt or tool configuration, point it at:

https://icom-civ.stevendodd.net/openapi.json

Your agent framework (or "create API skill" command) will read the spec and generate callable tools for each endpoint automatically — no manual schema authoring required. The spec carries the validation rules, so the agent can construct valid requests without guessing.

Workflow once the agent is connected

1
GET/radios

Discover — list the valid radio ids and CI-V addresses. Never guess a radio id.

2
GET/commands?q=...

Search — find commands by keyword or code. q is plain substring matching across cmd, sub_cmd, data, and description. Add radio_id to narrow.

3
GET/radios/{id}/capabilities

Drill down — confirm whether a feature is supported on a specific radio.

4
POST/feedback

Correct — submit a correction only when the user explicitly asks. Validate radio_id against /radios first.

Key rules for agents

  • radio_id is a lowercase short code (7300, 7300mk2), not the model name (IC-7300).
  • If q returns too many rows, narrow with radio_id or raise limit (max 500). Use offset to page through large result sets.
  • When reporting a command, include the radio_id, cmd, and sub_cmd so the user can verify.
  • The API is read-only except POST /feedback; feedback appends to a review queue and never mutates the live reference data.
If your coding harness doesn't read the OpenAPI spec, use this example skill definition

Paste this into your agent's skill configuration to give it CI-V reference capabilities without OpenAPI tool generation:

---
name: civ-reference
description: >
  Expert guidance for ICOM CI-V command reference. Queries the CI-V REST API
  (https://icom-civ.stevendodd.net) for authoritative command data. Advises on command
  construction, data encoding, sub-commands, and protocol details. Supports IC-705,
  IC-7100, IC-7300, IC-7300 MK2, IC-7610, IC-7760, and IC-9700. Load this skill for
  any question about CI-V commands, hex codes, data formats, or encoding for any
  supported Icom radio.
---

# CI-V Reference Skill

## Data Sources

### 1. Primary — CI-V REST API (authoritative, always up-to-date)

**Base URL:** `https://icom-civ.stevendodd.net`

A read-only HTTPS REST API serving the parsed CI-V command tables and capability
data for all supported radios. The API is the single source of truth — it is
maintained alongside the CSVs and reflects the latest corrections. Always query
the API first when network access is available.

Use `curl` (or `webfetch`) against these endpoints:

| Endpoint | Purpose |
|----------|---------|
| `GET /radios` | List all radios with default CI-V addresses and command counts |
| `GET /radios/{radio_id}` | Single radio summary (id, name, address, command_count) |
| `GET /radios/{radio_id}/capabilities` | Per-radio capability flags (satellite_mode, dv_mode, attenuator, …) |
| `GET /radios/{radio_id}/commands?q=<query>&limit=<n>&offset=<n>` | List/search commands for one radio |
| `GET /commands?radio_id=<id>&q=<query>&limit=<n>&offset=<n>` | Cross-radio command search |
| `POST /feedback` | Submit a correction for human review (see below) |
| `GET /health` | Service health check |

`radio_id` values: `705`, `7100`, `7300`, `7300mk2`, `7610`, `7760`, `9700`.

**Command object schema** (from `/commands` and `/radios/{id}/commands`):
```json
{
  "radio_id": "9700",
  "cmd": "16",
  "sub_cmd": "02",
  "data": "00 to 03",
  "description": "Send/read the Preamp/External Preamp ..."
}
```
- `cmd` — primary command hex (no `0x` prefix), e.g. `"16"`, `"1A"`
- `sub_cmd` — sub-command hex; may be empty `""` or a comma-separated multi-byte
  string like `"05,02"` (meaning sub-cmd bytes `05` then `02`). Empty string = no sub-command
- `data` — data payload description from the manual (free text, e.g. `"00 to 03"`)
- `description` — human-readable description of the command

**Capability object schema** (from `/radios/{id}/capabilities`):
```json
{
  "name": "satellite_mode",
  "label": "Satellite Mode",
  "description": "Satellite mode (split TX/RX bands with independent VFOs)",
  "command_evidence": "0x16 0x5A",
  "radios": {"705": false, "7100": false, ..., "9700": true}
}
```
- `name` — machine-readable capability name (snake_case, e.g. `"satellite_mode"`)
- `label` — human-friendly display label (Title Case, e.g. `"Satellite Mode"`)
The `radios` map values are booleans or descriptive strings (e.g. `"external (AH-705)`,
`"20 dB"`). When filtering for one radio, read the matching key.

**Pagination:** `limit` (1–500, default 100) and `offset` (default 0). The response
includes `total`, so iterate with offset if `total > limit`. Always check `total`
and page through for complete results.

**Search (`q`):** matches across `cmd`, `sub_cmd`, `data`, and `description`. It is a
substring/keyword search, not a structured lookup. For an exact command-byte
lookup, filter client-side on `cmd` + `sub_cmd` from a `q` query, or fetch the
radio's full command list (page through if needed) and match.

**Feedback / corrections:** the API is read-only; `POST /feedback` is the one write
path. Submissions are appended to a JSONL log for human review and never mutate the
reference data. Do not POST feedback autonomously — surface the discrepancy to the
user and let them decide (see "Handling Discrepancies" below).

### Supported Radios

| Radio | ID | CI-V Address | API radio_id | Notes |
|-------|-----|-------------|--------------|-------|
| IC-9700 | 9700 | 0xA2 | `9700` | VHF/UHF, satellite mode, DV/DD |
| IC-7610 | 7610 | 0x98 | `7610` | HF/50MHz, dual watch, band selector |
| IC-7300 | 7300 | 0x94 | `7300` | HF/50MHz, built-in tuner, APF |
| IC-7300 MK2 | 7300mk2 | 0xB6 | `7300mk2` | HF/50MHz, newer revision of 7300 |
| IC-705 | 705 | 0xA4 | `705` | HF/VHF/UHF portable, DV, WFM, GPS |
| IC-7100 | 7100 | 0x88 | `7100` | HF/VHF/UHF, DV/DD, WFM, GPS |
| IC-7760 | 7760 | 0xB2 | `7760` | HF/50MHz, similar to 7610 |

## CI-V Frame Structure

All CI-V frames follow this structure:

```
FE FE [from_addr] [to_addr] [cmd] [sub_cmd...] [data...] FD
```

- Preamble: `FE FE` (2 bytes)
- From address: Controller address (typically `E0` for serial, `E1` for UDP)
- To address: Radio address (`A2` for IC-9700, `98` for IC-7610, …)
- Command byte: From the API `cmd` field
- Sub-command bytes: From the API `sub_cmd` field (comma-separated → individual bytes; empty → none)
- Data bytes: From the API `data` field description (0 or more bytes)
- Postamble: `FD` (1 byte)

## Data Encoding Rules

### Frequencies
- Frequencies use **BCD encoding, little-endian** (2 digits per byte, LSB first).
- Example: 144.000000 MHz → `00 00 00 44 01` (5 bytes for IC-9700)

### Percentage Values (Power, SQL, Meter levels)
- Use **decimal-nibble encoding**: each nibble holds one decimal digit (0-9).
- 2-byte format: `0x0255` = 100%, `0x0128` = 50%, `0x0000` = 0%.
- Extract with `int(b.hex())`. **NOT** `int.from_bytes()` — that would interpret as binary.

### ON/OFF Values
- `00` = OFF, `01` = ON (single byte)

### Mode Encoding
- Command `0x06` (Set mode) uses specific byte values per mode
- See the IC-9700 manual for the complete mode table

## Handling Discrepancies

The API is the authoritative source, but it is not assumed to be 100% correct.
CI-V reference data is hand-transcribed from Icom manuals and may contain errors.
When this skill detects a discrepancy **do not silently assume the API is right.**

Instead:

1. **Surface the discrepancy explicitly** to the user. State what each source says:
   the API value, the conflicting source value (codebase/CSV/manual), and where each
   came from. Lead with the assumption that the API is the likely error.
2. **Do not auto-correct** implementation code or pick a "winner" on the skill's own
   judgement. CI-V byte errors cause silent data corruption or radio malfunction so a human must decide.
3. **Offer options** to the user, for example:
   - Treat the API as wrong (default when it conflicts with the codebase) and submit
     feedback to correct the API
   - Investigate further (consult the manual text in `icom manuals/`)
   - Trust the API value anyway and change the codebase (requires explicit approval)
4. **Use the `question` tool** when the choice materially affects the answer or any
   code change. Present the conflicting values as the options, with the
   "API is wrong / trust the codebase" option first and marked as recommended.
5. **Feedback submissions are user-driven only.** Never POST to `/feedback`
   autonomously. If the user wants to report an API error (the usual case for a
   codebase conflict), build the feedback payload and show it to them for
   confirmation before sending, or let them submit it themselves. The feedback
   schema is:
   ```json
   {
     "radio_id": "9700",
     "cmd": "1A",
     "sub_cmd": "05",
     "field": "data",
     "capability_name": "",
     "suggested_value": "00 to 03",
     "notes": "Manual says ...",
     "submitter": ""
   }
   ```
   Required: `radio_id`, `field`, `suggested_value`.

Treat API and CSV command-count mismatches, differing `sub_cmd` byte sequences,
differing `data` ranges, and missing/extra commands as discrepancies to confirm —
not as bugs to fix unilaterally.

## Expert Guidance Workflow

When using this skill to answer CI-V questions:

1. **Query the API first.** Use `curl` against the relevant endpoint:
   ```bash
   curl -s "https://icom-civ.stevendodd.net/radios/9700/commands?q=1A%2005&limit=50" | python3 -m json.tool
   curl -s "https://icom-civ.stevendodd.net/commands?q=satellite&limit=100" | python3 -m json.tool
   curl -s "https://icom-civ.stevendodd.net/radios/7610/capabilities" | python3 -m json.tool
   ```
   Note: `q` is a substring search. For an exact command-byte lookup, fetch the
   radio's commands with a broad `q` (or no `q`, paging through `limit`/`offset`)
   and filter client-side on `cmd`+`sub_cmd`.

2. **Always specify the radio.** The SET menu sub-commands (`0x1A 0x05`) are
   completely different between radios. Always pass the `radio_id` / `--radio`.

3. **Check capabilities.** Query `/radios/{id}/capabilities` to confirm a radio supports a feature before
   advising on its command bytes.

4. **Encoding matters.** Remind about decimal-nibble encoding for percentages, BCD
   for frequencies, and `byteorder="big"` for `int.from_bytes()` calls. These are
   the highest-risk areas. The API `data` field is a description (e.g. `"00 to 03"`),
   not the encoded bytes — apply the encoding rules to convert.

5. **Send vs Read.** The API does not currently tag direction. Distinguish
   manually: Read commands omit data bytes; the radio replies with the current
   value. Send (write) commands include the data bytes. The `data` field's phrasing
   often hints at this (`"Send/read the …"` = both; a bare range like `"00 to 03"`
   is the data payload for Send, and the value returned for Read).

6. **Discrepancies are findings, not fixes.** If API, CSV, implementation, or manual
   disagree on a command byte, sub-command, or data encoding, surface it to the user
   per "Handling Discrepancies" above. Do not auto-correct.

Endpoints

MethodPathDescription
GET/radiosList all supported radios with CI-V addresses and command counts.
GET/radios/{radio_id}Single radio info (id is a lowercase short code like 7300).
GET/radios/{radio_id}/capabilitiesCapability matrix for one radio.
GET/commands?radio_id=7300&q=frequency&limit=100&offset=0Search/filter commands. q matches cmd, sub_cmd, data, and description (case-insensitive).
GET/radios/{radio_id}/commands?q=vfoCommands for one radio, with optional search.
POST/feedbackSubmit a correction (JSON body). Only write path; appends to a review queue.
GET/feedbackDownload all submitted feedback as a JSONL file (one JSON object per line).
GET/healthHealth check.

Quick facts

Calling the API from code

curl

curl https://icom-civ.stevendodd.net/radios
curl "https://icom-civ.stevendodd.net/commands?radio_id=7300&q=frequency&limit=10"

Python

import requests

BASE = "https://icom-civ.stevendodd.net"

# List radios
for r in requests.get(f"{BASE}/radios").json():
    print(r["id"], r["name"], r["address"])

# Search the IC-7300 for frequency commands
hits = requests.get(f"{BASE}/commands", params={"radio_id": "7300", "q": "frequency"}).json()
for c in hits["items"]:
    print(c["cmd"], c["sub_cmd"], c["description"])

# Capabilities of the IC-7300
for cap in requests.get(f"{BASE}/radios/7300/capabilities").json():
    print(cap["name"], cap["radios"]["7300"])

Submitting feedback

curl -X POST https://icom-civ.stevendodd.net/feedback \
  -H "Content-Type: application/json" \
  -d '{"radio_id":"7300","cmd":"05","sub_cmd":"","field":"description",\
        "suggested_value":"Set operating frequency (corrected)",\
        "notes":"Manual p.19-9","submitter":"tester"}'