openapi: 3.1.0
info:
  title: Tazo API
  version: 0.1.0
  description: >-
    Turn spoken audio into typed decisions. Send audio and questions, and get
    probability-backed answers.
servers:
  - url: https://api.tazolabs.com
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Decisions
  - name: Models
  - name: API keys
  - name: Health
paths:
  /v1/decisions:
    post:
      tags: [Decisions]
      operationId: createDecision
      summary: Create a decision
      description: >-
        Sends audio and a set of questions, and waits for the typed answers.
        The request is persisted before processing. If the caller disconnects
        or the wait times out, processing continues. Send an `Idempotency-Key`
        to make retries safe.
      security:
        - bearerAuth: []
        - accessToken: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: >-
            Optional key, 1 to 256 characters, that deduplicates retries of the
            same request for the same account. Reusing a key with different
            input returns `409`.
          schema:
            type: string
            minLength: 1
            maxLength: 256
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [request, audio]
              properties:
                request:
                  $ref: "#/components/schemas/DecisionRequest"
                audio:
                  type: string
                  format: binary
                  description: One audio file, up to 16 MB.
            encoding:
              request:
                contentType: application/json
      responses:
        "200":
          description: The decision completed.
          headers:
            X-Tazo-Request-Id:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DecisionResponse"
              example:
                id: dec_example
                model: tazo-large
                answers:
                  action:
                    type: choice
                    choice: get_weather
                    probabilities:
                      NO_TOOL: 0.08
                      get_weather: 0.92
                    confidence: 0.92
                usage:
                  audio_seconds: 2.4
                elapsed_ms: 52.0
        "400":
          description: The `Idempotency-Key` is blank or longer than 256 characters (`invalid_idempotency_key`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Unknown model (`model_not_found`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: The `Idempotency-Key` was already used for a different request (`idempotency_conflict`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "413":
          description: The audio is over 16 MB (`audio_too_large`), or the request is too large to persist (`request_too_large`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: >-
            The `request` JSON does not match the v1 contract (`invalid_request`),
            or the model rejected it (`model_rejected_request`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "502":
          description: The model failed or returned an invalid response (`model_runtime_error`, `runtime_contract_violation`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "503":
          description: >-
            The inference service or durable execution service is unavailable
            (`tazo_unavailable`, `durable_execution_unavailable`). Without an
            `Idempotency-Key`, the request may already have been accepted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "504":
          description: >-
            The inference service timed out (`tazo_timeout`), or the decision is
            still processing (`decision_pending`). For a pending decision,
            `X-Tazo-Request-Id` and `request_id` identify it, and processing
            continues. `error.retryable` is `true` only when an `Idempotency-Key`
            was supplied.
          headers:
            X-Tazo-Request-Id:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/models:
    get:
      tags: [Models]
      operationId: listModels
      summary: List models
      description: Lists the available models, their capabilities, and whether each is ready.
      security:
        - bearerAuth: []
        - accessToken: []
      responses:
        "200":
          description: The model list. `tazo-large` is listed first.
          content:
            application/json:
              schema:
                type: object
                required: [models]
                properties:
                  models:
                    type: array
                    items:
                      $ref: "#/components/schemas/Model"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "502":
          description: The model runtime returned an invalid response (`runtime_contract_violation`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "503":
          description: One or more models are unavailable (`tazo_unavailable`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/api-keys:
    post:
      tags: [API keys]
      operationId: createApiKey
      summary: Create an API key
      description: >-
        Creates a key for the signed-in account. The full secret is returned
        once and cannot be recovered. Requires a sign-in access token. API keys
        cannot manage keys. A timed-out creation may already have committed, so
        reload the list before retrying.
      security:
        - accessToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateApiKeyRequest"
      responses:
        "201":
          description: The key was created.
          headers:
            Cache-Control:
              $ref: "#/components/headers/NoStore"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreatedApiKey"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/RequiresLogin"
        "422":
          $ref: "#/components/responses/ValidationError"
        "503":
          $ref: "#/components/responses/KeysUnavailable"
    get:
      tags: [API keys]
      operationId: listApiKeys
      summary: List API keys
      description: >-
        Lists the signed-in account's keys, newest first. Expired and revoked
        keys stay for about 30 days. Secrets are never returned.
      security:
        - accessToken: []
      responses:
        "200":
          description: The account's keys.
          headers:
            Cache-Control:
              $ref: "#/components/headers/NoStore"
          content:
            application/json:
              schema:
                type: object
                required: [keys]
                properties:
                  keys:
                    type: array
                    items:
                      $ref: "#/components/schemas/ApiKey"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/RequiresLogin"
        "503":
          $ref: "#/components/responses/KeysUnavailable"
  /v1/api-keys/{key_id}:
    delete:
      tags: [API keys]
      operationId: revokeApiKey
      summary: Revoke an API key
      description: >-
        Revokes a key. Repeating the call for an already revoked key succeeds.
        Decisions already accepted keep processing. An unknown key, a key
        belonging to another account, and a key past its retention period all
        return the same `404`.
      security:
        - accessToken: []
      parameters:
        - name: key_id
          in: path
          required: true
          description: The key `id` from the create or list response.
          schema:
            type: string
      responses:
        "204":
          description: The key is revoked.
          headers:
            Cache-Control:
              $ref: "#/components/headers/NoStore"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/RequiresLogin"
        "404":
          description: The key was not found (`api_key_not_found`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "503":
          $ref: "#/components/responses/KeysUnavailable"
  /healthz:
    get:
      tags: [Health]
      operationId: getHealth
      summary: Check readiness
      description: Reports whether Tazo is ready. This endpoint is public.
      security: []
      responses:
        "200":
          description: Ready.
          content:
            application/json:
              schema:
                type: object
                required: [status]
                properties:
                  status:
                    type: string
                    enum: [ok]
        "503":
          description: Not ready.
          content:
            application/json:
              schema:
                type: object
                required: [status]
                properties:
                  status:
                    type: string
                    enum: [unavailable]
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: An API key (`tazo_sk_...`). Also accepts a sign-in access token.
    accessToken:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: A signed-in user's OIDC access token. Required to manage API keys.
  headers:
    RequestId:
      description: Identifier of the persisted request.
      schema:
        type: string
    NoStore:
      description: Always `no-store`.
      schema:
        type: string
        enum: [no-store]
  responses:
    Unauthorized:
      description: >-
        The credential is missing (`missing_access_token`) or invalid
        (`invalid_access_token`, `invalid_api_key`).
      headers:
        WWW-Authenticate:
          schema:
            type: string
            enum: [Bearer]
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    RequiresLogin:
      description: An API key was used to manage keys (`api_key_management_requires_login`).
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    ValidationError:
      description: The request body is invalid. This response uses the default validation format.
      content:
        application/json:
          schema:
            type: object
            properties:
              detail:
                type: array
                items:
                  type: object
                  additionalProperties: true
    KeysUnavailable:
      description: >-
        API keys are not configured (`api_keys_not_configured`), sign-in is not
        configured (`authentication_not_configured`), or key storage is
        unavailable (`api_key_service_unavailable`).
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, retryable]
          properties:
            code:
              type: string
              description: Stable machine-readable code. Branch on this, not `message`.
              example: model_not_found
            message:
              type: string
              example: "Unknown model. Choose 'tazo-large' or 'tazo-small'."
            retryable:
              type: boolean
              description: Whether repeating the same request can succeed.
        request_id:
          type: string
          description: Present on `decision_pending` responses.
    StructuredValue:
      description: A string, a JSON object, or a list of strings.
      oneOf:
        - type: string
        - type: object
          additionalProperties: true
        - type: array
          items:
            type: string
    DecisionRequest:
      type: object
      additionalProperties: false
      required: [state, questions]
      properties:
        model:
          type: string
          default: tazo-large
          description: >-
            `tazo-large` or `tazo-small`. Legacy model IDs and the previous
            `general` and `specialised` aliases are still accepted.
          example: tazo-large
        state:
          allOf:
            - $ref: "#/components/schemas/StructuredValue"
          description: Context shared by all questions.
          example:
            context: Choose the tool matching the spoken request.
        questions:
          type: object
          minProperties: 1
          description: Map of caller-chosen question ID to question.
          additionalProperties:
            $ref: "#/components/schemas/Question"
          example:
            action:
              type: choice
              instructions: Which action should be taken?
              criteria:
                NO_TOOL: Respond directly without calling a tool.
                get_weather: Get weather for a city.
    Question:
      oneOf:
        - $ref: "#/components/schemas/ChoiceQuestion"
        - $ref: "#/components/schemas/NoulQuestion"
        - $ref: "#/components/schemas/ScoreQuestion"
      discriminator:
        propertyName: type
        mapping:
          choice: "#/components/schemas/ChoiceQuestion"
          noul: "#/components/schemas/NoulQuestion"
          score: "#/components/schemas/ScoreQuestion"
    ChoiceQuestion:
      type: object
      additionalProperties: false
      required: [type, instructions, criteria]
      properties:
        type:
          type: string
          enum: [choice]
        instructions:
          $ref: "#/components/schemas/StructuredValue"
        criteria:
          type: object
          minProperties: 2
          maxProperties: 255
          description: Map of choice ID to description. Choice IDs must not be blank.
          additionalProperties:
            oneOf:
              - $ref: "#/components/schemas/StructuredValue"
              - type: "null"
    NoulQuestion:
      type: object
      additionalProperties: false
      required: [type, instructions]
      properties:
        type:
          type: string
          enum: [noul]
        instructions:
          $ref: "#/components/schemas/StructuredValue"
        criteria:
          type: object
          description: Optional. If sent, must contain exactly the keys `true` and `false`.
          additionalProperties:
            oneOf:
              - $ref: "#/components/schemas/StructuredValue"
              - type: "null"
    ScoreQuestion:
      type: object
      additionalProperties: false
      description: Defined in v1, but not supported by any model yet.
      required: [type, instructions, criteria]
      properties:
        type:
          type: string
          enum: [score]
        instructions:
          $ref: "#/components/schemas/StructuredValue"
        criteria:
          type: array
          minItems: 2
          maxItems: 10
          description: Ordered criteria.
          items:
            $ref: "#/components/schemas/StructuredValue"
    DecisionResponse:
      type: object
      required: [id, model, answers, usage, elapsed_ms]
      properties:
        id:
          type: string
          pattern: "^dec_[A-Za-z0-9_-]+$"
          example: dec_example
        model:
          type: string
          description: The public model name.
          example: tazo-large
        answers:
          type: object
          description: Answers keyed by the question IDs you supplied.
          additionalProperties:
            $ref: "#/components/schemas/Answer"
        usage:
          type: object
          required: [audio_seconds]
          properties:
            audio_seconds:
              type: number
              minimum: 0
              example: 2.4
            input_tokens:
              type: integer
              minimum: 0
              nullable: true
            output_tokens:
              type: integer
              minimum: 0
              nullable: true
        elapsed_ms:
          type: number
          minimum: 0
          example: 52.0
    Answer:
      oneOf:
        - $ref: "#/components/schemas/ChoiceAnswer"
        - $ref: "#/components/schemas/NoulAnswer"
        - $ref: "#/components/schemas/ScoreAnswer"
      discriminator:
        propertyName: type
        mapping:
          choice: "#/components/schemas/ChoiceAnswer"
          noul: "#/components/schemas/NoulAnswer"
          score: "#/components/schemas/ScoreAnswer"
    ChoiceAnswer:
      type: object
      required: [type, choice, probabilities, confidence]
      properties:
        type:
          type: string
          enum: [choice]
        choice:
          type: string
          description: The selected choice ID.
          example: get_weather
        probabilities:
          type: object
          description: Probability for each choice ID. Values sum to 1.
          additionalProperties:
            type: number
            minimum: 0
            maximum: 1
          example:
            NO_TOOL: 0.08
            get_weather: 0.92
        confidence:
          type: number
          minimum: 0
          maximum: 1
          description: The probability of the selected choice.
          example: 0.92
    NoulAnswer:
      type: object
      required: [type, noul]
      properties:
        type:
          type: string
          enum: [noul]
        noul:
          type: number
          minimum: 0
          maximum: 1
          description: Probability that the statement is true.
          example: 0.87
    ScoreAnswer:
      type: object
      required: [type, score, legend, probabilities, confidence]
      properties:
        type:
          type: string
          enum: [score]
        score:
          type: number
        legend:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/StructuredValue"
        probabilities:
          type: object
          additionalProperties:
            type: number
            minimum: 0
            maximum: 1
        confidence:
          type: number
          minimum: 0
          maximum: 1
    Model:
      type: object
      required: [id, display_name, description, contract, capabilities, ready]
      properties:
        id:
          type: string
          enum: [tazo-large, tazo-small]
        display_name:
          type: string
          example: Tazo Large
        description:
          type: string
        contract:
          type: string
          enum: [typed-decisions-v1]
        capabilities:
          $ref: "#/components/schemas/ModelCapabilities"
        ready:
          type: boolean
    ModelCapabilities:
      type: object
      required: [question_types, max_questions]
      properties:
        question_types:
          type: array
          items:
            type: string
            enum: [choice, noul, score]
        max_questions:
          type: integer
          minimum: 1
        max_choice_options:
          type: integer
          minimum: 2
          nullable: true
        requires_no_tool_choice:
          type: boolean
        max_audio_seconds:
          type: number
          nullable: true
        required_sample_rate_hz:
          type: integer
          nullable: true
    CreateApiKeyRequest:
      type: object
      additionalProperties: false
      required: [name]
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 40
          description: Trimmed before validation.
          example: Backend
        expiry:
          type: string
          enum: [7d, 30d, never]
          default: 30d
    ApiKey:
      type: object
      required: [id, name, last4, createdAt, expiresAt, revokedAt, lastUsedAt]
      properties:
        id:
          type: string
          description: Key ID, used to revoke the key.
        name:
          type: string
        last4:
          type: string
          description: Last four characters of the secret.
        createdAt:
          type: string
          format: date-time
        expiresAt:
          type: string
          format: date-time
          nullable: true
        revokedAt:
          type: string
          format: date-time
          nullable: true
        lastUsedAt:
          type: string
          format: date-time
          nullable: true
          description: Best effort. Updated at most once a minute.
    CreatedApiKey:
      type: object
      required: [key, secret]
      properties:
        key:
          $ref: "#/components/schemas/ApiKey"
        secret:
          type: string
          description: The full credential, shown only once.
          example: tazo_sk_0123456789abcdef0123456789abcdef.exampleSecret
