{
  "openapi": "3.0.3",
  "info": {
    "title": "geko API",
    "version": "1.0.0",
    "description": "Text-to-speech for Kazakh and beyond. Send text and a voice name; get back WAV audio (24 kHz, 16-bit PCM, mono). A thin, familiar HTTP API \u2014 call it from any language, or use the typed SDK and CLI.\n\nThe catalog endpoints (`/v1/models`, `/v1/voices`, `/health`) are open. Synthesis endpoints require a bearer key \u2014 create one in the [console](https://app.geko.sh)."
  },
  "servers": [
    {
      "url": "https://geko--tokay-serve-web.modal.run",
      "description": "Text-to-speech service"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/v1/transcribe": {
      "servers": [
        {
          "url": "https://geko--seta-serve-transcriber-api.modal.run"
        }
      ],
      "post": {
        "operationId": "createTranscription",
        "summary": "Transcribe speech",
        "description": "Transcribe Kazakh/Russian audio, including speakers who switch language mid-sentence. Send audio as a raw request body (naming the container with an `x-filename` header), as a multipart `file`, or pass `?url=` and let the server fetch it. Billed on audio duration at 2.5 credits per second, from the same credit balance as text-to-speech.",
        "tags": [
          "Speech-to-text"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "timestamps",
            "in": "query",
            "required": false,
            "description": "Also return `words` and `segments` with start/end times in seconds. Absent from the response unless requested. No extra cost.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "url",
            "in": "query",
            "required": false,
            "description": "Public URL of the audio. Use instead of sending a body.",
            "schema": {
              "type": "string",
              "format": "uri"
            }
          },
          {
            "name": "x-filename",
            "in": "header",
            "required": false,
            "description": "Filename hint; only the extension matters, so the server knows the container. Defaults to `audio.wav`.",
            "schema": {
              "type": "string",
              "example": "call.wav"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The transcript, with timing and the credits charged.",
            "headers": {
              "X-Seta-Audio-Seconds": {
                "description": "Duration of the submitted audio \u2014 what you were billed for.",
                "schema": {
                  "type": "number"
                }
              },
              "X-Seta-Credits": {
                "description": "Credits charged for this request.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Seta-Model": {
                "description": "Model that produced the transcript.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transcription"
                }
              }
            }
          },
          "400": {
            "description": "No audio supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Audio longer than 2 hours. Split it and send the parts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Out of credits \u2014 top up in the console.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://geko--seta-serve-transcriber-api.modal.run",
            "description": "Speech-to-text service (scaled separately from text-to-speech)"
          }
        ]
      }
    },
    "/v1/audio/transcriptions": {
      "servers": [
        {
          "url": "https://geko--seta-serve-transcriber-api.modal.run"
        }
      ],
      "post": {
        "operationId": "createTranscriptionOpenAI",
        "summary": "Transcribe speech (OpenAI-compatible)",
        "description": "Drop-in replacement for OpenAI's transcription endpoint \u2014 an existing client changes only its `baseURL`. `language`, `prompt` and `temperature` are accepted and ignored: the model shares one Kazakh+Russian vocabulary, so it is never told which language to expect. `srt` and `vtt` return 400, because a character-CTC model produces no word timings.",
        "tags": [
          "Speech-to-text"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The audio to transcribe."
                  },
                  "model": {
                    "type": "string",
                    "default": "seta-kk-ru-v2"
                  },
                  "response_format": {
                    "type": "string",
                    "enum": [
                      "json",
                      "text",
                      "verbose_json",
                      "srt",
                      "vtt"
                    ],
                    "default": "json",
                    "description": "`json` (default) returns `{text}`. `text` returns the bare transcript. `verbose_json` adds `segments` and `words`. `srt` and `vtt` return subtitles."
                  },
                  "language": {
                    "type": "string",
                    "description": "Accepted and ignored."
                  },
                  "prompt": {
                    "type": "string",
                    "description": "Accepted and ignored."
                  },
                  "temperature": {
                    "type": "number",
                    "description": "Accepted and ignored."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OpenAI-shaped transcription.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "text"
                  ],
                  "properties": {
                    "text": {
                      "type": "string"
                    }
                  }
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "No audio, or an unsupported `response_format` (`srt`/`vtt`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Out of credits \u2014 top up in the console.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://geko--seta-serve-transcriber-api.modal.run",
            "description": "Speech-to-text service (scaled separately from text-to-speech)"
          }
        ]
      }
    },
    "/v1/tts": {
      "post": {
        "operationId": "createSpeech",
        "summary": "Synthesize speech",
        "description": "Synthesize text into speech. Returns raw **WAV** bytes (`audio/wav`, 24 kHz, 16-bit PCM, mono). The `X-Tokay-Chars` response header reports the billed character count.",
        "tags": [
          "Speech"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TtsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "WAV audio.",
            "headers": {
              "X-Tokay-Chars": {
                "description": "Billed character count for this request.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "audio/wav": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/OutOfCredits"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "TypeScript",
            "label": "SDK",
            "source": "import { Geko } from \"@gekoai/sdk\";\nimport { writeFile } from \"node:fs/promises\";\n\nconst geko = new Geko({ apiKey: process.env.GEKO_API_KEY });\n\nconst audio = await geko.tts.create({\n  text: \"\u0421\u04d9\u043b\u0435\u043c\u0435\u0442\u0441\u0456\u0437 \u0431\u0435! \u0422\u0430\u043f\u0441\u044b\u0440\u044b\u0441 \u043d\u04e9\u043c\u0456\u0440\u0456 152.\",\n  voice: \"Aigerim\",\n});\n\nawait writeFile(\"hello.wav\", Buffer.from(audio));"
          },
          {
            "lang": "Python",
            "label": "requests",
            "source": "import os, requests\n\nres = requests.post(\n    \"https://geko--tokay-serve-web.modal.run/v1/tts\",\n    headers={\"Authorization\": f\"Bearer {os.environ['GEKO_API_KEY']}\"},\n    json={\"text\": \"\u0421\u04d9\u043b\u0435\u043c\u0435\u0442\u0441\u0456\u0437 \u0431\u0435! \u0422\u0430\u043f\u0441\u044b\u0440\u044b\u0441 \u043d\u04e9\u043c\u0456\u0440\u0456 152.\", \"voice\": \"Aigerim\"},\n)\nres.raise_for_status()\nwith open(\"hello.wav\", \"wb\") as f:\n    f.write(res.content)"
          }
        ]
      }
    },
    "/v1/tts/stream": {
      "post": {
        "operationId": "streamSpeech",
        "summary": "Stream speech sentence-by-sentence",
        "description": "Same request body as `POST /v1/tts`, but the response is delivered as each sentence finishes synthesizing \u2014 lower time-to-first-audio on long text. Auth and metering are identical to `/v1/tts`.\n\nThe body is a sequence of **frames**: a 4-byte big-endian length, followed by that many bytes of one complete WAV. Read a length, read that many bytes (one playable WAV chunk), repeat until the stream closes. The `X-Tokay-Stream: wav-frames-v1` header identifies the framing. Most callers should use the SDK's `geko.tts.stream()`, which wraps this as an async iterable \u2014 see the [streaming guide](/guides/streaming).",
        "tags": [
          "Speech"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TtsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A stream of length-prefixed WAV frames (`wav-frames-v1`).",
            "headers": {
              "X-Tokay-Stream": {
                "description": "Framing identifier. Always `wav-frames-v1`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/OutOfCredits"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "TypeScript",
            "label": "SDK",
            "source": "import { Geko } from \"@gekoai/sdk\";\n\nconst geko = new Geko({ apiKey: process.env.GEKO_API_KEY });\n\nfor await (const wav of geko.tts.stream({\n  text: longText,\n  voice: \"Aigerim\",\n})) {\n  // each `wav` is a standalone, playable WAV chunk\n  play(wav);\n}"
          }
        ]
      }
    },
    "/v1/audio/speech": {
      "post": {
        "operationId": "createSpeechOpenAICompatible",
        "summary": "OpenAI-compatible speech",
        "description": "OpenAI-compatible alias \u2014 point the OpenAI SDK at geko by swapping the base URL and model. Maps OpenAI's request shape onto the Tokay engine. See the [OpenAI compatibility guide](/guides/openai).",
        "tags": [
          "Speech"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SpeechRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "WAV audio (or raw PCM when `response_format` is `pcm`).",
            "content": {
              "audio/wav": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/OutOfCredits"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Python",
            "label": "openai",
            "source": "from openai import OpenAI\n\nclient = OpenAI(\n    base_url=\"https://geko--tokay-serve-web.modal.run/v1\",\n    api_key=os.environ[\"GEKO_API_KEY\"],\n)\n\nres = client.audio.speech.create(\n    model=\"tts-1\",\n    voice=\"Aigerim\",\n    input=\"\u0421\u04d9\u043b\u0435\u043c\u0435\u0442\u0441\u0456\u0437 \u0431\u0435!\",\n    response_format=\"wav\",\n)\nres.write_to_file(\"hello.wav\")"
          }
        ]
      }
    },
    "/v1/models": {
      "get": {
        "operationId": "listModels",
        "summary": "List models",
        "description": "List the models on the platform and their voices. **Open** \u2014 no API key required.",
        "tags": [
          "Catalog"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The model catalog.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelsResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/voices": {
      "get": {
        "operationId": "listVoices",
        "summary": "List voices",
        "description": "The voice catalog for a model. **Open** \u2014 no API key required.",
        "tags": [
          "Catalog"
        ],
        "security": [],
        "parameters": [
          {
            "name": "model",
            "in": "query",
            "required": false,
            "description": "Model id. Defaults to `tokay-kk-v1`.",
            "schema": {
              "type": "string",
              "default": "tokay-kk-v1"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The voice catalog for the requested model.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoicesResponse"
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "health",
        "summary": "Service status",
        "description": "Liveness probe. **Open** \u2014 no API key required.",
        "tags": [
          "Catalog"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your geko API key (`sk-tokay-\u2026`). Create and manage keys in the [console](https://app.geko.sh)."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Bad request \u2014 missing `text`, malformed body, or an invalid value.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "detail": "text is required"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "detail": "invalid API key"
            }
          }
        }
      },
      "NotFound": {
        "description": "Unknown model or voice.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "detail": "unknown voice"
            }
          }
        }
      },
      "OutOfCredits": {
        "description": "Out of credits \u2014 top up in the console. Not retried by the SDK.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "detail": "out of credits \u2014 top up in the geko console"
            }
          }
        }
      },
      "Unavailable": {
        "description": "Auth backend temporarily unavailable (transient \u2014 retry).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "detail": "service temporarily unavailable"
            }
          }
        }
      }
    },
    "schemas": {
      "TtsRequest": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "description": "Text to synthesize (up to ~5000 characters).",
            "example": "\u0421\u04d9\u043b\u0435\u043c\u0435\u0442\u0441\u0456\u0437 \u0431\u0435! \u0422\u0430\u043f\u0441\u044b\u0440\u044b\u0441 \u043d\u04e9\u043c\u0456\u0440\u0456 152, \u0441\u043e\u043c\u0430\u0441\u044b 5500 \u20b8."
          },
          "model": {
            "type": "string",
            "default": "tokay-kk-v1",
            "description": "Model id."
          },
          "voice": {
            "type": "string",
            "default": "Aigerim",
            "description": "Voice name from `GET /v1/voices`. Defaults to the model's default voice."
          },
          "speed": {
            "type": "number",
            "default": 1.0,
            "minimum": 0.5,
            "maximum": 2,
            "description": "Speed multiplier."
          },
          "nfe": {
            "type": "integer",
            "default": 32,
            "description": "Diffusion steps: 16 fast, 32 quality."
          },
          "normalize": {
            "type": "boolean",
            "default": true,
            "description": "Expand numbers, currency, and dates into spoken Kazakh."
          }
        }
      },
      "SpeechRequest": {
        "type": "object",
        "required": [
          "input"
        ],
        "properties": {
          "input": {
            "type": "string",
            "description": "Text to synthesize.",
            "example": "\u0421\u04d9\u043b\u0435\u043c\u0435\u0442\u0441\u0456\u0437 \u0431\u0435!"
          },
          "model": {
            "type": "string",
            "default": "tokay-kk-v1",
            "description": "OpenAI ids (`tts-1`, `tts-1-hd`, `gpt-4o-mini-tts`) are accepted and mapped to the default."
          },
          "voice": {
            "type": "string",
            "default": "Aigerim",
            "description": "Voice name."
          },
          "response_format": {
            "type": "string",
            "default": "wav",
            "enum": [
              "wav",
              "pcm"
            ],
            "description": "`wav` or `pcm` (raw 16-bit PCM @ 24 kHz). `mp3`/`opus`/`aac`/`flac` return 400 (not transcoded yet)."
          },
          "speed": {
            "type": "number",
            "default": 1.0,
            "description": "Speed multiplier."
          }
        }
      },
      "Transcription": {
        "type": "object",
        "required": [
          "text",
          "model",
          "audio_seconds",
          "credits_charged"
        ],
        "properties": {
          "text": {
            "type": "string",
            "description": "The transcript. Lowercase and unpunctuated \u2014 the model's vocabulary contains neither, so nothing is being stripped.",
            "example": "\u0441\u04d9\u043b\u0435\u043c\u0435\u0442\u0441\u0456\u0437 \u0431\u0435 \u0431\u04af\u0433\u0456\u043d \u0430\u0443\u0430 \u0440\u0430\u0439\u044b \u04e9\u0442\u0435 \u0436\u0430\u049b\u0441\u044b"
          },
          "model": {
            "type": "string",
            "example": "seta-kk-ru-v2"
          },
          "audio_seconds": {
            "type": "number",
            "description": "Duration of the submitted audio. This is the billed quantity.",
            "example": 2.84
          },
          "processing_seconds": {
            "type": "number",
            "example": 0.153
          },
          "rtf": {
            "type": "number",
            "nullable": true,
            "description": "Real-time factor: processing_seconds / audio_seconds. Lower is faster.",
            "example": 0.0539
          },
          "x_realtime": {
            "type": "number",
            "nullable": true,
            "description": "How many times faster than real time this ran.",
            "example": 18.5
          },
          "chunks": {
            "type": "integer",
            "description": "How many pieces the audio was split into at pause boundaries.",
            "example": 1
          },
          "sample_rate_in": {
            "type": "integer",
            "description": "Sample rate of the submitted audio, before resampling to 16 kHz.",
            "example": 24000
          },
          "credits_charged": {
            "type": "integer",
            "description": "Credits billed: ceil(audio_seconds \u00d7 2.5).",
            "example": 8
          },
          "words": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Word"
            },
            "description": "Every word with start/end. Present only when `timestamps=true`."
          },
          "segments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TranscriptSegment"
            },
            "description": "Transcript grouped into runs of speech. Present only when `timestamps=true`."
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "detail": {
            "type": "string",
            "description": "Human-readable error message."
          }
        }
      },
      "Model": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "example": "tokay-kk-v1"
          },
          "lang": {
            "type": "string",
            "example": "kk"
          },
          "sample_rate": {
            "type": "integer",
            "example": 24000
          },
          "voices": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "Aigerim",
              "Arman",
              "Ainur",
              "Aruzhan",
              "Sanzhar",
              "Yerlan"
            ]
          },
          "default_voice": {
            "type": "string",
            "example": "Aigerim"
          }
        }
      },
      "ModelsResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Model"
            }
          }
        }
      },
      "Voice": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "example": "Aigerim"
          },
          "gender": {
            "type": "string",
            "nullable": true,
            "example": "female"
          },
          "style": {
            "type": "string",
            "nullable": true,
            "example": "Warm, professional"
          },
          "best_for": {
            "type": "string",
            "nullable": true,
            "example": "Reception, customer support"
          }
        }
      },
      "VoicesResponse": {
        "type": "object",
        "properties": {
          "model": {
            "type": "string",
            "example": "tokay-kk-v1"
          },
          "voices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Voice"
            }
          }
        }
      },
      "HealthResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "ok"
          },
          "models": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "tokay-kk-v1"
            ]
          }
        }
      },
      "Word": {
        "type": "object",
        "required": [
          "word",
          "start",
          "end"
        ],
        "description": "One word, located in time.",
        "properties": {
          "word": {
            "type": "string",
            "description": "The word. Lowercase and unpunctuated, like the rest of the transcript.",
            "example": "\u0441\u04d9\u043b\u0435\u043c\u0435\u0442\u0441\u0456\u0437"
          },
          "start": {
            "type": "number",
            "format": "float",
            "description": "Seconds from the start of the audio.",
            "example": 0.2
          },
          "end": {
            "type": "number",
            "format": "float",
            "description": "Seconds from the start of the audio.",
            "example": 0.72
          }
        }
      },
      "TranscriptSegment": {
        "type": "object",
        "required": [
          "id",
          "text",
          "start",
          "end"
        ],
        "description": "A run of speech between two pauses. Use these for subtitles.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Zero-based index, in playback order.",
            "example": 0
          },
          "text": {
            "type": "string",
            "example": "\u0441\u04d9\u043b\u0435\u043c\u0435\u0442\u0441\u0456\u0437 \u0431\u0435"
          },
          "start": {
            "type": "number",
            "format": "float",
            "description": "The first word's start.",
            "example": 0.2
          },
          "end": {
            "type": "number",
            "format": "float",
            "description": "The last word's end.",
            "example": 1.12
          }
        }
      }
    }
  }
}
