> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gravixlayer.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Send PTY Input

> Write keystrokes, commands and control characters to a PTY session

`POST /v1/agents/runtime/{runtime_id}/pty/{session_id}/input`

Writes to the session's terminal exactly as if it had been typed.

<CodeGroup>
  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  from gravixlayer import GravixLayer

  client = GravixLayer()  # defaults to cloud="aws", region="us-east-1"
  sandbox = client.runtime.create()  # defaults to template="base-small"
  session = sandbox.pty.create()
  sid = session.session_id

  # Text: a shell only acts on a line once it sees a newline.
  sandbox.pty.send_input(sid, "ls -la\n")

  # Bytes: control characters and escape sequences go through verbatim.
  sandbox.pty.send_input(sid, b"\x03")   # Ctrl-C
  sandbox.pty.send_input(sid, b"\x04")   # Ctrl-D (EOF)

  sandbox.kill()
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import { GravixLayer } from 'gravixlayer';

  const client = new GravixLayer(); // defaults to cloud="aws", region="us-east-1"
  const sandbox = await client.runtime.create(); // defaults to template="base-small"
  const session = await sandbox.pty.create();
  const sid = session.sessionId;

  await sandbox.pty.sendInput(sid, 'ls -la\n');
  await sandbox.pty.sendInput(sid, new Uint8Array([0x03])); // Ctrl-C
  await sandbox.pty.sendInput(sid, new Uint8Array([0x04])); // Ctrl-D (EOF)

  await sandbox.kill();
  ```

  ```bash CLI theme={"theme":{"light":"github-light","dark":"github-dark"}}
  # --no-newline suppresses the trailing newline the CLI adds by default.
  gravixlayer runtime pty send "$RT" "$SID" "ls -la"

  gravixlayer runtime pty send "$RT" "$SID" $'\x03' --no-newline
  ```

  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST "https://api.gravixlayer.ai/v1/agents/runtime/$RT/pty/$SID/input" \
    -H "Authorization: Bearer $GRAVIXLAYER_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"data": "ls -la\n"}'
  ```
</CodeGroup>

## Parameters

Supply exactly one of the following.

| Parameter     | Type   | Description                                                 |
| ------------- | ------ | ----------------------------------------------------------- |
| `data`        | string | Text input, sent as UTF-8                                   |
| `data_base64` | string | Base64 encoded raw bytes, for binary and control characters |

The Python SDK picks the right one for you: pass `str` and it sends `data`, pass `bytes`
and it base64 encodes into `data_base64`.

## Response (`PtyInputResponse`)

| Field           | Type    | Description                             |
| --------------- | ------- | --------------------------------------- |
| `success`       | boolean | Whether the write was accepted          |
| `bytes_written` | integer | Number of bytes written to the terminal |

## Common control characters

| Bytes  | Key    | Effect                                    |
| ------ | ------ | ----------------------------------------- |
| `\x03` | Ctrl-C | `SIGINT` to the foreground job            |
| `\x04` | Ctrl-D | End of input; exits most shells and REPLs |
| `\x1a` | Ctrl-Z | `SIGTSTP`, suspends the foreground job    |
| `\x1b` | Escape | Leaves insert mode in editors such as vim |
| `\t`   | Tab    | Shell completion                          |

<Note>
  Sending `\x03` writes the interrupt character to the terminal, which the guest's line
  discipline turns into `SIGINT` for the foreground process group. This is usually what you
  want. Use [send signal](/documentation/agentruntime/pty-sessions/manage-sessions#send-a-signal)
  when you need to signal the session's own process regardless of what is in the foreground.
</Note>

## Waiting for a prompt

Input is asynchronous: the call returns once the bytes are written, not once the program has
reacted. When you need to drive a sequence of prompts, stream the output and wait for each
prompt before sending the next line.

<CodeGroup>
  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import re

  pending = ["python3\n", "import sys\n", "print(sys.version)\n"]
  buffer = ""

  for event in sandbox.pty.stream(sid):
      if event["type"] != "data":
          continue
      buffer += event["data"].decode("utf-8", "replace")
      if pending and re.search(r"(\$|>>>) $", buffer):
          sandbox.pty.send_input(sid, pending.pop(0))
          buffer = ""
      elif not pending:
          break
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const pending = ['python3\n', 'import sys\n', 'print(sys.version)\n'];
  let buffer = '';

  for await (const event of sandbox.pty.stream(sid)) {
    if (event.type !== 'data') continue;
    buffer += new TextDecoder().decode(event.data);
    if (pending.length > 0 && /(\$|>>>) $/.test(buffer)) {
      await sandbox.pty.sendInput(sid, pending.shift()!);
      buffer = '';
    } else if (pending.length === 0) {
      break;
    }
  }
  ```
</CodeGroup>
