openapi: 3.1.0
info:
  title: AI Gateway
  description: "Panggil model dari berbagai provider lewat satu base URL dan satu token APIPedia, memakai SDK resmi OpenAI (Authorization: Bearer) atau Anthropic (x-api-key). Mendukung chat completions dengan streaming, Anthropic Messages, embeddings, pembuatan gambar, text-to-speech, transkripsi audio, rerank, dan pembuatan video (asinkron: kirim job, polling, unduh MP4). Daftar model beserta harganya ada di GET /models. Model berbayar memotong kredit AI (dibeli dengan coin di menu Saldo) sesuai biaya pemakaian; model gratis bisa dipakai tanpa kredit dengan batas jumlah request harian per pengguna."
  version: 1.2.3
servers:
- url: https://api.apipedia.id/v1/ai
  description: "Satu API kompatibel OpenAI dan Anthropic untuk banyak model AI: chat, embedding, gambar, suara, rerank, dan video."
tags:
- name: AI
  description: "AI Gateway kompatibel SDK OpenAI dan Anthropic: chat (dengan streaming), messages, embeddings, gambar, suara, transkripsi, rerank, video (asinkron). Auth `Authorization: Bearer apk_...` atau `x-api-key: apk_...`. Respons memakai format provider, tanpa amplop."
security:
- bearerJWT: []
paths:
  /audio/speech:
    post:
      operationId: createSpeech
      summary: Text-to-speech (format OpenAI)
      description: Kompatibel dengan OpenAI Audio Speech (`client.audio.speech.create`). Respons berupa berkas audio biner (mis. `audio/mpeg`) yang diteruskan apa adanya dari provider, bukan JSON. `model` = public id model ber-modality `speech`. Biaya memakai harga per request model.
      tags:
      - AI
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              model: elevenlabs/eleven-multilingual-v2
              input: Selamat datang di APIPedia.
              voice: alloy
              response_format: mp3
      responses:
        "200":
          description: Berhasil. Body format provider apa adanya, tanpa amplop.
          content:
            application/json:
              schema:
                type: object
              example: <biner audio/mpeg>
        "400":
          description: Body tidak valid, atau model milik provider Anthropic (pakai POST /ai/messages)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"bansos/claude-haiku-4-5-20251001\" uses the Anthropic format. Call POST /v1/ai/messages."
                  type: invalid_request_error
        "401":
          description: Token tidak ada, tidak dikenal, kedaluwarsa, atau akun disuspend
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Unauthorized: invalid or expired token"
                data: null
        "402":
          description: Model berbayar dan saldo kredit AI habis
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: AI credits have run out. Buy AI credits in the Balance menu.
                  type: insufficient_credits
        "403":
          description: API key valid tetapi cakupannya (scopes) tidak mencakup endpoint ini
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Forbidden: this API key has not been granted access to that endpoint. Change its scopes on the Tokens page."
                data: null
        "404":
          description: "`model` bukan public id yang ada di GET /ai/models"
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"gpt-x\" is not available. Use a name from GET /v1/ai/models, or specify its provider as \"<provider>/gpt-x\"."
                  type: invalid_request_error
        "429":
          description: Kuota request gratis hari ini habis (model gratis, reset 00.00 WIB)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Today's free quota is used up (10 requests). Top up AI credits to continue."
                  type: rate_limit_error
        "500":
          description: Kesalahan internal; detail tidak pernah dibocorkan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: An internal server error occurred.
                data: null
        "502":
          description: Sumber data hulu (BMKG, ECB, OpenStreetMap, Composio, Tripay, mesin OCR) gagal
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: The earthquake data source (BMKG) cannot be reached right now.
                data: null
  /audio/transcriptions:
    post:
      operationId: createTranscription
      summary: Transkripsi audio (format OpenAI)
      description: "Kompatibel dengan OpenAI Audio Transcriptions (`client.audio.transcriptions.create`). Body `multipart/form-data` diteruskan apa adanya ke provider; hanya field `model` yang ditukar ke id model di provider. Field: `file` (berkas audio), `model` (public id ber-modality `transcription`), opsional `language` (mis. `id`) dan `response_format`."
      tags:
      - AI
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              file: "@rekaman.m4a"
              model: openai/whisper-1
              language: id
      responses:
        "200":
          description: Berhasil. Body format provider apa adanya, tanpa amplop.
          content:
            application/json:
              schema:
                type: object
              example:
                text: Halo, ini rekaman uji coba transkripsi.
        "400":
          description: Body tidak valid, atau model milik provider Anthropic (pakai POST /ai/messages)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"bansos/claude-haiku-4-5-20251001\" uses the Anthropic format. Call POST /v1/ai/messages."
                  type: invalid_request_error
        "401":
          description: Token tidak ada, tidak dikenal, kedaluwarsa, atau akun disuspend
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Unauthorized: invalid or expired token"
                data: null
        "402":
          description: Model berbayar dan saldo kredit AI habis
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: AI credits have run out. Buy AI credits in the Balance menu.
                  type: insufficient_credits
        "403":
          description: API key valid tetapi cakupannya (scopes) tidak mencakup endpoint ini
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Forbidden: this API key has not been granted access to that endpoint. Change its scopes on the Tokens page."
                data: null
        "404":
          description: "`model` bukan public id yang ada di GET /ai/models"
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"gpt-x\" is not available. Use a name from GET /v1/ai/models, or specify its provider as \"<provider>/gpt-x\"."
                  type: invalid_request_error
        "429":
          description: Kuota request gratis hari ini habis (model gratis, reset 00.00 WIB)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Today's free quota is used up (10 requests). Top up AI credits to continue."
                  type: rate_limit_error
        "500":
          description: Kesalahan internal; detail tidak pernah dibocorkan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: An internal server error occurred.
                data: null
        "502":
          description: Sumber data hulu (BMKG, ECB, OpenStreetMap, Composio, Tripay, mesin OCR) gagal
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: The earthquake data source (BMKG) cannot be reached right now.
                data: null
  /chat/completions:
    post:
      operationId: createChatCompletion
      summary: Chat completions (format OpenAI)
      description: |-
        Kompatibel penuh dengan OpenAI Chat Completions: arahkan SDK OpenAI ke base URL `https://api.apipedia.id/v1/ai` dan pakai API key APIPedia sebagai `apiKey` (dikirim sebagai `Authorization: Bearer apk_...`). Respons adalah body provider apa adanya, TANPA amplop { is_success, message, data }.

        `model` wajib berisi public id dari GET /ai/models; model yang tidak ada dijawab 404 `invalid_request_error` dengan pesan `Model "<x>" is not available. Use a name from GET /v1/ai/models, or specify its provider as "<provider>/<x>".`. Hanya model dari provider bertipe OpenAI/OpenRouter yang dilayani di sini; model Anthropic dipanggil lewat POST /ai/messages (400 bila tertukar).

        **Streaming**: kirim `stream: true` untuk menerima Server-Sent Events (`data: {...}` per chunk, diakhiri `data: [DONE]`) yang diteruskan byte-per-byte dari provider. Pemakaian token dihitung dari chunk terakhir, jadi tagihan dicatat setelah stream selesai.

        **Biaya**: model gratis tidak memotong kredit tetapi dihitung ke kuota harian (habis -> 429 `Today's free quota is used up (10 requests). Top up AI credits to continue.`). Model berbayar butuh saldo kredit AI > 0 (402 `AI credits have run out. Buy AI credits in the Balance menu.`); biayanya `usage.cost` dari provider bila ada, atau token x harga per 1M + harga per request.
      tags:
      - AI
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              model: openai/gpt-4o-mini
              messages:
              - role: system
                content: Kamu asisten yang menjawab singkat dalam bahasa Indonesia.
              - role: user
                content: "Ringkas paragraf berikut dalam satu kalimat: ..."
              temperature: 0.3
              stream: false
      responses:
        "200":
          description: Berhasil. Body format provider apa adanya, tanpa amplop.
          content:
            application/json:
              schema:
                type: object
              example:
                id: chatcmpl-AbC123
                object: chat.completion
                created: 1761707700
                model: openai/gpt-4o-mini
                choices:
                - index: 0
                  message:
                    role: assistant
                    content: Berikut ringkasannya dalam satu kalimat.
                  finish_reason: stop
                usage:
                  prompt_tokens: 38
                  completion_tokens: 12
                  total_tokens: 50
        "400":
          description: Body tidak valid, atau model milik provider Anthropic (pakai POST /ai/messages)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"bansos/claude-haiku-4-5-20251001\" uses the Anthropic format. Call POST /v1/ai/messages."
                  type: invalid_request_error
        "401":
          description: Token tidak ada, tidak dikenal, kedaluwarsa, atau akun disuspend
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Unauthorized: invalid or expired token"
                data: null
        "402":
          description: Model berbayar dan saldo kredit AI habis
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: AI credits have run out. Buy AI credits in the Balance menu.
                  type: insufficient_credits
        "403":
          description: API key valid tetapi cakupannya (scopes) tidak mencakup endpoint ini
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Forbidden: this API key has not been granted access to that endpoint. Change its scopes on the Tokens page."
                data: null
        "404":
          description: "`model` bukan public id yang ada di GET /ai/models"
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"gpt-x\" is not available. Use a name from GET /v1/ai/models, or specify its provider as \"<provider>/gpt-x\"."
                  type: invalid_request_error
        "429":
          description: Kuota request gratis hari ini habis (model gratis, reset 00.00 WIB)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Today's free quota is used up (10 requests). Top up AI credits to continue."
                  type: rate_limit_error
        "500":
          description: Kesalahan internal; detail tidak pernah dibocorkan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: An internal server error occurred.
                data: null
        "502":
          description: Sumber data hulu (BMKG, ECB, OpenStreetMap, Composio, Tripay, mesin OCR) gagal
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: The earthquake data source (BMKG) cannot be reached right now.
                data: null
  /embeddings:
    post:
      operationId: createEmbedding
      summary: Embeddings (format OpenAI)
      description: Kompatibel dengan OpenAI Embeddings (`client.embeddings.create`). `model` = public id model ber-modality `embedding` dari GET /ai/models. Respons body provider apa adanya. Biaya dihitung dari `usage.prompt_tokens` x harga input per 1M.
      tags:
      - AI
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              model: openai/text-embedding-3-small
              input:
              - Kota Bandung
              - Ibu kota Jawa Barat
      responses:
        "200":
          description: Berhasil. Body format provider apa adanya, tanpa amplop.
          content:
            application/json:
              schema:
                type: object
              example:
                object: list
                data:
                - object: embedding
                  index: 0
                  embedding:
                  - 0.0123
                  - -0.0456
                  - 0.0789
                - object: embedding
                  index: 1
                  embedding:
                  - 0.0111
                  - -0.0422
                  - 0.0801
                model: openai/text-embedding-3-small
                usage:
                  prompt_tokens: 8
                  total_tokens: 8
        "400":
          description: Body tidak valid, atau model milik provider Anthropic (pakai POST /ai/messages)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"bansos/claude-haiku-4-5-20251001\" uses the Anthropic format. Call POST /v1/ai/messages."
                  type: invalid_request_error
        "401":
          description: Token tidak ada, tidak dikenal, kedaluwarsa, atau akun disuspend
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Unauthorized: invalid or expired token"
                data: null
        "402":
          description: Model berbayar dan saldo kredit AI habis
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: AI credits have run out. Buy AI credits in the Balance menu.
                  type: insufficient_credits
        "403":
          description: API key valid tetapi cakupannya (scopes) tidak mencakup endpoint ini
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Forbidden: this API key has not been granted access to that endpoint. Change its scopes on the Tokens page."
                data: null
        "404":
          description: "`model` bukan public id yang ada di GET /ai/models"
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"gpt-x\" is not available. Use a name from GET /v1/ai/models, or specify its provider as \"<provider>/gpt-x\"."
                  type: invalid_request_error
        "429":
          description: Kuota request gratis hari ini habis (model gratis, reset 00.00 WIB)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Today's free quota is used up (10 requests). Top up AI credits to continue."
                  type: rate_limit_error
        "500":
          description: Kesalahan internal; detail tidak pernah dibocorkan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: An internal server error occurred.
                data: null
        "502":
          description: Sumber data hulu (BMKG, ECB, OpenStreetMap, Composio, Tripay, mesin OCR) gagal
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: The earthquake data source (BMKG) cannot be reached right now.
                data: null
  /images/generations:
    post:
      operationId: createImage
      summary: Buat gambar (format OpenAI)
      description: Kompatibel dengan OpenAI Images (`client.images.generate`). `model` = public id model ber-modality `image`. Biaya memakai harga per request model (ditambah token bila provider melaporkannya). Respons body provider apa adanya (`url` atau `b64_json`).
      tags:
      - AI
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              model: openai/gpt-image-1
              prompt: Ilustrasi flat warung kopi di Yogyakarta saat senja
              size: 1024x1024
      responses:
        "200":
          description: Berhasil. Body format provider apa adanya, tanpa amplop.
          content:
            application/json:
              schema:
                type: object
              example:
                created: 1761707700
                data:
                - b64_json: iVBORw0KGgoAAAANSUhEUgAA...
        "400":
          description: Body tidak valid, atau model milik provider Anthropic (pakai POST /ai/messages)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"bansos/claude-haiku-4-5-20251001\" uses the Anthropic format. Call POST /v1/ai/messages."
                  type: invalid_request_error
        "401":
          description: Token tidak ada, tidak dikenal, kedaluwarsa, atau akun disuspend
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Unauthorized: invalid or expired token"
                data: null
        "402":
          description: Model berbayar dan saldo kredit AI habis
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: AI credits have run out. Buy AI credits in the Balance menu.
                  type: insufficient_credits
        "403":
          description: API key valid tetapi cakupannya (scopes) tidak mencakup endpoint ini
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Forbidden: this API key has not been granted access to that endpoint. Change its scopes on the Tokens page."
                data: null
        "404":
          description: "`model` bukan public id yang ada di GET /ai/models"
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"gpt-x\" is not available. Use a name from GET /v1/ai/models, or specify its provider as \"<provider>/gpt-x\"."
                  type: invalid_request_error
        "429":
          description: Kuota request gratis hari ini habis (model gratis, reset 00.00 WIB)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Today's free quota is used up (10 requests). Top up AI credits to continue."
                  type: rate_limit_error
        "500":
          description: Kesalahan internal; detail tidak pernah dibocorkan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: An internal server error occurred.
                data: null
        "502":
          description: Sumber data hulu (BMKG, ECB, OpenStreetMap, Composio, Tripay, mesin OCR) gagal
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: The earthquake data source (BMKG) cannot be reached right now.
                data: null
  /messages:
    post:
      operationId: createAnthropicMessage
      summary: Messages (format Anthropic)
      description: |-
        Kompatibel dengan Anthropic Messages API. Dengan SDK Anthropic, set `baseURL` ke `https://api.apipedia.id/v1/ai` dan `apiKey` ke API key APIPedia: SDK mengirimnya sebagai header `x-api-key`, yang diterima gateway sama seperti `Authorization: Bearer`. Header `anthropic-version` boleh dikirim; gateway selalu memakai `2023-06-01` ke provider. Respons adalah body provider apa adanya (tanpa amplop).

        `model` wajib berisi public id model ber-provider Anthropic dari GET /ai/models. `stream: true` mengembalikan SSE Anthropic (`message_start`, `content_block_delta`, `message_delta`, `message_stop`); pemakaian token dibaca dari `message_start` dan `message_delta` lalu ditagihkan setelah stream selesai.

        Error memakai format Anthropic `{ type: "error", error: { type, message } }`: 402 kredit AI habis, 429 kuota gratis harian habis, 404 model tidak tersedia.
      tags:
      - AI
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              model: bansos/claude-haiku-4-5-20251001
              max_tokens: 512
              messages:
              - role: user
                content: Tulis tiga ide judul artikel tentang UMKM digital.
      responses:
        "200":
          description: Berhasil. Body format provider apa adanya, tanpa amplop.
          content:
            application/json:
              schema:
                type: object
              example:
                id: msg_01AbCdEf
                type: message
                role: assistant
                model: bansos/claude-haiku-4-5-20251001
                content:
                - type: text
                  text: "1. UMKM Go Digital: ..."
                stop_reason: end_turn
                usage:
                  input_tokens: 22
                  output_tokens: 64
        "400":
          description: Parameter atau body tidak valid. `message` menyebut field yang salah.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Field \"text\" is required."
                data: null
        "401":
          description: Token tidak ada, tidak dikenal, kedaluwarsa, atau akun disuspend
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Unauthorized: invalid or expired token"
                data: null
        "402":
          description: Model berbayar dan saldo kredit AI habis
          content:
            application/json:
              schema:
                type: object
              example:
                type: error
                error:
                  type: insufficient_credits
                  message: AI credits have run out. Buy AI credits in the Balance menu.
        "403":
          description: API key valid tetapi cakupannya (scopes) tidak mencakup endpoint ini
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Forbidden: this API key has not been granted access to that endpoint. Change its scopes on the Tokens page."
                data: null
        "404":
          description: Model tidak ada di GET /ai/models
          content:
            application/json:
              schema:
                type: object
              example:
                type: error
                error:
                  type: not_found_error
                  message: "Model \"claude-x\" is not available. Use a name from GET /v1/ai/models, or specify its provider as \"<provider>/claude-x\"."
        "429":
          description: Kuota request gratis hari ini habis (model gratis)
          content:
            application/json:
              schema:
                type: object
              example:
                type: error
                error:
                  type: rate_limit_error
                  message: "Today's free quota is used up (10 requests). Top up AI credits to continue."
        "500":
          description: Kesalahan internal; detail tidak pernah dibocorkan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: An internal server error occurred.
                data: null
        "502":
          description: Sumber data hulu (BMKG, ECB, OpenStreetMap, Composio, Tripay, mesin OCR) gagal
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: The earthquake data source (BMKG) cannot be reached right now.
                data: null
  /models:
    get:
      operationId: listAiModels
      summary: Daftar model AI
      description: |-
        Format OpenAI `GET /models` (tanpa amplop { is_success, message, data }) sehingga bisa dibaca SDK resmi (`client.models.list()`). Field `model` di semua endpoint AI WAJIB berisi `id` dari daftar ini (public id APIPedia), bukan nama model di provider.

        Tambahan per model: `modality` (text, image, embedding, speech, transcription, rerank, video, audio), `context_length`, `pricing` (`input_per_1m`/`output_per_1m` per 1 juta token dan `request` per panggilan, dalam USD = kredit AI), dan `free`.

        Model `free: true` bisa dipakai tanpa kredit, dibatasi sejumlah request per pengguna per hari (bawaan 10, gabungan semua model gratis, reset 00.00 WIB). Model berbayar memotong kredit AI (1 kredit = 1 USD biaya provider) yang dibeli dengan coin lewat POST /credits/buy.
      tags:
      - AI
      responses:
        "200":
          description: Berhasil. Body format provider apa adanya, tanpa amplop.
          content:
            application/json:
              schema:
                type: object
              example:
                object: list
                data:
                - id: openai/gpt-4o-mini
                  object: model
                  created: 1721172741
                  owned_by: apipedia
                  modality: text
                  context_length: 128000
                  pricing:
                    input_per_1m: 0.15
                    output_per_1m: 0.6
                    request: 0
                    credits_unit: USD
                  free: false
                - id: meta-llama/llama-3.3-70b-instruct:free
                  object: model
                  created: 1733506137
                  owned_by: apipedia
                  modality: text
                  context_length: 131072
                  pricing:
                    input_per_1m: 0
                    output_per_1m: 0
                    request: 0
                    credits_unit: USD
                  free: true
        "400":
          description: Parameter atau body tidak valid. `message` menyebut field yang salah.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Field \"text\" is required."
                data: null
        "401":
          description: Token tidak ada, tidak dikenal, kedaluwarsa, atau akun disuspend
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Unauthorized: invalid or expired token"
                data: null
        "403":
          description: API key valid tetapi cakupannya (scopes) tidak mencakup endpoint ini
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Forbidden: this API key has not been granted access to that endpoint. Change its scopes on the Tokens page."
                data: null
        "404":
          description: Path tidak dikenal atau sumber daya tidak ditemukan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: Endpoint GET /v1/contoh not found.
                data: null
        "429":
          description: Cadangan untuk rate limit/kuota harian per paket (belum ditegakkan server saat ini)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: Daily quota exhausted. Please try again after 00:00 WIB (UTC+7).
                data: null
        "500":
          description: Kesalahan internal; detail tidak pernah dibocorkan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: An internal server error occurred.
                data: null
        "502":
          description: Sumber data hulu (BMKG, ECB, OpenStreetMap, Composio, Tripay, mesin OCR) gagal
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: The earthquake data source (BMKG) cannot be reached right now.
                data: null
  /rerank:
    post:
      operationId: rerankDocuments
      summary: Rerank dokumen
      description: Mengurutkan `documents` menurut relevansinya terhadap `query`. Body `{ model, query, documents }` (plus field tambahan yang dikenali provider, mis. `top_n`) diteruskan apa adanya ke provider; `model` = public id ber-modality `rerank`. Respons body provider apa adanya.
      tags:
      - AI
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              model: cohere/rerank-v3.5
              query: cara bayar pajak kendaraan online
              documents:
              - Samsat Digital Nasional (SIGNAL) ...
              - Resep rendang padang ...
              - e-Samsat via mobile banking ...
              top_n: 2
      responses:
        "200":
          description: Berhasil. Body format provider apa adanya, tanpa amplop.
          content:
            application/json:
              schema:
                type: object
              example:
                results:
                - index: 0
                  relevance_score: 0.94
                - index: 2
                  relevance_score: 0.88
        "400":
          description: Body tidak valid, atau model milik provider Anthropic (pakai POST /ai/messages)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"bansos/claude-haiku-4-5-20251001\" uses the Anthropic format. Call POST /v1/ai/messages."
                  type: invalid_request_error
        "401":
          description: Token tidak ada, tidak dikenal, kedaluwarsa, atau akun disuspend
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Unauthorized: invalid or expired token"
                data: null
        "402":
          description: Model berbayar dan saldo kredit AI habis
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: AI credits have run out. Buy AI credits in the Balance menu.
                  type: insufficient_credits
        "403":
          description: API key valid tetapi cakupannya (scopes) tidak mencakup endpoint ini
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Forbidden: this API key has not been granted access to that endpoint. Change its scopes on the Tokens page."
                data: null
        "404":
          description: "`model` bukan public id yang ada di GET /ai/models"
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"gpt-x\" is not available. Use a name from GET /v1/ai/models, or specify its provider as \"<provider>/gpt-x\"."
                  type: invalid_request_error
        "429":
          description: Kuota request gratis hari ini habis (model gratis, reset 00.00 WIB)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Today's free quota is used up (10 requests). Top up AI credits to continue."
                  type: rate_limit_error
        "500":
          description: Kesalahan internal; detail tidak pernah dibocorkan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: An internal server error occurred.
                data: null
        "502":
          description: Sumber data hulu (BMKG, ECB, OpenStreetMap, Composio, Tripay, mesin OCR) gagal
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: The earthquake data source (BMKG) cannot be reached right now.
                data: null
  /videos:
    post:
      operationId: createVideo
      summary: Buat video (asinkron)
      description: |-
        Mengirim job pembuatan video dan langsung dijawab **202** dengan `id` job; video dibuat di latar (biasanya 1-5 menit). Polling `GET /ai/videos/{id}` sampai `status` = `completed`, lalu unduh MP4 dari `GET /ai/videos/{id}/content`.

        Body: `model` (public id ber-modality `video`), `prompt` (maks 4.000 karakter), opsional `duration` (detik), `resolution` (mis. `720p`), `aspect_ratio` (mis. `16:9`), `size`, `generate_audio`, `seed`, `frame_images` (image-to-video: `[{ type: "image_url", image_url: { url }, frame_type: "first_frame" | "last_frame" }]`) dan `input_references` (gambar acuan). URL gambar harus `https://` atau `data:image/...;base64,` (maks ~8 MB, maks 4 gambar). Nilai yang tidak didukung model dijawab 400 beserta pilihannya (lihat GET /ai/videos/options).

        **Biaya.** Video ditagih per detik. Saat dikirim, **perkiraan** biaya langsung ditahan dari kredit AI (atau dompet hadiah provider, mis. Bansos); saldo kurang dari perkiraan dijawab 402. Setelah selesai, tagihan disesuaikan ke biaya nyata provider: kelebihan dikembalikan, kekurangan ditagih. Job yang gagal, dibatalkan, atau kedaluwarsa mengembalikan seluruh tahanan. Model gratis (id berakhiran `:free`) memakai kuota harian model gratis.
      tags:
      - AI
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              model: alibaba/happyhorse-1.0
              prompt: Drone melintas pelan di atas sawah terasering saat matahari terbit, sinematik
              duration: 5
              resolution: 720p
              aspect_ratio: "16:9"
      responses:
        "200":
          description: Berhasil. Body format provider apa adanya, tanpa amplop.
          content:
            application/json:
              schema:
                type: object
              example:
                id: 0f3c2a8e-4b1d-4c55-9a3e-2f7a1d9b0c11
                object: video.generation
                model: alibaba/happyhorse-1.0
                status: pending
                created_at: 2026-11-02T07:00:00Z
                completed_at: null
                polling_url: /v1/ai/videos/0f3c2a8e-4b1d-4c55-9a3e-2f7a1d9b0c11
                content_urls: []
                error: null
                usage:
                  estimate_usd: 0.494
                  cost_usd: null
                  credits_charged: 0.494
                  is_free: false
        "400":
          description: Body tidak valid, atau model milik provider Anthropic (pakai POST /ai/messages)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"bansos/claude-haiku-4-5-20251001\" uses the Anthropic format. Call POST /v1/ai/messages."
                  type: invalid_request_error
        "401":
          description: Token tidak ada, tidak dikenal, kedaluwarsa, atau akun disuspend
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Unauthorized: invalid or expired token"
                data: null
        "402":
          description: Model berbayar dan saldo kredit AI habis
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: AI credits have run out. Buy AI credits in the Balance menu.
                  type: insufficient_credits
        "403":
          description: API key valid tetapi cakupannya (scopes) tidak mencakup endpoint ini
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Forbidden: this API key has not been granted access to that endpoint. Change its scopes on the Tokens page."
                data: null
        "404":
          description: "`model` bukan public id yang ada di GET /ai/models"
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"gpt-x\" is not available. Use a name from GET /v1/ai/models, or specify its provider as \"<provider>/gpt-x\"."
                  type: invalid_request_error
        "429":
          description: Kuota request gratis hari ini habis (model gratis, reset 00.00 WIB)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Today's free quota is used up (10 requests). Top up AI credits to continue."
                  type: rate_limit_error
        "500":
          description: Kesalahan internal; detail tidak pernah dibocorkan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: An internal server error occurred.
                data: null
        "502":
          description: Sumber data hulu (BMKG, ECB, OpenStreetMap, Composio, Tripay, mesin OCR) gagal
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: The earthquake data source (BMKG) cannot be reached right now.
                data: null
  /videos/{id}:
    get:
      operationId: getVideo
      summary: Status job video
      description: "Status: `pending`, `in_progress`, `completed`, `failed`, `cancelled`, `expired`. Saat `completed`, `content_urls` berisi alamat unduhan dan `usage` memuat biaya nyata (`cost_usd`) serta kredit yang ditagih (`credits_charged`); sebelum selesai `credits_charged` adalah tahanan perkiraan. Polling tiap 5-10 detik. Hanya pemilik job yang bisa membacanya."
      tags:
      - AI
      parameters:
      - name: id
        in: path
        required: true
        description: Id job dari POST /ai/videos.
        schema:
          type: string
          example: 0f3c2a8e-4b1d-4c55-9a3e-2f7a1d9b0c11
      responses:
        "200":
          description: Berhasil. Body format provider apa adanya, tanpa amplop.
          content:
            application/json:
              schema:
                type: object
              example:
                id: 0f3c2a8e-4b1d-4c55-9a3e-2f7a1d9b0c11
                object: video.generation
                model: alibaba/happyhorse-1.0
                status: completed
                created_at: 2026-11-02T07:00:00Z
                completed_at: 2026-11-02T07:01:42Z
                polling_url: /v1/ai/videos/0f3c2a8e-4b1d-4c55-9a3e-2f7a1d9b0c11
                content_urls:
                - /v1/ai/videos/0f3c2a8e-4b1d-4c55-9a3e-2f7a1d9b0c11/content?index=0
                error: null
                usage:
                  estimate_usd: 0.494
                  cost_usd: 0.494
                  credits_charged: 0.494
                  is_free: false
        "400":
          description: Body tidak valid, atau model milik provider Anthropic (pakai POST /ai/messages)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"bansos/claude-haiku-4-5-20251001\" uses the Anthropic format. Call POST /v1/ai/messages."
                  type: invalid_request_error
        "401":
          description: Token tidak ada, tidak dikenal, kedaluwarsa, atau akun disuspend
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Unauthorized: invalid or expired token"
                data: null
        "402":
          description: Model berbayar dan saldo kredit AI habis
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: AI credits have run out. Buy AI credits in the Balance menu.
                  type: insufficient_credits
        "403":
          description: API key valid tetapi cakupannya (scopes) tidak mencakup endpoint ini
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Forbidden: this API key has not been granted access to that endpoint. Change its scopes on the Tokens page."
                data: null
        "404":
          description: "`model` bukan public id yang ada di GET /ai/models"
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"gpt-x\" is not available. Use a name from GET /v1/ai/models, or specify its provider as \"<provider>/gpt-x\"."
                  type: invalid_request_error
        "429":
          description: Kuota request gratis hari ini habis (model gratis, reset 00.00 WIB)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Today's free quota is used up (10 requests). Top up AI credits to continue."
                  type: rate_limit_error
        "500":
          description: Kesalahan internal; detail tidak pernah dibocorkan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: An internal server error occurred.
                data: null
        "502":
          description: Sumber data hulu (BMKG, ECB, OpenStreetMap, Composio, Tripay, mesin OCR) gagal
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: The earthquake data source (BMKG) cannot be reached right now.
                data: null
  /videos/{id}/content:
    get:
      operationId: getVideoContent
      summary: Unduh video (MP4)
      description: Berkas video biner (`video/mp4`) dari job yang sudah `completed`, diambil lewat gateway dengan kunci provider milik server. Query opsional `index` (bawaan 0) bila model menghasilkan lebih dari satu video. Job yang belum selesai dijawab 409.
      tags:
      - AI
      parameters:
      - name: id
        in: path
        required: true
        description: Id job dari POST /ai/videos.
        schema:
          type: string
          example: 0f3c2a8e-4b1d-4c55-9a3e-2f7a1d9b0c11
      - name: index
        in: query
        required: false
        description: Urutan keluaran, bawaan 0.
        schema:
          type: integer
          example: 0
      responses:
        "200":
          description: Berhasil. Body format provider apa adanya, tanpa amplop.
          content:
            application/json:
              schema:
                type: object
              example: <biner video/mp4>
        "400":
          description: Body tidak valid, atau model milik provider Anthropic (pakai POST /ai/messages)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"bansos/claude-haiku-4-5-20251001\" uses the Anthropic format. Call POST /v1/ai/messages."
                  type: invalid_request_error
        "401":
          description: Token tidak ada, tidak dikenal, kedaluwarsa, atau akun disuspend
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Unauthorized: invalid or expired token"
                data: null
        "402":
          description: Model berbayar dan saldo kredit AI habis
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: AI credits have run out. Buy AI credits in the Balance menu.
                  type: insufficient_credits
        "403":
          description: API key valid tetapi cakupannya (scopes) tidak mencakup endpoint ini
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Forbidden: this API key has not been granted access to that endpoint. Change its scopes on the Tokens page."
                data: null
        "404":
          description: "`model` bukan public id yang ada di GET /ai/models"
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"gpt-x\" is not available. Use a name from GET /v1/ai/models, or specify its provider as \"<provider>/gpt-x\"."
                  type: invalid_request_error
        "429":
          description: Kuota request gratis hari ini habis (model gratis, reset 00.00 WIB)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Today's free quota is used up (10 requests). Top up AI credits to continue."
                  type: rate_limit_error
        "500":
          description: Kesalahan internal; detail tidak pernah dibocorkan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: An internal server error occurred.
                data: null
        "502":
          description: Sumber data hulu (BMKG, ECB, OpenStreetMap, Composio, Tripay, mesin OCR) gagal
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: The earthquake data source (BMKG) cannot be reached right now.
                data: null
  /videos/options:
    get:
      operationId: getVideoModelOptions
      summary: Opsi model video + perkiraan harga per detik
      description: Durasi, resolusi, rasio, dan dukungan gambar awal yang diterima satu model video, plus perkiraan biaya USD (= kredit AI) per detik per resolusi, dibaca dari katalog provider. Pakai sebelum POST /ai/videos supaya nilai yang dikirim pasti didukung.
      tags:
      - AI
      parameters:
      - name: model
        in: query
        required: true
        description: Public id model ber-modality `video` (lihat GET /ai/catalog).
        schema:
          type: string
          example: alibaba/happyhorse-1.0
      responses:
        "200":
          description: Berhasil. Body format provider apa adanya, tanpa amplop.
          content:
            application/json:
              schema:
                type: object
              example:
                model: alibaba/happyhorse-1.0
                free: false
                known: true
                durations:
                - 3
                - 4
                - 5
                - 6
                - 8
                - 10
                - 15
                resolutions:
                - 720p
                - 1080p
                aspectRatios:
                - "16:9"
                - "9:16"
                - "1:1"
                frameImages:
                - first_frame
                audio: true
                perSecondUsd:
                  720p: 0.0988
                  1080p: 0.1694
        "400":
          description: Body tidak valid, atau model milik provider Anthropic (pakai POST /ai/messages)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"bansos/claude-haiku-4-5-20251001\" uses the Anthropic format. Call POST /v1/ai/messages."
                  type: invalid_request_error
        "401":
          description: Token tidak ada, tidak dikenal, kedaluwarsa, atau akun disuspend
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Unauthorized: invalid or expired token"
                data: null
        "402":
          description: Model berbayar dan saldo kredit AI habis
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: AI credits have run out. Buy AI credits in the Balance menu.
                  type: insufficient_credits
        "403":
          description: API key valid tetapi cakupannya (scopes) tidak mencakup endpoint ini
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: "Forbidden: this API key has not been granted access to that endpoint. Change its scopes on the Tokens page."
                data: null
        "404":
          description: "`model` bukan public id yang ada di GET /ai/models"
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Model \"gpt-x\" is not available. Use a name from GET /v1/ai/models, or specify its provider as \"<provider>/gpt-x\"."
                  type: invalid_request_error
        "429":
          description: Kuota request gratis hari ini habis (model gratis, reset 00.00 WIB)
          content:
            application/json:
              schema:
                type: object
              example:
                error:
                  message: "Today's free quota is used up (10 requests). Top up AI credits to continue."
                  type: rate_limit_error
        "500":
          description: Kesalahan internal; detail tidak pernah dibocorkan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: An internal server error occurred.
                data: null
        "502":
          description: Sumber data hulu (BMKG, ECB, OpenStreetMap, Composio, Tripay, mesin OCR) gagal
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                is_success: false
                message: The earthquake data source (BMKG) cannot be reached right now.
                data: null
components:
  securitySchemes:
    bearerJWT:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: API key `apk_...` dari halaman Token (disarankan untuk integrasi; bisa dibatasi cakupannya) atau JWT sesi console. Satu token berlaku untuk semua API di gateway yang masuk cakupannya.
  schemas:
    ApiEnvelope:
      type: object
      description: Amplop seragam yang membungkus setiap respons gateway.
      required:
      - is_success
      - message
      - data
      properties:
        is_success:
          type: boolean
          description: Apakah permintaan berhasil diproses.
        message:
          type: string
          description: Penjelasan singkat yang layak ditampilkan ke pengguna.
        data:
          description: Muatan jawaban; bentuknya berbeda per endpoint dan null saat gagal.
    ApiErrorEnvelope:
      allOf:
      - $ref: "#/components/schemas/ApiEnvelope"
      description: "Amplop untuk respons gagal: `is_success` selalu false dan `data` selalu null."
