# Rehbere SMS gönderme

> Rehberdeki kişi gruplarına, istekte verilen kişilere ya da her ikisine tek bir metinle gönderimi kuyruğa alır.

- **İstek:** `POST https://ext-api.netgsm.com.tr/sms/v3/directory/send`
- **Canlı ortam:** `https://ext-api.netgsm.com.tr/sms/v3`
- **Kimlik doğrulama:** `Authorization: Bearer <token>`
- **HTML sayfası:** https://api-docs.netgsm.com.tr/send-to-directory/
- **OpenAPI tanımı:** https://api-docs.netgsm.com.tr/openapi/sms-v3.yaml (operationId: `sendToDirectory`)

## Açıklama

Rehberdeki kişi gruplarına, istekte verilen kişilere ya da her ikisine tek bir metinle
gönderimi kuyruğa alır. Gruplar ve kişiselleştirme, yanıt döndükten sonra işlenir.

- En az bir grup (`groupIds`) ya da kişi (`contacts`) belirtmelisiniz. Yalnızca numaralara
  göndermek için `POST /send`'i kullanın.
- `UNICODE` kodlaması bu uç noktada desteklenmez.
- Onay için bekletilen bir görev de 202 alır; neyin beklettiğini `review` söyler.

**İstek sınırı.** Hesap başına dakikada 60 istek, `/send` sınırından ayrı. Süre penceresi ilk
istekle başlar ve sabittir. Aşıldığında 429 `Retry-After` başlığıyla döner.

**Yeniden deneme.** Aynı istek iki kez gönderilirse iki görev oluşur ve gruplara iki kez mesaj
gider. 503 ya da 504 aldıysanız yeniden göndermeden önce `GET /report` ile kontrol etmelisiniz.

## İstek

### Gövde (`application/json`, zorunlu)

- `sender` (string, zorunlu, en az uzunluk 1, en fazla uzunluk 35): Gönderici başlığı, `POST /send`'deki gibi: hesaba kayıtlı bir başlıkla yazıldığı gibi birebir eşleşmelidir, büyük/küçük harf dahil. Eşleşmezse istek 422 `REJECTED_HEADER` ile reddedilir. Örnek: `NETGSM`.
- `text` (string, zorunlu, en az uzunluk 1, en fazla uzunluk 917): Mesaj metni; tüm alıcılara aynı metin gider, `addressType` ile kişiselleştirilebilir. En fazla uzunluk `DEFAULT` için 917, `TURKISH` için 888 karakterdir (`çğışĞİŞ` iki karakter sayılır); alfanümerik bir gönderici başlığında her sınır 5 karakter azdır. Sınırı aşan metin 400 ile reddedilir.
- `encoding` (string, isteğe bağlı): Metnin karakter kodlaması, `POST /send`'deki gibi. Büyük/küçük harfe duyarlıdır. `UNICODE` bu uç noktada 400 ile reddedilir. Değerler: `DEFAULT`, `TURKISH`. Varsayılan: `DEFAULT`.
- `iysFilter` (string, isteğe bağlı): İYS filtresi; değerler `POST /send`'deki gibidir ve büyük/küçük harf fark etmez. Listede olmayan bir değer `NONE` sayılır. Filtreyi platform uygular; bu başlık ya da hesap için uygulanamıyorsa istek 422 `REJECTED_IYS` ile reddedilir. Değerler: `NONE`, `BRAND`, `RETAILER`, `BRAND_AND_RETAILER`. Varsayılan: `NONE`.
- `groupIds` (array<integer, int64>, isteğe bağlı): Hesabın rehberindeki kişi grupları.
- `contacts` (array<object>, isteğe bağlı)
- `contacts[].number` (string, zorunlu, en az uzunluk 1, en fazla uzunluk 20): Alıcının numarası. Biçimini platform denetler.
- `contacts[].name` (string, isteğe bağlı, en fazla uzunluk 100): Alıcının adı. İsteğe bağlı.
- `contacts[].surname` (string, isteğe bağlı, en fazla uzunluk 100): Alıcının soyadı. İsteğe bağlı.
- `contacts[].address` (string, isteğe bağlı, en fazla uzunluk 500): Kişiye özel hitap, örneğin "Sayın". Kullanılıp kullanılmayacağını `addressType` belirler. İsteğe bağlı.
- `recipients` (array<string>, isteğe bağlı): Kişiselleştirme uygulanmayan düz numaralar. Yalnızca `groupIds` ya da `contacts` ile birlikte kullanılabilir. Numaranın biçimini platform denetler.
- `address1` (string, isteğe bağlı, en fazla uzunluk 500): Tüm alıcılar için aynı olan ön hitap, örneğin "Sayın". Kullanılıp kullanılmayacağını `addressType` belirler.
- `address2` (string, isteğe bağlı, en fazla uzunluk 500): Tüm alıcılar için aynı olan son hitap, örneğin "Bey/Hanım,". Kullanılıp kullanılmayacağını `addressType` belirler.
- `addressType` (integer, isteğe bağlı, en az 0, en fazla 17): Mesaj metninin başına hangi kişisel bilgilerin ekleneceğini belirler. Seçilen parçalar aşağıdaki sırayla ve aralarında birer boşlukla metnin başına eklenir; alıcıda boş olan parça atlanır. 0 ile 17 dışındaki bir değer `INVALID_REQUEST` ile reddedilir. Varsayılan: `0`.

  - **Ad** ve **soyad**, rehberdeki kişi kaydından ya da `contacts` içindeki `name` ve `surname` alanlarından gelir.
  - **Kişi hitabı**, rehberdeki kişinin hitap bilgisi ya da `contacts` içindeki `address` alanıdır; her alıcı için ayrıdır.
  - `recipients` içinde düz numara olarak verilen alıcılara kişiselleştirme uygulanmaz.

  | Değer | Metnin başına eklenen |
  |---|---|
  | `0` | Hiçbir şey; metin olduğu gibi gönderilir (varsayılan) |
  | `1` | address1 + ad |
  | `2` | address1 + ad + soyad |
  | `3` | address1 + soyad |
  | `4` | address1 + ad + address2 |
  | `5` | address1 + ad + soyad + address2 |
  | `6` | address1 + soyad + address2 |
  | `7` | ad |
  | `8` | soyad |
  | `9` | ad + soyad |
  | `10` | kişi hitabı |
  | `11` | kişi hitabı + ad |
  | `12` | kişi hitabı + ad + soyad |
  | `13` | kişi hitabı + soyad |
  | `14` | kişi hitabı + address2 |
  | `15` | kişi hitabı + ad + address2 |
  | `16` | kişi hitabı + ad + soyad + address2 |
  | `17` | kişi hitabı + soyad + address2 |

  Örnek: `addressType` 5, `address1` "Sayın", `address2` "Bey," ve metin "siparişiniz kargoya verildi." ise
  Ahmet Yılmaz'a giden mesaj "Sayın Ahmet Yılmaz Bey, siparişiniz kargoya verildi." olur.

- `startAt` (string, date-time, isteğe bağlı): En erken gönderim zamanı, saat dilimi farkıyla. Europe/Istanbul saatine çevrilir ve dakikaya yuvarlanır; saniyeler dikkate alınmaz.
- `stopAt` (string, date-time, isteğe bağlı): En geç gönderim zamanı, saat dilimi farkıyla; `startAt` gibi dakikaya yuvarlanır. `startAt`'tan 1 ile 21 saat sonra olmalıdır. Aralığı platform denetler; kabul etmezse istek 400 `INVALID_SEND_WINDOW` ile reddedilir.

Örnek gövde:

```json
{
  "sender": "NETGSM",
  "text": "Merhaba",
  "encoding": "TURKISH",
  "groupIds": [
    7,
    8
  ],
  "contacts": [
    {
      "number": "905xxxxxxxxx",
      "name": "Ali",
      "surname": "Veli"
    }
  ]
}
```

## Örnek istek

```bash
curl -X POST 'https://ext-api.netgsm.com.tr/sms/v3/directory/send' \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
  "sender": "NETGSM",
  "text": "Merhaba",
  "encoding": "TURKISH",
  "groupIds": [
    7,
    8
  ],
  "contacts": [
    {
      "number": "905xxxxxxxxx",
      "name": "Ali",
      "surname": "Veli"
    }
  ]
}'
```

## Yanıtlar

### 202 — Kuyruğa alındı. Gönderim, gönderim aralığı içinde yapılır.

Gövde (`application/json`):

- `jobId` (string, zorunlu): Oluşturulan görevin numarası. Rapor ve istatistik için kullanılır; destek taleplerinde belirtmelisiniz.
- `status` (string, zorunlu): Her zaman `QUEUED`; `startAt` ileride olsa da. Değerler: `QUEUED`.
- `review` (string, zorunlu): Görevin gönderilmeden önce bekletilip bekletilmediği: Değerler: `NONE`, `ADMIN_APPROVAL`, `FORBIDDEN_WORD`, `UNKNOWN`.

  | Değer | Anlamı |
  |---|---|
  | `NONE` | Bekletilmedi; gönderim aralığında gönderilir. |
  | `ADMIN_APPROVAL` | Yönetici onayı bekliyor: hesap onay kontrolünde ya da gönderim hesabın sınırından büyük. |
  | `FORBIDDEN_WORD` | Metin yasaklı bir kelime içerdiği için bekletiliyor. |
  | `UNKNOWN` | Görev kuyruğa alındı ama bekletme nedeni bilinmiyor. |

Örnek:

```json
{
  "jobId": "179309912974612345062827",
  "status": "QUEUED",
  "review": "NONE"
}
```

### 400 — İstek geçersiz. `code` ve `message`:

| `code` | `message` | Ne zaman |
|---|---|---|
| `INVALID_REQUEST` | `N field(s) rejected` | Alanlar doğrulamadan geçmedi (eksik `sender` ya da `text`, uzunluk sınırları, `addressType` 0–17 dışında, `contacts[].number` boş) ya da bir değer türüne uymuyor (`encoding` listede yok, tarih saat dilimi farkı olmadan yazılmış gibi). `violations` belirtir. |
| `INVALID_REQUEST` | `request body could not be read as json` | Gövde geçerli bir JSON değil. |
| `INVALID_REQUEST` | `encoding UNICODE is not supported for directory sends` | `encoding` `UNICODE`. |
| `INVALID_REQUEST` | `a directory send names at least one group or contact; for numbers alone use /sms/v3/send` | `groupIds` ve `contacts` ikisi de boş. |
| `INVALID_REQUEST` | `text exceeds N characters for this encoding and sender` | Metin, `encoding` ve gönderici türüne göre sınırı aşıyor; bkz. `text`. |
| `INVALID_REQUEST` | `a required field is missing` | Platform eksik bir alan bildirdi. |
| `INVALID_SEND_WINDOW` | `the send window was rejected` | Platform `startAt` / `stopAt` aralığını kabul etmedi; bkz. `stopAt`. |

Gövde (`application/json`):

- `code` (string, zorunlu): Değerler: `INVALID_REQUEST`, `INVALID_SEND_WINDOW`.
- `message` (string, zorunlu): Okunabilir ayrıntı. `UNAUTHENTICATED` için `no bearer token` ya da `invalid or expired token`. `REJECTED_HEADER` için ret adıdır; yalnızca rakamlardan oluşan bir göndericide ` (subscriber number headers were not offered)` ekini taşıyabilir. `RATE_LIMITED` için sınırı belirtir; `FORBIDDEN` ve `REJECTED_IYS` için sabit bir cümledir. `SERVICE_UNAVAILABLE` ve `INTERNAL_ERROR` genel bir cümle taşır; bildirirken `requestId` değerini paylaşmanız gerekir.
- `requestId` (string, zorunlu): İsteğe özgü kimlik, 32 onaltılık karakter. Bir sorun bildirirken bu değeri paylaşmanız, isteğin kayıtlarda bulunmasını sağlar.
- `timestamp` (string, date-time, zorunlu)
- `violations` (array<object>, null olabilir, zorunlu): Yalnızca gövde doğrulamasından gelen INVALID_REQUEST için dolu, aksi halde `null`.
- `violations[].field` (string, isteğe bağlı)
- `violations[].message` (string, isteğe bağlı)
- `rejectedItems` (array<object>, isteğe bağlı, en fazla kayıt 100): Yalnızca `NO_DELIVERABLE_MESSAGES` yanıtında bulunur; diğer hatalarda alan hiç yer almaz. Reddedilen kayıtlar, `index` sırasıyla ilk 100 tanesi; 202 yanıtındaki `rejectedItems` ile aynıdır.
- `rejectedItems[].index` (integer, zorunlu): Kaydın istekteki `messages` dizisindeki sırası, sıfırdan başlar.
- `rejectedItems[].recipient` (string, null olabilir, zorunlu): Kayıttaki alıcı. Kayıt `null` ise ya da alıcısı yoksa `null`.
- `rejectedItems[].reason` (string, zorunlu): Değerler: `INVALID_RECIPIENT`, `INVALID_MESSAGE`, `MESSAGE_TOO_LONG`, `MALFORMED_ENTRY`, `DUPLICATE_RECIPIENT`, `NO_INTERNATIONAL_PERMISSION`.

  | Değer | Ne zaman |
  |---|---|
  | `INVALID_RECIPIENT` | Numara eksik ya da biçimi yanlış; bkz. `recipient`. |
  | `INVALID_MESSAGE` | Metin eksik ya da boş. |
  | `MESSAGE_TOO_LONG` | Metin, `encoding` ve gönderici türüne göre sınırı aşıyor; bkz. `encoding`. |
  | `MALFORMED_ENTRY` | Kayıt `null`. |
  | `DUPLICATE_RECIPIENT` | Aynı alıcı istekte daha sonra yeniden geçiyor. Yalnızca son geçen kayıt gönderilir, öncekiler bu nedenle reddedilir; son kayıt başka bir nedenle reddedilse bile. Numaralar yazıldığı gibi karşılaştırılır. Mükerrer gönderime izin verilen hesaplarda uygulanmaz. |
  | `NO_INTERNATIONAL_PERMISSION` | Numara yurt dışı (`-` ile başlıyor) ve hesabın yurt dışı gönderim izni yok. İzin için destek ekibiyle iletişime geçmelisiniz. Yalnızca başka bir hatası olmayan kayıtlarda bildirilir. |

  Bir kayıt birden fazla nedenle hatalıysa tek neden bildirilir; öncelik: `DUPLICATE_RECIPIENT`,
  `MALFORMED_ENTRY`, `MESSAGE_TOO_LONG`, `INVALID_RECIPIENT`, `INVALID_MESSAGE`,
  `NO_INTERNATIONAL_PERMISSION`.

- `rejectedItemsTruncated` (boolean, isteğe bağlı): Yalnızca `rejectedItems` ile birlikte bulunur. 100'den fazla kayıt reddedildiğinde true.

Örnek (şemadan üretildi):

```json
{
  "code": "INVALID_REQUEST",
  "message": "string",
  "requestId": "string",
  "timestamp": "2026-01-01T10:00:00+03:00",
  "violations": [
    {
      "field": "string",
      "message": "string"
    }
  ],
  "rejectedItems": [
    {
      "index": 0,
      "recipient": "string",
      "reason": "INVALID_RECIPIENT"
    }
  ],
  "rejectedItemsTruncated": true
}
```

### 401 — UNAUTHENTICATED. Bearer token yok ya da token geçersiz veya süresi dolmuş; `message` `no bearer token` ya da `invalid or expired token`.

Gövde (`application/json`): alanlar 400 yanıtıyla aynı; farklı olanlar:

- `code` (string, zorunlu): Değerler: `UNAUTHENTICATED`.

Örnek (şemadan üretildi):

```json
{
  "code": "UNAUTHENTICATED",
  "message": "string",
  "requestId": "string",
  "timestamp": "2026-01-01T10:00:00+03:00",
  "violations": [
    {
      "field": "string",
      "message": "string"
    }
  ],
  "rejectedItems": [
    {
      "index": 0,
      "recipient": "string",
      "reason": "INVALID_RECIPIENT"
    }
  ],
  "rejectedItemsTruncated": true
}
```

### 403 — Hesap bu gönderimi yapamaz. `code` ve `message`:

| `code` | `message` | Anlamı |
|---|---|---|
| `FORBIDDEN` | `account is not activated for this operation` | Hesapta SMS gönderimi etkin değil. |
| `FORBIDDEN` | `sub user may not use the sms api` | Alt kullanıcının SMS API'sini kullanma izni yok. |
| `FORBIDDEN` | `account operator is not served for this operation` | Hesabın operatörüne bu uç noktadan hizmet verilmiyor. |
| `FORBIDDEN` | `account is not permitted to send` | Platform, hesabın gönderim yetkisi olmadığını bildirdi. |
| `E_SIGNATURE_REQUIRED` | `the requested menu requires an e-signature login on the account` | Bu gönderim için hesapta e-imza ile giriş gerekiyor. |

Gövde (`application/json`): alanlar 400 yanıtıyla aynı; farklı olanlar:

- `code` (string, zorunlu): Değerler: `FORBIDDEN`, `E_SIGNATURE_REQUIRED`.

Örnek (şemadan üretildi):

```json
{
  "code": "FORBIDDEN",
  "message": "string",
  "requestId": "string",
  "timestamp": "2026-01-01T10:00:00+03:00",
  "violations": [
    {
      "field": "string",
      "message": "string"
    }
  ],
  "rejectedItems": [
    {
      "index": 0,
      "recipient": "string",
      "reason": "INVALID_RECIPIENT"
    }
  ],
  "rejectedItemsTruncated": true
}
```

### 422 — İstek geçerli ama gönderim yapılamaz. `code` ve `message`:

| `code` | `message` | Anlamı ve çözüm |
|---|---|---|
| `REJECTED_HEADER` | `NO_HEADERS_REGISTERED` | Hesapta kayıtlı başlık yok. |
| `REJECTED_HEADER` | `NOT_REGISTERED` | `sender` hesaba kayıtlı değil. Kayıtlı başlıkları `GET /senders` ile görebilirsiniz. |
| `REJECTED_HEADER` | `CASE_MISMATCH` | Başlık kayıtlı ama büyük/küçük harfi farklı yazılmış; kayıtlı hâliyle göndermelisiniz. |
| `REJECTED_HEADER` | `ROUTING_CODE_MISSING` | Alfanümerik başlık için hesapta yönlendirme kodu tanımlı değil. |
| `REJECTED_HEADER` | `SUBSCRIBER_NUMBER_RESTRICTED` | Hesapta abone numarasından gönderim kısıtlı. |
| `REJECTED_IYS` | `consent filtering cannot be applied to this sender or account` | Platform, istenen İYS filtresini bu başlık ya da hesap için uygulayamadı. |

Abone numarasından gönderime onaylı olmayan bir hesapta, tamamı rakamlardan oluşan bir `sender`
için `NOT_REGISTERED` ve `NO_HEADERS_REGISTERED` mesajlarının sonuna
` (subscriber number headers were not offered)` eklenir.

Gövde (`application/json`): alanlar 400 yanıtıyla aynı; farklı olanlar:

- `code` (string, zorunlu): Değerler: `REJECTED_HEADER`, `REJECTED_IYS`.

Örnek (şemadan üretildi):

```json
{
  "code": "REJECTED_HEADER",
  "message": "string",
  "requestId": "string",
  "timestamp": "2026-01-01T10:00:00+03:00",
  "violations": [
    {
      "field": "string",
      "message": "string"
    }
  ],
  "rejectedItems": [
    {
      "index": 0,
      "recipient": "string",
      "reason": "INVALID_RECIPIENT"
    }
  ],
  "rejectedItemsTruncated": true
}
```

### 429 — RATE_LIMITED, `message` `directory send rate limit exceeded`. Hesap başına dakikada 60 istek aşıldı; `Retry-After` kadar bekleyip yeniden denemelisiniz.

Başlıklar:

- `Retry-After` (integer): Yeniden denemeden önce beklenecek süre (saniye).

Gövde (`application/json`): alanlar 400 yanıtıyla aynı; farklı olanlar:

- `code` (string, zorunlu): Değerler: `RATE_LIMITED`.

Örnek (şemadan üretildi):

```json
{
  "code": "RATE_LIMITED",
  "message": "string",
  "requestId": "string",
  "timestamp": "2026-01-01T10:00:00+03:00",
  "violations": [
    {
      "field": "string",
      "message": "string"
    }
  ],
  "rejectedItems": [
    {
      "index": 0,
      "recipient": "string",
      "reason": "INVALID_RECIPIENT"
    }
  ],
  "rejectedItemsTruncated": true
}
```

### 500 — INTERNAL_ERROR, `message` `internal error`. Beklenmeyen hata; bildirirken `requestId` değerini paylaşmanız gerekir.

Gövde (`application/json`): alanlar 400 yanıtıyla aynı; farklı olanlar:

- `code` (string, zorunlu): Değerler: `INTERNAL_ERROR`.

Örnek (şemadan üretildi):

```json
{
  "code": "INTERNAL_ERROR",
  "message": "string",
  "requestId": "string",
  "timestamp": "2026-01-01T10:00:00+03:00",
  "violations": [
    {
      "field": "string",
      "message": "string"
    }
  ],
  "rejectedItems": [
    {
      "index": 0,
      "recipient": "string",
      "reason": "INVALID_RECIPIENT"
    }
  ],
  "rejectedItemsTruncated": true
}
```

### 503 — SERVICE_UNAVAILABLE. Platforma ulaşılamadı, platform bir hata döndü ya da görev numarası vermedi. Platforma ulaşılamadıysa hiçbir şey kuyruğa alınmamıştır; diğer durumlarda görev kuyruğa alınmış olabilir. Yeniden göndermeden önce `GET /report` ile kontrol etmelisiniz.

Gövde (`application/json`): alanlar 400 yanıtıyla aynı; farklı olanlar:

- `code` (string, zorunlu): Değerler: `SERVICE_UNAVAILABLE`.

Örnek (şemadan üretildi):

```json
{
  "code": "SERVICE_UNAVAILABLE",
  "message": "string",
  "requestId": "string",
  "timestamp": "2026-01-01T10:00:00+03:00",
  "violations": [
    {
      "field": "string",
      "message": "string"
    }
  ],
  "rejectedItems": [
    {
      "index": 0,
      "recipient": "string",
      "reason": "INVALID_RECIPIENT"
    }
  ],
  "rejectedItemsTruncated": true
}
```

### 504 — QUEUE_UNCONFIRMED. Gönderim platforma iletildi ama yanıtı alınamadı; görev kuyruğa alınmış olabilir. Yeniden göndermeden önce `GET /report` ile kontrol etmelisiniz.

Gövde (`application/json`): alanlar 400 yanıtıyla aynı; farklı olanlar:

- `code` (string, zorunlu): Değerler: `QUEUE_UNCONFIRMED`.

Örnek (şemadan üretildi):

```json
{
  "code": "QUEUE_UNCONFIRMED",
  "message": "string",
  "requestId": "string",
  "timestamp": "2026-01-01T10:00:00+03:00",
  "violations": [
    {
      "field": "string",
      "message": "string"
    }
  ],
  "rejectedItems": [
    {
      "index": 0,
      "recipient": "string",
      "reason": "INVALID_RECIPIENT"
    }
  ],
  "rejectedItemsTruncated": true
}
```
