{
  "openapi": "3.1.0",
  "info": {
    "title": "IROTP Public API",
    "version": "2.1.0",
    "description": "API publik untuk katalog OTP WhatsApp/SMS, saldo, pemesanan, status, aksi order, dan event webhook. Buat API key di web IROTP: Profil → API Key. Simpan kunci di backend. Semua harga dan saldo dalam rupiah."
  },
  "servers": [
    {
      "url": "https://irotp.my.id/api/public/v1"
    }
  ],
  "tags": [
    {
      "name": "Katalog",
      "description": "Pilih layanan dan varian SMS."
    },
    {
      "name": "Akun",
      "description": "Lihat saldo akun."
    },
    {
      "name": "Order",
      "description": "Pesan, pantau, dan kelola OTP."
    }
  ],
  "security": [
    {
      "ApiKey": []
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "IROTP API key",
        "description": "Authorization: Bearer ir_KUNCI_ANDA. Kunci dibuat di Profil → API Key dan hanya tampil sekali."
      }
    },
    "parameters": {
      "OrderId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "ID order IROTP, bukan ID penyedia.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Unik per pembelian, 8–100 karakter. Wajib untuk SMS; gunakan lagi dengan body yang sama saat retry.",
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9._:-]{8,100}$"
        }
      },
      "RequiredIdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "Unik per pembelian. Saat retry, kirim body dan kunci yang sama agar order tidak terduplikasi.",
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9._:-]{8,100}$"
        }
      },
      "SmsServer": {
        "name": "server",
        "in": "query",
        "required": true,
        "schema": {
          "type": "integer",
          "enum": [
            1,
            2,
            3,
            4,
            5
          ]
        }
      },
      "SmsCountry": {
        "name": "country",
        "in": "query",
        "required": true,
        "description": "Dari /sms/countries. Server 4 memakai ISO seperti id; server lain memakai angka seperti 6.",
        "schema": {
          "type": "string"
        }
      },
      "SmsProduct": {
        "name": "produk",
        "in": "query",
        "required": true,
        "description": "Kode produk dari layanan SMS di /services.",
        "schema": {
          "type": "string"
        }
      }
    },
    "schemas": {
      "Service": {
        "type": "object",
        "required": [
          "id",
          "name",
          "stock",
          "price",
          "needs_reprice",
          "channel"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "logo": {
            "type": [
              "string",
              "null"
            ]
          },
          "stock": {
            "type": "integer",
            "minimum": 0
          },
          "price": {
            "type": "integer",
            "minimum": 0,
            "description": "Harga jual per nomor dalam rupiah."
          },
          "needs_reprice": {
            "type": "boolean",
            "description": "Jika true, jangan pesan sampai harga diperbarui."
          },
          "channel": {
            "type": "string",
            "enum": [
              "whatsapp",
              "sms"
            ]
          },
          "server": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Server 1–5 untuk SMS; null untuk WhatsApp."
          },
          "country": {
            "type": [
              "string",
              "null"
            ]
          },
          "produk": {
            "type": [
              "string",
              "null"
            ]
          },
          "variant": {
            "type": [
              "string",
              "null"
            ]
          },
          "hot": {
            "type": "boolean"
          }
        }
      },
      "Order": {
        "type": "object",
        "required": [
          "id",
          "service_id",
          "service_name",
          "channel",
          "status",
          "price",
          "refunded"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "service_id": {
            "type": "integer"
          },
          "service_name": {
            "type": "string"
          },
          "channel": {
            "type": "string",
            "enum": [
              "whatsapp",
              "sms"
            ]
          },
          "server": {
            "type": [
              "integer",
              "null"
            ]
          },
          "phone_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nomor penuh. Bisa kosong selama order diproses."
          },
          "otp_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Kode yang dikenali; bisa kosong meskipun otp_message tersedia."
          },
          "otp_message": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pesan OTP lengkap jika tersedia."
          },
          "status": {
            "type": "string",
            "enum": [
              "creating",
              "active",
              "pending",
              "otp_received",
              "completed",
              "cancelled",
              "expired",
              "failed",
              "refunded",
              "needs_review"
            ]
          },
          "price": {
            "type": "integer",
            "description": "Harga order dalam rupiah."
          },
          "refunded": {
            "type": "boolean"
          },
          "expire_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Batas sewa dalam Unix detik."
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "current_price": {
            "type": "integer",
            "description": "Harga baru ketika respons 409 karena perubahan harga."
          },
          "detail": {
            "type": "string",
            "description": "Detail penolakan penyedia SMS bila tersedia."
          }
        }
      },
      "CreateOrder": {
        "type": "object",
        "required": [
          "service_id",
          "expected_price"
        ],
        "properties": {
          "service_id": {
            "type": "integer",
            "minimum": 1
          },
          "expected_price": {
            "type": "integer",
            "minimum": 0,
            "description": "Harga yang ditampilkan ke pengguna dari katalog terbaru."
          },
          "qty": {
            "type": "integer",
            "const": 1,
            "default": 1
          }
        }
      },
      "CreateBulkOrder": {
        "type": "object",
        "required": [
          "service_id",
          "expected_price"
        ],
        "properties": {
          "service_id": {
            "type": "integer",
            "minimum": 1
          },
          "expected_price": {
            "type": "integer",
            "minimum": 0
          },
          "qty": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10,
            "default": 1
          }
        }
      },
      "GroupItem": {
        "type": "object",
        "required": [
          "service_id",
          "expected_price"
        ],
        "properties": {
          "service_id": {
            "type": "integer",
            "minimum": 1
          },
          "expected_price": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "CreateGroupOrder": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "minItems": 2,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/GroupItem"
            }
          },
          "qty": {
            "type": "integer",
            "const": 1,
            "default": 1
          }
        }
      },
      "OrderEnvelope": {
        "type": "object",
        "properties": {
          "order": {
            "$ref": "#/components/schemas/Order"
          }
        }
      },
      "GroupResult": {
        "type": "object",
        "properties": {
          "group_id": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "creating",
              "active",
              "partial",
              "needs_review",
              "failed",
              "completed"
            ]
          },
          "requested_qty": {
            "type": "integer",
            "description": "Ada pada respons awal bulk; saat retry bisa tidak ada."
          },
          "orders": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Order"
            }
          },
          "message": {
            "type": "string",
            "description": "Catatan saat hasil parsial atau perlu pemeriksaan."
          }
        }
      },
      "ActionResult": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "order": {
            "$ref": "#/components/schemas/Order"
          },
          "resend_count": {
            "type": "integer"
          },
          "can_resend": {
            "type": "boolean"
          }
        }
      },
      "ServiceList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Service"
            }
          },
          "last_sync": {
            "type": "string"
          }
        }
      },
      "SmsOptions": {
        "type": "object",
        "properties": {
          "services": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Service"
            }
          }
        }
      },
      "SmsCountries": {
        "type": "object",
        "properties": {
          "countries": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "flag": {
                  "type": "string",
                  "description": "Emoji bendera."
                }
              }
            }
          }
        }
      },
      "OrderList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Order"
            }
          }
        }
      },
      "Balance": {
        "type": "object",
        "properties": {
          "balance": {
            "type": "integer",
            "description": "Rupiah."
          }
        }
      },
      "WebhookOrder": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "service_id": {
            "type": "integer"
          },
          "phone_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "otp_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "otp_message": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "price": {
            "type": "integer"
          },
          "refunded": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "expire_at": {
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "required": [
          "id",
          "type",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "ID event untuk deduplikasi."
          },
          "type": {
            "type": "string",
            "enum": [
              "order.created",
              "order.updated",
              "order.otp_received",
              "webhook.test"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "order": {
            "$ref": "#/components/schemas/WebhookOrder",
            "description": "Tidak ada pada event webhook.test."
          }
        }
      }
    }
  },
  "paths": {
    "/services": {
      "get": {
        "tags": [
          "Katalog"
        ],
        "operationId": "listServices",
        "summary": "Daftar layanan aktif",
        "description": "Pilih id dan price dari layanan dengan stock > 0 serta needs_reprice=false. Harga diperiksa lagi sebelum pembelian.",
        "responses": {
          "200": {
            "description": "Katalog layanan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceList"
                }
              }
            }
          },
          "401": {
            "description": "API key tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Batas permintaan tercapai",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Penyedia sedang bermasalah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/sms/countries": {
      "get": {
        "tags": [
          "Katalog"
        ],
        "operationId": "listSmsCountries",
        "summary": "Negara SMS menurut server",
        "parameters": [
          {
            "$ref": "#/components/parameters/SmsServer"
          }
        ],
        "responses": {
          "200": {
            "description": "Negara, kode, dan emoji bendera",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmsCountries"
                }
              }
            }
          },
          "400": {
            "description": "Data atau aksi tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Batas permintaan tercapai",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Penyedia sedang bermasalah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/sms/options": {
      "get": {
        "tags": [
          "Katalog"
        ],
        "operationId": "listSmsOptions",
        "summary": "Varian SMS menurut server, negara, dan produk",
        "parameters": [
          {
            "$ref": "#/components/parameters/SmsServer"
          },
          {
            "$ref": "#/components/parameters/SmsCountry"
          },
          {
            "$ref": "#/components/parameters/SmsProduct"
          }
        ],
        "responses": {
          "200": {
            "description": "Maksimal tiga varian termurah yang tersedia",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmsOptions"
                }
              }
            }
          },
          "400": {
            "description": "Data atau aksi tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Batas permintaan tercapai",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Penyedia sedang bermasalah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/balance": {
      "get": {
        "tags": [
          "Akun"
        ],
        "operationId": "getBalance",
        "summary": "Saldo akun dalam rupiah",
        "responses": {
          "200": {
            "description": "Saldo terbaru",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                },
                "example": {
                  "balance": 5000
                }
              }
            }
          },
          "401": {
            "description": "API key tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Batas permintaan tercapai",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Penyedia sedang bermasalah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/orders": {
      "get": {
        "tags": [
          "Order"
        ],
        "operationId": "listOrders",
        "summary": "Riwayat 200 order terbaru",
        "responses": {
          "200": {
            "description": "Array order dalam data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderList"
                }
              }
            }
          },
          "401": {
            "description": "API key tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Batas permintaan tercapai",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Penyedia sedang bermasalah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Order"
        ],
        "operationId": "createOrder",
        "summary": "Pesan satu nomor",
        "description": "SMS wajib memakai Idempotency-Key; WhatsApp sangat disarankan. expected_price harus sama dengan price katalog terbaru. qty hanya 1.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOrder"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Hasil order yang pernah dibuat dengan kunci sama",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderEnvelope"
                }
              }
            }
          },
          "201": {
            "description": "Order dibuat",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderEnvelope"
                },
                "example": {
                  "order": {
                    "id": 456,
                    "service_id": 123,
                    "service_name": "WhatsApp",
                    "channel": "sms",
                    "server": 1,
                    "phone_number": "628123456789",
                    "otp_code": "123456",
                    "otp_message": "Kode verifikasi Anda 123456",
                    "status": "otp_received",
                    "price": 1500,
                    "refunded": false,
                    "expire_at": 1780000000,
                    "created_at": "2026-10-16 10:00:00",
                    "updated_at": "2026-10-16 10:02:00"
                  }
                }
              }
            }
          },
          "202": {
            "description": "Order dibuat tetapi perlu pemeriksaan admin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderEnvelope"
                }
              }
            }
          },
          "400": {
            "description": "Data atau aksi tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Tidak ditemukan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Harga, stok, atau kunci idempotensi berubah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Batas permintaan tercapai",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Penyedia sedang bermasalah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "Penyedia tidak merespons tepat waktu",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/orders/bulk": {
      "post": {
        "tags": [
          "Order"
        ],
        "operationId": "createBulkOrder",
        "summary": "Pesan 1–10 nomor dari satu varian",
        "description": "Berlaku untuk WhatsApp dan SMS. Setiap nomor menjadi order sendiri. Gunakan kunci dan body yang sama saat retry.",
        "parameters": [
          {
            "$ref": "#/components/parameters/RequiredIdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBulkOrder"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Hasil permintaan yang sama sebelumnya",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GroupResult"
                }
              }
            }
          },
          "201": {
            "description": "Semua nomor dibuat",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GroupResult"
                },
                "example": {
                  "group_id": 71,
                  "status": "active",
                  "requested_qty": 2,
                  "orders": [
                    {
                      "id": 456,
                      "service_id": 123,
                      "service_name": "WhatsApp",
                      "channel": "sms",
                      "server": 1,
                      "phone_number": "628123456789",
                      "otp_code": null,
                      "otp_message": null,
                      "status": "active",
                      "price": 1500,
                      "refunded": false,
                      "expire_at": 1780000000,
                      "created_at": "2026-10-16 10:00:00",
                      "updated_at": "2026-10-16 10:02:00"
                    },
                    {
                      "id": 457,
                      "service_id": 123,
                      "service_name": "WhatsApp",
                      "channel": "sms",
                      "server": 1,
                      "phone_number": "628123456789",
                      "otp_code": null,
                      "otp_message": null,
                      "status": "active",
                      "price": 1500,
                      "refunded": false,
                      "expire_at": 1780000000,
                      "created_at": "2026-10-16 10:00:00",
                      "updated_at": "2026-10-16 10:02:00"
                    }
                  ]
                }
              }
            }
          },
          "202": {
            "description": "Ada order yang perlu diperiksa admin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GroupResult"
                }
              }
            }
          },
          "207": {
            "description": "Sebagian nomor berhasil",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GroupResult"
                }
              }
            }
          },
          "400": {
            "description": "Data atau aksi tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Tidak ditemukan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Harga, stok, atau kunci idempotensi berubah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Batas permintaan tercapai",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Penyedia sedang bermasalah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "Penyedia tidak merespons tepat waktu",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/orders/group": {
      "post": {
        "tags": [
          "Order"
        ],
        "operationId": "createGroupOrder",
        "summary": "Pesan 2–10 layanan WhatsApp berbeda",
        "description": "Satu nomor per layanan. SMS tidak tersedia pada endpoint ini. Gunakan kunci dan body yang sama saat retry.",
        "parameters": [
          {
            "$ref": "#/components/parameters/RequiredIdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateGroupOrder"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Hasil permintaan yang sama sebelumnya",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GroupResult"
                }
              }
            }
          },
          "201": {
            "description": "Grup dibuat",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GroupResult"
                }
              }
            }
          },
          "202": {
            "description": "Ada order yang perlu diperiksa admin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GroupResult"
                }
              }
            }
          },
          "400": {
            "description": "Data atau aksi tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Tidak ditemukan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Harga, stok, atau kunci idempotensi berubah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Batas permintaan tercapai",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Penyedia sedang bermasalah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "Penyedia tidak merespons tepat waktu",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/orders/active": {
      "get": {
        "tags": [
          "Order"
        ],
        "operationId": "listActiveOrders",
        "summary": "Order aktif",
        "description": "Status active, pending, atau otp_received; maksimal 200 order.",
        "responses": {
          "200": {
            "description": "Array order dalam data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderList"
                }
              }
            }
          },
          "401": {
            "description": "API key tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Batas permintaan tercapai",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Penyedia sedang bermasalah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/orders/{id}": {
      "get": {
        "tags": [
          "Order"
        ],
        "operationId": "getOrder",
        "summary": "Cek nomor, OTP, status, dan batas sewa",
        "description": "Respons berupa objek order langsung. Polling setiap 3–5 detik.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          }
        ],
        "responses": {
          "200": {
            "description": "Detail order",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                },
                "example": {
                  "id": 456,
                  "service_id": 123,
                  "service_name": "WhatsApp",
                  "channel": "sms",
                  "server": 1,
                  "phone_number": "628123456789",
                  "otp_code": "123456",
                  "otp_message": "Kode verifikasi Anda 123456",
                  "status": "otp_received",
                  "price": 1500,
                  "refunded": false,
                  "expire_at": 1780000000,
                  "created_at": "2026-10-16 10:00:00",
                  "updated_at": "2026-10-16 10:02:00"
                }
              }
            }
          },
          "401": {
            "description": "API key tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Tidak ditemukan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Batas permintaan tercapai",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "Penyedia tidak merespons tepat waktu",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/orders/{id}/ack": {
      "post": {
        "tags": [
          "Order"
        ],
        "operationId": "completeOrder",
        "summary": "Selesaikan setelah OTP masuk",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          }
        ],
        "responses": {
          "200": {
            "description": "Order selesai",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionResult"
                },
                "example": {
                  "ok": true
                }
              }
            }
          },
          "400": {
            "description": "Data atau aksi tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Tidak ditemukan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Batas permintaan tercapai",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Penyedia sedang bermasalah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/orders/{id}/cancel": {
      "post": {
        "tags": [
          "Order"
        ],
        "operationId": "cancelOrder",
        "summary": "Batalkan sebelum OTP masuk",
        "description": "Server SMS 1, 2, 3, dan 5 menerima pembatalan setelah dua menit; server 4 kapan saja. Jika penyedia menolak, saldo dan status tidak berubah.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          }
        ],
        "responses": {
          "200": {
            "description": "Order dibatalkan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionResult"
                },
                "example": {
                  "ok": true
                }
              }
            }
          },
          "400": {
            "description": "Data atau aksi tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Tidak ditemukan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Batas permintaan tercapai",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Penyedia sedang bermasalah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/orders/{id}/resend": {
      "post": {
        "tags": [
          "Order"
        ],
        "operationId": "resendOtp",
        "summary": "Minta OTP ulang",
        "description": "WhatsApp setelah OTP pertama masuk. SMS hanya jika penyedia berstatus success dan waktu order belum habis; respons 429 jika masih harus menunggu. Respons SMS dapat berisi order terbaru.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          }
        ],
        "responses": {
          "200": {
            "description": "Permintaan ulang diterima",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionResult"
                }
              }
            }
          },
          "400": {
            "description": "Data atau aksi tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Tidak ditemukan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Harga, stok, atau kunci idempotensi berubah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Batas permintaan tercapai",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Penyedia sedang bermasalah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/orders/{id}/reactivate": {
      "post": {
        "tags": [
          "Order"
        ],
        "operationId": "reactivateNumber",
        "summary": "Reaktivasi nomor WhatsApp sebagai order baru",
        "description": "SMS tidak didukung. Membuat order baru dan memotong saldo setelah harga dikonfirmasi.",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "expected_price": {
                    "type": "integer",
                    "minimum": 0
                  }
                },
                "required": [
                  "expected_price"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Order reaktivasi baru",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderEnvelope"
                }
              }
            }
          },
          "400": {
            "description": "Data atau aksi tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key tidak valid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Tidak ditemukan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Harga, stok, atau kunci idempotensi berubah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Batas permintaan tercapai",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Penyedia sedang bermasalah",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "orderEvent": {
      "post": {
        "summary": "Event order dan OTP dikirim ke URL webhook pelanggan",
        "description": "Aktifkan di Profil → Webhook OTP. Hanya URL HTTPS publik. Verifikasi HMAC-SHA256 atas timestamp + titik + raw body; terima duplikat secara idempoten dan jangan mengandalkan urutan pengiriman. HTTP 2xx menandai sukses; kegagalan dicoba sampai 8 kali.",
        "security": [],
        "parameters": [
          {
            "name": "X-IROTP-Event-ID",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-IROTP-Timestamp",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Unix detik; tolak jika terlalu lama."
          },
          {
            "name": "X-IROTP-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "v1=<hex HMAC-SHA256(timestamp + . + raw_body, signing_secret)>"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Event diterima dan disimpan"
          }
        }
      }
    }
  }
}
