Příklady použití [OTP API](https://www.bulkgate.com/cs/dokumentace/otp-api/odeslani-jednorazoveho-hesla/). Kód se odesílá přes `send`, ověřuje přes `verify` a v případě potřeby posílá znovu přes `resend` — všechny tři pracují s `id`, které vrátí první požadavek.

## Nejmenší možný požadavek

Povinné je jen číslo příjemce a `request_quota_identification` — identifikace žadatele, na kterou se váže kvóta za minutu. Doporučujeme jeho IP adresu. Bez `channel` odchází kód SMS.

```http-json
POST /api/1.0/otp/send HTTP/1.1
Host: portal.bulkgate.com
Content-Type: application/json

{
  "application_id": 451,
  "application_token": "NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj",
  "number": "420777777777",
  "request_quota_identification": "127.0.0.1"
}
```

## Vlastní podoba kódu

`code_type` volí znaky (`int`, `string`, `combined`), `code_length` délku (4–100). `language` vybírá jazyk textu ze 100 podporovaných ISO kódů.

```http-json
POST /api/1.0/otp/send HTTP/1.1
Host: portal.bulkgate.com
Content-Type: application/json

{
  "application_id": 451,
  "application_token": "NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj",
  "number": "420777777777",
  "request_quota_identification": "127.0.0.1",
  "code_type": "combined",
  "code_length": 8,
  "language": "cs"
}
```

## Automatické vyplnění kódu na webu (WebOTP)
[developer.mozilla.org/WebOTP_API](https://developer.mozilla.org/en-US/docs/Web/API/WebOTP_API)

`domain` v SMS objektu připojí ke zprávě řádek, podle kterého prohlížeč nabídne kód sám. Doména musí odpovídat webu, kde se kód zadává.

```http-json
POST /api/1.0/otp/send HTTP/1.1
Host: portal.bulkgate.com
Content-Type: application/json

{
  "application_id": 451,
  "application_token": "NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj",
  "number": "420777777777",
  "request_quota_identification": "127.0.0.1",
  "channel": {
    "sms": {
      "sender_id": "gText",
      "sender_id_value": "BulkGate",
      "domain": "https://example.com"
    }
  }
}
```

## Kaskáda kanálů

Pořadí klíčů je pořadím kaskády. Tady se nejdřív zkusí Viber, po 60 sekundách bez doručení RCS a nakonec SMS. `expiration` pod `60` se ignoruje.

```http-json
POST /api/1.0/otp/send HTTP/1.1
Host: portal.bulkgate.com
Content-Type: application/json

{
  "application_id": 451,
  "application_token": "NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj",
  "number": "420777777777",
  "request_quota_identification": "127.0.0.1",
  "channel": {
    "viber": {
      "sender": "BulkGate",
      "expiration": 60
    },
    "rcs": {
      "sender": "BulkGate",
      "expiration": 60
    },
    "sms": {
      "sender_id": "gText",
      "sender_id_value": "BulkGate"
    }
  }
}
```

## Kód hlasem

Text2Voice kód přečte. `repeat` ho v hovoru zopakuje, ať ho příjemce stihne zapsat.

```http-json
POST /api/1.0/otp/send HTTP/1.1
Host: portal.bulkgate.com
Content-Type: application/json

{
  "application_id": 451,
  "application_token": "NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj",
  "number": "420777777777",
  "request_quota_identification": "127.0.0.1",
  "code_type": "int",
  "code_length": 4,
  "channel": {
    "text2voice": {
      "sender": "420777777777",
      "language": "cs",
      "prefer_gender": "female",
      "repeat": 2
    }
  }
}
```

## Limity proti zneužití

`trials_available_*` omezí počet přeposlání a ověření jednoho kódu, `daily_limit` počet požadavků za 24 hodin. `daily_limit_country` navíc povolí **jen** uvedené země — na ostatní požadavek skončí chybou `country_is_disabled`.

```http-json
POST /api/1.0/otp/send HTTP/1.1
Host: portal.bulkgate.com
Content-Type: application/json

{
  "application_id": 451,
  "application_token": "NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj",
  "number": "420777777777",
  "request_quota_identification": "127.0.0.1",
  "request_quota_number": 3,
  "trials_available_resend": 2,
  "trials_available_verification": 5,
  "daily_limit": 100,
  "daily_limit_country": {
    "cz": 100,
    "sk": 50
  },
  "expiration": 600
}
```

## Ověření kódu

`id` je hodnota z odpovědi na `send`. Odpověď vrací `verified`; `false` znamená neplatný kód, ne chybu požadavku.

```http-json
POST /api/1.0/otp/verify HTTP/1.1
Host: portal.bulkgate.com
Content-Type: application/json

{
  "application_id": 451,
  "application_token": "NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj",
  "id": "otp-65cca8b4b6a6a2.24308291",
  "code": "123456"
}
```

## Přeposlání kódu

Vygeneruje nový kód pro tentýž požadavek a odešle ho stejným kanálem. Mezi dvěma přeposláními musí uplynout 60 sekund.

```http-json
POST /api/1.0/otp/resend HTTP/1.1
Host: portal.bulkgate.com
Content-Type: application/json

{
  "application_id": 451,
  "application_token": "NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj",
  "id": "otp-65cca8b4b6a6a2.24308291"
}
```

## Ukázky kódu

Níže je tentýž požadavek `send` v různých programovacích jazycích:

 - [PHP](#php)
 - [PHP cURL](#php-curl)
 - [PHP Guzzle](#php-guzzle)
 - [Go](#go)
 - [Python](#python)
 - [Node.js](#node-js)
 - [Java](#java)
 - [Kotlin](#kotlin)
 - [C# .NET](#c-net)
 - [C++ libcurl](#c-libcurl)
 - [Ruby](#ruby)
 - [Bash cURL](#bash-curl)

Význam jednotlivých parametrů popisuje [specifikace](https://www.bulkgate.com/cs/dokumentace/otp-api/odeslani-jednorazoveho-hesla/).

### PHP
[php.net](https://www.php.net/)
```php
$context = stream_context_create(['http' => [
    'method' => 'POST',
    'header' => 'Content-Type: application/json',
    'content' => json_encode([
        'application_id' => 451,
        'application_token' => 'NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj',
        'number' => '420777777777',
        'request_quota_identification' => '127.0.0.1',
        'language' => 'cs',
        'code_length' => 6,
        'channel' => [
            'sms' => [
                'sender_id' => 'gText',
                'sender_id_value' => 'BulkGate',
            ],
        ],
    ], JSON_THROW_ON_ERROR),
    'timeout' => 30,
    'ignore_errors' => true,
]]);

$body = file_get_contents('https://portal.bulkgate.com/api/1.0/otp/send', false, $context);

if ($body === false)
{
    throw new RuntimeException('BulkGate API neodpovědělo.');
}

$response = json_decode($body, true, flags: JSON_THROW_ON_ERROR);

if (isset($response['error']))
{
    throw new RuntimeException("BulkGate: {$response['error']} ({$response['type']})");
}

print_r($response['data']);
```

### PHP cURL
[php.net/curl](https://www.php.net/manual/en/book.curl.php)
```php
$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => 'https://portal.bulkgate.com/api/1.0/otp/send',
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode([
        'application_id' => 451,
        'application_token' => 'NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj',
        'number' => '420777777777',
        'request_quota_identification' => '127.0.0.1',
        'language' => 'cs',
        'code_length' => 6,
        'channel' => [
            'sms' => [
                'sender_id' => 'gText',
                'sender_id_value' => 'BulkGate',
            ],
        ],
    ], JSON_THROW_ON_ERROR),
]);

$body = curl_exec($curl);
$error = curl_error($curl);

curl_close($curl);

if ($error !== '')
{
    throw new RuntimeException("BulkGate API: $error");
}

print_r(json_decode($body, true, flags: JSON_THROW_ON_ERROR));
```

### PHP Guzzle
[docs.guzzlephp.org](https://docs.guzzlephp.org/)
```php
// composer require guzzlehttp/guzzle
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

$client = new Client(['timeout' => 30, 'http_errors' => false]);

try
{
    $result = $client->post('https://portal.bulkgate.com/api/1.0/otp/send', ['json' => [
        'application_id' => 451,
        'application_token' => 'NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj',
        'number' => '420777777777',
        'request_quota_identification' => '127.0.0.1',
        'language' => 'cs',
        'code_length' => 6,
        'channel' => [
            'sms' => [
                'sender_id' => 'gText',
                'sender_id_value' => 'BulkGate',
            ],
        ],
    ]]);
}
catch (GuzzleException $e)
{
    throw new RuntimeException('BulkGate: ' . $e->getMessage(), previous: $e);
}

print_r(json_decode((string) $result->getBody(), true, flags: JSON_THROW_ON_ERROR));
```

### Go
[go.dev](https://go.dev/)
```golang
package main

import (
	"fmt"
	"io"
	"net/http"
	"strings"
)

func main() {
	url := "https://portal.bulkgate.com/api/1.0/otp/send"

	payload := strings.NewReader(`{"application_id": 451, "application_token": "NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj", "number": "420777777777", "request_quota_identification": "127.0.0.1", "language": "cs", "code_length": 6, "channel": {"sms": {"sender_id": "gText", "sender_id_value": "BulkGate"}}}`)

	req, _ := http.NewRequest("POST", url, payload)
	req.Header.Add("Content-Type", "application/json")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	body, _ := io.ReadAll(res.Body)
	fmt.Println(res.StatusCode, string(body))
}
```

### Python
[python.org](https://www.python.org/)
```python
# pip install requests
import requests

response = requests.post('https://portal.bulkgate.com/api/1.0/otp/send', json={
    'application_id': 451,
    'application_token': 'NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj',
    'number': '420777777777',
    'request_quota_identification': '127.0.0.1',
    'language': 'cs',
    'code_length': 6,
    'channel': {
        'sms': {
            'sender_id': 'gText',
            'sender_id_value': 'BulkGate',
        },
    },
}, timeout=30)

data = response.json()

if 'error' in data:
    raise RuntimeError(f"BulkGate: {data['error']} ({data['type']})")

print(data['data']['id'])
```

### Node.js
[nodejs.org](https://nodejs.org/)
```javascript
// Node.js 18+ má fetch globálně. Top-level await vyžaduje ESM (.mjs nebo "type": "module").
const response = await fetch("https://portal.bulkgate.com/api/1.0/otp/send", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
  "application_id": 451,
  "application_token": "NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj",
  "number": "420777777777",
  "request_quota_identification": "127.0.0.1",
  "language": "cs",
  "code_length": 6,
  "channel": {
    "sms": {
      "sender_id": "gText",
      "sender_id_value": "BulkGate"
    }
  }
}),
});

const result = await response.json();

if (result.error) {
  throw new Error(`BulkGate: ${result.error} (${result.type})`);
}

console.log(result.data.id);
```

### Java
[java.net.http.HttpClient](https://docs.oracle.com/en/java/javase/21/docs/api/java.net.http/java/net/http/HttpClient.html)
```java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
  .uri(URI.create("https://portal.bulkgate.com/api/1.0/otp/send"))
  .header("Content-Type", "application/json")
  .POST(HttpRequest.BodyPublishers.ofString("""
      {
        "application_id": 451,
        "application_token": "NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj",
        "number": "420777777777",
        "request_quota_identification": "127.0.0.1",
        "language": "cs",
        "code_length": 6,
        "channel": {
          "sms": {
            "sender_id": "gText",
            "sender_id_value": "BulkGate"
          }
        }
      }"""))
  .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

System.out.println(response.statusCode() + " " + response.body());
```

### Kotlin
[kotlinlang.org](https://kotlinlang.org/)
```kotlin
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse

val payload = """
    {
      "application_id": 451,
      "application_token": "NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj",
      "number": "420777777777",
      "request_quota_identification": "127.0.0.1",
      "language": "cs",
      "code_length": 6,
      "channel": {
        "sms": {
          "sender_id": "gText",
          "sender_id_value": "BulkGate"
        }
      }
    }
""".trimIndent()

val request = HttpRequest.newBuilder()
    .uri(URI.create("https://portal.bulkgate.com/api/1.0/otp/send"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(payload))
    .build()

val response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString())

println("${response.statusCode()} ${response.body()}")
```

### C# .NET
[learn.microsoft.com](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient)
```csharp
using System.Net.Http;
using System.Text;

var payload = """
    {
      "application_id": 451,
      "application_token": "NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj",
      "number": "420777777777",
      "request_quota_identification": "127.0.0.1",
      "language": "cs",
      "code_length": 6,
      "channel": {
        "sms": {
          "sender_id": "gText",
          "sender_id_value": "BulkGate"
        }
      }
    }
    """;

using var client = new HttpClient();

var response = await client.PostAsync(
    "https://portal.bulkgate.com/api/1.0/otp/send",
    new StringContent(payload, Encoding.UTF8, "application/json"));

Console.WriteLine($"{(int) response.StatusCode} {await response.Content.ReadAsStringAsync()}");
```

### C++ libcurl
[curl.se/libcurl](https://curl.se/libcurl/)
```cpp
#include <curl/curl.h>
#include <iostream>
#include <string>

static size_t collect(char *data, size_t size, size_t nmemb, std::string *out)
{
    out->append(data, size * nmemb);
    return size * nmemb;
}

int main()
{
    const std::string payload = R"json(
{
  "application_id": 451,
  "application_token": "NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj",
  "number": "420777777777",
  "request_quota_identification": "127.0.0.1",
  "language": "cs",
  "code_length": 6,
  "channel": {
    "sms": {
      "sender_id": "gText",
      "sender_id_value": "BulkGate"
    }
  }
}
)json";

    CURL *curl = curl_easy_init();

    if (curl == nullptr)
    {
        std::cerr << "curl_easy_init failed\n";
        return 1;
    }

    curl_slist *headers = curl_slist_append(nullptr, "Content-Type: application/json");
    std::string body;

    curl_easy_setopt(curl, CURLOPT_URL, "https://portal.bulkgate.com/api/1.0/otp/send");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, collect);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body);

    const CURLcode result = curl_easy_perform(curl);

    long status = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (result != CURLE_OK)
    {
        std::cerr << curl_easy_strerror(result) << "\n";
        return 1;
    }

    std::cout << status << " " << body << "\n";
    return 0;
}
```

### Ruby
[ruby-lang.org](https://www.ruby-lang.org/)
```ruby
require 'json'
require 'net/http'

uri = URI('https://portal.bulkgate.com/api/1.0/otp/send')

response = Net::HTTP.post(uri, {
  'application_id' => 451,
  'application_token' => 'NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj',
  'number' => '420777777777',
  'request_quota_identification' => '127.0.0.1',
  'language' => 'cs',
  'code_length' => 6,
  'channel' => {
    'sms' => {
      'sender_id' => 'gText',
      'sender_id_value' => 'BulkGate'
    }
  }
}.to_json, 'Content-Type' => 'application/json')

puts "#{response.code} #{response.body}"
```

### Bash cURL
[curl.se](https://curl.se/)
```bash
curl --request POST \
  --url https://portal.bulkgate.com/api/1.0/otp/send \
  --header 'Content-Type: application/json' \
  --data '{"application_id": 451, "application_token": "NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj", "number": "420777777777", "request_quota_identification": "127.0.0.1", "language": "cs", "code_length": 6, "channel": {"sms": {"sender_id": "gText", "sender_id_value": "BulkGate"}}}'
```
