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
Still can't access Ads API
GET stats/accounts/:account_id giving me 401 Unauthorized Access
Unable to use start and end time while querying the API
Twitter Ada Api access error for Stats related endpoint
How to get Ad campaign performance data through api
Getting UNAUTHORIZED_ACCESS with Ads API
#9

I am having the same error. I am using the oauth2 token for authentication. Does that method not work here?

GET /4/stats/accounts/?entity=CAMPAIGN&entity_ids=bh64g&start_time=2018-09-29&end_time=2018-10-02&granularity=TOTAL&placement=ALL_ON_TWITTER&metric_groups=ENGAGEMENT HTTP/1.1
Host: ads-api.twitter.com
User-Agent: app name goes here
Accept-Encoding: gzip
Authorization: Bearer oauth2 token goes here


#10

You should we using oauth1 though. AFAIK twitter never switched to OAuth2.


#11

It works when i use the same credentials based generated Oauth2 token to pull out tweet data. I guess the Ads API uses Oauth 1.0 while other APIs use Oauth 2.0?


#12

I would recommend using twurl with the -t flag, change other setting to avoid this and compare the headers between twurl and your own use other Infotinfoapplication to ensure that the authentication is correct


#13

Thats may be the case. I always used OAuth 1 for the Marketing API but I didn’t have to pull tweets in.


#14

Well I am using an automated API call system so what would i put in the oauth_callback then. I cannot have a human user do a browser based authentication. I am having difficulty in understanding this authentication system


#15

So to get an oauth signature, i need an oauth token and to get an oauth token, i need an oauth signature?


#16

So to get an oauth signature, i need an oauth token and to get an oauth token, i need an oauth signature? Is there any documentation available on how to create an oauth signature for getting an oauth token?


#17

You need to get an oauth token doing the oauth dance through registering an twitter app. I would suggest you use a library for that since you have sign each request. I use twitter4j-ads 1.1 for creating the headers and the I just use RestTemplate (java) for every HTTP call to the API.

This is pretty much what I do for getting the right headers.

HttpRequest request = new HttpRequest(RequestMethod.valueOf([method.name](http://method.name)()), path, null, null,
    null, null);
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION,
    getAdsInstance(settings).getAuthorization().getAuthorizationHeader(request));
headers.set(HttpHeaders.CONTENT_TYPE, "application/json");

And this is how I create TwitterAdsFactory (NetworkAccountSettings is just an internal class to manager placeholders).

private TwitterAdsFactory create(NetworkAccountSettings settings) {
  ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
  configurationBuilder
      .setOAuthConsumerKey(settings.getConfigurationValueAsString("consumerKey"))
      .setOAuthConsumerSecret(settings.getConfigurationValueAsString("consumerSecret"))
      .setOAuthAccessToken(settings.getAccessToken().getValue())
      .setOAuthAccessTokenSecret(settings.getConfigurationValueAsString("accessTokenSecret"))
      .setHttpRetryCount(0).setHttpConnectionTimeout(5000)
      .setDebugEnabled(true).setPrettyDebugEnabled(true);
  return new TwitterAdsFactory(configurationBuilder.build());
}


#18

Thank you for replying Matias!

I have a registered app which gives me 4 values - Consumer API key, Consumer API secret, Access token & Access token secret.

To create the oauth signature i am being asked for a oauth token and to create an oauth token i am being asked for a secret.

What am i missing?


#19

No. All the properties you saw in the code snippet are provided to you when you register an application in the apps.twitter.com site. Try registering an app there and you’ll get them.


#20

I do have a registered app. It is from the app that i am getting the Consumer API key, Consumer API secret, Access token & Access token secret.

However, to be able to use the API, i need the oauth token and the oauth signature. The oauth token which as i understand is different from the Access Token and requires the oauth signature to successfully generate.

The issue which i am having is that in the process of generating the oauth signature for generating the oauth token, the process is asking me for an oauth token which as you see i have still not generated as that is what i am trying to generate.

Hope that explains my problem a little better.

Thank you for the help Matias