{
  "openapi": "3.0.3",
  "info": {
    "title": "Nomerin Reseller API",
    "version": "1.0.0",
    "summary": "Beli nomor virtual untuk OTP lewat API: katalog, order, status, dan webhook.",
    "description": "API untuk reseller Nomerin. Autentikasi Bearer API key, semua harga IDR, respons dalam envelope {success, data, meta} atau {success, error}. Dokumentasi lengkap: https://api.nomerin.id/docs (HTML), https://api.nomerin.id/llms.txt (ringkas untuk AI), https://api.nomerin.id/docs.md (lengkap, Markdown).",
    "contact": {
      "name": "Nomerin",
      "url": "https://nomerin.id/kontak"
    }
  },
  "servers": [
    {
      "url": "https://api.nomerin.id",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Akun",
      "description": "Saldo akun."
    },
    {
      "name": "Katalog",
      "description": "Layanan, negara, dan harga. Data sama dengan yang tampil di website Nomerin mode Advanced."
    },
    {
      "name": "Order",
      "description": "Membuat, memantau, dan membatalkan order nomor."
    }
  ],
  "paths": {
    "/v1/balance": {
      "get": {
        "tags": [
          "Akun"
        ],
        "operationId": "getBalance",
        "summary": "Cek saldo",
        "description": "Saldo akun Nomerin pemilik API key, dalam IDR. Saldo diisi lewat deposit di dashboard Nomerin. Rate limit: 120 request per menit per API key.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Berhasil",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Batas request per menit untuk endpoint ini."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Sisa request di menit ini."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Detik sampai kuota direset."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "balance": {
                          "type": "integer"
                        },
                        "currency": {
                          "type": "string"
                        }
                      }
                    },
                    "meta": {
                      "type": "object"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "data": {
                    "balance": 250000,
                    "currency": "IDR"
                  }
                }
              }
            }
          },
          "401": {
            "description": "UNAUTHORIZED: API key tidak dikirim, formatnya salah, atau tidak dikenal. | KEY_REVOKED: API key sudah dinonaktifkan dari dashboard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "UNAUTHORIZED",
                    "message": "API key tidak dikirim, formatnya salah, atau tidak dikenal."
                  }
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: Melebihi batas request per menit. Hormati header Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "RATE_LIMIT_EXCEEDED",
                    "message": "Melebihi batas request per menit. Hormati header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR: Kesalahan di server Nomerin. Aman untuk diulang dengan Idempotency-Key yang sama.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "INTERNAL_ERROR",
                    "message": "Kesalahan di server Nomerin. Aman untuk diulang dengan Idempotency-Key yang sama."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/services": {
      "get": {
        "tags": [
          "Katalog"
        ],
        "operationId": "listServices",
        "summary": "Daftar layanan",
        "description": "Semua layanan (WhatsApp, Telegram, dan lainnya) yang saat ini punya stok. countries adalah jumlah negara yang tersedia. Daftar ini berubah mengikuti stok, ambil ulang secara berkala, cukup tiap beberapa menit. Rate limit: 120 request per menit per API key.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Berhasil",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Batas request per menit untuk endpoint ini."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Sisa request di menit ini."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Detik sampai kuota direset."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "code": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "countries": {
                            "type": "integer"
                          }
                        }
                      }
                    },
                    "meta": {
                      "type": "object"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": 3,
                      "code": "whatsapp",
                      "name": "WhatsApp",
                      "countries": 42
                    },
                    {
                      "id": 8,
                      "code": "telegram",
                      "name": "Telegram",
                      "countries": 57
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "UNAUTHORIZED: API key tidak dikirim, formatnya salah, atau tidak dikenal. | KEY_REVOKED: API key sudah dinonaktifkan dari dashboard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "UNAUTHORIZED",
                    "message": "API key tidak dikirim, formatnya salah, atau tidak dikenal."
                  }
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: Melebihi batas request per menit. Hormati header Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "RATE_LIMIT_EXCEEDED",
                    "message": "Melebihi batas request per menit. Hormati header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR: Kesalahan di server Nomerin. Aman untuk diulang dengan Idempotency-Key yang sama.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "INTERNAL_ERROR",
                    "message": "Kesalahan di server Nomerin. Aman untuk diulang dengan Idempotency-Key yang sama."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/countries": {
      "get": {
        "tags": [
          "Katalog"
        ],
        "operationId": "listCountries",
        "summary": "Daftar negara untuk satu layanan",
        "description": "Negara yang tersedia untuk service_id beserta harga termurah dan termahal (IDR) dan jumlah pilihan harga. low_stock true berarti semua pilihan harga stoknya tipis. Rate limit: 120 request per menit per API key.",
        "parameters": [
          {
            "name": "service_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "ID layanan dari /v1/services."
          }
        ],
        "responses": {
          "200": {
            "description": "Berhasil",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Batas request per menit untuk endpoint ini."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Sisa request di menit ini."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Detik sampai kuota direset."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "code": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "min_price": {
                            "type": "integer"
                          },
                          "max_price": {
                            "type": "integer"
                          },
                          "offers": {
                            "type": "integer"
                          },
                          "low_stock": {
                            "type": "boolean"
                          }
                        }
                      }
                    },
                    "meta": {
                      "type": "object"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": 7,
                      "code": "62",
                      "name": "Indonesia",
                      "min_price": 4500,
                      "max_price": 12000,
                      "offers": 6,
                      "low_stock": false
                    },
                    {
                      "id": 12,
                      "code": "MY",
                      "name": "Malaysia",
                      "min_price": 6000,
                      "max_price": 9500,
                      "offers": 3,
                      "low_stock": true
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "UNAUTHORIZED: API key tidak dikirim, formatnya salah, atau tidak dikenal. | KEY_REVOKED: API key sudah dinonaktifkan dari dashboard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "UNAUTHORIZED",
                    "message": "API key tidak dikirim, formatnya salah, atau tidak dikenal."
                  }
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: Melebihi batas request per menit. Hormati header Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "RATE_LIMIT_EXCEEDED",
                    "message": "Melebihi batas request per menit. Hormati header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR: Kesalahan di server Nomerin. Aman untuk diulang dengan Idempotency-Key yang sama.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "INTERNAL_ERROR",
                    "message": "Kesalahan di server Nomerin. Aman untuk diulang dengan Idempotency-Key yang sama."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/offers": {
      "get": {
        "tags": [
          "Katalog"
        ],
        "operationId": "listOffers",
        "summary": "Daftar harga untuk layanan + negara",
        "description": "Semua pilihan harga untuk pasangan layanan dan negara, urut dari termurah. Nilai price inilah yang dikirim saat membuat order. Harga lebih tinggi umumnya nomor dengan kualitas atau tingkat keberhasilan lebih baik. label bisa null. low_stock true berarti stok tipis, bisa habis sebelum order diproses. Rate limit: 120 request per menit per API key.",
        "parameters": [
          {
            "name": "service_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "ID layanan."
          },
          {
            "name": "country_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "ID negara dari /v1/countries."
          }
        ],
        "responses": {
          "200": {
            "description": "Berhasil",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Batas request per menit untuk endpoint ini."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Sisa request di menit ini."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Detik sampai kuota direset."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "service": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer"
                            },
                            "code": {
                              "type": "string"
                            },
                            "name": {
                              "type": "string"
                            }
                          }
                        },
                        "country": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer"
                            },
                            "code": {
                              "type": "string"
                            },
                            "name": {
                              "type": "string"
                            }
                          }
                        },
                        "offers": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "price": {
                                "type": "integer"
                              },
                              "label": {
                                "type": "string"
                              },
                              "low_stock": {
                                "type": "boolean"
                              }
                            }
                          }
                        }
                      }
                    },
                    "meta": {
                      "type": "object"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "data": {
                    "service": {
                      "id": 3,
                      "code": "whatsapp",
                      "name": "WhatsApp"
                    },
                    "country": {
                      "id": 7,
                      "code": "62",
                      "name": "Indonesia"
                    },
                    "offers": [
                      {
                        "price": 4500,
                        "label": "Hemat",
                        "low_stock": false
                      },
                      {
                        "price": 6000,
                        "label": null,
                        "low_stock": false
                      },
                      {
                        "price": 8500,
                        "label": "Premium",
                        "low_stock": true
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "UNAUTHORIZED: API key tidak dikirim, formatnya salah, atau tidak dikenal. | KEY_REVOKED: API key sudah dinonaktifkan dari dashboard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "UNAUTHORIZED",
                    "message": "API key tidak dikirim, formatnya salah, atau tidak dikenal."
                  }
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: Melebihi batas request per menit. Hormati header Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "RATE_LIMIT_EXCEEDED",
                    "message": "Melebihi batas request per menit. Hormati header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR: Kesalahan di server Nomerin. Aman untuk diulang dengan Idempotency-Key yang sama.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "INTERNAL_ERROR",
                    "message": "Kesalahan di server Nomerin. Aman untuk diulang dengan Idempotency-Key yang sama."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders": {
      "post": {
        "tags": [
          "Order"
        ],
        "operationId": "createOrder",
        "summary": "Buat order nomor",
        "description": "Memesan satu nomor. price harus salah satu nilai dari /v1/offers; kalau harga itu sudah tidak ada, respons 409 OFFER_UNAVAILABLE dengan daftar harga terbaru di details.offers. Saldo dipotong saat order diterima dan dikembalikan otomatis bila order berakhir refunded atau failed. Order dikembalikan dengan status processing; nomor keluar beberapa detik kemudian (webhook order.number atau cek GET /v1/orders/{id}). Kirim header Idempotency-Key (string unik maksimal 64 karakter) supaya request yang diulang karena timeout tidak membuat order ganda: ulangan dengan body sama mengembalikan order yang sama dengan HTTP 200 dan meta.idempotent_replay true. Rate limit: 30 request per menit per API key.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Kunci unik per order dari sisi Anda, disimpan 24 jam."
          }
        ],
        "responses": {
          "201": {
            "description": "Berhasil",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Batas request per menit untuk endpoint ini."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Sisa request di menit ini."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Detik sampai kuota direset."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "$ref": "#/components/schemas/Order"
                    },
                    "meta": {
                      "type": "object"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "data": {
                    "id": 184212,
                    "external_id": "inv-2026-0001",
                    "status": "processing",
                    "service": {
                      "id": 3,
                      "code": "whatsapp",
                      "name": "WhatsApp"
                    },
                    "country": {
                      "id": 7,
                      "code": "62",
                      "name": "Indonesia"
                    },
                    "price": 4500,
                    "number": null,
                    "otp_code": null,
                    "otp_codes": [],
                    "otp_received_at": null,
                    "created_at": "2026-10-14T20:29:40+07:00",
                    "expires_at": null,
                    "cancel_available_at": "2026-10-14T20:31:40+07:00",
                    "refunded_at": null,
                    "failure_reason": null
                  }
                }
              }
            }
          },
          "401": {
            "description": "UNAUTHORIZED: API key tidak dikirim, formatnya salah, atau tidak dikenal. | KEY_REVOKED: API key sudah dinonaktifkan dari dashboard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "UNAUTHORIZED",
                    "message": "API key tidak dikirim, formatnya salah, atau tidak dikenal."
                  }
                }
              }
            }
          },
          "422": {
            "description": "VALIDATION_ERROR: Body atau query tidak valid. details berisi field yang salah.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "VALIDATION_ERROR",
                    "message": "Body atau query tidak valid. details berisi field yang salah."
                  }
                }
              }
            }
          },
          "404": {
            "description": "SERVICE_NOT_FOUND: service_id tidak ada atau layanan sedang tidak tersedia.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "SERVICE_NOT_FOUND",
                    "message": "service_id tidak ada atau layanan sedang tidak tersedia."
                  }
                }
              }
            }
          },
          "409": {
            "description": "OFFER_UNAVAILABLE: Harga yang dikirim sudah tidak ada. details.offers berisi daftar harga saat ini, pilih lalu kirim ulang. | IDEMPOTENCY_KEY_REUSED: Idempotency-Key sama dipakai dengan body berbeda. | REQUEST_IN_PROGRESS: Request dengan Idempotency-Key yang sama masih diproses.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "OFFER_UNAVAILABLE",
                    "message": "Harga yang dikirim sudah tidak ada. details.offers berisi daftar harga saat ini, pilih lalu kirim ulang."
                  }
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_BALANCE: Saldo tidak cukup. details berisi balance dan price.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "INSUFFICIENT_BALANCE",
                    "message": "Saldo tidak cukup. details berisi balance dan price."
                  }
                }
              }
            }
          },
          "429": {
            "description": "TOO_MANY_ACTIVE_ORDERS: Sudah ada 10 order aktif (processing atau waiting_otp) di API key ini. | ORDER_BLOCKED: Terlalu banyak order dibatalkan atau kedaluwarsa tanpa OTP. details.retry_after dalam detik. | RATE_LIMIT_EXCEEDED: Melebihi batas request per menit. Hormati header Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "TOO_MANY_ACTIVE_ORDERS",
                    "message": "Sudah ada 10 order aktif (processing atau waiting_otp) di API key ini."
                  }
                }
              }
            }
          },
          "503": {
            "description": "PURCHASE_DISABLED: Pembelian nomor dinonaktifkan sementara. Coba lagi sesuai Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "PURCHASE_DISABLED",
                    "message": "Pembelian nomor dinonaktifkan sementara. Coba lagi sesuai Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR: Kesalahan di server Nomerin. Aman untuk diulang dengan Idempotency-Key yang sama.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "INTERNAL_ERROR",
                    "message": "Kesalahan di server Nomerin. Aman untuk diulang dengan Idempotency-Key yang sama."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "service_id",
                  "country_id",
                  "price"
                ],
                "properties": {
                  "service_id": {
                    "type": "integer",
                    "description": "ID layanan."
                  },
                  "country_id": {
                    "type": "integer",
                    "description": "ID negara."
                  },
                  "price": {
                    "type": "integer",
                    "description": "Harga pilihan dari /v1/offers (IDR)."
                  },
                  "external_id": {
                    "type": "string",
                    "description": "Referensi dari sistem Anda, maksimal 64 karakter. Ikut dikirim di webhook."
                  }
                }
              },
              "example": {
                "service_id": 3,
                "country_id": 7,
                "price": 4500,
                "external_id": "inv-2026-0001"
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Order"
        ],
        "operationId": "listOrders",
        "summary": "Daftar order",
        "description": "Order yang dibuat lewat API key ini, terbaru lebih dulu. Untuk halaman berikutnya kirim before_id = meta.next_before_id. Rate limit: 120 request per menit per API key.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filter status: processing, waiting_otp, success, refunded, failed."
          },
          {
            "name": "external_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cari order berdasarkan external_id Anda."
          },
          {
            "name": "before_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Hanya order dengan id lebih kecil dari ini (paging)."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Jumlah per halaman, 1 sampai 100, default 20."
          }
        ],
        "responses": {
          "200": {
            "description": "Berhasil",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Batas request per menit untuk endpoint ini."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Sisa request di menit ini."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Detik sampai kuota direset."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Order"
                      }
                    },
                    "meta": {
                      "type": "object"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": 184212,
                      "external_id": "inv-2026-0001",
                      "status": "success",
                      "service": {
                        "id": 3,
                        "code": "whatsapp",
                        "name": "WhatsApp"
                      },
                      "country": {
                        "id": 7,
                        "code": "62",
                        "name": "Indonesia"
                      },
                      "price": 4500,
                      "number": "+628123456789",
                      "otp_code": "482913",
                      "otp_codes": [
                        "482913"
                      ],
                      "otp_received_at": "2026-10-14T20:31:05+07:00",
                      "created_at": "2026-10-14T20:29:40+07:00",
                      "expires_at": "2026-10-14T20:49:40+07:00",
                      "cancel_available_at": null,
                      "refunded_at": null,
                      "failure_reason": null
                    }
                  ],
                  "meta": {
                    "has_more": true,
                    "next_before_id": 184212
                  }
                }
              }
            }
          },
          "401": {
            "description": "UNAUTHORIZED: API key tidak dikirim, formatnya salah, atau tidak dikenal. | KEY_REVOKED: API key sudah dinonaktifkan dari dashboard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "UNAUTHORIZED",
                    "message": "API key tidak dikirim, formatnya salah, atau tidak dikenal."
                  }
                }
              }
            }
          },
          "422": {
            "description": "VALIDATION_ERROR: Body atau query tidak valid. details berisi field yang salah.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "VALIDATION_ERROR",
                    "message": "Body atau query tidak valid. details berisi field yang salah."
                  }
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: Melebihi batas request per menit. Hormati header Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "RATE_LIMIT_EXCEEDED",
                    "message": "Melebihi batas request per menit. Hormati header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR: Kesalahan di server Nomerin. Aman untuk diulang dengan Idempotency-Key yang sama.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "INTERNAL_ERROR",
                    "message": "Kesalahan di server Nomerin. Aman untuk diulang dengan Idempotency-Key yang sama."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders/{order_id}": {
      "get": {
        "tags": [
          "Order"
        ],
        "operationId": "getOrder",
        "summary": "Detail dan status order",
        "description": "Status terkini satu order termasuk nomor dan semua OTP yang masuk. Kalau tidak memakai webhook, polling endpoint ini tiap 3 sampai 5 detik sudah cukup. Rate limit: 120 request per menit per API key.",
        "parameters": [
          {
            "name": "order_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "ID order."
          }
        ],
        "responses": {
          "200": {
            "description": "Berhasil",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Batas request per menit untuk endpoint ini."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Sisa request di menit ini."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Detik sampai kuota direset."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "$ref": "#/components/schemas/Order"
                    },
                    "meta": {
                      "type": "object"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "data": {
                    "id": 184212,
                    "external_id": "inv-2026-0001",
                    "status": "success",
                    "service": {
                      "id": 3,
                      "code": "whatsapp",
                      "name": "WhatsApp"
                    },
                    "country": {
                      "id": 7,
                      "code": "62",
                      "name": "Indonesia"
                    },
                    "price": 4500,
                    "number": "+628123456789",
                    "otp_code": "482913",
                    "otp_codes": [
                      "482913"
                    ],
                    "otp_received_at": "2026-10-14T20:31:05+07:00",
                    "created_at": "2026-10-14T20:29:40+07:00",
                    "expires_at": "2026-10-14T20:49:40+07:00",
                    "cancel_available_at": null,
                    "refunded_at": null,
                    "failure_reason": null
                  }
                }
              }
            }
          },
          "401": {
            "description": "UNAUTHORIZED: API key tidak dikirim, formatnya salah, atau tidak dikenal. | KEY_REVOKED: API key sudah dinonaktifkan dari dashboard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "UNAUTHORIZED",
                    "message": "API key tidak dikirim, formatnya salah, atau tidak dikenal."
                  }
                }
              }
            }
          },
          "404": {
            "description": "ORDER_NOT_FOUND: Order tidak ada atau bukan milik API key ini.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "ORDER_NOT_FOUND",
                    "message": "Order tidak ada atau bukan milik API key ini."
                  }
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: Melebihi batas request per menit. Hormati header Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "RATE_LIMIT_EXCEEDED",
                    "message": "Melebihi batas request per menit. Hormati header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR: Kesalahan di server Nomerin. Aman untuk diulang dengan Idempotency-Key yang sama.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "INTERNAL_ERROR",
                    "message": "Kesalahan di server Nomerin. Aman untuk diulang dengan Idempotency-Key yang sama."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders/{order_id}/cancel": {
      "post": {
        "tags": [
          "Order"
        ],
        "operationId": "cancelOrder",
        "summary": "Batalkan order",
        "description": "Membatalkan order yang masih waiting_otp dan belum menerima OTP, paling cepat 2 menit setelah nomor keluar (lihat cancel_available_at). Saldo kembali penuh dan status menjadi refunded. Order yang sudah refunded atau failed mengembalikan 200 dengan data order apa adanya. Terlalu sering membatalkan atau membiarkan order kedaluwarsa tanpa OTP akan memblokir order baru sementara (ORDER_BLOCKED). Rate limit: 30 request per menit per API key.",
        "parameters": [
          {
            "name": "order_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "ID order."
          }
        ],
        "responses": {
          "200": {
            "description": "Berhasil",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Batas request per menit untuk endpoint ini."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Sisa request di menit ini."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Detik sampai kuota direset."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "$ref": "#/components/schemas/Order"
                    },
                    "meta": {
                      "type": "object"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "data": {
                    "id": 184212,
                    "external_id": "inv-2026-0001",
                    "status": "refunded",
                    "service": {
                      "id": 3,
                      "code": "whatsapp",
                      "name": "WhatsApp"
                    },
                    "country": {
                      "id": 7,
                      "code": "62",
                      "name": "Indonesia"
                    },
                    "price": 4500,
                    "number": "+628123456789",
                    "otp_code": null,
                    "otp_codes": [],
                    "otp_received_at": null,
                    "created_at": "2026-10-14T20:29:40+07:00",
                    "expires_at": "2026-10-14T20:49:40+07:00",
                    "cancel_available_at": null,
                    "refunded_at": "2026-10-14T20:35:02+07:00",
                    "failure_reason": null
                  }
                }
              }
            }
          },
          "401": {
            "description": "UNAUTHORIZED: API key tidak dikirim, formatnya salah, atau tidak dikenal. | KEY_REVOKED: API key sudah dinonaktifkan dari dashboard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "UNAUTHORIZED",
                    "message": "API key tidak dikirim, formatnya salah, atau tidak dikenal."
                  }
                }
              }
            }
          },
          "404": {
            "description": "ORDER_NOT_FOUND: Order tidak ada atau bukan milik API key ini.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "ORDER_NOT_FOUND",
                    "message": "Order tidak ada atau bukan milik API key ini."
                  }
                }
              }
            }
          },
          "409": {
            "description": "CANCEL_NOT_ALLOWED: Order bukan waiting_otp, sudah selesai, atau sudah berubah. | CANCEL_TOO_EARLY: Belum lewat cancel_available_at (2 menit setelah nomor keluar). | OTP_ALREADY_RECEIVED: OTP sudah masuk, order tidak bisa dibatalkan. Cek ulang status order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "CANCEL_NOT_ALLOWED",
                    "message": "Order bukan waiting_otp, sudah selesai, atau sudah berubah."
                  }
                }
              }
            }
          },
          "503": {
            "description": "CANCEL_FAILED: Pembatalan gagal sementara, coba lagi beberapa detik kemudian.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "CANCEL_FAILED",
                    "message": "Pembatalan gagal sementara, coba lagi beberapa detik kemudian."
                  }
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: Melebihi batas request per menit. Hormati header Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "RATE_LIMIT_EXCEEDED",
                    "message": "Melebihi batas request per menit. Hormati header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR: Kesalahan di server Nomerin. Aman untuk diulang dengan Idempotency-Key yang sama.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "INTERNAL_ERROR",
                    "message": "Kesalahan di server Nomerin. Aman untuk diulang dengan Idempotency-Key yang sama."
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Authorization: Bearer nmr_xxxxxxxx. Buat API key di dashboard Nomerin menu API Reseller."
      }
    },
    "schemas": {
      "Order": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "nullable": false,
            "description": "ID order di Nomerin. Pakai ini untuk cek status dan cancel."
          },
          "external_id": {
            "type": "string",
            "nullable": true,
            "description": "ID dari sistem Anda yang dikirim saat membuat order (maksimal 64 karakter)."
          },
          "status": {
            "type": "string",
            "nullable": false,
            "description": "processing, waiting_otp, success, refunded, atau failed. Lihat bagian Siklus order.",
            "enum": [
              "processing",
              "waiting_otp",
              "success",
              "refunded",
              "failed"
            ]
          },
          "service": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "code": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "country": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "code": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "price": {
            "type": "integer",
            "nullable": false,
            "description": "Harga yang dipotong dari saldo (IDR). Sudah final, tidak ada biaya lain."
          },
          "number": {
            "type": "string",
            "nullable": true,
            "description": "Nomor telepon format internasional (+62...). Terisi saat status waiting_otp."
          },
          "otp_code": {
            "type": "string",
            "nullable": true,
            "description": "Kode OTP terakhir yang masuk."
          },
          "otp_codes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "otp_received_at": {
            "type": "string",
            "nullable": true,
            "description": "Waktu OTP terakhir masuk (ISO 8601 dengan zona waktu)."
          },
          "created_at": {
            "type": "string",
            "nullable": false,
            "description": "Waktu order dibuat."
          },
          "expires_at": {
            "type": "string",
            "nullable": true,
            "description": "Batas nomor menerima OTP. Setelah ini order tanpa OTP otomatis refunded."
          },
          "cancel_available_at": {
            "type": "string",
            "nullable": true,
            "description": "Waktu paling awal order boleh dibatalkan (2 menit setelah nomor keluar)."
          },
          "refunded_at": {
            "type": "string",
            "nullable": true,
            "description": "Waktu saldo dikembalikan (status refunded atau failed)."
          },
          "failure_reason": {
            "type": "string",
            "nullable": true,
            "description": "NUMBER_UNAVAILABLE bila status failed: stok nomor habis di harga itu."
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "success",
          "error"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "CANCEL_FAILED",
                  "CANCEL_NOT_ALLOWED",
                  "CANCEL_TOO_EARLY",
                  "COUNTRY_NOT_FOUND",
                  "IDEMPOTENCY_KEY_REUSED",
                  "INSUFFICIENT_BALANCE",
                  "INTERNAL_ERROR",
                  "KEY_REVOKED",
                  "NOT_FOUND",
                  "OFFER_UNAVAILABLE",
                  "ORDER_BLOCKED",
                  "ORDER_NOT_FOUND",
                  "OTP_ALREADY_RECEIVED",
                  "PURCHASE_DISABLED",
                  "RATE_LIMIT_EXCEEDED",
                  "REQUEST_IN_PROGRESS",
                  "SERVICE_NOT_FOUND",
                  "TOO_MANY_ACTIVE_ORDERS",
                  "UNAUTHORIZED",
                  "VALIDATION_ERROR"
                ]
              },
              "message": {
                "type": "string"
              },
              "details": {}
            }
          }
        }
      },
      "WebhookPayload": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "order.number",
              "order.otp",
              "order.refunded",
              "order.failed",
              "webhook.test"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "order": {
            "$ref": "#/components/schemas/Order",
            "nullable": true
          }
        },
        "example": {
          "event": "order.otp",
          "created_at": "2026-10-14T20:31:05+07:00",
          "order": {
            "id": 184212,
            "external_id": "inv-2026-0001",
            "status": "success",
            "service": {
              "id": 3,
              "code": "whatsapp",
              "name": "WhatsApp"
            },
            "country": {
              "id": 7,
              "code": "62",
              "name": "Indonesia"
            },
            "price": 4500,
            "number": "+628123456789",
            "otp_code": "482913",
            "otp_codes": [
              "482913"
            ],
            "otp_received_at": "2026-10-14T20:31:05+07:00",
            "created_at": "2026-10-14T20:29:40+07:00",
            "expires_at": "2026-10-14T20:49:40+07:00",
            "cancel_available_at": null,
            "refunded_at": null,
            "failure_reason": null
          }
        }
      }
    }
  },
  "x-webhooks": {
    "events": [
      {
        "event": "order.number",
        "description": "Nomor sudah keluar, status waiting_otp."
      },
      {
        "event": "order.otp",
        "description": "OTP masuk, status success. Dikirim lagi setiap ada OTP baru di nomor yang sama."
      },
      {
        "event": "order.refunded",
        "description": "Order dibatalkan atau kedaluwarsa tanpa OTP, saldo kembali."
      },
      {
        "event": "order.failed",
        "description": "Nomor tidak didapat, saldo kembali."
      },
      {
        "event": "webhook.test",
        "description": "Tes dari tombol di dashboard. Field order bernilai null."
      }
    ],
    "headers": [
      {
        "name": "X-Nomerin-Event",
        "description": "Nama event, misalnya order.otp."
      },
      {
        "name": "X-Nomerin-Delivery",
        "description": "ID pengiriman, unik per percobaan kirim. Pakai untuk mengabaikan duplikat."
      },
      {
        "name": "X-Nomerin-Timestamp",
        "description": "Unix timestamp (detik) saat dikirim."
      },
      {
        "name": "X-Nomerin-Signature",
        "description": "t=<timestamp>,v1=<hex HMAC-SHA256>. Dihitung dari secret webhook atas string \"<timestamp>.<body mentah>\"."
      }
    ],
    "retry": {
      "success": "Hanya HTTP 200 yang dianggap sukses.",
      "max_attempts": 5,
      "delays_seconds": [
        5,
        15,
        40,
        90
      ]
    }
  }
}