{
  "openapi": "3.1.1",
  "info": {
    "title": "Dialt HTTP API",
    "version": "1.0.0",
    "description": "HTTP endpoints used to prepare Dialt realtime voice sessions, plus the session history API (transcripts, response times, audio) for sessions on your account. The realtime conversation protocol is described by asyncapi.json."
  },
  "servers": [
    {
      "url": "https://dialt.com"
    }
  ],
  "paths": {
    "/api/v1/session-keys": {
      "post": {
        "operationId": "createSessionKey",
        "summary": "Create a scoped realtime session credential",
        "description": "Exchange a server-held persistent ck_ key for a short-lived credential bound to exactly one session_id. Call this only from a trusted backend.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SessionKeyRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Scoped credential created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionKeyResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid session_id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, expired or revoked persistent key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type must be application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/voices": {
      "get": {
        "operationId": "listVoices",
        "summary": "List available regional and designed voices",
        "security": [],
        "responses": {
          "200": {
            "description": "Current voice roster",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoiceRoster"
                }
              }
            }
          }
        }
      }
    },
    "/api/app/sessions": {
      "get": {
        "operationId": "listSessions",
        "summary": "List sessions",
        "description": "Newest first, 50 per page. Defaults to sessions on your account; Dialt team accounts may request scope=all. Sessions appear shortly after they end. Authenticate with your persistent API key (Authorization: Bearer ck_...) or the signed-in browser cookie.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "scope",
            "in": "query",
            "description": "mine (default) lists the authenticated account's sessions. all lists every user's sessions and is restricted to Dialt team accounts.",
            "schema": {
              "type": "string",
              "enum": [
                "mine",
                "all"
              ],
              "default": "mine"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "user",
            "in": "query",
            "description": "Case-insensitive owner email substring. Restricted to Dialt team accounts using scope=all.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Only sessions started at or after this date (YYYY-MM-DD, UTC) or ISO8601 datetime.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Only sessions started at or before this date (whole day, UTC) or ISO8601 datetime.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "min_turns",
            "in": "query",
            "description": "Only sessions with at least this many turns (e.g. 1 hides connection tests).",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "interrupted",
            "in": "query",
            "description": "1 to keep only sessions where the user interrupted the assistant at least once; 0 is the explicit no-op.",
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "0"
              ]
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Case-insensitive substring match on session_id or on the words said in the transcript.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of sessions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionPage"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials"
          },
          "400": {
            "description": "A filter value could not be parsed; the body names the parameter."
          },
          "403": {
            "description": "scope=all was requested by a non-team account."
          }
        }
      }
    },
    "/api/app/sessions/{uuid}": {
      "get": {
        "operationId": "getSession",
        "summary": "One session with its transcript",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Session detail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionDetail"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials"
          },
          "404": {
            "description": "No such session on this account"
          }
        }
      }
    },
    "/api/app/sessions/{uuid}/artifacts/{name}": {
      "get": {
        "operationId": "getSessionArtifact",
        "summary": "Download a session's audio track",
        "description": "Redirects (302) to a short-lived signed URL for the track: mic is your side of the call, assistant the assistant's. Both are mono, and sample 0 of each is session time 0, so they overlay. Newer recordings are lossless FLAC (mic.flac, assistant.flac) and older ones PCM16 WAV (mic.wav, assistant.wav) -- take the names from the session's own `artifacts` list rather than assuming one. The signed URL expires after 10 minutes; request a fresh redirect rather than storing it.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "mic.flac",
                "assistant.flac",
                "mic.wav",
                "assistant.wav"
              ]
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the signed audio URL (also returned as {\"url\": ...})",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials"
          },
          "404": {
            "description": "No such session on this account, no such track, or the audio has expired"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Persistent Dialt API key beginning ck_. Keep it on your server."
      }
    },
    "schemas": {
      "SessionId": {
        "type": "string",
        "pattern": "^[A-Za-z0-9_-][A-Za-z0-9._-]{0,63}$",
        "minLength": 1,
        "maxLength": 64
      },
      "SessionKeyRequest": {
        "type": "object",
        "required": [
          "session_id"
        ],
        "properties": {
          "session_id": {
            "$ref": "#/components/schemas/SessionId"
          }
        },
        "additionalProperties": false
      },
      "SessionKeyResponse": {
        "type": "object",
        "required": [
          "api_key",
          "session_id",
          "expires_in"
        ],
        "properties": {
          "api_key": {
            "type": "string",
            "description": "Short-lived credential for the matching session_id."
          },
          "session_id": {
            "$ref": "#/components/schemas/SessionId"
          },
          "expires_in": {
            "type": "integer",
            "minimum": 1,
            "description": "Credential lifetime in seconds."
          }
        },
        "additionalProperties": false
      },
      "Voice": {
        "type": "object",
        "required": [
          "key",
          "name",
          "kind",
          "descriptor"
        ],
        "properties": {
          "key": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "regional",
              "designed"
            ]
          },
          "descriptor": {
            "type": "string",
            "description": "Display context such as Classic's regional accent; may be empty."
          }
        },
        "additionalProperties": false
      },
      "VoiceRoster": {
        "type": "object",
        "required": [
          "voices",
          "default"
        ],
        "properties": {
          "voices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Voice"
            }
          },
          "default": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "ResponseTime": {
        "type": "object",
        "description": "Per-session aggregate of the per-turn response time, in milliseconds: from the last word transcribed in the user's turn to the first assistant audio sent by the server, for the first reply to each user turn. Estimated from the server's clock; network and client playback are not included. null when no turn has one.",
        "properties": {
          "n": {
            "type": "integer"
          },
          "mean": {
            "type": "number"
          },
          "p50": {
            "type": "number"
          },
          "max": {
            "type": "number"
          }
        }
      },
      "TranscriptEntry": {
        "type": "object",
        "required": [
          "role",
          "text"
        ],
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "user",
              "assistant"
            ]
          },
          "t": {
            "type": [
              "number",
              "null"
            ],
            "description": "Seconds from session start."
          },
          "text": {
            "type": "string",
            "description": "User: the final transcript of the turn. Assistant: what was actually spoken; a barge-in leaves a trailing [interrupted] marker."
          },
          "turn_id": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "description": "User turns only, and only when the input was not voice (e.g. text)."
          },
          "response_ms": {
            "type": "number",
            "description": "Assistant turns only: this turn's response time (see ResponseTime)."
          },
          "interrupted": {
            "type": "boolean",
            "description": "Assistant turns only: the user barged in."
          },
          "welcome": {
            "type": "boolean",
            "description": "Assistant turns only: the session greeting."
          },
          "start_t": {
            "type": "number",
            "description": "Exact playback start in seconds from session start. Voice user turns: Ink speech onset. Assistant turns: first audio sent. Omitted when no reliable anchor exists."
          }
        }
      },
      "SessionSummary": {
        "type": "object",
        "properties": {
          "owner_name": {
            "type": "string",
            "description": "Display name of the session owner. Present only in responses to Dialt team accounts."
          },
          "owner_email": {
            "type": "string",
            "format": "email",
            "description": "Email address of the session owner. Present only in responses to Dialt team accounts."
          },
          "uuid": {
            "type": "string",
            "format": "uuid",
            "description": "Stable id for this record; use it in the detail and artifact URLs."
          },
          "session_id": {
            "$ref": "#/components/schemas/SessionId"
          },
          "attempt": {
            "type": "integer",
            "description": "1 for the first connection; a reconnect reuses session_id with attempt 2, 3, ..."
          },
          "provider": {
            "type": "string"
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "ended_at": {
            "type": "string",
            "format": "date-time"
          },
          "duration_s": {
            "type": [
              "number",
              "null"
            ]
          },
          "turn_count": {
            "type": "integer"
          },
          "interrupted": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How many assistant turns the user interrupted."
          },
          "truncated": {
            "type": "boolean",
            "description": "The recording hit the per-session cap before the session ended."
          },
          "response_ms": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ResponseTime"
              },
              {
                "type": "null"
              }
            ]
          },
          "artifacts": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "mic.flac",
                "assistant.flac",
                "mic.wav",
                "assistant.wav"
              ]
            },
            "description": "Audio tracks available from the artifacts endpoint -- FLAC for newer recordings, WAV for older ones, so read the names from here rather than constructing them. Audio is retained for a limited period; names drop off this list when it expires."
          }
        }
      },
      "SessionDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SessionSummary"
          },
          {
            "type": "object",
            "properties": {
              "mic_audio_s": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Seconds of user speech captured."
              },
              "assistant_audio_s": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Seconds of assistant speech played."
              },
              "transcript": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/TranscriptEntry"
                },
                "description": "In time order. Empty when no turn completed."
              },
              "stage_times_ms": {
                "type": "object",
                "description": "Curated per-session processing stage times in milliseconds. These are server processing measurements, not the qwenalign benchmark latency.",
                "properties": {
                  "asr": {
                    "$ref": "#/components/schemas/StageTimeStat"
                  },
                  "brain": {
                    "$ref": "#/components/schemas/StageTimeStat"
                  },
                  "ttft": {
                    "$ref": "#/components/schemas/StageTimeStat"
                  },
                  "ttfa": {
                    "$ref": "#/components/schemas/StageTimeStat"
                  },
                  "tts_connect": {
                    "$ref": "#/components/schemas/StageTimeStat"
                  }
                },
                "additionalProperties": false
              }
            }
          }
        ]
      },
      "SessionPage": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer"
          },
          "next": {
            "type": [
              "string",
              "null"
            ],
            "description": "URL of the next page, or null."
          },
          "previous": {
            "type": [
              "string",
              "null"
            ]
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SessionSummary"
            }
          }
        }
      },
      "StageTimeStat": {
        "type": "object",
        "properties": {
          "n": {
            "type": "integer"
          },
          "mean": {
            "type": "number"
          },
          "p50": {
            "type": "number"
          },
          "max": {
            "type": "number"
          }
        },
        "additionalProperties": false
      }
    }
  }
}
