NAV Navbar
CURL Arduino Python Circuitpython Ruby

Adafruit IO MQTT API

MQTT, or message queue telemetry transport, is a protocol for device communication that Adafruit IO supports. Using a MQTT library or client you can publish and subscribe to a feed to send and receive feed data.

If you aren't familiar with MQTT check out this introduction from the HiveMQ blog. All of the subsequent posts in the MQTT essentials series are great and worth reading too.

Client Libraries

To use the MQTT API that Adafruit IO exposes you'll need a MQTT client library. For Python, Ruby, and Arduino you can use Adafruit's IO libraries as they include support for MQTT. For other languages or platforms look for a MQTT library that ideally supports the MQTT 3.1.1 protocol.

MQTT Connection Details

We strongly recommend connecting using SSL (Port 8883) if your client allows it.

Host io.adafruit.com
Secure (SSL) Port 8883
Insecure Port 1883
Username Your Adafruit IO Username
Password Your Adafruit IO Key

Need to manually set a Client ID for your MQTT client? Use a unique value such as a random GUID.

MQTT QoS

One feature of MQTT is the ability to specify a QoS, or quality of service, level when publishing feed data. This allows an application to confirm that its data has been sucessfully published.

If you aren't familiar with MQTT QoS levels be sure to read this great blog post explaining their meaning.

For publishing feed values the Adafruit IO MQTT API supports QoS level 0 (at most once) and 1 (at least once) only.

QoS level 2 (exactly once) is not currently supported.

MQTT API Rate Limiting

Adafruit IO's MQTT server imposes a rate limit to prevent excessive load on the service. If a user performs too many publish actions in a short period of time then some of the publish requests might be rejected. The current rate limit is at most 1 request per second (or 60 requests within 60 seconds), without an Adafruit IO+ Boost applied to your account.

If you exceed this limit, a notice will be sent to the (username)/throttle topic. You can subscribe to the topic if you wish to know when the Adafruit IO rate limit has been exceeded for your user account.

This limit applies to all connections so if you have multiple devices or clients publishing data be sure to delay their updates enough that the total rate is below 2 requests/second.

MQTT Data Format

There are a few ways to send data to our MQTT API if you're writing your own client library.

The simplest way to send values to an IO Feed topic is to just send the value. For example, a temperature sensor is going to produce numeric values like 22.587. If you're sending to mosfet/feeds/photocell-one you can use the raw number or a string. That means either 22.587 or 22.587 will be accepted as a numeric value. Adafruit IO does its best to treat data as numeric values so that we can show you your data as a chart on an Adafruit IO dashboard and through our Charting API.

Sending data with location

To tag your data with a location value, you'll either need to wrap it in a JSON object first or send it to the special /csv formatted MQTT topic.

Sending JSON

JSON can be sent to either the base topic or the /json topic - for example mosfet/feeds/photocell-one or mosfet/feeds/photocell-one/json. The proper format for location tagged JSON data is:

Example JSON topic object:

  {
    "value": 22.587, 
    "lat": 38.1123,
    "lon": -91.2325, 
    "ele": 112
  }

Specifically, JSON objects must include a value key, and may include lat, lon, and ele keys.

Sending CSV

Alternatively, you can send location tagged data to /csv topics. In this example, that would be the topic mosfet/feeds/photocell-one/csv instead of mosfet/feeds/photocell-one. Both store data in the same feed. The format IO expects for location tagged CSV data is VALUE, LATITUDE, LONGITUDE, ELEVATION.

With the example data shown before, that means you could publish the string "22.587,38.1123,-91.2325,112" to mosfet/feeds/photocell-one/csv. to store the value "22.587" in the location latitude: 38.1123, longitude: -91.2325, elevation: 112.

An example is displayed on the sidbar of this page which uses a simple Ruby MQTT library and the data shown, all these examples publish the same data to the same feed.

  # first you'll need https://github.com/njh/ruby-mqtt
  require 'mqtt'

  username = 'test_username'
  key      = 'not-a-real-key'
  url      = "mqtts://#{ username }:#{ key }@io.adafruit.com"

  mqtt_client = MQTT::Client.connect(url, 8883)

  # simplest thing that could possibly work
  mqtt_client.publish('test_username/feeds/example', 22.587)

  # sending numbers as strings is fine, IO stores all data internally
  # as strings anyways
  mqtt_client.publish('test_username/feeds/example', '22.587')

  # CSV formatted, no location
  mqtt_client.publish('test_username/feeds/example/csv', '22.587')

  # CSV formatted, with location
  mqtt_client.publish('test_username/feeds/example/csv', 
                      '22.587,38.1123,-91.2325,112')

  # JSON formatted, no location 
  mqtt_client.publish('test_username/feeds/example', '{"value":22.587}')
  mqtt_client.publish('test_username/feeds/example/json', '{"value":22.587}')

  # JSON formatted, with location
  mqtt_client.publish('test_username/feeds/example', 
    '{"value":22.587,"lat":38.1123,"lon":-91.2325,"ele":112}')
  mqtt_client.publish('test_username/feeds/example/json',
    '{"value":22.587,"lat":38.1123,"lon":-91.2325,"ele":112}')

Sending JSON Data through Adafruit IO

Because Adafruit IO supports additional features beyond a basic MQTT brokering service, such as location tagging for data points, the service supports data in the JSON format described above. Namely, the example JSON response on the sidebar.

JSON Response Format Example

  {
    "value": 22.587, 
    "lat": 38.1123,
    "lon": -91.2325, 
    "ele": 112
  }

This lets us store the individual value, 22.587, and data about the value: its latitude, longitude, and elevation. Metadata!

But what happens when the value you want to send is itself JSON? Good news! There are a few solutions available to you in that situation.

IO-Formatted JSON

The simplest way to send JSON data to Adafruit IO is to wrap it in the format described above. For example, if instead of 22.587, I wanted to send something like, {"sensor-1":22.587,"sensor-2":13.182}, the "wrapped" version would look like this:

Example of IO-Formatted JSON
{
  "value": {"sensor-1":22.587,"sensor-2":13.182}, 
  "lat": 38.1123,
  "lon": -91.2325, 
  "ele": 112
}

It's worth noting that because Adafruit IO parses the entire JSON object that you send it, any valid JSON will be parsed and when it is stored in our system and forwarded to any subscribers, it will be regenerated. The significance of that is that if you publish JSON data with whitespace, it will be stored and republished without whitespace, because our generator produces the most compact JSON format possible.

Double-Encoded JSON Strings

The second way you can send JSON data as a value is to "double encode" it before sending, in which case IO will treat it as a raw string. If you're using something like javascript's JSON.stringify function or Ruby's JSON.generate, double encoding means passing the result of JSON.stringify through JSON.stringify a second time. In this node.js console example, you can see the difference:

Example of sending double-encoding strings before sending through Adafruit IO
  > JSON.stringify({"sensor-1":22.587,"sensor-2":13.182})
  '{"sensor-1":22.587,"sensor-2":13.182}'
  > JSON.stringify(JSON.stringify({"sensor-1":22.587,"sensor-2":13.182}))
  '"{\"sensor-1\":22.587,\"sensor-2\":13.182}"'

The double encoded JSON string can be sent directly through Adafruit IO without interference from our processing system, because the processing system will not interpret it as JSON. In your receiving code, because the value passed through includes surrounding double quotes, you have to call your parse function twice to restore the JSON object.

Example of sending Double-Encoded JSON Strings directly through Adafruit IO
  > var input = '"{\\\"sensor-1\\\":22.587,\\\"sensor-2\\\":13.182}"'
  > JSON.parse(JSON.parse(input))
  { 'sensor-1': 22.587, 'sensor-2': 13.182 }

Non-IO Formatted JSON

The third way you can send raw JSON data is to just send it. If Adafruit IO doesn't find a "value" key in the JSON object you send, it will treat the whole blob as plain text and store and forward the data. That means with our example JSON object, sending the string {"sensor-1":22.587,"sensor-2":13.182} will result in {"sensor-1":22.587,"sensor-2":13.182} being stored in IO and sent to MQTT subscribers.

Retrieving a Value

MQTT is a tremendously useful protocol for building small connected devices and is relatively simple to understand and implement (if implementing networking protocols is your thing). Unfortunately, a few features of the Adafruit IO platform make it difficult for us to support the entire MQTT 3.1+ protocol specification in our broker. Specifically, one particular feature, the publish retain flag.

In the MQTT protocol, setting the retain flag on a published message asks the MQTT broker (server) to store that message. Then any new clients which connect and subscribe to that topic will immediately receive the retained message. Retain makes writing basic MQTT-only Internet of Things clients easier, without it, a client that connects and subscribes to a feed topic has to wait until a new value is published on the feed to know what state it should be in. In the case of slowly updated feeds, there could be hours between updates which means a device that disconnects and reconnects (for example, due to power cycling or sleeping) might lose the current value for a long time between updates.

Among other factors, our scale, Adafruit IO's mix of MQTT & HTTP APIs, the speed at which we’re taking in new data, and the fact that we’re already storing almost every message that is sent mean that a “simple” feature like retain becomes difficult to support without making MQTT service performance worse for everyone.

Since we don’t actually store data in the broker but at a lower level and can’t support PUBLISH retain directly, we’re proposing a different solution for the retaining problem: the /get topic modifier.

The way it works is that for any given Adafruit IO MQTT feed or group, subscribe to the appropriate topic, then add /get to the topic you subscribed to and publish anything to that new topic (our Arduino library uses the null character: \0) . IO will immediately publish, just for that client, the most recent value received on the feed.

For example, let's imagine we have a device that subscribes to a counter feed: uname/f/counter. If we want to get the latest value, the /get topic we should publish to is uname/f/counter/get. After connecting, subscribing, and publishing to the /get topic, we will immediately receive a message on our uname/f/counter subscription with the last value that was published to the feed.

If you’re using the Adafruit IO Arduino library, you can add /get support to your project in one line of code, displayed on the sidebar of this section

// ... from the adafruitio_01_subscribe example sketch
AdafruitIO_Feed *counter = io.feed("counter");

void setup() {

  // 1. start IO connection
  io.connect();

  // 2. prepare MQTT subscription with handler function
  counter->onMessage(handleMessage);

  // 3. wait for successful connection
  while(io.mqttStatus() < AIO_CONNECTED) {
    delay(500);
  }

  // 4. send /get message, requesting last value
  counter->get(); // ask Adafruit IO to resend the last value

}
// ....

MQTT Topics

Feed Topic Format

Adafruit IO's MQTT API exposes feed data using special topics. You can publish a new value for a feed to its topic, or you can subscribe to a feed's topic to be notified when the feed has a new value.

Your_Adafruit_IO_Username/feeds/Feed-Key

Group Topic Format

Your_Adafruit_IO_Username/groups/Feed-Key

Small Topic Format

If you're using an embedded system and need to conserve space, use the /f/ or /g/ topic formats:

Using a Wildcard

You can also subscribe to the parent 'feeds' path to be notified when any owned feed changes using MQTT's # wildcard character. For example, the user could subscribe to either:

Once subscribed to the path above any change to a feed owned by Your_Adafruit_IO_Username will be sent to the MQTT client. The topic will specify the feed that was updated, and the payload will have the new value.

Be aware the MQTT server sends feed updates on all possible paths for a specific feed. For example, subscribing to IO-Username/f/# and publishing to IO-Username/f/photocell-one would produce messages from: IO-Username/f/photocell-one, IO-Username/f/photocell-one/json, and IO-Username/f/photocell-one/csv; each referring to the same updated value. To reduce noise, make sure to grab the specific topic of the feed / format you're interested in and change your subscription to that.

If you'd like to avoid the formatted feeds (/json and /csv topics) but still see all the feeds you're publishing to, you can use MQTT's + wildcard in place of #. In this case, subscribing to IO-Username/f/+ would produce output on IO-Username/f/photocell-one, but not IO-Username/f/photocell-one/json.

Group Topics

You aren't limited to just Feed based MQTT topics, Adafruit IO supports grouped feeds as well. Similar to our HTTP group data creation API, you can publish to multiple feeds or subscribe to multiple feeds through a single MQTT topic.

The topics formats for publishing or subscribing are:

Publish to Feeds in a Group

If you use the /json or /csv endings on your group MQTT topic, your data should be formatted in JSON or CSV, respectively. By default, IO expects published values to be in JSON format.

The JSON and CSV format IO expects are displayed on the sidebar of this section.

JSON Expected Response from Adafruit IO
{
  "feeds": {
    "key-1": "value 1",
    "key-2": "value 2",
    "key-3": "value 3"
  },
  "location": {
    "lat": 0.0,
    "lon": 0.0,
    "ele": 0.0
  }
}
CSV Format Expected Response from Adafruit IO
key-1,value 1
key-2,value 2
key-3,value 3
location,0.0,0.0,0.0

Group Guidelines

Subscribing to groups

If you use the /json or /csv endings when subscribing to your group MQTT topic, your data will be formatted in JSON or CSV, respectively. By default, IO publishes values in JSON format.

The formats your subscription will receive are the same as the formats IO expects to receive. Expected responses for JSON and CSV are displayed on the sidebar of this section.

Expected Group JSON Response

{
  "feeds": {
    "key-1": "value 1",
    "key-2": "value 2",
    "key-3": "value 3"
  },
  "location": {
    "lat": 1.0,
    "lon": 2.0,
    "ele": 3.0
  }
}
Expected Group CSV Response

  key-1,value 1
  key-2,value 2
  key-3,value 3
  location,1.0,2.0,3.0

It's important to note that you will only receive updated values for the feeds that received new values. That means if you're subscribed to {username}/groups/example and publish to {username}/feeds/key-1, the subscription will receive:

{ "feeds": { "key-1": "value 1" } }

It's also worth noting that JSON subscription formats will always receive string type values, regardless of whether a string or number was published.

Errors

The Adafruit IO API uses the following error codes:

Error Code Meaning
400 Bad Request -- Your request is invalid or was in the wrong format.
401 Unauthorized -- Your API key is wrong or you're trying to view a resource you don't own.
403 Forbidden -- This action is not permitted.
404 Not Found -- The specified record could not be found.
406 Not Acceptable -- You requested a format that we don't serve.
429 Too Many Requests -- You're sending or requesting data too quickly! Slow down!
500 Internal Server Error -- We had a problem with our server. Try again later.
503 Service Unavailable -- We're temporarily offline for maintenance. Please try again later.