Data Upload API & Samples scripts

The first step is to get a token from data-platform page.

Once you have your own token, you can use the following script to upload your data. After you upload your first station data, go to aqicn.org/data-feed/verification/ to configure your stations and verify the uploaded data.

Sample code (python)

import requests  
 
# Sensor parameter   
sensorReadings = [   
  {'specie':'pm25', 'value': 393.3},  
  {'specie':'pm10', 'value': 109.3}  
] 
 
# Station parameter   
station = { 
  'id':    "iitk-station-01",  
  'name':   "IIT Kanpur - Hall 1",  
  'location':  { 
    'latitude': 28.7501,  
    'longitude': 77.1177 
  } 
} 
 
# User parameter - get yours from https://aqicn.org/data-platform/token/ 
userToken = "dummy-token-for-test-purpose-only" 
 
# Then Upload the data  
params = {'station':station,'readings':sensorReadings,'token':userToken}  
request = requests.post( url = "https://aqicn.org/sensor/upload/",  json = params) 
#print(request.text) 
data = request.json()  
 
if data["status"]!="ok": 
  print("Something went wrong: %s" % data) 
else: 
  print("Data successfully posted: %s"%data) 

API Options

ParameterTypeOptional/MandatoryExplanations
tokenstringmandatory Get your own token from aqicn.org/data-platform/token-feedback page.
station
station.idstringmandatory Unique station ID - you can select any name with max 128 characters.
This name is only used internally for you. No one else will see this ID
station.namestringoptional Name of the station - could be for instance the name of your building, the name of a street, the name of a university departement, the code of your personal weather station.
This name will be used as the suffix for your station URL.
station.latitudefloatoptionalLongitude of your station
station.longitudefloatoptionalLatitude of your station
organization
org.websitestringoptional If you have a website with more information about your station/sensor, we will add this link on our map when used see your station
org.namestringoptional If you specify a website, this "organization name" will be associated to the website.
readings
readings[*].speciestringmandatory Name of the pollutant your are reporting. For gas sensors, use: "pm2.5", "pm10", "pm1.0", ... For gaz sensor, use: "co2", "no2", "o3", ... For weather sensor, use: "temp", "humidity", "pressure", "wind speed", "wind gust", "wind direction", ..
You can actually use any specie name you want. When you station is validated, the names will be normalized in our system.
readings[*].valuefloatmandatory If your sensor is producing values every second, and you only upload every minute, this value should be the average of all the values read during the past minute.
readings[*].unitstringoptional Unit of the value. Eg "mg/m3" for dust sensor, ppb for gas sensors, C for temp sensor..
readings[*].timestringoptional Date and Time of the reading in ISO 8601 format
readings[*].minfloatoptional If the reading values are based on the averaging of several values, then this correspond to the min value of all values used for the averaging.
readings[*].maxfloatoptional If the reading values are based on the averaging of several values, then this correspond to the max value of all values used for the averaging.
readings[*].medianfloatoptional If the reading values are based on the averaging of several values, then this correspond to the median value of all values used for the averaging.
readings[*].stddevfloatoptional If the reading values are based on the averaging of several values, then this correspond to the standard deviation of all values used for the averaging.
readings[*].averagingfloatoptional If the above values are based on the averaging of several values, then this correspond to the duration, in seconds, of the averaging period.
For instance, use 60 for a minute average data and 3600 for hourly average.

Example 1

{ 
  token: "......", 
  station: { 
    "id": "outdoor-station-01", 
    "name": "HCPA Santa Cecília", 
    "latitude": 103.37893, 
    "longitude": 43.17108, 
  }, 
  org: { 
    "website":"https://pacto.upsensor.com/", 
    "name":"Porto Ar Alegre", 
  }, 
  readings: [ 
    {"time":"2020-07-07T13:21:00+09:00","specie":"pm2.5", "value": 393.3, "unit":"mg/m3", "min":390.3, "max": 402.3, "stddev": 0.332},  
    {"time":"2020-07-07T13:21:00+09:00","specie":"pm10", "value": 109.3, "unit":"mg/m3"}, 
    {"time":"2020-07-07T13:21:00+09:00","specie":"co2", "value": 459.3, "unit":"ppb"}, 
    {"time":"2020-07-07T13:21:00+09:00","specie":"temp", "value": 26.8, "unit":"C"}, 
  ] 
}

Example 2

{ 
  token: "......", 
  station: { 
    "id": "outdoor-station-01", 
  }, 
  readings: [ 
    {"specie":"pm2.5", "value": 393.3} 
  ] 
}

Complete Code example

You can use this code for continuously reading from an SDS sensor, and uploading every minute: (script also available from https://github.com/aqicn/sds-sensor-reader).
import requests 
import random 
import time 
import math 
import json 
import sys 
from serial import Serial 
 
LOCATION = {'latitude': 28.7501, 'longitude': 77.1177} 
TOKEN    = "dummy-token-for-test-purpose-only" 
SENSORID = "YOUR-SENSOR-ID" 
USBPORT  = "/dev/ttyUSB0" 
 
class SensorDataUploader: 
 
    def __init__(self, station, token): 
        self.token = token 
        self.station = station 
 
 
    def send(self,readings): 
 
        params = {'station':self.station,'readings':readings,'token':self.token}  
        print("Uploading: %s"%json.dumps(params, indent=4)) 
 
        request = requests.post( url = "https://aqicn.org/sensor/upload/",  json = params) 
        data = request.json()  
        if data["status"]!="ok": 
            print("Something went wrong: %s" % data) 
        else: 
            print("Data successfully posted: %s"%data) 
 
 
 
 
class Accumulator: 
 
    def __init__(self, name): 
        self.name = name 
        self.values = [] 
 
    def add(self,val): 
        self.values.append(val) 
 
    def count(self): 
        return len(self.values) 
 
    def reset(self): 
        self.values=[] 
 
    def min(self): 
        return self.values[0] 
 
    def max(self): 
        return self.values[len(self.values)-1] 
 
    def median(self): 
        return self.values[len(self.values)/2] 
 
    def mean(self): 
        return float(sum(self.values)) / len(self.values) 
 
    def stddev(self): 
        l = len(self.values) 
        mean = self.mean() 
        return math.sqrt(float(reduce(lambda x, y: x + y, map(lambda x: (x - mean) ** 2, self.values))) / l) 
 
 
    def summary(self): 
        self.values.sort() 
        return {"specie":self.name,'value':self.mean(),'min':self.min(),'max':self.max(),'median':self.median(), 'stddev':self.stddev()}  
 
 
 
class DummyReader: 
 
    def read( self ): 
 
        time.sleep(1.1) 
        return {"pm2.5":random.random()*10,"pm10":random.random()*10} 
 
 
class SDS011Reader: 
 
    def __init__(self, inport): 
        self.serial = Serial(port=inport,baudrate=9600) 
        self.values = [] 
        self.step = 0 
 
    def read( self ): 
 
        # time.sleep(1) 
        # return {"pm2.5":random.random()*100,"pm10":random.random()*100} 
 
        while self.serial.inWaiting()!=0: 
            v=ord(self.serial.read()) 
 
            if self.step ==0: 
                if v==170: 
                    self.step=1 
 
            elif self.step==1: 
                if v==192: 
                    self.values = [0,0,0,0,0,0,0] 
                    self.step=2 
                else: 
                    self.step=0 
 
            elif self.step>8: 
                self.step =0 
                pm25 = (self.values[0]+self.values[1]*256)/10 
                pm10 = (self.values[2]+self.values[3]*256)/10 
                return {"pm2.5":pm25,"pm10":pm10} 
 
            elif self.step>=2: 
                self.values[self.step-2]=v 
                self.step= self.step+1 
 
        return None 
 
 
 
def readAndUpload(sensor, uploader): 
 
    try: 
 
        while True: 
            accumulators = {} 
            startTime = time.time() 
 
            while time.time() < startTime+60: 
                values = sensor.read() 
                if values==None: 
                    continue 
 
                print("Reading [%2d]: %s"%(int(time.time()-startTime),values)) 
                for specie, value in values.items(): 
                    if not (specie in accumulators): 
                        accumulators[specie]=Accumulator(specie) 
                    accumulators[specie].add(value) 
 
 
            readings = [] 
            for specie, accumulator in accumulators.items(): 
                readings.append(accumulator.summary()) 
 
            if len(readings)>0: 
                uploader.send(readings) 
            else: 
                print("No value read from the sensor...") 
 
 
    except KeyboardInterrupt: 
        print "Bye" 
        sys.exit() 
 
 
 
print("Starting reading sensor "+SENSORID+" on port "+USBPORT) 
 
# Station parameter   
station = {'id':SENSORID, 'location':LOCATION} 
uploader = SensorDataUploader(station, TOKEN) 
 
sensor = SDS011Reader(USBPORT) 
# sensor = DummyReader() 
readAndUpload(sensor,uploader) 
 

Want your own air quality monitoring station?

The GAIA air quality monitoring stations are using high-tech laser particle sensors to measure in real-time PM2.5 pollution, which is one of the most harmful air pollutants.

Very easy to set up, they only require a WIFI access point and a USB power supply. Once connected, air pollution levels are reported instantaneously and in real-time on our maps

About the Air Quality and Pollution Measurement:

About the Air Quality Levels

AQIAir Pollution LevelHealth ImplicationsCautionary Statement (for PM2.5)
0 - 50 Good Air quality is considered satisfactory, and air pollution poses little or no risk None
51 -100 Moderate Air quality is acceptable; however, for some pollutants there may be a moderate health concern for a very small number of people who are unusually sensitive to air pollution. Active children and adults, and people with respiratory disease, such as asthma, should limit prolonged outdoor exertion.
101-150 Unhealthy for Sensitive Groups Members of sensitive groups may experience health effects. The general public is not likely to be affected. Active children and adults, and people with respiratory disease, such as asthma, should limit prolonged outdoor exertion.
151-200 Unhealthy Everyone may begin to experience health effects; members of sensitive groups may experience more serious health effects Active children and adults, and people with respiratory disease, such as asthma, should avoid prolonged outdoor exertion; everyone else, especially children, should limit prolonged outdoor exertion
201-300 Very Unhealthy Health warnings of emergency conditions. The entire population is more likely to be affected. Active children and adults, and people with respiratory disease, such as asthma, should avoid all outdoor exertion; everyone else, especially children, should limit outdoor exertion.
300+ Hazardous Health alert: everyone may experience more serious health effects Everyone should avoid all outdoor exertion

To know more about Air Quality and Pollution, check the wikipedia Air Quality topic or the airnow guide to Air Quality and Your Health.

For very useful health advices of Beijing Doctor Richard Saint Cyr MD, check www.myhealthbeijing.com blog.


Usage Notice: All the Air Quality data are unvalidated at the time of publication, and due to quality assurance these data may be amended, without notice, at any time. The World Air Quality Index project has exercised all reasonable skill and care in compiling the contents of this information and under no circumstances will the World Air Quality Index project team or its agents be liable in contract, tort or otherwise for any loss, injury or damage arising directly or indirectly from the supply of this data.



Settings


Language Settings:


Temperature unit:
Celcius