New Version of the PurpleAir API on July 18th

Dear PurpleAir API Users,

We are excited to let you know that we will be releasing a new version of the PurpleAir API on July 18, 2022. This new API provides access to historic data that is now hosted on Google’s BigQuery. It is important to note this update provides no functional changes to our current API at, but rather adds new history endpoints.

What updates will the new version have?
We added features that provide access to historical data. You’ll find documentation within the API site explaining how to use these new endpoints.

Before the 18th, you may test them here: This preview URL will go away, so please be sure only to use it until the main URL is updated.

What do you need to do?
If you use ThingSpeak for historic data, you will need to migrate your code to use the PurpleAir API instead. The ThingSpeak service will stop working in or around August 2022. We encourage you to get familiar with and switch to the new API’s history endpoints as soon as possible before ThingSpeak is no longer available.

Rollback capability.
If you experience any issues with the new version, for a limited time, the previous version of the API will be available at

Look out for future announcements.
We are currently updating our API Terms of Service and Attribution guidelines. Please look out for an announcement in the coming weeks.

Aug 18, 2022 Update: Access to the historical API has been restricted. You can learn more at the following article: Historical API Endpoints are now Restricted.


My colleagues and I used the old API site to download more than 14,000 PurpleAir sites in Washington, Oregon, and California covering the years 2017-2021. (77 million hourly outdoor values and 20 million hourly indoor values). We compared them to the regulatory monitors in the three states to get a calibration factor. We found both the Plantower proprietary algorithms (CF_1 and CF_ATM) to be useless, reading zero an unacceptably high fraction of the time and also overestimating the actual PM2.5 by 40-90%. Instead we created our own algorithm (called ALT-CF3) available as a “conversion factor” on the PurpleAir map page and “PM2.5 alt” at the API site.

An important finding was that the PA-I and PA-II monitors had identical calibration factors, despite having different sensor models.

That’s interesting data, thanks Lance. So if I understand that correctly and want to use the CF 3.4 values that you propose, is it correct that I would simply need to multiply the “pm2.5_alt” value from the PurpleAir API by a factor of 1.12 to get this CF 3.4 number? Thanks!

I’ll certainly keep an eye on the differences versus nearby official records.


You are correct.

1 Like

Will this document Using PurpleAir Data - Google Docs
and the FAQS be updated also?

Good question. All of the relevant data from this document can now be found in the community forums at the following links (all that is excluded is how to use ThingSpeak and the JSON URLs).

The document will likely stay. We will update it so that links like these will be mentioned near the top or throughout.

2 questions:

  1. Lat, Long, and Altitude are all null when I pull them using the new API. Has anyone else has this issue? Will these fields be added soon?
  2. Do you have plans to extend the timeframes for pulling historical data? It would be helpful if I could pull at least 4 days worth of real-time/2-min data.


I second this: is it possible to retrieve more than 3 days worth of historical data with the API? Or do we just have to manually download it ourselves?

The number of days of data you can retrieve at a time depends on the average you use:

Real-time: 2 days
10 Minute : 3 days
30 minute: 7 days
1 hr: 14 days
6hr: 90 days
1 day: 1 year

I thought this was posted but am having trouble recalling where. If one was to loop, a call for real-time data, do you know if there is a limit?
Such as 26 PurpleAir sensors, looped to call real-time data for a month period?

Hi, I’m using the Python example mentioned at (Making API Calls in Other Languages) to download 1440 minutes-averaged historical data over the CONUS in 2021. I’m downloading historical data from all stations one by one in a for loop.
This is the URL I’m using in function “getSensorData” to download one station:
I ran the code and it used to download data, however, it’s now unable to get data and I get error “JSONDecodeError: Expecting value”.
Can anyone help me please?

After the function, I use below codes to retrieve data from JSON.
D = getSensorData(sensor_index) # sensor_index is 131075 here
One_Sensor_Info = D.json()

The error raises in line “One_Sensor_Info = D.json()”

Thanks in advance,

Hi @masoud_ghahremanloo, try removing the “sensor_index” field from your request, as that is not a valid field. It will return by default.

Dear Ethan, Thanks for the prompt response. There was a mistake in typing my question and I typed it again. I appreciate it if you can check it again.

No worries, thank you for that correction. I believe the error occurs due to the time you are trying to collect data. You are most likely receiving this status 400 error message, “10 minute average history maximum time span is three (3) days.” To display error messages, I believe you can print “D.text” to the terminal.

​If you go up a few comments, you can see one that includes the date ranges you can collect data for.

Dear Ethan. Thanks for checking.
I’m averaging in 1440 minutes (one day) in 2021 and the I think I can collect data up to 1 year this way.
I think the problem is with the line
One_Sensor_Info = D.json()
in the code.

Dear Ethan,
When I use “report = response.text”, I can see data (copied below) in the report. However the JSON file for the request is not working when I use “One_Sensor_Info = response.json()” or “One_Sensor_Info = json.loads(response.text)” and I get the error “JSONDecodeError: Expecting ‘,’ delimiter”.
I also realized that when I use “request” to pull data from PurpleAir servers, sometimes I get PM2.5 concentrations for that station, and sometimes not (for the same station). It seems that servers sometimes reject to send PM2.5 concentrations to users and data is empty.
Your help is really appreciated.

my_api_read_key = ***
my_url = “
my_headers = {‘X-API-Key’:my_api_read_key}
response = requests.get(my_url, headers=my_headers)

Report when using “response.text”:
“api_version” : “V1.0.11-0.0.31”,
“time_stamp” : 1659125099,
“sensor_index” : 131075,
“start_timestamp” : 1609459200,
“end_timestamp” : 1640995199,
“average” : 1440,
“fields” : [“time_stamp”,“pm2.5_atm”],
“data” : [

Is the Thingspeak historical endpoint deactivated already?

As I know, it’s been deactivated.

If true it’s unfortunate for me that they didn’t provide more time when both services would be available. A number of programs I’ve written will take time to rewrite not only due to the API change but the data is in one file now vs four.

No, the ThingSpeak API has not been deactivated. We do not have an exact date of when it will end, but it is expected to be sometime this month (August).