API call using Java

Two years ago I wrote a simple Java app for the Android OS. It does a sensor query using JsonObjectRequest. I’ve tried to modify the app to deal with the new API, but the Request
returns Response 403 – Unauthorized. I think I am not passing the API key correctly. I’d appreciate any help or advice on this.

@Bruno22 - which endpoint are you using on the new API, and are you getting a 403 Forbidden or a 401 Unauthorized? A 403 Forbidden error typically means that your request and authentication was well-formed, but the server isn’t allowing access to the resource. I recently started getting a 403 on the “Get Sensor History” endpoint on a job that previously worked, even though my API key still works fine on other endpoints like “Get Sensor Data”. I’ve reached out directly to PurpleAir on that but haven’t heard back yet.

1 Like

I am reading https://api.purpleair.com/v1/sensors/. I get a 403 response.
It’s been a few weeks since I encountered this problem, so I don’t think it’s a recent change on PurpleAir’s end. Thanks …

Thanks @Bruno22 - could you provide any additional detail on your request, especially the headers and query parameters you’re sending? I just tried omitting the “X-API-Key” header and got a 403 back, so maybe that’s part of the issue.

(I think the API should instead return a 401 if an API key isn’t provided at all, but that’s another matter)

Access to the historical API endpoints have recently been restricted. More information is available here: Historical API Endpoints are now Restricted.

Thanks @r1yk . I have pasted my code below, with my sensor ID and API key masked.
You will notice that I create a JsonObject named ‘obj’ that contains the API key, which I pass to the JsonObjectRequest. This is probably where the problem lies. With the old API, I set obj to NULL and this code worked fine.

public class QueryPurpleAir {
private String url = “https://api.purpleair.com/v1/sensors/<sensor_index>”; //

public Map getObject(final AsyncResponse callBack) throws JSONException {
    final Map M = new HashMap();
    JSONObject obj = new JSONObject();
    obj.put("X-API-KEY", "<API read key>");

    JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
            new Response.Listener<JSONObject>() {
                public void onResponse(JSONObject response) {
                    try {

                        // sensor_index, PM2.5, temperature, humidity, pressure.  sensor_index is longs, others are strings
                        M.put("ID", response.getJSONArray("sensor").getJSONObject(0).getString("sensor_index"));
                        M.put("PM2_5Value", response.getJSONArray("sensor").getJSONObject(0).getString("pm2.5"));
                        M.put("temp_f", response.getJSONArray("sensor").getJSONObject(0).getString("temperature"));
                        M.put("humidity", response.getJSONArray("sensor").getJSONObject(0).getString("humidity"));
                        M.put("pressure", response.getJSONArray("sensor").getJSONObject(0).getString("pressure"));

                    catch (JSONException e) {

                    if (null != callBack) callBack.processFinished(M);

            }, new Response.ErrorListener() {
        public void onErrorResponse(VolleyError error) {

    return M;



Thanks @Bruno22 - my suspicion is that your API key is getting placed in the body of your request instead of the headers. GET requests typically don’t include a body, so you would want to use null instead for the third argument when creating jsonObjectRequest. From there, it looks like custom headers are typically added to the JsonObjectRequest by overriding its “getHeaders” method. Here’s an example of adding headers for authentication that may be helpful:
Android: Volley set headers (simolation.com)

Additionally, you may want to take a peek at the full response that PurpleAir is sending along with its 403 status code. I’ve found that the API tends to return additional detail in the response body. If the API key isn’t making it into the headers, you should see a JSON response something like this:

	"api_version": "V1.0.11-0.0.34",
	"time_stamp": 1660135101,
	"error": "ApiKeyMissingError",
	"description": "No API key was found in the request."

Or maybe there’s a different error entirely! Hopefully it’s helpful in any case. Good luck!

Thankyou @r1yk , the example was extremely helpful. getHeaders is the method to add your API key. The API is returning a valid response to me, now I just have to parse it …

1 Like