ACloud SMS API

API Reference

Server-to-server REST API for virtual SMS numbers: receive messages, inspect numbers, manage senders, check credits and send outbound SMS.

Base URLhttps://app.aharon.cloud/api/v1AuthBearer tokenFormatJSON

Quick start

Use a server access key from your account settings. Never expose this key in frontend code.

curl \
  --request GET \
  --url "https://app.aharon.cloud/api/v1/messages" \
  --header "Authorization: Bearer ACCESS_KEY_HERE" \
  --header "Accept: application/json"

Authentication

HeaderAuthorization
ValueBearer ACCESS_KEY_HERE
Token typeserver
Failure401 missing_bearer_token / 401 invalid_token

Response model

Success

{
  "ok": true
}

Error

{
  "ok": false,
  "error": "invalid_token",
  "message": "Bad request"
}
StatusErrorMeaning
400bad_request, missing_senderInvalid input or missing field.
401missing_bearer_token, invalid_tokenToken is missing, revoked or invalid.
402insufficient_creditsNot enough sending credits.
423sms_sending_closed_weekendSending is closed in the configured weekend window.
429too_many_recipientsToo many recipients or rate limit.
502provider_send_failedSMS provider returned a failure.
503sms_sending_not_enabledSending is disabled.
GET

/me

Returns the authenticated account and current SMS credit balance.

{
  "ok": true,
  "user": {
    "id": 123,
    "email": "customer@example.com",
    "token_id": 77,
    "token_type": "server",
    "sms_credits": 1000
  }
}
GET

/numbers

Lists virtual numbers assigned to the authenticated account.

{
  "ok": true,
  "numbers": [
    {
      "id": 12,
      "number_raw": "0500000000",
      "label": "Main",
      "status": "assigned"
    }
  ]
}
GET

/messages

Returns up to limit inbound messages in ascending id order. Use after_id to return only messages with a greater id.

QueryRequiredNotes
limitNoInteger from 1 to 250. Default: 100.
after_idNoReturns only messages whose id is greater than this positive integer.
virtual_number_idNoFilters messages by an assigned virtual number id.
FieldTypeDescription
idnumberMessage id.
virtual_number_idnumberReceiving number id.
from_rawstringOriginal sender.
to_rawstringDestination number.
bodystringMessage body.
provider_received_atdatetimeProvider receive time.
first_seen_atdatetimeFirst import time in ACloud.

Datetime values use YYYY-MM-DD HH:mm:ss without a numeric offset and represent Asia/Jerusalem wall-clock time.

{
  "ok": true,
  "messages": [
    {
      "id": 991,
      "virtual_number_id": 12,
      "from_raw": "Google",
      "from_normalized": "Google",
      "from_type": "text",
      "to_raw": "0500000000",
      "body": "Your code is 123456",
      "provider_received_at": "2026-07-05 13:45:00",
      "first_seen_at": "2026-07-05 13:45:02"
    }
  ],
  "pagination": {
    "limit": 100,
    "count": 1,
    "after_id": null,
    "next_after_id": 991,
    "has_more": false
  }
}
GET

/messages/{id}

Returns one inbound message by id if it belongs to the authenticated account.

Pathid — required message id.
{
  "ok": true,
  "message": {
    "id": 991,
    "virtual_number_id": 12,
    "from_raw": "Google",
    "body": "Your code is 123456"
  }
}
{
  "ok": false,
  "error": "not_found"
}
GET

/sms/credits

Returns the current outbound SMS credit balance.

{
  "ok": true,
  "sms_credits": 1000
}
POST

/senders/state

Marks a sender as spam or blocked.

BodyRequiredNotes
sender / fromYesSender identifier.
stateNospam / blocked. Defaults to spam when omitted.
virtual_number_idNoOptional number scope.
{
  "sender": "0520000000",
  "state": "spam",
  "virtual_number_id": 12
}
{
  "ok": true,
  "state": "spam",
  "result": {
    "state": "spam"
  }
}
POST

/senders/block

Shortcut endpoint for blocking a sender.

{
  "sender": "0520000000",
  "virtual_number_id": 12
}
{
  "ok": true,
  "blocked": true,
  "result": {
    "state": "blocked"
  }
}
POST

/sms/send

Sends SMS from an assigned virtual number. Billing is calculated by recipients and message segments.

BodyRequiredNotes
fromYesAssigned sender number.
messageYesSMS text.
phones / toYesRecipients separated by colon, comma or newline.
flashNoBoolean alias: flash/sendFlashMessage/send_flash.
curl \
  --request POST \
  --url "https://app.aharon.cloud/api/v1/sms/send" \
  --header "Authorization: Bearer ACCESS_KEY_HERE" \
  --header "Content-Type: application/json" \
  --data '{
    "from": "0500000000",
    "to": "0520000000",
    "message": "Your code is 123456"
  }'
{
  "ok": true,
  "sms": {
    "outbound_message_id": 456,
    "provider_status": "OK",
    "recipients_count": 1,
    "segments": 1,
    "credits_used": 1,
    "balance_after": 999,
    "flash": false
  }
}

Webhook

Incoming-message webhooks are registered manually per number. ACloud does not expose webhook registration or deletion API endpoints.

  1. Create a custom webhook in Make and copy its public HTTPS address.
  2. In ACloud, open Numbers, choose the receiving number, and open Webhook.
  3. Paste the Make address into Webhook URL and save.

There is no event selector. ACloud sends the sms.received event for each incoming SMS.

{
  "event": "sms.received",
  "message_id": "msg_991",
  "number_id": "num_12",
  "to": "0500000000",
  "from": "Google",
  "from_type": "text",
  "body": "Your code is 123456",
  "received_at": "2026-07-05 13:45:00"
}

received_at uses YYYY-MM-DD HH:mm:ss without a numeric offset and represents Asia/Jerusalem wall-clock time.

X-SMS-Timestamp: 1782395100
X-SMS-Signature: sha256=...
HMAC_SHA256(webhook_secret, timestamp + "." + rawBody)

Examples

curl

curl \
  --request GET \
  --url "https://app.aharon.cloud/api/v1/messages" \
  --header "Authorization: Bearer ACCESS_KEY_HERE" \
  --header "Accept: application/json"

Node.js

const response = await fetch('https://app.aharon.cloud/api/v1/messages', {
  method: 'GET',
  headers: {
    Authorization: 'Bearer ACCESS_KEY_HERE',
    Accept: 'application/json'
  }
});

const data = await response.json();
console.log(data);

Python

import requests

response = requests.get(
    'https://app.aharon.cloud/api/v1/messages',
    headers={
        'Authorization': 'Bearer ACCESS_KEY_HERE',
        'Accept': 'application/json'
    },
    timeout=20
)

print(response.json())