Files
otaman/docs/api.md
T
2026-09-12 15:08:04 +03:00

13 KiB
Raw Blame History

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:

{"version": "2.0.0"}

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.

{"cmd": "select MF"}

Returns:

{"output": "..."}

POST /api/apdu

Send a raw APDU to the card.

{"apdu": "00A4040000..."}

Returns:

{"response": "...", "sw": "9000"}

POST /api/help

Get structured help for a shell command.

{"cmd": "apdu"}

Returns:

{"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:

{
  "sp": "00201516011515b00000...",
  "spi1": "16",
  "spi2": "01",
  "kic": "15",
  "kid": "15",
  "tar": "b00000",
  "cntr": "0000000001",
  "kicKey": "D6FCC023...",
  "kidKey": "1B07E7E0..."
}

Response (delivery PoR):

{"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:

{
  "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):

{"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):

{"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.

{"spi1": "16", "spi2": "01", "kic": "15", "kid": "15", "tar": "b00000",
 "cntr": "0000000001", "apdu": "00a40000023f00",
 "kicKey": "D6FCC023...", "kidKey": "1B07E7E0..."}

Response:

{"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:

{"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).

{"item_id": 128}

Response:

{"type": "display_text", "text": "Hello", "sw": "9122"}

or

{"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.

{"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.

{"active": true, "pending": true, "pending_type": "select_item"}

POST /api/read

Read file content. Auto-detects transparent vs record files.

{"name": "EF.ICCID", "fid": "2FE2", "parent_sel": "3F00", "mode": "raw"}

Returns transparent data:

{"success": true, "sw": "9000", "file_type": "transparent", "data": "...",
 "apdu_times": [{"type": "select", "ms": 12}, {"type": "read_binary", "ms": 9}]}

Returns records:

{"success": true, "sw": "9000", "file_type": "linear_fixed",
 "records": [{"num": 1, "data": "..."}, {"num": 2, "data": "..."}],
 "apdu_times": [{"type": "select", "ms": 12},
                {"type": "read_record", "ms": 11}, {"type": "read_record", "ms": 13}]}

apdu_times reports each command's duration (command sent to response received) classified as select, read_binary or read_record; the PWA uses it for snapshot timing statistics. Other commands are not reported.

POST /api/write

Write raw hex data to a file.

{"name": "EF.ICCID", "fid": "2FE2", "data": "A0A1A2...", "parent_sel": "3F00"}

For record files:

{"name": "EF.ADN", "fid": "6F3A", "data": "A0A1...", "record_nr": 1, "parent_sel": "7F10"}

Returns:

{"success": true, "sw": "9000"}

POST /api/select

Select a file by name or FID, with optional parent selection.

{"name": "EF.ICCID", "fid": "2FE2", "parent_sel": "3F00"}

Returns:

{"name": "EF.ICCID", "fid": "2FE2", "file_type": "transparent",
 "file_size": 10, "record_len": null, "num_of_rec": null,
 "fci_hex": "621082024021...",
 "apdu_times": [{"type": "select", "ms": 12}], "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.

{"name": "MF", "fid": "3F00"}

Returns:

{"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.

{"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:

{"sw": "9000", "data": "..."}

GET /api/proactive-log

Returns the last 50 proactive commands fetched during CAT sessions, newest first:

[{"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:

{"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:

{"menu": true, "events": [4, 5]}

GET /api/poll-status

Background STATUS polling state.

{"enabled": true, "interval": 300}

POST /api/poll-toggle

Turns background STATUS polling on or off.

{"enabled": true}

Returns the new state ({"enabled": ..., "interval": ...}).

GET /api/pli-qualifiers

Lists the PROVIDE LOCAL INFORMATION qualifier codes with their names.

[{"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.

{"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.