Authentication
Copy page
The API supports three authentication methods. For production integrations, use an API key — optionally with HMAC request signing.
| Method | Best for | Header(s) |
|---|---|---|
| API key | Production integrations | X-API-Key |
| API key + HMAC | High-security environments | X-API-Key, X-Timestamp, X-Signature |
| JWT token | Testing, portal-only endpoints | HttpOnly auth_token cookie or Authorization: Bearer with that cookie value |
API key (recommended)
Section titled “API key (recommended)”Create keys in the portal under
Settings → API Keys (see API keys & permissions).
Pass the key in the X-API-Key header with every request:
X-API-Key: <YOUR_API_KEY>curl "https://restlink23telecom.com/api/v1/sms/stats?period=7d" \ -H "X-API-Key: $API_KEY"const res = await fetch('https://restlink23telecom.com/api/v1/sms/stats?period=7d', { method: 'GET', headers: { 'X-API-Key': process.env.API_KEY },});
if (!res.ok) { const err = await res.json(); throw new Error(`${err.error_code}: ${err.description}`);}const data = await res.json();console.log(data);import osimport requests
res = requests.get( "https://restlink23telecom.com/api/v1/sms/stats", headers={"X-API-Key": os.environ["API_KEY"]}, params={ "period": "7d" },)res.raise_for_status()print(res.json())<?php$ch = curl_init('https://restlink23telecom.com/api/v1/sms/stats?period=7d');curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['X-API-Key: ' . getenv('API_KEY')],]);
$response = curl_exec($ch);$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);curl_close($ch);
if ($status !== 200) { throw new Exception("HTTP $status: $response");}$data = json_decode($response, true);print_r($data);require "net/http"require "json"
uri = URI("https://restlink23telecom.com/api/v1/sms/stats?period=7d")req = Net::HTTP::Get.new(uri)req["X-API-Key"] = ENV.fetch("API_KEY")
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }raise "HTTP #{res.code}: #{res.body}" unless res.is_a?(Net::HTTPSuccess)
puts JSON.parse(res.body)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://restlink23telecom.com/api/v1/sms/stats?period=7d")) .header("X-API-Key", System.getenv("API_KEY")) .GET() .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());System.out.println(response.body());package main
import ( "fmt" "io" "net/http" "os")
func main() { req, err := http.NewRequest("GET", "https://restlink23telecom.com/api/v1/sms/stats?period=7d", nil) if err != nil { panic(err) } req.Header.Set("X-API-Key", os.Getenv("API_KEY"))
res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close()
data, _ := io.ReadAll(res.Body) fmt.Println(string(data))}using System.Text;
using var client = new HttpClient();client.DefaultRequestHeaders.Add("X-API-Key", Environment.GetEnvironmentVariable("API_KEY"));
var response = await client.GetAsync("https://restlink23telecom.com/api/v1/sms/stats?period=7d");
response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());API key with HMAC signature
Section titled “API key with HMAC signature”For enhanced security, enable signature verification when creating the key. Every request must then carry three headers:
| Header | Description |
|---|---|
X-API-Key |
Your API key |
X-Timestamp |
Current Unix timestamp in seconds |
X-Signature |
HMAC-SHA256 signature (hex) |
Signature computation:
message = "{METHOD}|{path_and_query}|{timestamp}|{body}"signature = HMAC-SHA256(api_secret, message)Five rules to get it right:
-
path_and_queryis the escaped URL path plus the query string when present. Query parameters are sorted by key before signing, for example/api/v1/sms/stats?period=7d. -
bodyis the raw request body string; use an empty string""for GET. -
X-Timestampmay be at most 300 seconds in the past and 30 seconds in the future (server time); otherwise401 INVALID_SIGNATURE. Each timestamp + signature pair is accepted once; a repeat returns401 INVALID_SIGNATURE. -
If the replay store is unavailable,
POST,PUT,PATCHandDELETEreturn503 REPLAY_PROTECTION_UNAVAILABLE.GET,HEAD,OPTIONSare still served without replay protection. -
Send both headers or neither. A key that requires signatures and is missing either header gets
401 SIGNATURE_REQUIRED. A key with optional signing that sends only one of the two headers gets401 INVALID_SIGNATURE; with neither header the request is served unsigned.
import crypto from 'node:crypto';
const apiKey = process.env.API_KEY;const apiSecret = process.env.API_SECRET;
const timestamp = Math.floor(Date.now() / 1000).toString();const method = 'POST';const path = '/api/v1/sms/send';const body = JSON.stringify({ to: ['+14155551234'], message: 'Hello!', sender_id: 'MyApp',});
const signature = crypto .createHmac('sha256', apiSecret) .update(`${method}|${path}|${timestamp}|${body}`) .digest('hex');
const res = await fetch('https://restlink23telecom.com' + path, { method, headers: { 'Content-Type': 'application/json', 'X-API-Key': apiKey, 'X-Timestamp': timestamp, 'X-Signature': signature, }, body,});console.log(await res.json());import hmac, hashlib, time, os, requests
api_key = os.environ["API_KEY"]api_secret = os.environ["API_SECRET"]
timestamp = str(int(time.time()))method = "POST"path = "/api/v1/sms/send"body = '{"to":["+14155551234"],"message":"Hello!","sender_id":"MyApp"}'
message = f"{method}|{path}|{timestamp}|{body}"signature = hmac.new(api_secret.encode(), message.encode(), hashlib.sha256).hexdigest()
res = requests.post( "https://restlink23telecom.com" + path, headers={ "Content-Type": "application/json", "X-API-Key": api_key, "X-Timestamp": timestamp, "X-Signature": signature, }, data=body,)print(res.json())<?php$apiKey = getenv('API_KEY');$apiSecret = getenv('API_SECRET');
$timestamp = (string) time();$method = 'POST';$path = '/api/v1/sms/send';$body = json_encode(['to' => ['+14155551234'], 'message' => 'Hello!', 'sender_id' => 'MyApp']);
$signature = hash_hmac('sha256', "$method|$path|$timestamp|$body", $apiSecret);
$ch = curl_init("https://restlink23telecom.com$path");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_POSTFIELDS => $body, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', "X-API-Key: $apiKey", "X-Timestamp: $timestamp", "X-Signature: $signature", ],]);echo curl_exec($ch);package main
import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" "net/http" "os" "strconv" "strings" "time")
func main() { apiKey, apiSecret := os.Getenv("API_KEY"), os.Getenv("API_SECRET")
timestamp := strconv.FormatInt(time.Now().Unix(), 10) method, path := "POST", "/api/v1/sms/send" body := `{"to":["+14155551234"],"message":"Hello!","sender_id":"MyApp"}`
mac := hmac.New(sha256.New, []byte(apiSecret)) mac.Write([]byte(method + "|" + path + "|" + timestamp + "|" + body)) signature := hex.EncodeToString(mac.Sum(nil))
req, _ := http.NewRequest(method, "https://restlink23telecom.com"+path, strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-API-Key", apiKey) req.Header.Set("X-Timestamp", timestamp) req.Header.Set("X-Signature", signature)
res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close() fmt.Println(res.Status)}Signature failures return 401 SIGNATURE_REQUIRED (the key requires signing and a header is missing) or
401 INVALID_SIGNATURE (only one header on an optional-signing key, or verification failed) — see errors.
JWT token
Section titled “JWT token”You can log in with portal credentials for testing and session-only endpoints such as workspaces. Use API keys for production integrations.
curl -c cookies.txt -X POST https://restlink23telecom.com/api/v1/user/auth \ -H "Content-Type: application/json" \ -d '{"login": "your_username", "password": "your_password"}'{"status": true, "user": {"id": 123}}The response sets an HttpOnly auth_token cookie and a readable csrf_token cookie. The token is not returned in the response body. If two-factor authentication is required, the response is:
{"status": true, "requires_twofa": true, "twofa_method": "totp"}The method is totp, email, or both. Repeat the login request with twofa_code, or with totp_code and email_code for both methods. You can also supply trust_device and device_name.
Use the session
Section titled “Use the session”Use -b cookies.txt with curl. Every cookie-authenticated method other than GET, HEAD, or OPTIONS requires X-CSRF-Token with the value of the csrf_token cookie; otherwise it returns 403 CSRF_FAILED. Alternatively, use the auth_token cookie value as an Authorization: Bearer token. Do not send an API key and session cookie together: this returns 400 AMBIGUOUS_AUTHENTICATION.
Login failures include 401 INVALID_CREDENTIALS; 403 CROSS_SITE_LOGIN for a request from another site, including the docs playground; 429 TOO_MANY_ATTEMPTS or ACCOUNT_TEMPORARILY_LOCKED with Retry-After (the lock applies from every IP until it expires, and a password reset is an alternative); and 503 AUTH_PROTECTION_UNAVAILABLE or PASSWORD_HASH_BUSY with Retry-After: 1. Retry a 503 after that delay. A wrong two-factor code returns 401 ERROR_CODE with Invalid 2FA code, Invalid TOTP code or Invalid email code.
