Receiving 401 Unauthorized for ads-api.twitter.com/2/stats/accounts/


#1

I am a developer trying to get a response back with the following GET call from the Twitter Ads API using Postman OAuth 1.0 security. I’ve been able to use almost every endpoint without a problem (creating campaigns, tailored audiences, targeting specs, etc).

Using the same mechanism with Stats API fails with a 401 status code. This is the URL I’m using

GET https://ads-api.twitter.com/2/stats/accounts/MY_ACCOUNT_ID?entity_ids=15184838&entity=CAMPAIGN&start_time=2017-07-15T18:00:00Z&end_time=2017-07-18T18:00:00Z&granularity=TOTAL&metric_groups=ENGAGEMENT,BILLING&placement=ALL_ON_TWITTER

And this is the sample curl I got from Postman (I’ve removed sensitive data).

curl -X GET \ ‘https://ads-api.twitter.com/2/stats/accounts/MY_ACCOUNT_ID?entity_ids=15184838&entity=CAMPAIGN&start_time=2017-07-15T18%3A00%3A00Z&end_time=2017-07-18T18%3A00%3A00Z&granularity=TOTAL&metric_groups=ENGAGEMENT%2CBILLING&placement=ALL_ON_TWITTER
-H ‘authorization: OAuth oauth_consumer_key=“MY_CONSUMER_KEY”,oauth_token=“MY_OAUTH_TOKEN”,oauth_signature_method=“HMAC-SHA1”,oauth_timestamp=“1500586577”,oauth_nonce=“XxXECwOGOkO”,oauth_version=“1.0”,oauth_signature=“OAUTH_SIGNATURE”’

I also tested it with twurl and using twurl I get a successful response which tells there is nothing wrong with the endpoint but on the way I’m using it. I’m completely out of ideas, any pointers on what may I be doing wrong? The part that throws me off is that this same approach works for every other endpoint in the API.

Any help or guidance would be appreciated.


#2

Hey @matias_suarez

I would recommend using twurl with the -t flag, and compare the headers between twurl and your own application to ensure that the authentication is correct. That being said, we aren’t able to help with specific tools or implementations, however I would recommend using one of our SDKs instead.

Thanks!


#3

Hi, thanks for the reply. I tried that as well, I compares every header and
they are all the same except the obvious ones on the oauth token (nonce,
timestamp, signature, etc).


#4

Hey @imit8me, I finally figure it out. The problem was not related to authentication at all rather on how the Ads API expects the start_time & end_time to be formatted. If I do the same call sending the dat as 2017-07-15 instead of 2017-07-15T18:00:00Z the api works.

This is really really frustrating and misleading. Shouldn’t the API return a 400 instead of 401 in this scenario?


GET stats/accounts/:account_id giving me 401 Unauthorized Access
#5

Hey @matias_suarez

Thank you for posting your solution. You’re right in that the correct error code returned in the response should be 400 BAD REQUEST as opposed to a 401 UNAUTHORIZED error. We’ll dig into the issue and reach out in case there’s any updates.

Thanks!


#6

Hey @matias_suarez

Can you let us know the exact request and response bodies for both, the successful and un-successful API calls?

Thanks!


#7

Sure. This is the successful request

curl -X GET _
_ ‘https://ads-api.twitter.com/2/stats/accounts/18ce543l4j8?entity_ids=93b4x&entity=CAMPAIGN&start_time=2017-07-19&end_time=2017-07-25&granularity=DAY&metric_groups=BILLING%2CWEB_CONVERSION%2CENGAGEMENT&placement=ALL_ON_TWITTER’ _
_ -H ‘authorization: OAuth oauth_consumer_key=“uuf0HsTN6VbQ1gzN0wCURtoxi”,oauth_token=“17517713-IbUthR8LAGHU4INd8QPTqfXT1fB9SG3T8yZgC0vPH”,oauth_signature_method=“HMAC-SHA1”,oauth_timestamp=“1500918605”,oauth_nonce=“rxbkQAViFdn”,oauth_version=“1.0”,oauth_signature=“o1KLyRIayys3ZDJTjtuV1ryx1K0%3D”’

And this is the request that fails for the same data:

curl -X GET
https://ads-api.twitter.com/2/stats/accounts/18ce543l4j8?entity_ids=93b4x&entity=CAMPAIGN&start_time=2017-07-19T07%3A00%3A00Z&end_time=2017-07-25T07%3A00%3A00Z&granularity=DAY&metric_groups=BILLING%2CWEB_CONVERSION%2CENGAGEMENT&placement=ALL_ON_TWITTER
-H ‘authorization: OAuth oauth_consumer_key=“uuf0HsTN6VbQ1gzN0wCURtoxi”,oauth_token=“17517713-IbUthR8LAGHU4INd8QPTqfXT1fB9SG3T8yZgC0vPH”,oauth_signature_method=“HMAC-SHA1”,oauth_timestamp=“1500918687”,oauth_nonce=“brTQ6dF7Ra4”,oauth_version=“1.0”,oauth_signature=“b517x7bbr1o8gCZ9C%2FDqFsRkxtg%3D”’

As you can see, the only difference (apart from the token temporal values) is the date format.


#8

As our GET stats/accounts/:account_id endpoint describes, we expect the start_time and end_time to be expressed in ISO8601. Example: 2017-05-19T07:00:00Z.

Here is an example using twurl:

$ twurl -H ads-api.twitter.com  "/2/stats/accounts/18ce54d4x5t?entity=LINE_ITEM&entity_ids=8u94t&start_time=2017-05-19T07:00:00Z&end_time=2017-05-26T07:00:00Z&granularity=TOTAL&placement=ALL_ON_TWITTER&metric_groups=ENGAGEMENT"
{
  "data_type": "stats",
  "time_series_length": 1,
  "data": [
    {
      "id": "8u94t",
      "id_data": [
        {
          "segment": null,
          "metrics": {
            "impressions": [
              1233
            ],
            "tweets_send": null,
            "qualified_impressions": null,
            "follows": null,
            "app_clicks": null,
            "retweets": null,
            "likes": [
              1
            ],
            "engagements": [
              58
            ],
            "clicks": [
              58
            ],
            "card_engagements": null,
            "poll_card_vote": null,
            "replies": null,
            "url_clicks": null,
            "carousel_swipes": null
          }
        }
      ]
    }
  ],
  "request": {
    "params": {
      "start_time": "2017-05-19T07:00:00Z",
      "segmentation_type": null,
      "entity_ids": [
        "8u94t"
      ],
      "end_time": "2017-05-26T07:00:00Z",
      "country": null,
      "placement": "ALL_ON_TWITTER",
      "granularity": "TOTAL",
      "entity": "LINE_ITEM",
      "platform": null,
      "metric_groups": [
        "ENGAGEMENT"
      ]
    }
  }
}

Note: use the -t flag to get the full trace of your request.

The same request using cURL:

$ curl -X GET "https://ads-api.twitter.com/2/stats/accounts/18ce54d4x5t?entity=LINE_ITEM&entity_ids=8u94t&start_time=2017-05-19T07%3A00%3A00Z&end_time=2017-05-26T07%3A00%3A00Z&granularity=TOTAL&placement=ALL_ON_TWITTER&metric_groups=ENGAGEMENT" -H 'Authorization: OAuth oauth_consumer_key="{consumer_key}", oauth_nonce="CcmsZAEEUDLhH1MkEpOzlpMfIBwOi5UrpcM9j5Q4", oauth_signature="{signature}", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1501127965", oauth_token="{token}", oauth_version="1.0"'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   649  100   649    0     0   2364      0 --:--:-- --:--:-- --:--:--  2368
{
  "data_type": "stats",
  "time_series_length": 1,
  "data": [
    {
      "id": "8u94t",
      "id_data": [
        {
          "segment": null,
          "metrics": {
            "impressions": [
              1233
            ],
            "tweets_send": null,
            "qualified_impressions": null,
            "follows": null,
            "app_clicks": null,
            "retweets": null,
            "likes": [
              1
            ],
            "engagements": [
              58
            ],
            "clicks": [
              58
            ],
            "card_engagements": null,
            "poll_card_vote": null,
            "replies": null,
            "url_clicks": null,
            "carousel_swipes": null
          }
        }
      ]
    }
  ],
  "request": {
    "params": {
      "start_time": "2017-05-19T07:00:00Z",
      "segmentation_type": null,
      "entity_ids": [
        "8u94t"
      ],
      "end_time": "2017-05-26T07:00:00Z",
      "country": null,
      "placement": "ALL_ON_TWITTER",
      "granularity": "TOTAL",
      "entity": "LINE_ITEM",
      "platform": null,
      "metric_groups": [
        "ENGAGEMENT"
      ]
    }
  }
}

Given that this works when you specify the start_time and end_time values without the timestamp, the likely issue is with the oauth_signature. Remember:

Ensure that you’re encoding reserved characters appropriately within URLs and POST bodies before preparing OAuth signature base strings.

Source

Otherwise, you’ll see:

{
  "errors": [
    {
      "code": "UNAUTHORIZED_ACCESS",
      "message": "This request is not properly authenticated"
    }
  ],
  "request": {
    "params": {}
  }
}

This means that the 401 HTTP status code is correct.


+1 @imit8me for helping debug :100:


Could not access Ads API with UNAUTHORIZED_ACCESS error
Guidance for UNAUTHORIZED_ACCESS issues
GET stats/accounts/:account_id giving me 401 Unauthorized Access
Unable to use start and end time while querying the API
Still can't access Ads API
Twitter Ada Api access error for Stats related endpoint