Top-level radio info: default CI-V transceiver address and command count. Click a row for capabilities.
| ID | Model | CI-V Address | Commands | Capabilities |
|---|
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.
| Radio | Cmd | Sub cmd | Data | Description |
|---|
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
/radiosDiscover — list the valid radio ids and CI-V addresses. Never guess a radio id.
/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.
/radios/{id}/capabilitiesDrill down — confirm whether a feature is supported on a specific radio.
/feedbackCorrect — submit a correction only when the user explicitly asks. Validate radio_id against /radios first.
Key rules for agents
radio_idis a lowercase short code (7300,7300mk2), not the model name (IC-7300).- If
qreturns too many rows, narrow withradio_idor raiselimit(max 500). Useoffsetto page through large result sets. - When reporting a command, include the
radio_id,cmd, andsub_cmdso 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
| Method | Path | Description |
|---|---|---|
| GET | /radios | List 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}/capabilities | Capability matrix for one radio. |
| GET | /commands?radio_id=7300&q=frequency&limit=100&offset=0 | Search/filter commands. q matches cmd, sub_cmd, data, and description (case-insensitive). |
| GET | /radios/{radio_id}/commands?q=vfo | Commands for one radio, with optional search. |
| POST | /feedback | Submit a correction (JSON body). Only write path; appends to a review queue. |
| GET | /feedback | Download all submitted feedback as a JSONL file (one JSON object per line). |
| GET | /health | Health check. |
Quick facts
- Radio ids are lowercase short codes (
7300,7300mk2). The model name (IC-7300) is not accepted as an id. qis case-insensitive substring matching — not a query language.- Feedback
cmd/sub_cmdmust be 1-4 hex chars; free-text fields are length-capped (see the OpenAPI schema for exact limits). - All responses are JSON;
limit(1-500) andoffset(>=0) paginate list endpoints.
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"}'