Understanding SMS API Error Responses

Every request to the 160.com.au SMS API answers with a standard HTTP status and, when something is wrong, a JSON errors array. This guide shows exactly what comes back for each status, what it means, whether you are charged, and how to retry safely.

The error format

Every error response from every endpoint uses the same shape: an errors array where each item carries a numeric statusCode and a human readable status. The HTTP status of the response always matches the statusCode inside the body.

{
  "errors": [
    {
      "statusCode": 400,
      "status": "No SMS to send"
    }
  ]
}

When a batch item fails validation, the item that caused the failure is attached as object so you can identify it without counting array positions:

{
  "errors": [
    {
      "statusCode": 400,
      "status": "Phone# is too short. 9 digits minimum",
      "object": {
        "recipient": "000-000",
        "message": "hello world"
      }
    }
  ]
}

Switch on statusCode in your code. Treat status as text for logs and support tickets, not as a value to parse: the wording may be refined over time, the numbers will not.

HTTP status reference

These are the statuses the API returns and what each one tells you:

Status Meaning Retry?
200 Success. The requested data is in the body. A lookup that matches nothing (for example a messageId that does not exist) also returns 200 with an empty array [], not a 404. No
201 Created. Every message in the request was accepted and queued. Each message in the response carries its own messageId, cost and initial statusCode. No
204 Success with no body, for example after a delete. No
400 Bad request. The body could not be parsed, a required field is missing, or a value failed validation (most often a recipient number). Nothing was sent and nothing was charged. Only after fixing the request
401 Unauthorized. The Authorization header is missing or the username and API secret do not match. The response is identical in both cases. Only after fixing credentials
404 Not found. The endpoint path does not exist, for example a typo such as /v1/infa instead of /v1/info. This is the only condition that returns 404. Only after fixing the path
429 Too many requests. You exceeded 2 requests per second, or your account exceeded its per minute message allowance. The request was not processed. Yes, after a pause
5xx Server error. A temporary fault on our side. The request was not processed and the body is empty, so do not try to parse it. Yes, with backoff

What each response looks like

Real bodies, exactly as the API returns them, starting with a successful send for comparison.

201 Created (one message accepted)

HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8

{"messages":[{"messageId":59763680,"datePosted":"2026-09-08 12:39:53","sender":"+61 400000000","recipient":"61412345678","message":"Your order has shipped.","cost":1,"statusCode":-1,"status":"Message queued (A)"}]}

400 Bad Request (body is not valid JSON, or contains no messages)

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8

{"errors":[{"statusCode":400,"status":"No SMS to send"}]}

400 Bad Request (a message is missing its recipient)

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8

{"errors":[{"statusCode":400,"status":"Empty recipient value","object":{"message":"hello world"}}]}

400 Bad Request (a recipient number fails validation)

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8

{"errors":[{"statusCode":400,"status":"Phone# is too short. 9 digits minimum","object":{"recipient":"000-000","message":"hello world"}}]}

401 Unauthorized

HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8

{"errors":[{"statusCode":401,"status":"Credentials are wrong"}]}

404 Not Found

HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8

{"errors":[{"statusCode":404,"status":"Method is not found"}]}

429 Too Many Requests

HTTP/1.1 429 Too Many Requests
Content-Type: application/json; charset=utf-8

{"errors":[{"statusCode":429,"status":"Requests to any endpoint are rate limited to ensure that the API remains responsive to all users. Rate limits are 2 requests per second"}]}

Credentials are checked before the path is routed, so a request to a wrong path without valid credentials returns 401 rather than 404. Rate limiting is applied before authentication too, so a burst of unauthenticated requests still receives 429.

How batch rejection works

A POST to /v1/messages is all or nothing. If every item passes validation the API returns 201 and queues the whole batch. If any item fails, the API returns 400, queues nothing, and charges nothing. There is no partial success.

The errors array lists every failing item, in the order they appeared in your request, with the offending item attached as object. The result is deterministic: submitting the same batch again produces the same errors, so one response is enough to know exactly what to remove.

The recommended pattern is:

  1. Submit the batch (up to 100 messages).
  2. On 400, read each object in the errors array and remove that item from your batch. Log the status text against the recipient so you can fix the data later.
  3. Resubmit the remaining items. They will be accepted with 201.

Which failures cost credits

Two different things can go wrong with a message, at two different times, and only one of them uses credit:

  • Submission failures (400, 401, 404, 429, 5xx) happen before anything is sent to the carrier. No credit is used.
  • Delivery failures are reported later through the message statusCode and your webhook, after we have attempted delivery. These messages do use credit, because the carrier has processed them.

Delivery failures are covered in the SMS delivery statuses guide.

Rate limits and retries

Two limits apply. Requests to any endpoint are limited to 2 per second, and each account has a default allowance of 1,600 messages per minute, which can be raised on request. Exceeding either returns 429.

The API does not send Retry-After or X-RateLimit headers, so pace requests on your side:

  • Send at most one request every 500 ms. For bulk sends, use batches of up to 100 messages and leave 1 to 2 seconds between batches.
  • On 429, wait at least one second, then retry the same request. Nothing from a 429 response was processed, so a retry cannot duplicate messages.
  • On 5xx, or on any response whose body is not JSON, retry with exponential backoff (for example 2, 4, 8 seconds) up to a small number of attempts, then alert.
  • Never retry a 400, 401 or 404 unchanged. The same request will fail the same way.

Handling responses in PHP

A minimal send with response handling that follows the rules above:

<?php
$payload = ['messages' => [
  ['recipient' => '61412345678', 'message' => 'Your order has shipped.'],
]];

$ch = curl_init('https://api.160.com.au/v1/messages');
curl_setopt_array($ch, [
  CURLOPT_POST           => true,
  CURLOPT_POSTFIELDS     => json_encode($payload),
  CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
  CURLOPT_USERPWD        => 'YOUR_USERNAME:YOUR_API_SECRET',
  CURLOPT_RETURNTRANSFER => true,
]);
$body   = curl_exec($ch);
$http   = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$data   = json_decode($body, true);

switch (true) {
  case $http === 201:
    // every message queued; store $data['messages'][n]['messageId']
    break;
  case $http === 400:
    // remove each $error['object'] from the batch and resubmit the rest
    foreach ($data['errors'] as $error) {
      error_log($error['status'] . ' ' . json_encode($error['object'] ?? null));
    }
    break;
  case $http === 401:
  case $http === 404:
    // configuration problem: fix credentials or path, do not retry as is
    break;
  case $http === 429:
    // slow down, wait 1 s, retry the same request
    break;
  case $http >= 500 || !is_array($data):
    // transient: retry with exponential backoff
    break;
}

Note: a 201 means the batch was accepted, not that every message was delivered. Each message starts at statusCode -1 (queued) and moves through 0, 1 and 2 as delivery progresses. Track those through the delivery statuses guide, and use the Swagger sandbox to see live responses before you go to production.

Ready to build?

Sign up for a free trial — free SMS credits included — and send your first message through our Australian SMS API.

Get a free API key API docs & Swagger sandbox