Our REST SMS API makes it fast and easy for you to SMS-enable your software, app or website.
All URLs referenced in this documentation begin with this base URL:
https://api.voimio.com/v1
Unencrypted HTTP is not supported.
As the SMS API is based on JSON, you can expect the Content-Type header on all of our requests and responses to be application/json
. We also expect the same from your requests whenever you POST to the SMS API.
If you require XML, please contact support explaining your use case for our consideration.
To authenticate, just provide your API Key in your request headers. If you don't have an account, create one here.
You can provide it as a Authorization: Bearer
token:
POST /v1/sms HTTP/1.1
Content-Type: application/json
Authorization: Bearer db955ce8106368e6d21dfa02ac21facd
or as a custom Api-Key
header:
POST /v1/sms HTTP/1.1
Content-Type: application/json
Api-Key: db955ce8106368e6d21dfa02ac21facd
If your request succeeded, the response header will have a HTTP 2xx status code and the JSON returned in the response body will have:
For example:
{
"messages": [
{
"source_address": "GeorgeLucas",
"destination_address": "+447956218236",
"content": "There is a civil war between the Galactic Empire and a Rebel Alliance."
}
]
}
{
"messages": [
{
"object_type": "sms",
"id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
"source_address": "GeorgeLucas",
"source_country": null,
"destination_address": "+447956218236",
"destination_country": "GB",
"direction": "outbound",
"body": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
"encoding": 0,
"delivery_state": "ACCEPTED",
"created": "2018-01-18T17:06:43+00:00",
"updated": "2018-01-18T17:06:43+00:00",
"expires": "2018-01-21T17:06:00+00:00",
"cost_per_fragment": 0.05,
"fragment_count": 1,
"total_cost": 0.05,
"custom_args": null
}
],
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.08493
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 158.95,
"auto_purchase": true
}
}
If your request failed, you'll receive a non-2xx status code and the response body will contain:
For example:
{
"errors": [
{
"code": 1005,
"description": "Payload is malformed",
"learn_more": "https://www.voimio.com/sms-api/#error1005"
}
],
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.00605
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 6973.465,
"auto_purchase": false
}
}
The errors
array is made of an array of Error Objects that represent each error that was thrown.
To test your code on a cost-incurring resource without incurring a cost, add "sandbox": true
to the main object:
{
"sandbox": true,
"messages": [
{
"source_address": "GeorgeLucas",
"content": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
"destination_address": "+447956218236"
}
]
}
Attribute | Type | Description |
---|---|---|
account_id | integer | The ID of the account being used. |
balance | integer | Your account balance after completion of your request. |
auto_purchase | boolean | Whether you have auto-purchase setup or not. |
For example:
{
"object_type": "account",
"account_id": 2232,
"balance": 7014.073,
"auto_purchase": false
}
If you do not authenticate, account
will be set to null
.
If you don't set up auto-purchase, use the Account Object to track your account's balance.
Attribute | Type | Description |
---|---|---|
id | integer | The unique identifier for your request. |
sandbox | boolean | Whether you are in the sandbox or not. |
execution_time | decimal | The amount of time taken to execute the request. |
For example:
{
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.08022
}
If you need technical support regarding a specific request, please quote the Request ID.
Attribute | Type | Description |
---|---|---|
code | integer | A unique identifier for the error. |
description | string | A short explanation of the error. |
learn_more | url | An online resource to get more details about the error. |
For example:
{
"object_type": "error",
"code": 1005,
"description": "Payload is malformed",
"learn_more": "https://www.voimio.com/sms-api/#error1005"
}
The contents of description
and learn_more
are subject to continuous change.
Attribute | Type | Description |
---|---|---|
id | string | The unique identifier for the SMS. |
source_address | string | The source from which the SMS was sent.
If you sent it, it will be the Sender ID that you specified. If a mobile user sent it, it will be their phone number.
|
source_country | string | The country associated with the source_address
This is formated using ISO 3166-1 alpha-2.
|
destination_address | string | The destination to which the SMS was sent.
If you sent it, it will be the phone number of the recipient. If a mobile user sent it, it will be your virtual number.
|
destination_country | string | The country associated with the destination_address
This is formated using ISO 3166-1 alpha-2.
|
direction | string | Whether it was sent from you to a mobile device or vice versa.
'outbound' means 'sent to a mobile user' and 'inbound' means 'sent from a mobile user'.
|
body | string | The message conveyed through the SMS. |
encoding | integer | The character encoding of the message body.
8 is Unicode and 0 is GSM 03.38
|
delivery_state | string | The current delivery state of the SMS. |
created | timestamp | When the SMS was created. |
updated | timestamp | When the SMS was last updated. |
expires | timestamp | When the SMS is/was scheduled to expire. |
cost_per_fragment | decimal | The cost of each individual SMS if the message is a long SMS. |
fragment_count | integer | The total number of individual SMS if the message is a long SMS. |
total_cost | decimal | The total cost of the SMS. |
custom_args | object | A JSON object containing a collection of key/value pairs.
This will only exist if you supplied when sending an SMS.
|
For example:
{
"id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
"source_address": "Crawler",
"source_country": null,
"destination_address": "+447956218236",
"destination_country": "GB",
"direction": "outbound",
"body": "Luke Skywalker has vanished.",
"encoding": 0,
"delivery_state": "ACCEPTED",
"created": "2018-01-18T19:38:54+00:00",
"updated": "2018-01-18T19:38:54+00:00",
"expires": "2018-01-21T19:38:00+00:00",
"cost_per_fragment": 0.05,
"fragment_count": 1,
"total_cost": 0.05,
"custom_args": null
}
The different values for state
are:
State | Description |
---|---|
REJECTED | Failed local validation. |
ACCEPTED | Passed local validation and added to our internal queue. |
QUEUED | Waiting to be dispatched for delivery. |
SENT | Taken out of our internal queue and dispatched for delivery. |
DELIVERED | Delivered to the destination handset. |
FAILED | Could not be delivered. |
EXPIRED | Handset remained unavailable for too long. |
UNKNOWN | Final delivery state remains unknown. |
To send SMS, request https://api.voimio.com/v1/sms
POST with your messages in the request body. You should provide your messages as objects using the mandatory/optional key/value attributes shown below. You should list your message objects as an array that is the value for messages
. For example...
{
"messages": [
{
"source_address": "GeorgeLucas",
"destination_address": "+447956218236",
"content": "There is a civil war between the Galactic Empire and a Rebel Alliance."
},
{
"source_address": "DarthVader",
"destination_address": "+447956218236",
"content": "لوق أنا والدك",
"expiry": 60
}
]
}
Attribute | Type | Description |
---|---|---|
destination_country | alphabetic | The ISO 3166-1 alpha-2 code for the destination country.
If set, we will ensure that your number is formatted and validated according to the country's numbering plan.
|
retry_period | integer | The number of minutes after which the SMS should expire.
If set, should be between 1-4320.
|
callback_url | url | The URL of a webhook you want us to post delivery state updates to
This is in addition to the webhooks you've saved in the Client Portal (if any).
|
custom_args | object | A JSON object containing a collection of key/value pairs.
Ensure your JSON is strictly formatted.
|
If your request was successful, messages
will contain an array of objects representing the outcome of each message that you submitted (in the same order that you submitted them).
If the message was accepted, you will see an sms
object. Otherwise, you will see an error
object.
Let's send 1 short English SMS to 1 recipient.
{
"messages": [
{
"source_address": "GeorgeLucas",
"destination_address": "+447956218236",
"content": "There is a civil war between the Galactic Empire and a Rebel Alliance."
}
]
}
{
"messages": [
{
"object_type": "sms",
"id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
"source_address": "GeorgeLucas",
"source_country": null,
"destination_address": "+447956218236",
"destination_country": "GB",
"direction": "outbound",
"body": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
"encoding": 0,
"delivery_state": "ACCEPTED",
"created": "2018-01-18T17:06:43+00:00",
"updated": "2018-01-18T17:06:43+00:00",
"expires": "2018-01-21T17:06:00+00:00",
"cost_per_fragment": 0.05,
"fragment_count": 1,
"total_cost": 0.05,
"custom_args": null
}
],
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.08493
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 158.95,
"auto_purchase": true
}
}
Let's send the same SMS to 2 recipients.
{
"messages": [
{
"source_address": "GeorgeLucas",
"destination_address": "+447711961111",
"content": "There is a civil war between the Galactic Empire and a Rebel Alliance."
},
{
"source_address": "GeorgeLucas",
"destination_address": "+97150349030",
"content": "There is a civil war between the Galactic Empire and a Rebel Alliance."
}
]
}
{
"messages": [
{
"object_type": "sms",
"id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
"source_address": "GeorgeLucas",
"source_country": null,
"destination_address": "+447711961111",
"destination_country": "GB",
"direction": "outbound",
"body": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
"encoding": 0,
"delivery_state": "ACCEPTED",
"created": "2018-01-18T17:14:45+00:00",
"updated": "2018-01-18T17:14:45+00:00",
"expires": "2018-01-21T17:14:00+00:00",
"cost_per_fragment": 0.05,
"fragment_count": 1,
"total_cost": 0.05,
"custom_args": null
},
{
"object_type": "sms",
"id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
"source_address": "GeorgeLucas",
"source_country": null,
"destination_address": "+97150349030",
"destination_country": "AE",
"direction": "outbound",
"body": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
"encoding": 0,
"delivery_state": "ACCEPTED",
"created": "2018-01-18T17:14:45+00:00",
"updated": "2018-01-18T17:14:45+00:00",
"expires": "2018-01-21T17:14:00+00:00",
"cost_per_fragment": 0.05,
"fragment_count": 1,
"total_cost": 0.05,
"custom_args": null
}
],
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.1282
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 158.75,
"auto_purchase": true
}
}
In this example, the messages
array contains 2 message objects because you sent 2 separate messages.
Let's send a long message to 1 recipient.
{
"messages": [
{
"source_address": "Jabba",
"destination_address": "+447956218236",
"content": "Han, Han! If only you hadn't had to dump that shipment of spice... you understand I just can't make an exception. Where would I be if every pilot who smuggled for me dumped their shipment at the first sign of an Imperial starship?"
}
]
}
{
"messages": [
{
"object_type": "sms",
"id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
"source_address": "Jabba",
"source_country": null,
"destination_address": "+447956218236",
"destination_country": "GB",
"direction": "outbound",
"body": "Han, Han! If only you hadn't had to dump that shipment of spice... you understand I just can't make an exception. Where would I be if every pilot who smuggled for me dumped their shipment at the first sign of an Imperial starship?",
"encoding": 0,
"delivery_state": "ACCEPTED",
"created": "2018-01-18T17:21:19+00:00",
"updated": "2018-01-18T17:21:19+00:00",
"expires": "2018-01-21T17:21:00+00:00",
"cost_per_fragment": 0.05,
"fragment_count": 2,
"total_cost": 0.1,
"custom_args": null
}
],
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.12408
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 158.3,
"auto_purchase": true
}
}
In this example, your message was split into 2 SMS because it exceeded the 160 character limit placed on plain English SMS. The only point of interest are that the fragment_count
is now 2 and total_cost
has doubled.
Let's send an SMS to a malformed recipient number.
{
"messages": [
{
"source_address": "GeorgeLucas",
"destination_address": "07956218236",
"content": "There is a civil war between the Galactic Empire and a Rebel Alliance."
}
]
}
{
"messages": [
{
"error": [
{
"object_type": "error",
"code": 1016,
"description": "Attribute 'to' is malformed; should begin with a '+' sign.",
"learn_more": "https://www.voimio.com/sms-api/#error1016"
}
]
}
],
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.01432
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 157.65,
"auto_purchase": true
}
}
The HTTP status of the response is 200 because having a problem with a message does not mean there is a problem with the request.
Let's simultaneously send 1 SMS to a correct number and another to a malformed number.
{
"messages": [
{
"source_address": "GeorgeLucas",
"destination_address": "+447956218236",
"content": "There is a civil war between the Galactic Empire and a Rebel Alliance."
},
{
"source_address": "GeorgeLucas",
"destination_address": "07951292422",
"content": "There is a civil war between the Galactic Empire and a Rebel Alliance."
}
]
}
{
"messages": [
{
"object_type": "sms",
"id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
"source_address": "GeorgeLucas",
"source_country": null,
"destination_address": "+447956218236",
"destination_country": "GB",
"direction": "outbound",
"body": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
"encoding": 0,
"delivery_state": "ACCEPTED",
"created": "2018-01-18T19:40:06+00:00",
"updated": "2018-01-18T19:40:06+00:00",
"expires": "2018-01-21T19:40:00+00:00",
"cost_per_fragment": 0.05,
"fragment_count": 1,
"total_cost": 0.05,
"custom_args": null
}
],
"error": [
{
"object_type": "error",
"code": 1016,
"description": "Attribute 'to' is malformed; should begin with a '+' sign.",
"learn_more": "https://www.voimio.com/sms-api/#error1016"
}
]
}
],
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.0907
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 157.6,
"auto_purchase": true
}
}
As explained in the previous example, it's perfectly normal for a request to be successful as a whole, but for one of the messages to be rejected.
As such, you should iterate through the response messages
array and use conditional statements. If you find an sms
object, then your message was accepted. If you find a error
object, then your message was rejected.
Let's send an SMS in in the Arabic language.
{
"messages": [
{
"source_address": "DarthVader",
"destination_address": "+447956218236",
"content": "لوق أنا والدك"
}
]
}
{
"messages": [
{
"object_type": "sms",
"id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
"source_address": "DarthVader",
"source_country": null,
"destination_address": "+447956218236",
"destination_country": "GB",
"direction": "outbound",
"body": "لوق أنا والدك",
"encoding": 8,
"delivery_state": "ACCEPTED",
"created": "2018-01-18T19:40:44+00:00",
"updated": "2018-01-18T19:40:44+00:00",
"expires": "2018-01-21T19:40:00+00:00",
"cost_per_fragment": 0.05,
"fragment_count": 1,
"total_cost": 0.05,
"custom_args": null
}
],
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.07646
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 157.55,
"auto_purchase": true
}
}
Nothing fancy! Just be mindful that Unicode messages are limited to 70 characters per SMS.
Let's send an SMS with some custom arguments.
{
"messages": [
{
"source_address": "GeorgeLucas",
"destination_address": "+447711961111",
"content": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
"custom_args":
{
"internal_id": "e6d21dfa02ac21facd",
"customer": "Loyal Company PLC"
}
}
]
}
{
"messages": [
{
"object_type": "sms",
"id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
"source_address": "GeorgeLucas",
"source_country": null,
"destination_address": "+447711961111",
"destination_country": "GB",
"direction": "outbound",
"body": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
"encoding": 0,
"delivery_state": "ACCEPTED",
"created": "2018-01-18T19:41:05+00:00",
"updated": "2018-01-18T19:41:05+00:00",
"expires": "2018-01-21T19:41:00+00:00",
"cost_per_fragment": 0.05,
"fragment_count": 1,
"total_cost": 0.05,
"custom_args":
{
"internal_id":"e6d21dfa02ac21facd",
"customer":"Loyal Company PLC"
}
}
],
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.08564
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 157.5,
"auto_purchase": true
}
}
Let's send an SMS that should only be delivered within the next hour.
{
"messages": [
{
"source_address": "GeorgeLucas",
"destination_address": "+447956218236",
"content": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
"retry_period": 60
}
]
}
{
"messages": [
{
"object_type": "sms",
"id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
"source_address": "GeorgeLucas",
"source_country": null,
"destination_address": "+447956218236",
"destination_country": "GB",
"direction": "outbound",
"body": "There is a civil war between the Galactic Empire and a Rebel Alliance.",
"encoding": 0,
"delivery_state": "ACCEPTED",
"created": "2018-01-18T19:51:04+00:00",
"updated": "2018-01-18T19:51:04+00:00",
"expires": "2018-01-18T20:51:00+00:00",
"cost_per_fragment": 0.05,
"fragment_count": 1,
"total_cost": 0.05,
"custom_args": null
}
],
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.0918
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 157.4,
"auto_purchase": true
}
}
Notice that expires
is set to 1 hour after created
whilst all of the previous messages was set to 3 days after created
.
To query an existing SMS, request https://api.voimio.com/v1/sms/{id}
GET with an empty body.
If your request was successful, our response will contain the sms object for the SMS you queried.
Let's query an SMS that you sent.
{
"sms": {
"object_type": "sms",
"id": "b13ebb9d-f5af-43fb-bff4-8442f410e9d0",
"source_address": "+44768254543",
"source_country": "GB",
"destination_address": "+447711161110",
"destination_country": "GB",
"direction": "outbound",
"body": "test",
"encoding": 0,
"delivery_state": "DELIVERED",
"created": "2018-01-18T20:19:21+00:00",
"updated": "2018-01-18T20:19:00+00:00",
"expires": "2018-01-21T20:19:00+00:00",
"cost_per_fragment": 0.05,
"fragment_count": 1,
"total_cost": 0.05,
"custom_args": null
},
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.00605
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 6973.465,
"auto_purchase": false
}
}
Let's query an SMS that you received.
{
"sms": {
"object_type": "sms",
"id": "6f7e065c-7ee3-4b58-a379-b43744424d0a",
"source_address": "+447711961111",
"source_country": "GB",
"destination_address": "+447937985895",
"destination_country": "GB",
"direction": "inbound",
"body": "TEST",
"encoding": 0,
"status": null,
"created": "2018-01-02T17:19:58+00:00",
"updated": null,
"expires": null,
"cost_per_fragment": 0,
"fragment_count": null,
"total_cost": 0,
"custom_args": null
},
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.00676
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 6973.465,
"auto_purchase": false
}
}
To query our coverage for a specific country, request https://api.voimio.com/v1/coverage/{id}
GET with an empty body, where id
is the country's 2-letter ISO 3166-1 alpha-2 country code.
If your request was successful, the response will contain a country
, inbound
and outbound
objects. The inbound
object provides boolean values to let you know if we provide Virtual Mobile Numbers / Virtual Land Lines in that country. The outbound
object lists all of the networks which we support in that country. Each network is provided as an array as we may provide more network information in the future.
Let's query Australia, where our comprehensive coverage includes Outbound SMS and Inbound SMS (through Virtual Mobile Numbers and Virtual Landlines).
{
"country": {
"object_type": "country",
"name": "Australia",
"iso_alpha_2": "AU",
"calling_code": "+61"
},
"inbound": {
"virtual_mobile_number": true,
"virtual_land_line": true
},
"outbound": {
"networks": [
{
"name": "Telstra"
},
{
"name": "Vodafone Australia"
},
{
"name": "YES OPTUS (Singtel Optus Ltd)"
}
]
},
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.02957
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 6973.465,
"auto_purchase": false
}
}
Let's query the UAE, where our coverage is limited to Outbound SMS.
{
"country": {
"object_type": "country",
"name": "United Arab Emirates",
"iso_alpha_2": "AE",
"calling_code": "+971"
},
"inbound": null,
"outbound": {
"networks": [
{
"name": "DU"
},
{
"name": "ETISALAT"
}
]
},
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.03394
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 6973.465,
"auto_purchase": false
}
}
Let's query North Korea, where we do not have any coverage.
{
"country": {
"object_type": "country",
"name": "North Korea",
"iso_alpha_2": "KP",
"calling_code": "+850"
},
"inbound": null,
"outbound": null,
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.03201
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 6973.465,
"auto_purchase": false
}
}
To query our complete pricing, request https://api.voimio.com/v1/pricing
GET with an empty body.
We will throttle requests for our complete pricing to just twice per day. Do not request it more than that.
To query pricing for a specific country, request https://api.voimio.com/v1/pricing/{id}
GET with an empty body, where id
is the country's 2-letter ISO 3166-1 alpha-2 country code.
If you are requesting the pricing for one country, our response will contain a country
object as well as inbound
and outbound
objects with their respective pricing.
If you are requesting our complete pricing, our response will be an array of all countries, with the above 3 objects listed for each country.
Let's query Australia, where our comprehensive coverage includes Outbound SMS and Inbound SMS (through Virtual Mobile Numbers and Virtual Landlines).
{
"country": {
"object_type": "country",
"name": "Australia",
"iso_alpha_2": "AU",
"calling_code": "+61"
},
"inbound": {
"virtual_mobile_number": {
"setup": 0,
"per_month": 30,
"per_sms": 0
},
"virtual_land_line": {
"setup": 0,
"per_month": 30,
"per_sms": 0
}
},
"outbound": {
"setup": 0,
"per_month": 0,
"per_sms": 0.055
},
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.03024
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 6973.465,
"auto_purchase": false
}
}
Let's query the UAE, where our coverage is limited to Outbound SMS.
{
"country": {
"object_type": "country",
"name": "United Arab Emirates",
"iso_alpha_2": "AE",
"calling_code": "+971"
},
"inbound": null,
"outbound": {
"setup": 0,
"per_month": 0,
"per_sms": 0.034
},
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.03067
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 6973.465,
"auto_purchase": false
}
}
Let's query North Korea, where we do not have any coverage.
{
"country": {
"object_type": "country",
"name": "North Korea",
"iso_alpha_2": "KP",
"calling_code": "+850"
},
"inbound": null,
"outbound": null,
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.02905
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 6973.465,
"auto_purchase": false
}
}
To validate your account independently of other resources, request https://api.voimio.com/v1/account
GET with an empty body.
If your request was successful, you'll get the Account Object and the Request Object as usual.
Don't use /account to monitor your balance. Do this in real-time as part of all other requests.
{
"request": {
"object_type": "request",
"id": "504b483e-35ac-42fe-ad94-549b1216e265",
"sandbox": false,
"execution_time": 0.00465
},
"account": {
"object_type": "account",
"account_id": 2232,
"balance": 6973.465,
"auto_purchase": false
}
}
We can provide you with real-time updates for your Outbound SMS and notify you whenever you receive new Inbound SMS. All you have to do is set up scripts on your server that an accept HTTP POST requests and then parse the JSON that we POST to you.
For outbound_sms
updates, you can provide message-specific URLs as part of your request using the callback_url
attribute. Otherwise, you can set general URLs through the Client Portal.
For inbound_sms
updates, you can only set general URLs through the Client Portal.
You can provide up to 3 general URLs by default, with more upon request, and we send your updates to all 3.
You can also view recent webhooks made by our API in the Client Portal.
When you request /sms
using HTTP GET, you're requesting the details of an SMS identified by its id
in the URI (e.g. /sms/{id}).
Error 1001 means that there is something wrong with the id
that you've provided.
It should be a UUID.
Although you requested /sms/{id}
using HTTP GET and a well-formed {id}
, we could not find an SMS on your account with the {id}
you've given.
If you have more than one account, ensure you're using the correct API Key. Otherwise, check our logs to ensure the {id}
being sent to us is correct.
Making a HTTP POST request means that you're giving an instruction which should be provided as a set of arguments formatted as a JSON object in the body of your request.
Error 1004 means that the body of your POST request is completely empty.
Making a HTTP POST request means that you're giving an instruction which should be provided as a set of arguments formatted as a JSON object in the body of your request.
Error 1005 means that something is wrong with your JSON object. Either it's not JSON at all or its malformed somehow.
Paste your object into jsonlint.com to find out what's breaking it.
Error 1006 means that you're using HTTP POST on a resource which only supports HTTP GET.
For example, you may be using HTTP POST on /sms/{id}
, whilst you can only use it on /sms
.
If you're trying to create/send a resource, then stick to HTTP POST and remove the identifier.
If you're trying to pull/get a resource, then use HTTP GET and leave the URI as it is.
We can tell which resource you're requesting because, in the URI, the resource comes right after the version number.
Error 1007 means you've requested a resource that doesn't exist. Most likely, your URI is malformed, so compare it to the list of resources we provide.
To send SMS, you need to provide the details of all your messages as an array of objects that are wrapped into an attribute called messages
. This is even the case when sending a single SMS.
But seeing Error 1008 means that we could not detect that attribute.
To use the SMS API, you need to let us know which account you're using and that you have permission to use it.
You do this by generating an API key in the account itself and then presenting it in the headers of your request either as an Authorization: Bearer
or as a custom Api-Key
header.
If you're seeing Error 1009, you've forgotten to provide the API key in either place (or there's something wrong with your implementation).
The API key is 32 characters long and is made up numbers between 0-9 and letters between a-f.
But if you're seeing Error 1010, it means that you've provided an API key which breaks one or more of these rules.
So check your API key and, if it's correct, make sure that your code is not inadvertently modifying it.
If you're seeing Error 1011, you've provided an API Key that follows the structure of an API key. The only problem is that we could not match it to any of our client accounts.
This should never happen. So, if it's happening to you, please contact us right away.
The content
attribute is where you specify the body of the message you're trying to send.
Error 1013 means that, whilst parsing your message object, we were unable to find this attribute. In other words, you're trying to send an empty SMS and we don't dispatch empty messages.
When you provide a recipient's phone number, you have 2 choices. The first is to provide it in its international E.164format (beginning with a '+' sign). The second is to provide it however you like, but to also include the destination_country
so we can format it for you.
Error 1016 means that you didn't specify the destination_country
, nor did you begin the recipient's phone number with the '+' sign.
But don't rush off to just add it to the existing number. Quite often, if you're pulling numbers from somewhere else, like your database, you'll need to reformat it (as mentioned above). Otherwise, you'll just be adding a '+' sign to a number that is formatted incorrectly. So understand how your data is organised and then choose which option is better for you.
The source_address
attribute is where you specify the Sender ID of the message you're trying to send.
Error 1018 means that, whilst parsing your message object, we were unable to find this attribute. In other words, you're trying to send an SMS without a Sender ID and we can't accept such messages.
When you send an SMS, you use the source_address
parameter to set the Sender ID.
The Sender ID supports letters, numbers and the "." sign. And Error 1019 means that your Sender ID includes a character that isn't supported.
When you send an SMS, you can set the Sender ID to either be a number (so that recipients can reply), a word or a mix of numbers and letters.
If you set it as a number, you're limited to 15 digits. Otherwise, you're limited to just 11 characters.
Error 1020 means that you set it to a number, but that it had more than 15 digits.
The destination_address
attribute is where you specify the recipient of the message you're trying to send.
Error 1021 means that, whilst parsing your message object, we were unable to find this attribute. In other words, you're trying to send an SMS without specifying the recipient and we can't accept such messages.
As much as we'd like to provide our service free of charge, we're not able to!
Error 1022 means that you've tried to make a cost-incurring request with insufficient credit on your balance.
Although your short term solution is to buy credit, your long term solution is to set up auto-purchase so that you never run out of credit again.
Error 1023 means that the API key is valid, but that it's associated with an account that is closed or frozen.
If this is news to you, the contact our support team to discuss your options.
If you want to simulate the SMS sending, you've got to specify "sandbox": true
. Otherwise, specify "sandbox": false
or exclude the sandbox
attribute altogether.
Error 1025 means that you've declared sandbox
, but that you've set its value as non-boolean. Maybe you've wrapped boolean value into a string or maybe you've providing something else.
We let you provide custom_args
so that you can add relevant meta data to your SMS. We include them when we send you updates and, in the future, we may let you query SMS using them.
But if you want to provide them, you have to provide them as a JSON object containing a collection of key/value pairs.
Error 1026 means that you provided custom_args
in a manner that is not a well-formatted JSON object containing key/value pairs.
An SMS can't be delivered to a handset if it's unavailable (e.g. switched off).
In such circumstances, network operators keep trying to deliver it, but there's only so long they'll try. That period is the retry period of the SMS (also know as its 'life') and, unless you tell us otherwise, we tell operators to keep retrying for as long as possible.
To tell us otherwise, you specify the number of minutes you want the SMS to live for using the retry_period
attribute and we expect to see it as an integer.
Error 1027 means that you've specified the retry_period
attribute, but that the value is not an integer.
An SMS can't be delivered to a handset if it's unavailable (e.g. switched off).
In such circumstances, network operators keep trying to deliver it, but there's only so long they'll try. That period is the retry period of the SMS (also know as its 'life') and, unless you tell us otherwise, we tell operators to keep retrying for as long as possible.
To tell us otherwise, you specify the number of minutes you want the SMS to live for using the retry_period
attribute and the most you can set it to is 4321 (which is 60 minutes x 24 hours x 3 days).
Error 1029 means that you specified an retry_period
value greater than 4321.
To ensure data privacy, the SMS API is only served over HTTPS.
Error 1046 means that you requested "http" instead of "https". Just fix that and all should be well.
If you make a HTTP Post request, you must set the Content-Type
to be application/json
.
Error 1047 means that you set it to something else.
Error 1048 means that you didn't specify the API version in the URI that you requested.
Most likely, the URI is malformed as a whole. So take a look at your URI and ensure it matches the examples in our documentation.
You indicate which resource you are requesting in the URI of your request.
Error 1049 means that you specified a version, but not the resource.
Most likely, the URI is malformed as a whole. So take a look at your URI and ensure it matches the examples in our documentation.
At the moment, the SMS API only has 1 version which is... "v1".
Error 1050 means that you specified something other than v1
as the version in the URI that you're requesting.
Most likely, the URI is malformed as a whole. So take a look at your URI and ensure it matches the examples in our documentation.
You can send up to 100 messages in one request and Error 1051 means that you've exceeded that limit.
When you send an SMS, you can set the Sender ID to either be a number (so that recipients can reply), a word or a mix of numbers and letters.
If you include any letters, the length of the Sender ID gets limited to just 11 characters.
Error 1054 means that your Sender ID included a letter, but that it had more than 11 characters.
If you make a HTTP Post request, you must set the Content-Type
to be application/json
.
Error 1055 means we could not find any Content-Type
.
When you request coverage
using HTTP GET, you're requesting the coverage of a specific country identified by the country's id
in the URI (e.g. /coverage/{id}). We expect you to provide it in ISO 3166-1 alpha-2 format.
Error 1057 means that the id
you've provided has non-letters in it.
When you request coverage
using HTTP GET, you're requesting the coverage of a specific country identified by its id
in the URI (e.g. /coverage/{id}). We expect you to provide it in ISO 3166-1 alpha-2 format.
Error 1058 means that the id
you've provided is too short. It should be 2 characters long.
When you request coverage
using HTTP GET, you're requesting the coverage of a specific country identified by its id
in the URI (e.g. /coverage/{id}). We expect you to provide it in ISO 3166-1 alpha-2 format.
Error 1059 means that the id
you've provided is too long. It should be 2 characters long.
When you request coverage
using HTTP GET, you're requesting the coverage of a specific country. You specify that country by including its ISO 3166-1 alpha-2 code in the URI. For example, /coverage/gb
.
Error 1060 means that we could not detect a country id
value in the URI.
If you're seeing Error 1061, you've provided a valid API Key. However, it has been deactivated in your Client Portal. So you should either reactivate it or generate a new API Key.
The callback_url
can only be the URL of a live website.
You provide the destination_country
to help us validate and format your numbers more effectively. So, if you tell us that a message is destined for the United Kingdom, for example, we can ensure that it is valid for mobile numbers in the UK and formatted accordingly. As a result, we can pass malformed numbers and reject invalid numbers, saving you from wasted money.
But if you're seeing error 1064, it means that the destination_country
you've provided is invalid.
It should be formatted in the ISO 3166-1 alpha-2 format.
The recipient's number was invalid. So we rejected the message to save you from wasting money on a message that was otherwise destined to fail.
Check the number to ensure it is accurate.
If you're seeing this error, there's a high chance that you tried to send a single SMS without wrapping it inside a numeric index array.
To explain, the API handles multiple SMS by default and you send multiple SMS by compiling the details of each SMS into its own object and then listing the objects as a numeric index array.
If you just want to send 1 SMS, you still need to provide it as an object within the numeric index array - even if it's the only message.
If you're seeing this error, you've tried to send an SMS with a Sender ID of a recognised brand.
If you do represent the brand, please contact us so that we can whitelist it for you.
If you do not, please note that attempting to mislead others as to the identity of the sender or the origin or contents of a message is against our Acceptable Use Policy and may lead to the termination of your account.
All of our resources only accept HTTP POST and HTTP GET.
So, either you're using another method that we don't support, or you're using HTTP POST on a resource that only supports HTTP GET (or vice versa).
Your OTP should not be any shorter than 6 digits long.
Your OTP should not be any longer than 12 digits long.
If you want to customise the message of your 1-Time Password, your customised message needs to include %code% as a placeholder for where you want the verification code to go.
If you're seeing this error, you've tried to customise the message but forgot to include the placeholder.
You've requested /otp
using HTTP GET. However, you have not specified an identifier.
If you are trying to create an OTP, please use HTTP POST. Otherwise, please add an identifier to your URI.
Although you requested /otp/{id}
using HTTP GET and a well-formed {id}
, we could not find an OTP on your account with the {id}
you've given.
If you have more than one account, ensure you're using the correct API Key. Otherwise, check our logs to ensure the {id}
being sent to us is correct.
You've request an OTP, but the otp_code
you've given is not correct.
Notify your user that their OTP verification has failed and be on the watch for repeated OTP verification failures from the same user.
You cannot verify an OTP which has already expired.
If you're not sure how much time you had to verify the OTP, query the OTP again without otp_code
and reference otp_maximum_retry_period
You are trying to verify an OTP more times than the OTP allows.
If you're not sure how many times you were allowed to try, query the OTP again without otp_code
and reference otp_maximum_retry_count
If you want to specify the maximum_attempt_count
, you must provide it as an integer.
To ensure that users cannot force their way through your OTPs, the default maximum_attempt_count
is set to 3.
If you want to give them more chances than that, you're free to do so. However, we don't allow more than 10 in any circumstance.
If you're seeing this error, you've specified a maximum_attempt_period
in excess of 10 minutes.
If you want to specify the maximum_attempt_count
, you must provide it as an integer.
If you want to customise the message of your 1-Time Password, your customised message needs to include %brand% as a placeholder for where you want your brand name to go.
If you're seeing this error, you've tried to customise the message but forgot to include the placeholder.