# pysim-otaman-server — HTTP API reference The server exposes a JSON HTTP API under `/api/*`. All responses carry `Access-Control-Allow-Origin: *` (plus `Access-Control-Allow-Private-Network: true` on the preflight), so the API is reachable from a separately-hosted PWA. ## Version compatibility | Server | PWA (OTAMan) | Status | |--------|-------------|--------| | 1.x.x | 1.x.x | ✅ Compatible | | 0.x.x | 1.x.x | ❌ Outdated — update server | | 2.x.x+ | 1.x.x | ⚠️ Server newer — update PWA | The server reports its version via `GET /api/version`. The PWA checks this on connect and warns if versions are incompatible. ## Endpoints | Endpoint | Method | Purpose | |----------|--------|---------| | `/api/version` | GET | Server version string | | `/api/status` | GET | Card reader + card info + current selection | | `/api/command` | POST | pySim command (equip, status, tree, etc.) | | `/api/commands` | GET | List available pySim commands | | `/api/tree` | POST | File tree browser for given FID/name | | `/api/select` | POST | Select a file by name or FID | | `/api/read` | POST | Read file content | | `/api/write` | POST | Write raw hex data to a file | | `/api/apdu` | POST | Raw APDU send | | `/api/help` | POST | pySim help for a given command | | `/api/send-ota` | POST | SCP80 OTA secured packet delivery | | `/api/ram-install` | POST | Install a Java Card `.cap` file via SCP80 (INSTALL[for load] → LOAD ×N → INSTALL[for install]) | | `/api/sp-verify` | POST | Verify secured packet against pySim reference | | `/api/menu` | GET | Current STK menu (title + items + active) | | `/api/menu-select` | POST | ENVELOPE(Menu Selection) with item_id | | `/api/menu-respond` | POST | TERMINAL RESPONSE for paused STK command | | `/api/stk-status` | GET | STK session state (active/pending/type) | | `/api/events` | GET | Event list from SET UP EVENT LIST | | `/api/event-send` | POST | Send ENVELOPE(Event Download) | | `/api/proactive-log` | GET | Last 50 proactive commands | | `/api/status-poll` | POST | Manual STATUS poll + FETCH if 91XX | | `/api/rescue` | POST | Re-send TERMINAL PROFILE to recover CAT session | | `/api/poll-status` | GET | Background STATUS polling state | | `/api/poll-toggle` | POST | Enable/disable background polling | | `/api/pli-qualifiers` | GET | List of qualifier codes with descriptions | | `/api/pli-dict` | GET | Current dictionary (hex values per qualifier) | | `/api/pli-dict` | POST | Update dictionary entries | ## Endpoint details ### `GET /api/version` Returns server version for compatibility checking. **Example response:** ```json {"version": "1.9.28"} ``` ### `GET /api/status` Card reader, card type, current selection, and card state. ### `GET /api/commands` List all available shell commands for the current card profile. ### `POST /api/command` Execute any pysim-shell command. ```json {"cmd": "select MF"} ``` Returns: ```json {"output": "..."} ``` ### `POST /api/apdu` Send a raw APDU to the card. ```json {"apdu": "00A4040000..."} ``` Returns: ```json {"response": "...", "sw": "9000"} ``` ### `POST /api/help` Get structured help for a shell command. ```json {"cmd": "apdu"} ``` Returns: ```json {"usage": "apdu [-h] [--expect-sw EXPECT_SW] [--raw] APDU", "description": "...", "args": [{"name": "APDU", "type": "positional", "help": "..."}]} ``` ### `POST /api/send-ota` Send an OTA command (SCP80) to the card via SMS-PP-DOWNLOAD ENVELOPE. The secured packet is delivered in an SMS-DELIVER TPDU wrapped in an ENVELOPE command. **Request body:** ```json { "sp": "00201516011515b00000...", "spi1": "16", "spi2": "01", "kic": "15", "kid": "15", "tar": "b00000", "cntr": "0000000001", "kicKey": "D6FCC023...", "kidKey": "1B07E7E0..." } ``` **Response (delivery PoR):** ```json {"success": true, "sw": "9000", "response_data": "027100000e0a...", "por": {"response_status": "por_ok", "tar": "B00000", "pcntr": 0, "decoded": {"number_of_commands": 1, "last_status_word": "6e00", "last_response_data": ""}}} ``` **Response (submit PoR):** PoR is extracted from the SMS-SUBMIT TPDU fetched via a proactive command (FETCH). The response contains the same `por` structure if decoding succeeds. The SPI2 `por_in_submit` bit (0x20) selects submit-mode PoR. ### `POST /api/ram-install` Install a Java Card `.cap` file on the card via GlobalPlatform commands (INSTALL[for load] → LOAD ×N → INSTALL[for install (+ make selectable)]) wrapped in SCP80 secured packets. Each step is sent via ENVELOPE and its PoR is checked; the sequence aborts on the first PoR error. The `.cap` archive (a ZIP of nested components) is parsed server-side in `_cap_parse`; no external tooling is required. **Request body:** ```json { "cap_hex": "DECAFFED...", "sd_aid": "A000000003000000", "install_params": "C90000", "stk_params": "", "nv_quota": 0, "volatile_quota": 0, "make_selectable": true, "spi1": "0E", "spi2": "01", "kic": "15", "kid": "15", "tar": "000000", "cntr": "0000000001", "kicKey": "D6FCC023...", "kidKey": "1B07E7E0..." } ``` | Field | Req | Description | |---|---|---| | `cap_hex` | yes | Even-length hex of the `.cap` file (zipped Java Card CAP), max 48 kB (98304 hex chars) | | `sd_aid` | no | Security Domain AID for INSTALL[for load]; empty → default ISD `A000000003000000` | | `install_params` | no | Hex C9 TLV install parameters; if empty, `gen_install_parameters()` is used with the quota/stk params | | `stk_params` | no | Hex CA TLV (TS 102 226 §8.2.1.3.2.1) for SIM toolkit app-specific params | | `nv_quota` / `volatile_quota` | no | Integer memory quotas (bytes) for `gen_install_parameters()` | | `make_selectable` | no | If true (default), final INSTALL uses P1=`0C` (install + make selectable) | **Response (success):** ```json {"success": true, "failed_step": null, "steps": [{"name": "install_for_load", "apdu": "80E60200...", "por_status": "por_ok", "sw": "9000"}, {"name": "load_0", "apdu": "80E80000...", "por_status": "por_ok", "sw": "9000"}, {"name": "install_for_install", "apdu": "80E60C00...", "por_status": "por_ok", "sw": "9000"}], "final_cntr": "0000000004", "load_file_aid": "A000000003000000", "module_aid": "A000000003000000", "application_aid": "A000000003000000"} ``` **Response (failure):** ```json {"success": false, "failed_step": "load_1", "steps": [{"name": "install_for_load", "por_status": "por_ok", "sw": "9000"}, {"name": "load_1", "por_status": "rc_error", "sw": null}], "error": "..."} ``` The `steps` array contains one entry per GP command. `final_cntr` is the counter value after all successful steps (use it to update the card preset). The response is not streamed — all steps run server-side before the JSON is returned. ### `POST /api/sp-verify` Cross-check a secured packet against pySim's `OtaDialectSms.encode_cmd` reference. Returns the JS-generated packet, pySim reference, a match flag, and the decoded SPI fields. ```json {"spi1": "16", "spi2": "01", "kic": "15", "kid": "15", "tar": "b00000", "cntr": "0000000001", "apdu": "00a40000023f00", "kicKey": "D6FCC023...", "kidKey": "1B07E7E0..."} ``` **Response:** ```json {"js_sp": "...", "py_sp": "...", "match": true, "diffs": [], "spi": {"counter": "counter_must_be_higher", ...}} ``` ### `GET /api/menu` Returns the SIM Toolkit SETUP MENU captured from the card's TERMINAL PROFILE response at startup. Empty `{"items": []}` if the card didn't send a menu. **Response:** ```json {"command_number": 1, "items": [{"id": 128, "text": "Настройки/Settings"}], "title": "Alfa Mobile", "active": false} ``` ### `POST /api/menu-select` Sends an `ENVELOPE(MENU SELECTION)` with the selected item ID, then handles the card's proactive response (DISPLAY TEXT or SELECT ITEM). ```json {"item_id": 128} ``` **Response:** ```json {"type": "display_text", "text": "Hello", "sw": "9122"} ``` or ```json {"type": "select_item", "items": [{"id": 1, "text": "Sub-menu"}], "sw": "9122"} ``` ### `POST /api/menu-respond` Sends `TERMINAL RESPONSE` to the current proactive command with the given result code. Continues the proactive chain if the card responds with `91XX`. If no response arrives within `--menu-timeout` seconds (default 60, `0` disables), the server watchdog sends the `timeout` result itself. ```json {"result": "ok", "item_id": 1} ``` | `result` | TERMINAL RESPONSE code | Meaning | |---|---|---| | `ok` | `0x00` | Command performed successfully | | `cancel` | `0x10` | Proactive session terminated by the user | | `back` | `0x11` | Backward move in the proactive session requested by the user | | `timeout` | `0x12` | No response from the user | ### `GET /api/stk-status` Returns the current STK session state. ```json {"active": true, "pending": true, "pending_type": "select_item"} ``` ### `POST /api/read` Read file content. Auto-detects transparent vs record files. ```json {"name": "EF.ICCID", "fid": "2FE2", "parent_sel": "3F00", "mode": "raw"} ``` Returns transparent data: ```json {"success": true, "sw": "9000", "file_type": "transparent", "data": "..."} ``` Returns records: ```json {"success": true, "sw": "9000", "file_type": "linear_fixed", "records": [{"num": 1, "data": "..."}, {"num": 2, "data": "..."}]} ``` ### `POST /api/write` Write raw hex data to a file. ```json {"name": "EF.ICCID", "fid": "2FE2", "data": "A0A1A2...", "parent_sel": "3F00"} ``` For record files: ```json {"name": "EF.ADN", "fid": "6F3A", "data": "A0A1...", "record_nr": 1, "parent_sel": "7F10"} ``` Returns: ```json {"success": true, "sw": "9000"} ``` ### `POST /api/select` Select a file by name or FID, with optional parent selection. ```json {"name": "EF.ICCID", "fid": "2FE2", "parent_sel": "3F00"} ``` Returns: ```json {"name": "EF.ICCID", "fid": "2FE2", "file_type": "transparent", "file_size": 10, "record_len": null, "num_of_rec": null, "fci_hex": "621082024021...", "exists": true} ``` `fci_hex` is the raw FCP template (`'62'`) from the SELECT response, used by the PWA's Exact FCI checks; `file_size` / `record_len` / `num_of_rec` drive the profiler's size and record checks. When the file does not exist the endpoint responds `404` with `{"error": "...", "exists": false}`. ### `POST /api/tree` Get directory listing with typed children. ```json {"name": "MF", "fid": "3F00"} ``` Returns: ```json {"exists": true, "name": "MF", "fid": "3F00", "file_type": "df", "children": [{"name": "EF.ICCID", "fid": "2fe2", "isDir": false}]} ``` ### `GET /api/events` Returns the event list captured from the card's SET UP EVENT LIST (an array of event byte values, or `[]` when none was received). ### `POST /api/event-send` Sends an `ENVELOPE(Event Download)` for a subscribed event. ```json {"event_type": 4, "event_data": "01A0"} ``` `event_type` is required (the SET UP EVENT LIST event byte); `event_data` is optional hex for events that carry data. Returns the SW and any response data: ```json {"sw": "9000", "data": "..."} ``` ### `GET /api/proactive-log` Returns the last 50 proactive commands fetched during CAT sessions, newest first: ```json [{"type_hex": "25", "type_name": "SET UP MENU", "elapsed": 3.2, "bytes": 97}] ``` ### `POST /api/status-poll` Manually sends `STATUS` (F2) and, if the card answers `91XX`, runs the proactive chain (FETCH → TERMINAL RESPONSE) until it settles. Returns: ```json {"sw": "9000", "proactive": true} ``` ### `POST /api/rescue` Recovers a stuck CAT session by clearing the pending state and re-sending the TERMINAL PROFILE. Returns whether a menu and event list were captured again: ```json {"menu": true, "events": [4, 5]} ``` ### `GET /api/poll-status` Background STATUS polling state. ```json {"enabled": true, "interval": 300} ``` ### `POST /api/poll-toggle` Turns background STATUS polling on or off. ```json {"enabled": true} ``` Returns the new state (`{"enabled": ..., "interval": ...}`). ### `GET /api/pli-qualifiers` Lists the PROVIDE LOCAL INFORMATION qualifier codes with their names. ```json [{"code": "00", "name": "Location Information"}, {"code": "0A", "name": "Battery Charge Level"}] ``` ### `GET /api/pli-dict` Returns the current PLI data dictionary as a qualifier-code map. ```json {"00": "0291...", "0A": "64"} ``` ### `POST /api/pli-dict` Updates dictionary entries. Body is a map of qualifier code to hex value; keys must be known qualifiers and values valid hex, otherwise they are ignored. Returns the updated dictionary.