Breaking Tweet Down to App Card and Text

creative
campaign-management

#1

I want to break Tweet ID down to App Card and Tweet Text. I can pull in Text by joining at Tweet ID, but don’t currently see a way to join the App Card to the Tweet ID or the Promoted Tweet ID.

Can you please let us know how this can be done?


#2

Hi @fjcatd33! I don’t understand your question. You first speak of “breaking down”, then of “joining” - what exactly do you want to achieve? Do you want to extract the app card details from a Tweet ID? If so, you can get the card id from the card’s link in the tweet’s urls’ entities, and then make a GET request specific to the card type and get the card details.


#3

We want to reference the app card from promoted tweet (or tweet). Which ID do we use? I think what you mentioned above will work, could you share an example for tweet’s url entities? What is the call we should make to get Tweet URL?


#4

Ok @fjcatd33. Suppose you have the ad id, you first make the following call to get the tweet id associated with this ad:

GET https://ads-api.twitter.com/0/accounts/:account_id/promoted_tweets/:ad_id
(this endpoint does not appear in the docs, but it works)

And for that tweet you get a property called tweet_id, which is the id of the tweet. Then, you make a request to get the tweet details, to

GET https://api.twitter.com/1.1/statuses/show.json with the parameter id = tweet_id above.

Then (this is in PHP):

$tweetBody = $tweet->text;

if (!empty($tweet->entities) && !empty($tweet->entities->urls)) {
	foreach ($tweet->entities->urls as $i => $url) {
		if (!empty($url->expanded_url) && strpos($url->expanded_url, '://cards.twitter.com/cards/')) {
			// this ad has a card
			$tweetBody = trim(str_replace($url->url, '', $tweetBody));
			$idTwCard = str_replace('https://cards.twitter.com/cards/' . $idTwAccount . '/', '', $url->expanded_url);
			break;
		}
	}
}

And now you have the tweet text in $tweetBody and the card id in $idTwCard. Now, if you want to get the card details you need to know what the card type is, to be able to do something like GET accounts/:account_id/cards/app_download/:card_id (the card type = that app_download in the url). For this, I just get all cards in an array and index that array by card id, so I don’t need the card type anymore (I use this approach because I need to get all the cards anyway):

function getCardsByType ($type) {
	if (empty($type)) {
		return array();
	}

	if (($type !== 'website') && ($type !== 'app_download') && ($type !== 'image_app_download') && ($type !== 'video_app_download') && ($type !== 'lead_gen')) {
		return array();
	}

	global $tw, $twAccount;

	$allCards = array();
	$cards = $tw->get('accounts/' . $twAccount['id_tw_account'] . '/cards/' . $type);

	while (!empty($cards) && empty($cards->error) && !empty($cards->data)) {
		$allCards = array_merge($allCards, $cards->data);

		if (empty($cards->next_cursor)) {
			break;
		}

		$cards = $tw->get('accounts/' . $twAccount['id_tw_account'] . '/cards/' . $type, array('cursor' => $cards->next_cursor));
	}

	// index the final array by card ids
	foreach ($allCards as $i => $item) {
		$allCards[$item->id] = $item;
		$allCards[$item->id]->card_type = $type; // overwrite the 'card_type' property
		unset($allCards[$i]);
	}

	return $allCards;
}

function getAllCards () {
	return array_merge(getCardsByType('website'), getCardsByType('app_download'), getCardsByType('image_app_download'), getCardsByType('video_app_download'), getCardsByType('lead_gen'));
}

Then, you continue the first block of code with:

$cards = getAllCards();

if (!empty($cards[$idTwCard])) {
	$card = $cards[$idTwCard];

	// $card is the card object, which holds all the details about that card,
	// and you can use it here to get what you want.
}

And there you have it! :slight_smile: I hope this answers your question.


#5

@fjcatd33 as @majoritasdev showed you, the card ID can be retrieved from the expanded_url in the tweet which you can fetch easily by ID from the public API. This will work for all card types.

However, this won’t work for promoted videos. There’s currently no way to get the video ID from the tweet containing the promoted video.


#6

Perfect, thanks!


#7

@brandonmblack: I beg to differ. If among the url entities of the tweet there is an expanded_url containing the string “://amp.twimg.com/v/” (which happens for VIDEO_VIEWS campaigns), that means we have a video attached to that tweet. Then, using that expanded_url, one can load its html and get the video id from the meta with the name twitter:amplify:content_id, and the thumbnail url from the meta with the name twitter:image:src. Futhermore, if one wants to also get the CTA (call to action), one can load the vmap file at the url present in the meta twitter:amplify:vmap, and find there either cta_watch_now or cta_open_url tag with the desired value.


Permissions issues while getting Twitter Ads videos using the API
#8

@majoritasdev what I said was actually correct. For videos, the ID in the expanded_url field of the tweet is not the video ID (just want to be clear on that for anyone else reading this). It’s the identifier for the amplify media object containing the video. If you’re currently relying on this, your implementation is not working as you expect.

Additionally, there will be a change to video IDs soon (still a string, but different format) and it will differ entirely from what you see in the amp.twimg.com URLs or currently in the associated vmap. The information you see in the vmap is not intended to be consumed externally (its a private format, specific to the current generation of Twitter hosted media) and there’s no guarantee it won’t change or is what you think it is.

In fact, I can guarantee it will change soon unfortunately.


#9

Hi @brandonmblack! Thank you for the heads up on that change!

Regarding the video ID, I never said that it is part of the expanded_url, but that by loading the expanded_url's html one can find out the video ID by looking at the meta tag with the name twitter:amplify:content_id - that is where you can find the video ID; I checked it and it really is the video ID.

I am left with two questions, please:

1) If that vmap is not officially for external consumption, is Twitter planning to provide a way for developers to find the video ID associated with a tweet?

2) So you are saying that the format of the vmap will change (the tag names, the layout etc.) ? Apart from the video ID format that I understood that will change soon.

Thank you!


#10

Hi @brandonmblack,

Sorry to come on an old thread, but as we review, one critical question comes up:

what value to put in as the “Ad ID” when making the GET https://ads-api.twitter.com/0/accounts/:account_id/promoted_tweets/:ad_id call?

We assumed promoted_tweet_id, and when call is made plugging in the account_id and an associated promoted_tweet_id from the account, the call itself can be made, but we receive error message on return that the account id and ad_id don’t match. Thus, my guess is another ID is the ad_id. We tried tweet_id, but this is incorrect as expected, the call cannot be made.

Please shoot me an email if we can troubleshoot better by sharing the precise call and precise error message, including exact account id / promoted tweet id.

Thanks!


#11

For the GET promoted_tweets endpoint, the only inputs for IDs that are accepted are line_item ids. If you want to call for a specific promoted tweet, you can call with the promoted_tweet_id. Note that the promoted_tweet_id is an alpha-numeric identifier that is based on the unique combination of the line_item_id and the tweet_id. That is auto-generated and not something you can derive on your own.

Sample of the GET promoted_tweets query:

$ twurl -H ads-api.twitter.com "/1/accounts/abc1/promoted_tweets?count=3" |jq . 
{
  "request": {
    "params": {
      "count": 3,
      "account_id": "abc1"
    }
  },
  "data": [
    {
      "line_item_id": "vrv9",
      "display_properties": [],
      "id": "22cs7",
      "paused": false,
      "created_at": "2013-10-01T22:37:29Z",
      "updated_at": "2014-10-24T19:32:30Z",
      "approval_status": "ACCEPTED",
      "tweet_id": "372218875217059840",
      "deleted": false
    },
    {
      "line_item_id": "vxdm",
      "display_properties": [],
      "id": "22vht",
      "paused": false,
      "created_at": "2013-10-04T23:49:09Z",
      "updated_at": "2014-10-24T19:32:30Z",
      "approval_status": "ACCEPTED",
      "tweet_id": "382871353973411842",
      "deleted": false
    },
    {
      "line_item_id": "vxdw",
      "display_properties": [],
      "id": "22vi9",
      "paused": false,
      "created_at": "2013-10-04T23:55:47Z",
      "updated_at": "2014-10-24T19:32:30Z",
      "approval_status": "ACCEPTED",
      "tweet_id": "382871353973411842",
      "deleted": false
    }
  ],
  "data_type": "promoted_tweet",
  "total_count": 53,
  "next_cursor": "8rubbk9a8"
}

Sample of a single GET promoted_tweet query:

$ twurl -H ads-api.twitter.com "/1/accounts/abc1/promoted_tweets/k18k5" |jq .
{
  "request": {
    "params": {
      "promoted_tweet_id": "k18k5",
      "account_id": "abc1"
    }
  },
  "data_type": "promoted_tweet",
  "data": {
    "line_item_id": "28gz1",
    "display_properties": [],
    "id": "k18k5",
    "paused": false,
    "created_at": "2016-01-22T02:03:57Z",
    "updated_at": "2016-01-22T15:38:40Z",
    "approval_status": "ACCEPTED",
    "tweet_id": "660187717414711296",
    "deleted": false
  }
}

If you’re getting an error on the latter call, then the promoted_tweet_id is either deleted and you should add the with_deleted=true param to your query, or it does not exist on that ads account.


#12