← Back to trustio.bg

Trustio API

Send review invitations to your guests directly from the system you already use — your PMS, booking platform, CRM or loyalty tool. One endpoint, one API key, no new software for your staff to learn.

Overview

The Trustio API has a single purpose: your system tells us to send a review invitation, and we deliver it by SMS or Viber. Everything else — message templates, delivery, quota, unsubscribes, the review landing page — is handled on our side.

A typical integration fires one request when a guest checks out or an order is closed. That is the entire flow.

You do not need a developer for every setup. If your system can call a webhook or is available on Zapier, the whole integration is a URL and a key. See No-code.

Getting started

  1. Sign in to app.trustio.bg as the account owner.
  2. Go to Настройки (Settings) and scroll to the API достъп section at the bottom.
  3. Click Създай ключ (Create key), give it a name that identifies the system — for example Booking system.
  4. Copy the key immediately. It is shown only once and cannot be recovered afterwards.
  5. Send one test request using the examples below.

The key is shown once. Trustio stores only a cryptographic hash of it, so nobody — including our support team — can retrieve it later. If it is lost, revoke it and create a new one.

Authentication

Pass your key as a bearer token in the Authorization header. Never put it in a URL, a query string or client-side code.

Authorization: Bearer trst_a7f3c81e94082591d3937a6669a76f0af0a31cd52571e37

Each business can hold up to five active keys. Use a separate key per connected system, so revoking one never affects the others. Keys can be revoked at any time from the same Settings screen; a revoked key stops working immediately.

A key can only send invitations. It cannot read your customer list, view reviews, or change any setting.

Send an invitation

POST https://ilmbmjeqbbrgswiqvlgh.supabase.co/functions/v1/public-api-send

Send a JSON body with Content-Type: application/json.

FieldTypeDescription
external_id required string Your own reference for this send — a booking number, order ID or reservation code. Used to prevent duplicate invitations. Max 128 characters.
phone required string The guest's mobile number. See Phone numbers for accepted formats.
message required string The message text, including any placeholders. See Message content.
name optional string The guest's name, used to personalise the message. If omitted, the phone number is used instead.
channel optional string sms or viber. Defaults to sms. Viber requires the Растеж plan.
// request body
{
  "external_id": "booking-88123",
  "phone":       "+4915112345678",
  "name":        "Klaus",
  "message":     "Zdraveite {име}, ostavete otziv: {линк}",
  "channel":     "sms"
}

Response

A successful request returns HTTP 200. The invitation is queued and delivered within about a minute.

{
  "status":      "queued",
  "message_id":  "266351e9-5671-45c2-92cb-570a7b885138",
  "external_id": "booking-88123",
  "phone":       "+4915112345678",
  "channel":     "sms",
  "duplicate":   false
}
FieldMeaning
statusqueued — accepted and scheduled for delivery. duplicate — this external_id was already used; nothing new was sent.
message_idTrustio's internal identifier for the message.
phoneThe number after normalisation to E.164 format.
duplicatetrue when the request was recognised as a repeat.

Every response — success or failure — is a JSON object. The API never returns an empty body.

Duplicate protection

The external_id field is what stops a guest receiving the same invitation twice. Before sending, Trustio checks whether that identifier has already been used by your business. If it has, the original result is returned with "duplicate": true and nothing is sent or charged.

This means retries are safe. If your system loses the connection mid-request and retries, the guest still receives exactly one message.

Use a stable identifier from your own system. A booking number, reservation code or order ID works well. Do not generate a random value per attempt — that defeats the protection entirely.

The identifier is unique within your business, so two different hotels using Trustio can both use booking-1 without any conflict.

Phone numbers

Trustio normalises numbers to E.164 before sending. Both Bulgarian and international numbers are supported.

You sendDelivered toNotes
0888123456+359888123456Bulgarian national format
359888123456+359888123456Country code without plus
+4915112345678+4915112345678International, as sent
0048512345678+4851234567800 prefix is converted
48512345678rejectedForeign number without + or 00

Foreign numbers must carry a plus sign or a leading 00. Without one, a number cannot be distinguished from a malformed Bulgarian number, so it is rejected rather than guessed. If your system stores country codes separately, concatenate them with a + before sending.

Message content

You supply the full message text. Three placeholders are replaced by Trustio at send time. They are written in Cyrillic, matching what business owners see in the app.

PlaceholderReplaced with
{име}The value of name, or the phone number if none was given
{бизнес}Your business name as configured in Settings
{линк}A unique review link for this guest — always include this

Messages containing blocked words are rejected. This protects your sender reputation with the mobile operators.

SMS and Viber

SMS

SMS is delivered in Latin characters only — a requirement of the Bulgarian mobile operators. Cyrillic text is transliterated automatically, so Здравейте becomes Zdraveite.

Keep the final message under 160 characters. Longer messages are split into multiple SMS segments, and each segment is deducted from your quota separately. Remember that {линк} expands to a full URL, so leave room for it — around 30 characters is a safe allowance.

Viber

Viber supports Cyrillic and allows up to 1000 characters, so no transliteration is applied. Viber sending requires the Растеж plan; a request with "channel": "viber" on a lower plan is rejected.

Note that Viber has strong coverage in Bulgaria and the Balkans, but is far less common in Western Europe. For German, British or Scandinavian guests, SMS is the reliable choice.

Errors

Failed requests return a JSON object with an error code and a human-readable message.

{
  "error":   "missing_external_id",
  "message": "external_id is required. Use your own booking/order reference."
}
HTTPCodeWhat to do
401missing_api_keyNo bearer token was supplied in the header.
401invalid_formatThe key is malformed. Check for truncation or stray whitespace.
401not_foundThe key does not exist. It may belong to a different environment.
401revokedThe key was revoked in Settings. Create a new one.
400invalid_jsonThe body is not valid JSON.
400missing_external_idSupply a reference from your own system.
400missing_phoneThe phone field is empty.
400missing_messageThe message field is empty.
400invalid_channelUse sms or viber.
400forbiddenThe key is not permitted to send.
400account_suspendedThe Trustio account is frozen. Check the subscription.
400blocked_wordThe message contains a disallowed word.
400send_failedThe send was rejected — commonly an invalid phone number, an exhausted quota, or Viber on a plan without it. Read message for the specific reason.
405method_not_allowedUse POST.
500internal_errorRetry with the same external_id. Duplicate protection makes this safe.

Retries are always safe as long as you reuse the same external_id. A retry after a timeout will never produce a second message.

Code examples

curl -X POST \
  'https://ilmbmjeqbbrgswiqvlgh.supabase.co/functions/v1/public-api-send' \
  -H 'Authorization: Bearer trst_YOUR_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "external_id": "booking-88123",
    "phone": "+4915112345678",
    "name": "Klaus",
    "message": "Zdraveite {име}, ostavete otziv: {линк}",
    "channel": "sms"
  }'
const res = await fetch(
  'https://ilmbmjeqbbrgswiqvlgh.supabase.co/functions/v1/public-api-send',
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.TRUSTIO_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      external_id: booking.id,
      phone:       booking.guestPhone,
      name:        booking.guestName,
      message:     'Zdraveite {име}, ostavete otziv: {линк}',
      channel:     'sms'
    })
  }
);

const data = await res.json();

if (!res.ok) {
  console.error('Trustio rejected the send:', data.error, data.message);
} else if (data.duplicate) {
  console.log('Already sent for this booking');
} else {
  console.log('Queued:', data.message_id);
}
$ch = curl_init('https://ilmbmjeqbbrgswiqvlgh.supabase.co/functions/v1/public-api-send');

curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('TRUSTIO_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode([
        'external_id' => $booking['id'],
        'phone'       => $booking['guest_phone'],
        'name'        => $booking['guest_name'],
        'message'     => 'Zdraveite {име}, ostavete otziv: {линк}',
        'channel'     => 'sms',
    ], JSON_UNESCAPED_UNICODE),
]);

$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);

if ($status !== 200) {
    error_log('Trustio error: ' . $data['message']);
}
import os, requests

resp = requests.post(
    'https://ilmbmjeqbbrgswiqvlgh.supabase.co/functions/v1/public-api-send',
    headers={
        'Authorization': f'Bearer {os.environ["TRUSTIO_API_KEY"]}',
        'Content-Type': 'application/json',
    },
    json={
        'external_id': booking.id,
        'phone':       booking.guest_phone,
        'name':        booking.guest_name,
        'message':     'Zdraveite {име}, ostavete otziv: {линк}',
        'channel':     'sms',
    },
    timeout=15,
)

data = resp.json()

if resp.status_code != 200:
    print('Trustio rejected:', data['error'], data['message'])
elif data['duplicate']:
    print('Already sent for this booking')
else:
    print('Queued:', data['message_id'])

Store the key as an environment variable or secret — never commit it to source control and never expose it in browser code. Anyone holding the key can send invitations from your account and consume your quota.

No-code integration

If your booking or POS system can call a webhook, or is available on Zapier or Make, no programming is required.

  1. Create a Zap or scenario triggered by the event you want — a completed stay, a closed order, a finished appointment.
  2. Add a Webhooks → POST action.
  3. Set the URL to the endpoint above and the method to POST.
  4. Add a header Authorization with the value Bearer trst_YOUR_KEY.
  5. Map the fields: booking reference to external_id, guest phone to phone, guest name to name, and type your message text into message.

Set the payload type to JSON. Mapping the booking reference into external_id matters here — automation platforms retry failed steps, and that field is what stops a guest receiving several invitations.

Limits

  • Up to five active API keys per business.
  • Every invitation is deducted from your monthly plan quota, exactly as if sent from the app. When the quota is exhausted, requests are rejected until it renews or you add a package.
  • SMS messages longer than 160 characters consume one quota unit per segment.
  • Guests who have unsubscribed will not receive messages.

You can see every message sent through the API in Покани inside the app, alongside those sent manually.

Support

Integration questions, or a system you are not sure can connect — write to support@trustio.bg. Tell us which system you use and we will confirm whether it can be connected.

When reporting a problem, include the error code, the external_id and the approximate time of the request. Never send us your API key.