{
  "info": {
    "name": "iletiniz API — Public v1",
    "description": "iletiniz public API — API key tabanlı SMS / Telegram / WhatsApp gönderimi ve rehber (kişi + grup) yönetimi.\n\n## Kimlik doğrulama\n`Authorization: Bearer iltz_live_...` (veya `iltz_test_...`). Anahtarı Dashboard > API Anahtarları bölümünden alın ve koleksiyonun `apiKey` değişkenine yazın. Tüm istekler bu Bearer token'ı miras alır (Health hariç).\n\n## Taban URL\n`{{baseUrl}}` = `https://api.iletiniz.com`\n\n## Hata formatı\nTüm hatalar `{ \"error\": \"kod\", \"message\": \"açıklama\" }` zarfında döner. Örn. `invalid_api_key`, `ip_not_allowed`, `invalid_phone`, `country_not_allowed`, `feature_not_in_plan`.\n\n## Sayfalama\nListe uçları cursor tabanlıdır: `limit` (1–100, varsayılan 50) ve `cursor` query parametreleri. Yanıt zarfı `{ data, hasMore, nextCursor }`.\n\n## Idempotency\nTekrar denenen (retry/timeout) gönderimlerde çift kota tüketimini önlemek için gövdede `idempotencyKey` (8–191 karakter) gönderin.\n\n## Rate limit & IP allowlist\nHer API anahtarı için ayrı rate limit uygulanır. Dashboard'dan tanımlanan IP allowlist dışından gelen istekler `403 ip_not_allowed` ile reddedilir.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [{ "key": "token", "value": "{{apiKey}}", "type": "string" }]
  },
  "variable": [
    { "key": "baseUrl", "value": "https://api.iletiniz.com", "type": "string" },
    { "key": "apiKey", "value": "iltz_live_xxxxxxxxxxxxxxxxxxxxxxxx", "type": "string" },
    { "key": "jobId", "value": "", "type": "string" },
    { "key": "contactId", "value": "con_01HZ7YQK8N3VBR5W2C9P0F4D6T", "type": "string" },
    { "key": "groupId", "value": "grp_01HZ7YQK8N3VBR5W2C9P0F4D6T", "type": "string" }
  ],
  "item": [
    {
      "name": "Health",
      "item": [
        {
          "name": "Sağlık kontrolü",
          "request": {
            "auth": { "type": "noauth" },
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/health",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "health"]
            },
            "description": "Liveness probe — `{ \"ok\": true }` döner. Kimlik doğrulama gerektirmez, DB sorgusu yapmaz."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Messages",
      "description": "Mesaj gönderimi. Kanal (SMS / Telegram / WhatsApp) seçilen bağlantıya göre otomatik belirlenir.",
      "item": [
        {
          "name": "Tekli mesaj gönder",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Dönen job_id'yi sonraki 'Mesaj durumu' isteği için sakla",
                  "if (pm.response.code === 201) {",
                  "  const body = pm.response.json();",
                  "  if (body.job_id) pm.collectionVariables.set('jobId', body.job_id);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"to\": \"+905551234567\",\n  \"body\": \"Merhaba!\",\n  \"provider\": \"my-provider\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/messages",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "messages"]
            },
            "description": "Tek mesaj gönderir (201).\n\n**Alanlar**\n- `to` (zorunlu) — SMS/WhatsApp için E.164 (`+905551234567`), Telegram için `chat_id` veya `@username`.\n- `body` veya `template` — **tam olarak biri** zorunludur (`body` max 1600 karakter).\n- `provider` (ops.) — bağlantı kodu; verilmezse workspace'in varsayılan bağlantısı kullanılır.\n- `sender` (ops.) — SMS msgheader. Telegram'da yok sayılır.\n- `iys` (ops.) — IYS (İleti Yönetim Sistemi) işaretleyici.\n- `idempotencyKey` (ops.) — 8–191 karakter; retry'da çift kota önler.\n\n**Yanıt** `{ job_id, status, to, provider, created_at, ... }` — `status`: `sent | queued | failed`.\n\n**400 hata kodları**: `invalid_phone`, `country_not_allowed`, `invalid_chat_id`."
          },
          "response": []
        },
        {
          "name": "Tekli mesaj gönder (template)",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"to\": \"+905551234567\",\n  \"template\": \"siparis_onay\",\n  \"variables\": {\n    \"ad\": \"Ali\",\n    \"kod\": \"4827\"\n  },\n  \"provider\": \"my-provider\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/messages",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "messages"]
            },
            "description": "Şablonla gönderim. `template` (a-z, 0-9, _) workspace'te tanımlı bir şablon anahtarıdır; `variables` yalnızca `template` ile birlikte kullanılır. `body` ile birlikte gönderilemez."
          },
          "response": []
        },
        {
          "name": "Fallback ile gönderim (SMS)",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"to\": \"+905321234567\",\n  \"body\": \"Sn. Mehmet, kodunuz: TR248491\",\n  \"sender\": \"MARKAM\",\n  \"provider\": \"netgsm\",\n  \"fallback\": [\"verimor\", \"iletimerkezi\"]\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/messages",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "messages"]
            },
            "description": "Yedek (fallback) sağlayıcılarla gönderim. Birincil sağlayıcı mesajı **reddederse** (sağlayıcı hatası veya bağlantı arızası — hard-fail) aynı mesaj sıradaki yedek sağlayıcıyla otomatik yeniden denenir; ilk başarıda durur.\n\n- `fallback` (ops.) — sıralı yedek SMS sağlayıcı kodları (`string[]`, en fazla 3). Örn. `[\"verimor\", \"iletimerkezi\"]`. Hepsi bağlı `kind=sms` sağlayıcı olmalı; birincilden ve birbirinden farklı olmalı.\n- **Kota tek sayım**: bir mantıksal mesaj kaç deneme yapılırsa yapılsın **bir** kez kota tüketir; tümü başarısızsa sıfır.\n- Yalnızca **reddte** (hard-fail) tetiklenir; zaman aşımı / teslim edilmedi durumunda değil (gelecek sürüm).\n- v1 yalnızca **SMS→SMS** çapraz sağlayıcıdır; kanal değiştirmez.\n\n**Yanıt** `attempts` dizisi içerebilir (denenen her sağlayıcı + durum); nihai `provider` = kabul eden sağlayıcı."
          },
          "response": []
        },
        {
          "name": "Toplu mesaj gönder (max 200)",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"provider\": \"my-provider\",\n  \"items\": [\n    { \"to\": \"+905551234567\", \"body\": \"Merhaba!\" },\n    { \"to\": \"+905559876543\", \"body\": \"Merhaba!\" }\n  ]\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/messages/bulk",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "messages", "bulk"]
            },
            "description": "Tek istekte 1–200 alıcı (200 döner — kısmi başarı mümkün).\n\nNormal modda her `items[].to` + `items[].body` zorunludur. Üst seviye `template` verilirse `items` içinde `body` kullanılamaz, bunun yerine `items[].variables` verilir.\n\n**Yanıt** `{ total, sent, failed, provider, created_at, results[] }`. Başarısız öğelerde `results[].status: \"failed\"` ve `error.code` (`invalid_phone` | `country_not_allowed`)."
          },
          "response": []
        },
        {
          "name": "Fallback ile toplu gönderim",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"provider\": \"netgsm\",\n  \"sender\": \"MARKAM\",\n  \"fallback\": [\"verimor\", \"iletimerkezi\"],\n  \"items\": [\n    { \"to\": \"+905551234567\", \"body\": \"Merhaba!\" },\n    { \"to\": \"+905559876543\", \"body\": \"Merhaba!\" }\n  ]\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/messages/bulk",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "messages", "bulk"]
            },
            "description": "Toplu gönderimde üst seviye `fallback` (sıralı yedek SMS sağlayıcı kodları, `string[]`, en fazla 3) tüm satırlara uygulanır. Bir satırın birincil sağlayıcısı **reddederse** (hard-fail) o satır sıradaki yedek sağlayıcıyla yeniden denenir; ilk başarıda durur.\n\n- **Kota tek sayım**: her mantıksal satır, deneme sayısından bağımsız **bir** kez kota tüketir; satır tümden başarısızsa sıfır.\n- Yalnızca **reddte** (hard-fail) tetiklenir; zaman aşımı / teslim edilmedi durumunda değil (gelecek sürüm).\n- v1 yalnızca **SMS→SMS** — kanal değiştirmez.\n- Kabul eden sağlayıcı, ilgili satır sonucunda `delivered_via` alanı olarak döner (örn. `\"delivered_via\": \"verimor\"`)."
          },
          "response": []
        },
        {
          "name": "Mesaj durumu",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/messages/{{jobId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "messages", "{{jobId}}"]
            },
            "description": "Gönderim durumunu ve teslim raporunu sorgular.\n\n**Yanıt** `{ job_id, status, to, provider, created_at, sent_at, delivered_at, error? }` — `status`: `sent | queued | failed | delivered | expired | rejected | unknown`.\n\n`{{jobId}}` değişkeni 'Tekli mesaj gönder' isteği çalıştırıldığında otomatik doldurulur."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Contacts",
      "description": "Rehber (kişi) yönetimi. `id` opaque public kimliktir (`con_…`).",
      "item": [
        {
          "name": "Kişi oluştur",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 201) {",
                  "  const body = pm.response.json();",
                  "  if (body.id) pm.collectionVariables.set('contactId', body.id);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"phone\": \"+905551234567\",\n  \"firstName\": \"Ali\",\n  \"lastName\": \"Yılmaz\",\n  \"email\": \"ali@ornek.com\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/contacts",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "contacts"]
            },
            "description": "Rehbere yeni kişi ekler (201). `phone` zorunlu, E.164 formatında (tüm ülke kodları). Daha önce silinmiş numara restore edilir; aktif kayıtlı numara için `409` döner.\n\nDönen `id` (`con_…`) sonraki get/update/delete çağrılarında kullanılır."
          },
          "response": []
        },
        {
          "name": "Kişileri listele",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/contacts?limit=50",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "contacts"],
              "query": [
                { "key": "limit", "value": "50", "description": "Sayfa boyutu (1–100, varsayılan 50)." },
                { "key": "cursor", "value": "", "description": "Önceki yanıtın nextCursor değeri.", "disabled": true }
              ]
            },
            "description": "Kişileri ekleme sırasına göre cursor tabanlı sayfalı döner. Yanıt: `{ data, hasMore, nextCursor }`."
          },
          "response": []
        },
        {
          "name": "Tek kişi getir",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/contacts/{{contactId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "contacts", "{{contactId}}"]
            },
            "description": "Opaque kimlik (`con_…`) ile tek kişiyi döner. Başka workspace'e aitse `404`."
          },
          "response": []
        },
        {
          "name": "Kişiyi güncelle",
          "request": {
            "method": "PATCH",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"firstName\": \"Ali\",\n  \"lastName\": \"Yılmaz\",\n  \"email\": \"ali@ornek.com\",\n  \"optOut\": false\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/contacts/{{contactId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "contacts", "{{contactId}}"]
            },
            "description": "Ad/soyad/e-posta/opt-out günceller. Bir alan `null` → temizlenir; gönderilmeyen alan korunur. `phone` değiştirilemez (gönderilirse `400`)."
          },
          "response": []
        },
        {
          "name": "Kişiyi sil",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/contacts/{{contactId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "contacts", "{{contactId}}"]
            },
            "description": "Kişiyi soft-delete eder. Aynı numara tekrar eklenirse kayıt restore edilir. Yanıt: `{ id, deleted: true }`."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Groups",
      "description": "Kişi grupları. `id` opaque public kimliktir (`grp_…`).",
      "item": [
        {
          "name": "Grup oluştur",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 201) {",
                  "  const body = pm.response.json();",
                  "  if (body.id) pm.collectionVariables.set('groupId', body.id);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"VIP Müşteriler\",\n  \"color\": \"#E97B3A\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/groups",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "groups"]
            },
            "description": "Yeni grup oluşturur (201). `name` workspace bazında tekildir (çakışırsa `409`). `color` opsiyonel `#RRGGBB`. Silinmiş aynı isimli grup eski üyeleriyle restore edilir."
          },
          "response": []
        },
        {
          "name": "Grupları listele",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/groups?limit=50",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "groups"],
              "query": [
                { "key": "limit", "value": "50", "description": "Sayfa boyutu (1–100, varsayılan 50)." },
                { "key": "cursor", "value": "", "description": "Önceki yanıtın nextCursor değeri.", "disabled": true }
              ]
            },
            "description": "Grupları cursor tabanlı sayfalı döner. Her grup yaşayan üye sayısını (`memberCount`) içerir."
          },
          "response": []
        },
        {
          "name": "Tek grubu getir",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/groups/{{groupId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "groups", "{{groupId}}"]
            },
            "description": "Opaque kimlik (`grp_…`) ile tek grubu döner."
          },
          "response": []
        },
        {
          "name": "Grubu güncelle",
          "request": {
            "method": "PATCH",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"VIP Müşteriler\",\n  \"color\": \"#C25A20\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/groups/{{groupId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "groups", "{{groupId}}"]
            },
            "description": "Grup adını ve/veya rengini günceller. En az bir alan zorunlu. `color: null` → temizlenir. Yeni ad çakışırsa `409`."
          },
          "response": []
        },
        {
          "name": "Grubu sil",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/groups/{{groupId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "groups", "{{groupId}}"]
            },
            "description": "Grubu soft-delete eder. Üyelikler korunur (aynı isimli grup tekrar oluşturulursa restore). Kişiler silinmez. Yanıt: `{ id, deleted: true }`."
          },
          "response": []
        },
        {
          "name": "Grup üyelerini listele",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/groups/{{groupId}}/members?limit=50",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "groups", "{{groupId}}", "members"],
              "query": [
                { "key": "limit", "value": "50", "description": "Sayfa boyutu (1–100, varsayılan 50)." },
                { "key": "cursor", "value": "", "description": "Önceki yanıtın nextCursor değeri (bir kişi kimliği).", "disabled": true }
              ]
            },
            "description": "Gruptaki kişileri cursor tabanlı sayfalı döner. Öğeler standart kişi nesnesidir."
          },
          "response": []
        },
        {
          "name": "Gruba kişi ekle (max 200)",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"contactIds\": [\n    \"{{contactId}}\"\n  ]\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/groups/{{groupId}}/members",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "groups", "{{groupId}}", "members"]
            },
            "description": "1–200 kişiyi gruba ekler (200). İdempotent. Yanıt: `{ groupId, added[], alreadyMembers[], notFound[], memberCount }`."
          },
          "response": []
        },
        {
          "name": "Gruptan kişi çıkar",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/groups/{{groupId}}/members/{{contactId}}",
              "host": ["{{baseUrl}}"],
              "path": ["v1", "groups", "{{groupId}}", "members", "{{contactId}}"]
            },
            "description": "Kişiyi gruptan çıkarır (kişi rehberden silinmez). Üye değilse `404`. Yanıt: `{ groupId, contactId, removed: true }`."
          },
          "response": []
        }
      ]
    },
    {
      "name": "MCP",
      "description": "Model Context Protocol — Streamable HTTP transport, JSON-RPC 2.0. Aynı `Bearer iltz_…` anahtarıyla kimlik doğrular. Claude, Cursor vb. istemciler bağlanır.",
      "item": [
        {
          "name": "MCP yetenek keşfi",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/mcp",
              "host": ["{{baseUrl}}"],
              "path": ["mcp"]
            },
            "description": "Sunucu yeteneklerini döner: `{ name, transport, endpoint, methods, auth, tools }`. Araçlar: `send_message`, `send_bulk`, `message_status`."
          },
          "response": []
        },
        {
          "name": "MCP — tools/list",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"tools/list\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/mcp",
              "host": ["{{baseUrl}}"],
              "path": ["mcp"]
            },
            "description": "JSON-RPC 2.0 — mevcut araçları listeler."
          },
          "response": []
        },
        {
          "name": "MCP — tools/call (send_message)",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 2,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"send_message\",\n    \"arguments\": {\n      \"to\": \"+905551234567\",\n      \"body\": \"Merhaba!\",\n      \"provider\": \"my-provider\"\n    }\n  }\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/mcp",
              "host": ["{{baseUrl}}"],
              "path": ["mcp"]
            },
            "description": "JSON-RPC 2.0 — `send_message` aracını çağırır. Tamamen notification olan istekler `202 Accepted` (gövdesiz) döner."
          },
          "response": []
        }
      ]
    }
  ]
}
