The SIMcloud API lets a PHP application send SMS messages, recharge airtime and data, buy digital vouchers and prepaid electricity, check wallet balance and look up the current network for a South African mobile number.
This tutorial builds a small procedural PHP client with cURL, explicit HTTP handling and no framework dependency. It is suitable as a starting point for an existing PHP/MySQL application.
Before you start
- A funded SIMcloud account with API access.
- An API token stored outside the public web root.
- PHP with the cURL extension enabled.
- A local database table for your own request and SIMcloud identifiers.
- Application logging that never records bearer tokens or completed voucher codes.
All documented requests use:
Authorization: Bearer YOUR_API_TOKEN
Store the token in an environment variable or private configuration file. Never place it in browser JavaScript, a repository, a URL query string or an error response.
Create a small PHP cURL client
<?php
function simcloudRequest(string $method, string $path, ?array $payload = null): array
{
$token = getenv('SIMCLOUD_API_TOKEN');
if (!is_string($token) || $token === '') {
throw new RuntimeException('SIMCLOUD_API_TOKEN is not configured');
}
$url = 'https://simcloud.co.za' . $path;
$ch = curl_init($url);
if ($ch === false) {
throw new RuntimeException('Could not initialise cURL');
}
$headers = [
'Authorization: Bearer ' . $token,
'Accept: application/json',
];
$options = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
];
if ($payload !== null) {
$body = json_encode($payload, JSON_THROW_ON_ERROR);
$headers[] = 'Content-Type: application/json';
$options[CURLOPT_HTTPHEADER] = $headers;
$options[CURLOPT_POSTFIELDS] = $body;
}
curl_setopt_array($ch, $options);
$responseBody = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($responseBody === false) {
throw new RuntimeException('SIMcloud transport error: ' . $error);
}
$decoded = json_decode($responseBody, true, 512, JSON_THROW_ON_ERROR);
return ['http_status' => $status, 'body' => $decoded];
}
This function returns the HTTP status separately from the JSON body. Your application needs both. A JSON message that says “error” and an HTTP 409 duplicate response require different action from a transport timeout.
Test authentication with the Balance API
try {
$result = simcloudRequest('GET', '/api/balance.php');
if ($result['http_status'] !== 200) {
throw new RuntimeException('Balance request failed');
}
$balance = (float) ($result['body']['balance'] ?? 0);
echo 'Current last known balance: R' . number_format($balance, 2);
} catch (Throwable $e) {
error_log('SIMcloud balance check failed: ' . $e->getMessage());
}
The balance endpoint returns the account’s stored last known balance. Treat it as a pre-check, not a reservation. Another order can spend wallet value before your next request is processed.
Call the different SIMcloud services
Send one SMS
$sms = simcloudRequest('POST', '/api/sms.php', [
'recipient' => '+27821234567',
'message' => 'Your service request has been received.',
]);
if ($sms['http_status'] === 200) {
$smsId = (int) $sms['body']['sms_id'];
// Persist $smsId before checking delivery status.
}
Queue airtime
$airtime = simcloudRequest('POST', '/api/airtime.php', [
'msisdn' => '0821234567',
'network' => 'mtn',
'amount' => 10.00,
'reference' => 'LOCAL-ORDER-10045',
]);
if ($airtime['http_status'] === 201) {
$requestId = (int) $airtime['body']['request_id'];
// Persist $requestId and queue_status.
}
Fetch the live data-bundle catalogue
$catalogue = simcloudRequest('GET', '/api/data.php?products=1&network=mtn');
Use the returned network and sell value when creating a data order. Do not maintain a permanent copy of bundle prices without a refresh process.
Fetch the live voucher catalogue
$vouchers = simcloudRequest('GET', '/api/vas.php');
Resolve the requested brand from the response and validate the amount against available_amounts before posting the order.
Persist identifiers before polling
SIMcloud recharge, voucher and electricity workflows can be asynchronous. The POST response is the handoff point between your local transaction and the remote order.
- Create a local pending order with a unique local reference.
- Send the SIMcloud request.
- Store the HTTP status, SIMcloud request/order ID, transaction ID or order reference returned.
- Commit the local record before scheduling a poll.
- Poll with the documented identifier.
- Update the existing record to a final status.
- Do not create a new purchase merely because a poll is pending or a transport request timed out.
Handle errors explicitly
| HTTP result | Meaning | Application action |
|---|---|---|
| 400 | Validation, product, denomination or balance problem. | Do not retry unchanged. Show or log the safe validation reason. |
| 401 | Missing or invalid bearer token. | Stop processing and alert operations. Do not ask an end user to change purchase data. |
| 404 | Requested order or number result not found. | Check ownership and identifier; do not create a replacement automatically. |
| 409 | Airtime or data duplicate inside the five-minute same-number-and-amount window. | Load the original local request and reconcile it. |
| 5xx | SIMcloud or downstream service problem. | Use a bounded retry for safe reads. For purchases, investigate the existing reference before resubmitting. |
| Transport timeout | Your client did not receive a response. | Outcome may be unknown. Search or poll using your persisted reference rather than assuming failure. |
Never log the Authorization header. Also exclude electricity PINs and completed digital-voucher codes from general logs. Log endpoint, local ID, SIMcloud ID, HTTP status and a safe failure reason.
Production checklist
- Token is private and rotated through configuration.
- Inputs are validated before the API call.
- Local references are unique and traceable.
- Returned identifiers are persisted before polling.
- HTTP 401 and 409 have dedicated handling.
- Retries are bounded and do not duplicate purchases.
- Logs exclude tokens, PINs and voucher codes.
- Final status is reconciled to billing and delivery.
Frequently asked questions
Does the SIMcloud API use OAuth?
The current documented API uses a bearer API token in the Authorization header.
Can I call the API from browser JavaScript?
Do not expose the bearer token in a browser. Call SIMcloud from your server.
Why must I store the request ID?
Queued services return identifiers used to query the original order and prevent duplicate resubmission.
Can I retry a 409 response?
Not unchanged. It signals a recent same-number-and-amount airtime or data request. Reconcile the original order.
Where is the full contract?
Use the SIMcloud API documentation and machine-readable OpenAPI document.