HTTP REST API

The REST API gives external tools access to the internal state of the emulated machine. All requests require authentication.

This page is the reference for reading; for live documentation, open the bundled Swagger UI at http://localhost:8386/api.html while the emulator runs. It shows every endpoint with schemas from the server's own /openapi.json and can execute requests against the running machine.

Authentication

Every request must include a bearer token in the Authorization header:

Authorization: Bearer <token>

The token is a random 64-character hex string generated at startup. The full token is never printed to the log. It can be injected via the DOSBOX_API_TOKEN environment variable, written to a file, or (as fallback) the first 8 characters are shown in the log. See the Webserver page for token provisioning details. There is no default password and no way to disable authentication.

export TOKEN="your-token"
curl -H "Authorization: Bearer $TOKEN" http://localhost:8386/api/v1/status

Requests without a valid token receive HTTP 401.

Consistency guarantees

All endpoints access the internal state atomically between steps of the emulated CPU. They never execute during natively implemented DOS functions or interrupts, so data structures are guaranteed to be consistent in any given snapshot.

Note

The API has been primarily tested with core = normal. Your experience with other CPU cores may vary.

Endpoints

System status

GET /api/v1/status

Returns the overall emulator status.

Example input:

curl -H "Authorization: Bearer $TOKEN" http://localhost:8386/api/v1/status

Output:

{
    "running": true,
    "shutdown_requested": false,
    "is_booted": true,
    "program": "KEEN4E",
    "canonical_name": "KEEN4E.EXE",
    "is_shell": false
}
  • running -- always true while the webserver is up
  • shutdown_requested -- true after a shutdown request has been sent
  • is_booted -- true once DOS has finished booting
  • program -- the currently running program's segment name
  • canonical_name -- the running program's canonical filename
  • is_shell -- true if the DOS shell (command.com) is the active program

GET /api/v1/program/state

Similar to status but focused on the running program.

Example input:

curl -H "Authorization: Bearer $TOKEN" http://localhost:8386/api/v1/program/state

Output:

{
    "segment_name": "KEEN4E",
    "canonical_name": "KEEN4E.EXE",
    "is_shell": false,
    "is_booted": true
}

GET /api/v1/dosbox/info

Returns the dosbox-automation version string and the capability features of this build.

Example input:

curl -H "Authorization: Bearer $TOKEN" http://localhost:8386/api/v1/dosbox/info

Output:

{
    "version": "0.84-da3",
    "features": {
        "memory": true,
        "input": true,
        "cpu_registers": true,
        "cpu_control": true,
        "port_io": true,
        "freeze": true,
        "debugger": false
    }
}

Clients should check the features block instead of assuming an endpoint exists; groups can be absent or disabled in future builds. The debugger group is reserved and reported as false: those routes are not registered in this release.


POST /api/v1/dosbox/shutdown and POST /api/v1/control/shutdown

Request a graceful shutdown. Both endpoints do the same thing.

Example input:

curl -X POST -H "Authorization: Bearer $TOKEN" http://localhost:8386/api/v1/dosbox/shutdown

Output:

{
    "status": "shutdown_requested"
}

CPU registers

GET /api/v1/cpu/state

Returns all x86 CPU registers wrapped in a registers object.

Example input:

curl -H "Authorization: Bearer $TOKEN" http://localhost:8386/api/v1/cpu/state

Output:

{
    "registers": {
        "eax": 0, "ebx": 0, "ecx": 0, "edx": 0,
        "esi": 0, "edi": 0, "esp": 0, "ebp": 0,
        "eip": 256, "flags": 0,
        "cs": 0, "ds": 0, "es": 0, "ss": 0, "fs": 0, "gs": 0
    }
}

PUT /api/v1/cpu/register

Write a single CPU register. Part of the cpu_control capability.

Example input:

curl -X PUT -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"register": "eax", "value": 4660}' \
  http://localhost:8386/api/v1/cpu/register

Output:

{
    "status": "ok",
    "register": "eax",
    "value": 4660
}

The request body takes register and value. Register names are lowercase, the same set that /api/v1/cpu/state returns.

I/O ports

Direct access to x86 I/O ports, for hardware states the memory and register endpoints cannot reach.

GET /api/v1/io/port

Read a port.

Example input:

curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8386/api/v1/io/port?port=0x3C1&width=1"

Output:

{
    "port": 961,
    "width": 1,
    "value": 255
}

Query parameters: port (0x0000 to 0xFFFF, hex with 0x prefix accepted) and width (1 for byte, the default, or 2 for word).


PUT /api/v1/io/port

Write a port.

Example input:

curl -X PUT -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"port": 961, "value": 255}' \
  http://localhost:8386/api/v1/io/port

The request body takes the same fields: port, value, and an optional width.

Memory

GET /api/v1/memory/:offset/:len GET /api/v1/memory/:segment/:offset/:len

Read memory at the given address.

Example input:

# Read 256 bytes at linear offset 0x1000
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8386/api/v1/memory/0x1000/256 -o mem.bin

# Read 64 bytes at DS:0x100 as JSON
curl -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json" \
  http://localhost:8386/api/v1/memory/DS/0x100/64

# Dump the entire 1 MB conventional memory area
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8386/api/v1/memory/0/0x100000 -o conv.bin

The segment parameter is optional and can be a segment register name (CS, SS, DS, ES, FS, GS) or a numeric value. All URL parameters accept hex with the 0x prefix.

Returns raw binary by default. Set Accept: application/json to get a JSON response with Base64-encoded data.

Maximum read size: 128 MiB per request.

PUT /api/v1/memory/:offset PUT /api/v1/memory/:segment/:offset

Write memory at the given address.

Example input:

# Write 4 bytes at offset 0x1000
printf '\x01\x02\x03\x04' | curl -X PUT \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @- http://localhost:8386/api/v1/memory/0x1000

Accepts raw binary with Content-Type: application/octet-stream, or JSON with a Base64-encoded data field with Content-Type: application/json.

Supports atomic compare-and-swap: set the If-Match header to Base64-encoded expected data. Returns HTTP 412 (Precondition Failed) if the current memory does not match.

POST /api/v1/memory/allocate

Allocate memory from the emulated machine.

Example input:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"size": 1024, "area": "conv", "strategy": "best_fit"}' \
  http://localhost:8386/api/v1/memory/allocate

Output:

{
    "addr": 655360
}

Request fields:

  • size -- bytes to allocate
  • area -- conv (conventional), UMA (upper memory), or XMS
  • strategy -- best_fit (default), first_fit, or last_fit. XMS only supports best_fit.

Returns HTTP 503 if allocation fails.


POST /api/v1/memory/free

Free previously allocated memory.

Example input:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"addr": 655360}' \
  http://localhost:8386/api/v1/memory/free

The request body takes the addr returned by the allocate call. Returns HTTP 400 on invalid addresses.


POST /api/v1/memory/search

Scan a physical memory range for a value. The classic trainer workflow: scan for the current value, let it change in the game, scan again among the matches.

Example input:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"value": 100, "start": 0, "end": 1048576}' \
  http://localhost:8386/api/v1/memory/search

Output:

{
    "matches": [65536, 131072]
}

Request fields:

  • value -- the value to search for
  • width -- match width: 1 byte (default), 2 word, or 4 dword
  • start, end -- physical scan range, end exclusive. Maximum span 16 MB per request.

POST /api/v1/memory/freeze

Pin a memory address to a value. The emulator rewrites it every frame, the way a trainer holds health or ammo steady.

Example input:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"address": 65536, "value": 100}' \
  http://localhost:8386/api/v1/memory/freeze

The request body takes address, value, and an optional width (1 byte, the default, 2 word, or 4 dword).


GET /api/v1/memory/freeze lists the active freezes. DELETE /api/v1/memory/freeze removes one ({"address": 65536}) or, with an empty body, all of them.

DOS internals

GET /api/v1/dos/internals

Returns pointers to internal DOS data structures and the DOS memory map.

Example input:

curl -H "Authorization: Bearer $TOKEN" http://localhost:8386/api/v1/dos/internals

Output:

{
    "listOfLists": 1234,
    "dosSwappableArea": 5678,
    "firstShell": 2560,
    "memoryMap": [
        {
            "segment": 2064,
            "type": "M",
            "pspSegment": 8,
            "sizeParas": 128,
            "sizeBytes": 2048,
            "filename": "SC",
            "isLast": false
        }
    ]
}
  • listOfLists -- DOS list of lists (INT 21h AH=52h)
  • dosSwappableArea -- DOS swappable area (INT 21h AX=5D06h)
  • firstShell -- PSP of the first shell (start of usable memory)
  • memoryMap -- the MCB chain, one entry per memory control block: its segment, type (M for a chain member, Z for the last block), owning PSP segment, size in paragraphs and bytes, and the owner filename from the MCB header where DOS records one

Video frame capture

GET /api/v1/video/frame

Captures the current video frame as an image or raw pixel data.

Example input:

# Grab a PNG screenshot
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8386/api/v1/video/frame?format=png" -o frame.png

# Grab a JPEG at lower quality
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8386/api/v1/video/frame?quality=75" -o frame.jpg

The response is the image itself. The output format depends on the format query parameter or the Accept header:

FormatContent-TypeAccept headerExample request
JPEG (default)image/jpegnone needed/api/v1/video/frame
PNGimage/pngAccept: image/png/api/v1/video/frame?format=png
Rawapplication/octet-streamAccept: application/octet-stream/api/v1/video/frame?format=raw

For JPEG, the quality parameter controls compression (1-100, default 98).

The raw format returns a binary header followed by pixel data:

FieldTypeDescription
widthuint32Frame width in pixels
heightuint32Frame height in pixels
pitchint32Bytes per row (may be negative for bottom-up)
pixel_formatuint8Format enum (see below)
palette_countuint16Number of palette entries (256 for indexed, 0 otherwise)
palette3 bytes x countRGB triplets, only present if palette_count > 0
datawidth x height x bppRaw pixel data

Pixel formats: Indexed8, RGB555_Packed16, RGB565_Packed16, BGR24_ByteArray, BGRX32_ByteArray.

The mode parameter selects the frame source, the same way it does for video capture: raw (default) is the emulator framebuffer at native resolution, rendered is the post-shader image as shown on screen at window resolution. A rendered grab waits for the next presented frame, so it returns 503 when nothing is being presented (paused or minimized emulator). Neither mode includes the on-screen automation overlay. See raw vs rendered for when to use which.

# Grab the rendered display as shown on screen
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8386/api/v1/video/frame?format=png&mode=rendered" -o shown.png

Info

Returns HTTP 503 if no frame is available yet (emulator still starting).

GET /api/v1/video/frame/info

Returns frame metadata without the actual pixel data.

Example input:

curl -H "Authorization: Bearer $TOKEN" http://localhost:8386/api/v1/video/frame/info

Output:

{
    "width": 320,
    "height": 200,
    "pixel_format": "Indexed8",
    "pitch": 320,
    "is_paletted": true,
    "video_mode": {
        "width": 320,
        "height": 200,
        "is_graphics_mode": true,
        "is_double_scanned": true,
        "graphics_standard": "VGA",
        "color_depth": "IndexedColor8Bit",
        "bios_mode_number": 19
    },
    "rendered_double_scan": false,
    "double_width": false,
    "double_height": false
}

Screen text

GET /api/v1/video/text

Read the text-mode screen as characters.

Example input:

curl -H "Authorization: Bearer $TOKEN" http://localhost:8386/api/v1/video/text

Output:

{
    "is_text_mode": true,
    "bios_mode": 3,
    "columns": 80,
    "rows": 25,
    "page": 0,
    "text": "Setup ver 1.666                  (C) 1994 id Software\n"
}

The response carries the character buffer as UTF-8 with CP437 box-drawing and shade glyphs preserved, plus the mode geometry. In a graphics mode, text is empty and is_text_mode is false. Use this to drive installers by what is actually on screen instead of fixed timings.

Works across CGA, Hercules, Tandy, and VGA text modes; the reader follows the active video page.

Input injection and recording

Sending input

POST /api/v1/input/sequence

Send a sequence of keyboard and mouse events into the emulated DOS environment. Events are dispatched in order, timed by either PIC emulation ticks or frame numbers.

Type "Y" followed by Enter, with a 2-second pause before the Enter:

Example input:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
        {"type": "key", "key": "KBD_y", "pressed": true, "t": 0},
        {"type": "key", "key": "KBD_y", "pressed": false, "t": 50},
        {"type": "key", "key": "KBD_enter", "pressed": true, "t": 2000},
        {"type": "key", "key": "KBD_enter", "pressed": false, "t": 2050}
    ]
  }' http://localhost:8386/api/v1/input/sequence

Output:

{
    "status": "ok",
    "events_scheduled": 4
}

Returns HTTP 409 if a replay is already in progress.

Each event has these fields:

FieldTypeRequiredDescription
typestringyeskey, mouse_move, mouse_button, or mouse_wheel
tfloatnoTimestamp in milliseconds from sequence start. Events at t=0 are dispatched immediately.
delay_msfloatnoDelay in milliseconds after the previous event. The natural form for hand-written sequences; mutually exclusive with absolute timing per event.
frameuint64noFrame number. If any event has a frame field, the entire sequence uses frame-based replay.
keystringfor keyKey name, e.g. KBD_enter, KBD_a, KBD_f1. See key name table below.
pressedboolnotrue for press, false for release. Default true.
buttonstringfor mouse_buttonleft, right, or middle
x_rel, y_relfloatfor mouse_moveRelative mouse movement
x_abs, y_absfloatnoAbsolute mouse position
deltafloatfor mouse_wheelScroll wheel delta

Maximum 32000 events per request. Events with unknown fields are rejected with an error naming the allowed ones, so a typo in a field name fails loudly instead of injecting a zero-motion event.

Timing modes: If events include frame fields, replay is frame-based: events fire when the emulator reaches the specified frame number. This gives deterministic replay regardless of host speed. Without frame fields, events are timed via the PIC (Programmable Interrupt Controller) emulation clock, which measures milliseconds of emulated time.

Warning

Replay determinism and randomness. Deterministic replay depends on the emulated program producing the same output for the same input. Games that use hardware timers, RNG seeded from real time, or nondeterministic behavior (Dr. Riptide, for example, randomizes enemy patterns) will diverge between runs even with identical input sequences. For these games, frame-based replay is more resilient than PIC-based, but not immune. Automated testing of such games should verify screen state between steps rather than relying on replaying a fixed recording blindly.

Warning

Replay needs the same machine settings. A recording only replays faithfully in the environment it was made in. Fix cpu_cycles and cpu_cycles_protected to the same values for the recording and every replay: with the default auto behavior the CPU speed ramps differently between runs, and frame-indexed events land on the wrong game state. The same goes for keyboard_layout. Recordings store raw key positions, and the active DOS layout decides which characters they produce, so a recording made under the US layout types garbage on a system that auto-detects a German one. Pin both settings in the config instead of leaving them on auto, and note them alongside the recording.


POST /api/v1/input/type

Type a string with paced key injection.

Example input:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"text": "dir /w"}' \
  http://localhost:8386/api/v1/input/type

Output:

{
    "status": "ok",
    "chars": 6
}

The characters are injected as paced key presses against the emulated keyboard controller's buffer, the same mechanism the Lua type() function uses, so nothing gets dropped no matter how long the string is. Request fields:

  • text -- the string to type, up to 4096 characters
  • cps -- typing speed in characters per second, default 30

Recording input

Record keyboard and mouse events as they happen, then retrieve the recording for later replay.

POST /api/v1/input/record/start

Start recording.

Example input:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8386/api/v1/input/record/start

Output:

{
    "status": "recording"
}

Events are captured with both PIC timestamps (milliseconds) and frame numbers, so the same recording can be replayed in either timing mode. Returns HTTP 409 if already recording.


POST /api/v1/input/record/pause

Toggle pause. While paused, events are not captured.

Example input:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8386/api/v1/input/record/pause

Output:

{
    "status": "paused"
}

Or "recording" if unpaused. Returns HTTP 409 if no recording is active.


POST /api/v1/input/record/stop

Stop recording and return all captured events.

Example input:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8386/api/v1/input/record/stop

Output:

{
    "event_count": 127,
    "duration_ms": 15230.5,
    "events": [
        {"t": 0, "frame": 0, "type": "key", "key": "KBD_enter", "pressed": true},
        {"t": 48.3, "frame": 3, "type": "key", "key": "KBD_enter", "pressed": false},
        {"t": 512.1, "frame": 31, "type": "mouse_move", "x_rel": 3.0, "y_rel": -1.5, "x_abs": 160.0, "y_abs": 100.0},
        {"t": 1024.7, "frame": 61, "type": "mouse_button", "button": "left", "pressed": true}
    ]
}

The response contains the full event sequence in the same format accepted by /api/v1/input/sequence, so it can be fed back directly for replay. Each event includes both t (PIC milliseconds) and frame (rendered frame count). When replaying, the presence of frame fields in the sequence triggers frame-based replay automatically. Returns HTTP 409 if no recording is active.


GET /api/v1/input/record/status

Check recording state without stopping.

Example input:

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8386/api/v1/input/record/status

Output:

{
    "recording": true,
    "paused": false,
    "event_count": 42,
    "duration_ms": 3150.2
}

Lua scripting

Load and run sandboxed Lua scripts inside the emulator. Scripts run as coroutines, yielding back to the emulator between frames. See the Lua scripting page for the full API available to scripts.

POST /api/v1/script/load

Load a Lua script from the request body.

Example input:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: text/plain" \
  --data-binary @myscript.lua \
  "http://localhost:8386/api/v1/script/load?name=my-test-script&debug=true"

Output:

{
    "status": "loaded",
    "name": "my-test-script"
}

The script source is sent as text/plain or application/x-lua. Parameters are passed as query strings:

ParameterTypeRequiredDescription
namestringnoScript name (alphanumeric, hyphens, underscores, max 64 chars). Defaults to "unnamed".
seedint64noRNG seed for deterministic runs.
debugboolnoEnable debug log output to the config logs/ directory.

Returns HTTP 400 if a script is already running, the body is empty, or validation fails. Returns HTTP 429 if called within 2 seconds of the previous load (rate limited).


POST /api/v1/script/start

Start the previously loaded script.

Example input:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8386/api/v1/script/start

Output:

{
    "status": "started"
}

The script runs as a coroutine, dispatched once per frame. Returns HTTP 400 if no script is loaded or a script is already running.


POST /api/v1/script/stop

Stop the running script and close any open debug log.

Example input:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8386/api/v1/script/stop

Output:

{
    "status": "stopped"
}

GET /api/v1/script/status

Check the state of the scripting engine.

Example input:

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8386/api/v1/script/status

Output:

{
    "state": "running",
    "frame": 1842,
    "name": "my-test-script"
}

When a script finishes or errors:

{
    "state": "error",
    "frame": 503,
    "name": "my-test-script",
    "error": "script exceeded instruction limit",
    "output": {"result": "partial data"}
}

The output field contains whatever the script wrote to the dosbox.output table, serialized as JSON. Possible state values: idle, loaded, running, yielded, completed, error.

Video capture (ZMBV)

Record the emulated screen to a ZMBV-encoded AVI file.

POST /api/v1/capture/video/start

Start video capture. The output file is written to the capture directory.

Example input:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode": "rendered"}' \
  http://localhost:8386/api/v1/capture/video/start

Output:

{
    "status": "recording"
}

The optional request body selects what feeds the encoder via mode:

  • raw (default) -- the emulator framebuffer at native resolution, frames as the emulator produces them
  • rendered -- the post-shader image as shown on screen, at window resolution and a constant frame rate

If rendered recording causes stutter at large window sizes, lower its compression level (see the compression endpoint below). Output is ZMBV in both modes; transcode with ffmpeg if another codec is needed. See raw vs rendered for when to use which.


POST /api/v1/capture/video/stop

Stop video capture and finalize the AVI file.

Example input:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8386/api/v1/capture/video/stop

Output:

{
    "status": "stopped"
}

GET /api/v1/capture/video/status

Check whether video capture is currently active.

Example input:

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8386/api/v1/capture/video/status

Output:

{
    "capturing": true,
    "mode": "raw",
    "last_stop_reason": "none"
}

last_stop_reason explains why the previous recording ended: none, clean, write_error, or disk_space_low. A recording stops on its own when the capture drive's free space falls below capture_min_free_space_mb, and a write error (full disk, removed device) ends it with a playable file up to the truncation point instead of a corrupt one.


GET /api/v1/capture/video/compression

Read the zlib compression levels used for video capture, one per capture mode.

Example input:

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8386/api/v1/capture/video/compression

Output:

{
    "raw": 9,
    "rendered": 6
}

PUT /api/v1/capture/video/compression

Set the compression level for one or both capture modes, 0 (store only) to 9 (maximum). The level is applied when the next recording starts. While a recording is running the request is refused with 409, because the running recording keeps the level it started with.

Example input:

curl -X PUT -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"rendered": 2}' \
  http://localhost:8386/api/v1/capture/video/compression

Output:

{
    "raw": 9,
    "rendered": 2
}

The response always reports both resulting levels. The startup values come from the capture_video_compression and capture_video_compression_rendered config settings; see the capture settings.

Drive swap

POST /api/v1/drive/swap

Swap a mounted disk image on a drive letter. Used for multi-disk game installs where the installer asks to insert the next disk.

Example input:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"drive": "A", "image": "/games/monkey-island/disk2.img"}' \
  http://localhost:8386/api/v1/drive/swap

Output:

{
    "status": "ok",
    "drive": "A"
}

Request fields:

  • drive -- single letter, A through Z
  • image -- absolute path to the disk image file on the host filesystem

The current drive is unmounted and replaced with the new image. The endpoint autodetects floppy images by matching the file size against known floppy geometries (360K, 720K, 1.2M, 1.44M, 2.88M). Anything else is mounted as a hard disk image.

The image path is validated against the mount policy before mounting: it must be a regular file (no symlinks, no system paths) with valid disk image structure. If mount_allowed_image_roots is configured, the image must be under one of those directories. Images are mounted read-only.

Returns HTTP 403 if the mount lock is engaged. Returns HTTP 400 if the file does not exist, the path is blocked by the mount policy, the drive letter is invalid, or the image fails to mount.

Example: three-disk install automation

# Installer is running from disk 1 on A:, asks for disk 2
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"drive": "A", "image": "/games/monkey-island/disk2.img"}' \
  http://localhost:8386/api/v1/drive/swap

# Send Enter to continue the installer
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"events": [{"type": "key", "key": "KBD_enter", "pressed": true, "t": 0}, {"type": "key", "key": "KBD_enter", "pressed": false, "t": 50}]}' \
  http://localhost:8386/api/v1/input/sequence

# When it asks for disk 3
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"drive": "A", "image": "/games/monkey-island/disk3.img"}' \
  http://localhost:8386/api/v1/drive/swap

Mount lock

POST /api/v1/mount/lock

Engage the mount lock.

Example input:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8386/api/v1/mount/lock

Output:

{
    "status": "locked"
}

Once locked, all mount operations are refused: directory mounts, image mounts, and BOOT. The lock is one-way and cannot be reversed without restarting the emulator.

Launchers should call this after the installation phase completes. For multi-disc games, all disc swaps must happen before locking.


GET /api/v1/mount/lock

Check the current lock state.

Example input:

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8386/api/v1/mount/lock

Output:

{
    "locked": false
}

Key names

The input endpoints use key names with the KBD_ prefix. Here are the most commonly used ones:

Key nameKey
KBD_a through KBD_zLetters
KBD_0 through KBD_9Number row
KBD_f1 through KBD_f12Function keys
KBD_enterEnter/Return
KBD_spaceSpacebar
KBD_escEscape
KBD_tabTab
KBD_backspaceBackspace
KBD_up, KBD_down, KBD_left, KBD_rightArrow keys
KBD_insert, KBD_delete, KBD_home, KBD_endNavigation
KBD_pageup, KBD_pagedownPage up/down
KBD_leftshift, KBD_rightshiftShift keys
KBD_leftctrl, KBD_rightctrlCtrl keys
KBD_leftalt, KBD_rightaltAlt keys
KBD_capslock, KBD_numlock, KBD_scrolllockLock keys
KBD_kp0 through KBD_kp9Numpad digits
KBD_kpenter, KBD_kpplus, KBD_kpminusNumpad operators
KBD_kpmultiply, KBD_kpdivide, KBD_kpperiodNumpad operators
KBD_minus, KBD_equals, KBD_gravePunctuation
KBD_comma, KBD_period, KBD_slashPunctuation
KBD_semicolon, KBD_quotePunctuation
KBD_leftbracket, KBD_rightbracket, KBD_backslashBrackets
KBD_leftgui, KBD_rightguiWindows/Super keys
KBD_oem102OEM 102 key (non-US keyboards)
KBD_abnt1Brazilian ABNT layout key
KBD_printscreen, KBD_pauseSpecial keys

Example tools

The extras/api/ directory in the dosbox-automation source tree contains ready-to-use HTML tools and a JavaScript API wrapper. Copy them to the webserver directory inside your configuration folder to use them.

  • Memory Monitor -- watch and edit memory locations live. Import a config string to set up named watch addresses. Try it with Commander Keen 4: run the game, import the config from the source tree, and watch lives/ammo update in real time. You can edit the values too.

  • Memory Scanner -- find where specific values are stored. Search for a known value (like your current ammo count), change it in the game, filter the results. Repeat until you have isolated the memory address. Same workflow as Cheat Engine, but running against an emulated machine.

  • Memory Viewer -- hex viewer with a built-in x86 disassembler.

  • JavaScript API wrapper (api.js) -- a JavaScript class for building custom tools. Handles Base64 encoding/decoding, segment register resolution, and compare-and-swap operations. TypeScript definitions in api.d.ts.