#Direct Input API V1

4 min read

Direct Input is an API-hosted, short-lived form used during an already-active AiDial call. It keeps the submitted value out of speech, SMS, browser URLs, Portal, logs, analytics, and model-visible results. There is no Portal page, BFF route, configurable form builder, room creation, or agent dispatch in this contract.

#Public operations

Only these exact method/path pairs are public:

MethodPathPurpose
GET/iReturn the identical native HTML shell with a per-response CSP nonce.
POST/v1/direct-input/contextResolve frozen safe display copy from a 16-character token.
POST/v1/direct-input/submitValidate one value and deliver its encrypted ciphertext to the bound runtime.

The form link places the token after #, for example https://api.example.test/i#AbCdEfGhIjKlMn_1. Fragments are not sent in the HTTP request. Before attempting browser storage, the shell removes the fragment with history.replaceState; guarded sessionStorage then preserves a valid token for JSON POST bodies only. It clears the token after acknowledgement or a proven terminal failure, and retains it for network, timeout, 413, 5xx, and other retryable failures. Query, trailing-slash, and encoded variants of the exact /i route are rejected rather than served.

POST /v1/direct-input/context accepts exactly:

JSON
{"token":"AbCdEfGhIjKlMn_1"}

Its success response contains only the frozen project display name, field ID, email or string type, form label/help, optional string max_length, HTTPS privacy-policy URL, expiry, notice revision, and a safe value_received boolean that switches an already accepted request to resend-only mode. No tenant, call, room, participant, runtime, token digest, key ID, or raw request state is returned.

POST /v1/direct-input/submit accepts exactly token and value, with a raw body limit of 4 KiB. A successful exact runtime acknowledgement returns:

JSON
{"status":"received"}

Non-terminal field validation returns a fixed 400 response. Unknown, expired, superseded, malformed, or terminal requests share one generic 404 shape. A 413 uses the same generic unavailable body without clearing the browser token. Delivery or dependency unavailability returns a fixed 503; the caller may retry, and the API redelivers the identical frozen ciphertext.

All public responses use Cache-Control: no-store. The shell also uses a nonce-bound CSP with default-src 'none' and frame-ancestors 'none', no third-party assets, native required/maximum-length validation, field-local accessible errors, a no-script fallback, and safe DOM text/attribute insertion only. The exact public paths are excluded from application and edge request logs while retaining the production public edge security controls.

Request creation and invalidation are private runtime operations and are not part of the public API. Their authentication and binding contract is documented in the internal endpoint inventory and authentication guide.

#Validation and delivery

Both field types trim surrounding Unicode whitespace, reject blank values and every Unicode Cc control character, and count Python Unicode code points. Email validation optionally removes mailto:, removes trailing whitespace or .,;:!?, applies ^[^@\s]+@[^@\s]+\.[^@\s]+$, preserves local-part case, lowercases the domain, and limits the result to 254 characters. Strings retain all remaining characters and enforce their frozen max_length from 1 to 500.

The normalized value and every request/call binding are encrypted using only the frozen projection encryption key. A reliable server-origin packet is targeted only to the authoritative runtime identity on aidial.direct_input.submitted. The API waits at most ten seconds for that identity's exact ciphertext-digest attribute. It deletes ciphertext on acknowledgement or terminal failure and retains it unchanged on timeout. API terminal notifications use aidial.direct_input.status. Once the first submission is frozen, every later submit body is ignored: the stored ciphertext and digest are redelivered unchanged until acknowledgement, terminal binding failure, or expiry.

#Retention and billing

No plaintext token or value is stored. Safe request metadata is retained until the original ten-minute expiry plus five minutes. The existing API lifespan runs cancellable recurring cleanup in bounded batches, expires active rows, best-effort notifies their bound runtimes, clears ciphertext, and deletes eligible metadata. Each cycle advances a fixed amount of backlog so recurring cycles converge without an unbounded transaction. Successful context records only first notice-presentation time and direct_input_notice_v1; runtime owns SMS-consent evidence.

Runtime-created SMS records with message_type="direct_input" use the existing delivery, idempotency, segment, spend-cap, exact metering, and reconciliation pipeline. Billing surfaces must never include the SMS body, destination, token, form URL, submitted value, ciphertext, or unsafe bindings.