DocumentationAPI v1 · 1.3.0

Guides

Send SMS

Single and bulk sending, cost estimate, segments and encoding, strip_accents, reference, tracking and message list.

Send an SMS#

POST /sms with recipient (one number) and body (1 to 1600 characters). Sending is asynchronous: 202 Accepted confirms queuing and credit debit, not delivery. Then track the status by webhook or GET /sms/{id}.

FieldDescription
recipientOne recipient (alternative to recipients).
recipients1 to 1000 recipients, not allowed with recipient.
bodyText, 1 to 1600 characters. Required.
senderApproved Sender ID of the account (11 characters max). Defaults to the platform sender.
referenceFree reference (64 characters max), returned in the message and the webhook, filterable.
strip_accentsReplaces accents and typographic characters with ASCII (GSM-7, lower cost).

Over HTTP#

Over HTTP

curl -X POST "https://api.labyrinthe-rdc.com/api/public/v1/sms" \
  -H "Authorization: Bearer lsk_test_xxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: order-1042-ready" \
  -H "Content-Type: application/json" \
  -d '{"recipient":"243812345678","body":"Votre commande 1042 est prête.","reference":"CMD-1042"}'

With the packages#

With the packages

<?php
use Labyrinthe\Connect\Client;

$client = new Client('lsk_test_xxxxxxxxxxxxxxxx'); // or Client::fromEnv() (LABYRINTHE_API_KEY)

$message = $client->sms->send(
    to: '243812345678',
    body: 'Votre commande 1042 est prête.',
    reference: 'CMD-1042',
    idempotencyKey: 'order-1042-ready', // replaying within 24 h sends nothing again
);

echo $message->id, ' ', $message->status->value; // queued
202 response
{
  "data": [
    {
      "id": "9d5c0a3e-6f0b-4c2e-9a51-3b7f2d8e4c10",
      "reference": "CMD-1042",
      "recipient": "243812345678",
      "sender": "LabConnect",
      "encoding": "ucs2",
      "status": "queued",
      "segments": 1,
      "credits_charged": 1,
      "campaign_id": null,
      "provider_message_id": null,
      "error_code": null,
      "error_message": null,
      "created_at": "2026-11-06T09:39:11+00:00",
      "submitted_at": null,
      "delivered_at": null
    }
  ]
}

Send to many recipients#

Replace recipient with recipients (up to 1000). One message is created per recipient; duplicates are merged after normalization.

All or nothing

All recipients, the Sender ID and the balance are checked before anything is sent. If a single number is refused, nothing is sent and RECIPIENT_NOT_SENDABLE lists every refusal (details.rejected). If the balance does not cover the whole send, nothing is sent (INSUFFICIENT_CREDITS).

Over HTTP

curl -X POST "https://api.labyrinthe-rdc.com/api/public/v1/sms" \
  -H "Authorization: Bearer lsk_test_xxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: reminder-2026-11-06" \
  -H "Content-Type: application/json" \
  -d '{"recipients":["243812345678","0899999999"],"body":"Rappel : reunion demain 9h.","sender":"MABOUTIQUE","reference":"RAPPEL-2026-11-06"}'

With the packages

<?php
use Labyrinthe\Connect\Client;

$client = new Client('lsk_test_xxxxxxxxxxxxxxxx'); // or Client::fromEnv() (LABYRINTHE_API_KEY)

$batch = $client->sms->sendMany(
    recipients: ['243812345678', '0899999999'],
    body: 'Rappel : reunion demain 9h.',
    sender: 'MABOUTIQUE', // an approved Sender ID of the account
    reference: 'RAPPEL-2026-11-06',
    idempotencyKey: 'reminder-2026-11-06',
);

foreach ($batch as $message) {
    echo $message->recipient, ' ', $message->status->value, PHP_EOL;
}

Credits, segments and encoding#

1 credit = 1 SMS segment. The cost depends on the text encoding:

EncodingOne segmentBeyond, per segmentTriggered by
gsm7160 characters153 charactersASCII text only
ucs270 characters67 charactersAny non-ASCII character: é, à, ç, emoji, em dash, curly quotes…

Lower the cost with strip_accents#

strip_accents: true (on POST /sms, /sms/estimate and /otp/send) replaces accents and typographic characters with their ASCII equivalent: the message switches to GSM-7, often half the price. Never applied without this parameter. Transliteration is server-defined (Café may become Cafe): do not rely on it for exact text.

Estimate the cost#

POST /sms/estimate computes encoding, segments and credits without sending anything. Recipients are counted as for a send (normalized, duplicates merged); invalid numbers are ignored. balance_after is negative when the balance is insufficient.

Over HTTP

curl -X POST "https://api.labyrinthe-rdc.com/api/public/v1/sms/estimate" \
  -H "Authorization: Bearer lsk_test_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"body":"Votre code est 482913","recipients":["243812345678","0812345678","0899999999"]}'

With the packages

<?php
use Labyrinthe\Connect\Client;

$client = new Client('lsk_test_xxxxxxxxxxxxxxxx'); // or Client::fromEnv() (LABYRINTHE_API_KEY)

// Body first, then the recipients (duplicates are merged as for a real send).
$estimate = $client->sms->estimate('Votre code est 482913', ['243812345678', '0812345678', '0899999999']);

echo $estimate->encoding?->value, ' ', $estimate->totalCredits; // gsm7 2
if (! $estimate->isAffordable()) {
    // top up before sending
}
200 response
{
  "data": {
    "encoding": "gsm7",
    "characters": 21,
    "segments_per_message": 1,
    "recipients_count": 2,
    "total_credits": 2,
    "balance_after": 98
  }
}

Reference#

reference (64 characters max) links a message to your business object (order number…). It is returned in Message.reference and in the message.status_changed webhook, and filterable with GET /sms?reference=. It is not unique: a bulk send shares it. To avoid duplicates, use idempotency instead.

Message statuses#

StatusMeaning
queuedQueued at Labyrinthe.
submittedHanded to the provider.
sentAccepted by the operator.
deliveredDelivered to the phone (final).
failed, rejected, expired, undeliverableFailure (final). error_code and error_message give the reason.

The message text is never returned by the API (it may contain an OTP code).

Get a message#

GET /sms/{id} searches by id across all the account's messages (whatever key or area sent them).

Over HTTP

curl "https://api.labyrinthe-rdc.com/api/public/v1/sms/9d5c0a3e-6f0b-4c2e-9a51-3b7f2d8e4c10" \
  -H "Authorization: Bearer lsk_test_xxxxxxxxxxxxxxxx"

With the packages

<?php
use Labyrinthe\Connect\Client;

$client = new Client('lsk_test_xxxxxxxxxxxxxxxx'); // or Client::fromEnv() (LABYRINTHE_API_KEY)

$message = $client->sms->get('9d5c0a3e-6f0b-4c2e-9a51-3b7f2d8e4c10');

if ($message->status->isFinal()) {
    echo $message->status->value, ' ', $message->errorCode ?? '';
}

List messages#

GET /sms lists the messages created by the calling API key, newest first, with pagination (page, per_page up to 100).

  • status: one of the statuses above.
  • recipient: number, any format (normalized before searching).
  • reference: exact match.
  • from, to: date (2026-11-01) or ISO 8601 date-time, inclusive.

Over HTTP

curl "https://api.labyrinthe-rdc.com/api/public/v1/sms?status=delivered&from=2026-11-01&per_page=50" \
  -H "Authorization: Bearer lsk_test_xxxxxxxxxxxxxxxx"

With the packages

<?php
use Labyrinthe\Connect\Client;

$client = new Client('lsk_test_xxxxxxxxxxxxxxxx'); // or Client::fromEnv() (LABYRINTHE_API_KEY)

$page = $client->sms->list(status: 'delivered', from: '2026-11-01', perPage: 50);
echo $page->total, ' messages';

// Walk every page lazily
foreach ($page->autoPaging() as $message) {
    echo $message->id, ' ', $message->recipient, PHP_EOL;
}