{
  "openapi": "3.1.0",
  "info": {
    "title": "esim.tc Katalog API",
    "version": "1.0.0",
    "summary": "eSIM paket kataloğunu okumaya yarayan, kimlik doğrulaması gerektirmeyen uçlar.",
    "description": "Bu belge esim.tc'nin **herkese açık, salt-okunur** katalog uçlarını tanımlar: ülkeler, bölgeler ve eSIM paketleri.\n\n## Kapsam\n\nBelge bilerek dardır. Sipariş, ödeme, cüzdan, kullanıcı ve yönetim uçları burada **yer almaz** ve dışarıya belgelenmez. Satın alma insan onaylı hesap akışıyla yapılır; ajanlar için kimlik doğrulama durumu https://esim.tc/auth.md adresinde açıklanmıştır.\n\n## Yanıt zarfı\n\nTüm uçlar aynı zarfla döner: `{ success, statusCode, message, data }`. Asıl veri her zaman `data` alanındadır.\n\n## Hata davranışı\n\nBulunamayan kayıt dahil hemen her hata **HTTP 400** ve `success: false` ile döner — tek istisna aşağıdaki hız sınırıdır (429). HTTP durum kodu hata türünü ayırt etmek için güvenilir değildir; `message` alanına bakın. Mesajlar Türkçedir.\n\n## Para birimi ve birimler\n\n`price` alanı **TRY** (Türk Lirası), KDV dahil. `quota` alanı `quotaType` ile birlikte okunur (`GB`). `validDays` gün sayısıdır.\n\n## Hız sınırı\n\nIP başına **60 saniyede 300 istek**. Sınır aşılırsa `429 Too Many Requests` ve `Retry-After` başlığı döner (gövde diğer hatalarla aynı zarfı kullanır). Bu sınır normal kullanımı kısıtlamak için değil, kaçak döngüleri durdurmak için var — insan kullanıcının onlarca katı bir eşikte duruyor.\n\nYine de nazik olun: ülke ve bölge listelerini en az bir saat önbelleğe alın, gereksiz yere sık çağırmayın.\n\n## İnsan ve ajan sürümleri\n\nSayfaların markdown sürümleri için https://esim.tc/llms.txt — her sayfa yolun sonuna `.md` eklenerek ya da `Accept: text/markdown` başlığıyla da alınabilir.",
    "contact": {
      "name": "eSIM Türkiye",
      "email": "info@esimtr.com",
      "url": "https://esim.tc/gelistirici"
    }
  },
  "servers": [
    {
      "url": "https://esim.tc",
      "description": "Canlı"
    }
  ],
  "externalDocs": {
    "description": "İnsan tarafından okunabilir API dokümantasyonu",
    "url": "https://esim.tc/gelistirici"
  },
  "tags": [
    {
      "name": "countries",
      "description": "Ülkeler ve ülkeye göre paketler"
    },
    {
      "name": "regions",
      "description": "Bölgeler (Avrupa, Balkanlar, Asya...) ve bölgeye göre paketler"
    },
    {
      "name": "products",
      "description": "Tekil paketler ve paket listeleri"
    }
  ],
  "paths": {
    "/api/countries": {
      "get": {
        "tags": [
          "countries"
        ],
        "operationId": "listCountries",
        "summary": "Tüm ülkeleri paket sayılarıyla listeler",
        "description": "Aranma sayısına göre azalan sıralıdır. Türkiye (`a2Code: \"tr\"`) yurt dışı hizmeti olduğumuz için bilerek listede yoktur.\n\nYanıt ~65 KB'dır. Sunucu tarafında 10 dakika önbelleklenir, yani sık çağırmak size taze veri getirmez.",
        "responses": {
          "200": {
            "description": "Ülke listesi",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Country"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/country/{uIdOrUrl}": {
      "get": {
        "tags": [
          "countries"
        ],
        "operationId": "getCountry",
        "summary": "Tek bir ülkenin bilgilerini getirir",
        "description": "**Dikkat:** Bu uçta `productCount` her zaman `0` döner. Paket sayısı için `GET /api/countries` kullanın.",
        "parameters": [
          {
            "$ref": "#/components/parameters/CountryRef"
          }
        ],
        "responses": {
          "200": {
            "description": "Ülke",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/Country"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/country/products/{uIdOrUrl}": {
      "get": {
        "tags": [
          "countries",
          "products"
        ],
        "operationId": "listCountryPackages",
        "summary": "Bir ülkede geçerli eSIM paketlerini listeler",
        "description": "Yalnızca aktif paketler döner ve liste **tekilleştirilmiştir**: aynı (kota, gün) kombinasyonundan yalnızca en ucuz paket listede yer alır, sağlayıcı ayrımı yapılmaz. Fiyata göre artan sıralıdır.\n\nSunucu sabit bir iş kuralı uygular: 1 GB'a kadar olan paketlerde kısa süreli seçenekler de gelir, üzerindekilerde en az 30 gün geçerli paketler döner.\n\nListe uçlarında `name`, `providerName` ve `isActive` alanları **doldurulmaz** (bkz. Package şeması). Bu alanlar için `GET /api/product/{uId}` kullanın.",
        "parameters": [
          {
            "$ref": "#/components/parameters/CountryRef"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "name": "size",
            "in": "query",
            "required": false,
            "description": "Sayfa başına kayıt sayısı.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 12
            }
          },
          {
            "name": "onlySinglePlans",
            "in": "query",
            "required": false,
            "description": "`true` ise yalnızca tek ülkeyi kapsayan paketler döner; bölgesel/çok ülkeli paketler elenir.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sayfalanmış paket listesi",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedPackagesResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/regions": {
      "get": {
        "tags": [
          "regions"
        ],
        "operationId": "listRegions",
        "summary": "Tüm bölgeleri listeler",
        "description": "**Dikkat:** Her bölgenin `icon` alanı base64 gömülü PNG'dir; yanıt bu yüzden ~48 KB'dır. Yalnızca isim/slug gerekiyorsa `icon` alanını yok sayın.\n\n`productCount` bu uçta **her zaman 0**'dır; bölgedeki paket sayısı için `GET /api/region/products/{uIdOrUrl}` yanıtındaki `itemCount` alanına bakın.",
        "responses": {
          "200": {
            "description": "Bölge listesi",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Region"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/region/{uIdOrUrl}": {
      "get": {
        "tags": [
          "regions"
        ],
        "operationId": "getRegion",
        "summary": "Tek bir bölgenin bilgilerini getirir",
        "parameters": [
          {
            "$ref": "#/components/parameters/RegionRef"
          }
        ],
        "responses": {
          "200": {
            "description": "Bölge",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/Region"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/region/products/{uIdOrUrl}": {
      "get": {
        "tags": [
          "regions",
          "products"
        ],
        "operationId": "listRegionPackages",
        "summary": "Bir bölgede geçerli eSIM paketlerini listeler",
        "description": "Tekilleştirme, sıralama ve doldurulmayan alanlar `GET /api/country/products/{uIdOrUrl}` ile aynıdır.",
        "parameters": [
          {
            "$ref": "#/components/parameters/RegionRef"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "name": "size",
            "in": "query",
            "required": false,
            "description": "Sayfa başına kayıt sayısı.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 12
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sayfalanmış paket listesi",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedPackagesResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/product/{uId}": {
      "get": {
        "tags": [
          "products"
        ],
        "operationId": "getPackage",
        "summary": "Tek bir paketin tüm ayrıntılarını getirir",
        "description": "Paket adı, sağlayıcı adı ve operatör/şebeke bilgisi **yalnızca** bu uçta doludur.\n\nAynı paketin insan tarafından okunabilir sayfası: `https://esim.tc/paket/{uId}`",
        "parameters": [
          {
            "name": "uId",
            "in": "path",
            "required": true,
            "description": "Paketin UUID'si.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "2074da68-9b65-4537-bdea-591d9762eda3"
          }
        ],
        "responses": {
          "200": {
            "description": "Paket",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/Package"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/products/best": {
      "get": {
        "tags": [
          "products"
        ],
        "operationId": "listBestSellingPackages",
        "summary": "Çok satan paketleri listeler",
        "description": "Ana sayfada gösterilen kısa listedir. Liste uçlarının alan kısıtları burada da geçerlidir.",
        "responses": {
          "200": {
            "description": "Paket listesi",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackageListResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/products/sameplans": {
      "get": {
        "tags": [
          "products"
        ],
        "operationId": "listEquivalentPackages",
        "summary": "Verilen paketin destinasyonunu kapsayan diğer paketleri listeler",
        "description": "**Adı yanıltıcı:** bu uç aynı kota/gün kombinasyonunu değil, aynı **destinasyonu** kapsayan paketleri döner. Referans paketin `region` alanı doluysa aynı bölgedeki diğer aktif paketler; değilse referansın **tüm** ülkelerini kapsayan aktif paketler döner.\n\nSıralama satış adedine göre azalandır ve en fazla 100 kayıt döner. Dönen paketlerin kotası ve süresi referanstan farklı olabilir — karşılaştırma yapacaksanız kendiniz eşleştirin.",
        "parameters": [
          {
            "name": "uId",
            "in": "query",
            "required": true,
            "description": "Referans alınacak paketin UUID'si.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paket listesi",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackageListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "CountryRef": {
        "name": "uIdOrUrl",
        "in": "path",
        "required": true,
        "description": "Ülkenin UUID'si ya da Türkçe slug'ı (`GET /api/countries` yanıtındaki `url` alanı).",
        "schema": {
          "type": "string"
        },
        "example": "yunanistan"
      },
      "RegionRef": {
        "name": "uIdOrUrl",
        "in": "path",
        "required": true,
        "description": "Bölgenin UUID'si ya da Türkçe slug'ı (`GET /api/regions` yanıtındaki `url` alanı).",
        "schema": {
          "type": "string"
        },
        "example": "balkanlar"
      },
      "Page": {
        "name": "page",
        "in": "query",
        "required": false,
        "description": "1'den başlayan sayfa numarası.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Hata. Kayıt bulunamadığında da bu yanıt döner — `message` alanına bakın.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "success": false,
              "statusCode": 400,
              "message": "Beklenmedik bir hata oluştu.",
              "data": null
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Hız sınırı aşıldı (IP başına 60 saniyede 300 istek). `Retry-After` başlığındaki saniye kadar bekleyip tekrar deneyin.",
        "headers": {
          "Retry-After": {
            "description": "Yeniden denemeden önce beklenecek saniye.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "success": false,
              "statusCode": 429,
              "message": "İstek limiti aşıldı. 60 saniye sonra yeniden deneyiniz.",
              "data": null
            }
          }
        }
      }
    },
    "schemas": {
      "Envelope": {
        "type": "object",
        "description": "Tüm yanıtların ortak zarfı.",
        "required": [
          "success",
          "statusCode"
        ],
        "properties": {
          "success": {
            "type": "boolean"
          },
          "statusCode": {
            "type": "integer",
            "examples": [
              200
            ]
          },
          "message": {
            "type": "string",
            "description": "Türkçe durum mesajı."
          }
        }
      },
      "ErrorResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Envelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "null"
              }
            }
          }
        ]
      },
      "Country": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "uId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "description": "Türkçe ülke adı.",
            "examples": [
              "Yunanistan"
            ]
          },
          "nameEnglish": {
            "type": "string",
            "examples": [
              "Greece"
            ]
          },
          "a2Code": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 kodu, **küçük harf**.",
            "examples": [
              "gr"
            ]
          },
          "a3Code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Şu an doldurulmuyor."
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Türkçe slug. Sayfa adresi: `https://esim.tc/ulke/{url}`",
            "examples": [
              "yunanistan"
            ]
          },
          "productCount": {
            "type": "integer",
            "description": "Ülkede geçerli aktif paket sayısı. `GET /api/country/{uIdOrUrl}` ucunda her zaman 0'dır."
          },
          "searchCount": {
            "type": "integer",
            "description": "Site içi arama/seçim sayacı."
          },
          "removeOnPocketDetail": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "removeIncludedPockets": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "active": {
            "type": [
              "boolean",
              "null"
            ]
          }
        }
      },
      "Region": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "uId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "examples": [
              "Balkanlar"
            ]
          },
          "nameEnglish": {
            "type": "string",
            "examples": [
              "Balkans"
            ]
          },
          "icon": {
            "type": [
              "string",
              "null"
            ],
            "description": "Base64 gömülü PNG (`data:image/png;base64,...`). Yanıt boyutunun neredeyse tamamı budur."
          },
          "color": {
            "type": [
              "string",
              "null"
            ]
          },
          "productCount": {
            "type": "integer",
            "description": "**Her zaman 0.** Bölge uçları bu alanı doldurmuyor; paket sayısı için `GET /api/region/products/{uIdOrUrl}` yanıtındaki `itemCount` alanını kullanın."
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Türkçe slug. Sayfa adresi: `https://esim.tc/bolge/{url}`"
          }
        }
      },
      "Package": {
        "type": "object",
        "description": "Bir eSIM paketi. Liste uçlarında `name`, `providerName`, `isActive`, `operators` ve `operatorsByCountry` alanları doldurulmaz; bunlar için `GET /api/product/{uId}` kullanın.",
        "properties": {
          "uId": {
            "type": "string",
            "format": "uuid",
            "description": "Paketin kimliği. Sayfa adresi: `https://esim.tc/paket/{uId}`"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Paket adı. Yalnızca tekil uçta doludur.",
            "examples": [
              "Balkans 1GB"
            ]
          },
          "price": {
            "type": "number",
            "description": "Fiyat, **TRY** (Türk Lirası), KDV dahil.",
            "examples": [
              79.9
            ]
          },
          "quota": {
            "type": "number",
            "description": "Veri kotası, `quotaType` biriminde.",
            "examples": [
              1
            ]
          },
          "quotaType": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "GB"
            ]
          },
          "validDays": {
            "type": "integer",
            "description": "İlk aktivasyondan itibaren geçerlilik, gün.",
            "examples": [
              7
            ]
          },
          "countries": {
            "type": [
              "array",
              "null"
            ],
            "description": "Paketin geçerli olduğu ülkelerin ISO 3166-1 alpha-2 kodları, küçük harf.",
            "items": {
              "type": "string"
            }
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Balkanlar"
            ]
          },
          "stock": {
            "type": "boolean",
            "description": "Satın alınabilir mi."
          },
          "provider": {
            "type": "integer",
            "description": "Sağlayıcının sayısal kodu."
          },
          "providerName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Sağlayıcı adı. Yalnızca tekil uçta doludur.",
            "examples": [
              "Yesim"
            ]
          },
          "providerToProvide": {
            "type": [
              "string",
              "null"
            ]
          },
          "externalId": {
            "type": [
              "string",
              "null"
            ]
          },
          "isActive": {
            "type": "boolean",
            "description": "Liste uçlarında doldurulmaz ve `false` gelir — ancak liste zaten yalnızca aktif paketleri içerir. Gerçek değer için tekil uca bakın."
          },
          "passiveExplanation": {
            "type": [
              "string",
              "null"
            ]
          },
          "operators": {
            "type": [
              "string",
              "null"
            ],
            "description": "Paketin bağlandığı operatörler, virgülle ayrılmış tek satır. Yalnızca bazı sağlayıcılarda (Yesim) doludur."
          },
          "operatorsByCountry": {
            "type": [
              "string",
              "null"
            ],
            "description": "Ülke kodu → operatör adları eşlemesinin **JSON metni** (nesne değil, string). Yalnızca bazı sağlayıcılarda (NextSim) doludur."
          },
          "createdTime": {
            "type": "string",
            "format": "date-time"
          },
          "updatedTime": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PackageListResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Envelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Package"
                }
              }
            }
          }
        ]
      },
      "PaginatedPackagesResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Envelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "properties": {
                  "page": {
                    "type": "integer",
                    "description": "İstenen sayfa."
                  },
                  "size": {
                    "type": "integer",
                    "description": "Sayfa başına kayıt."
                  },
                  "itemCount": {
                    "type": "integer",
                    "description": "Toplam kayıt sayısı."
                  },
                  "pageCount": {
                    "type": "integer",
                    "description": "Toplam sayfa sayısı."
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Package"
                    }
                  }
                }
              }
            }
          }
        ]
      }
    }
  }
}
