When trying to post a tweet with media, the Twitter API returns an error code.

  • API version: 1.1
  • API method: POST statuses/update_with_media
  • Language used to access API: JavaScript

JavaScript has problems working with raw binary data within strings,
so the media file to send along with the tweet has been encoded in base64.

According to /Net/dxcern/userd/tbl/hypertext/WWW/Protocols/rfc1341/5_Content-Transfer-Encoding.html, the corresponding header field has been set in the MIME request.

Here is a complete dump of the sent request:

POST /1.1/statuses/update_with_media.json HTTP/1.1
Host: api.twitter.com
Connection: keep-alive
Content-Length: 64682
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36
Origin: http://local
Authorization: OAuth oauth_consumer_key="PNVfyHvoowa9h0Tt4fF3VQ", oauth_nonce="tgWeGMeH", oauth_signature="3%2BDGYLibvHRQ15q4jtqgj%2F%2FNzIw%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1370095947", oauth_token="14648265-rPn8EJwfBG1FAzGmYUd1YxJB18LJwdEpzlNvEM8SZ", oauth_version="1.0"
Content-Type: multipart/form-data; boundary=--------------------sDUUB6pr
Accept: */*
Referer: http://local/dev/codebird-js/test-multipart.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
Cookie: guest_id=v1%3A136179970190134552; __qca=P0-1800224371-1362343699183; ELOQUA=GUID=D7F1BCFF76114003909680A8A303706C; ELQSTATUS=OK; external_referer=vC8TI7P7q9VULaQby8GjGVMlOM6mdzuhVU%2ByWiXFBFM%3D%7C0; twll="l=1370087765"; remember_checked=0; __utma=43838368.2113840127.1361799708.1370087750.1370094443.51; __utmb=43838368.1.10.1370094443; __utmz=43838368.1370087750.50.16.utmcsr=api.twitter.com|utmccn=(referral)|utmcmd=referral|utmcct=/oauth/authorize; __utmv=43838368.lang%3A%20de; lang=de; twid=u%3D14648265%7ChLcHOslbn6Y9mryuGLojr4XgDw8%3D; _twitter_sess=BAh7CDoMY3NyZl9pZCIlMWEzY2U4NDA3MDhhNjJlZDFmODQ4ZjI3ODk4MjY4%250AZDk6B2lkIiU5ZDg1MTE0ZmFjMzg4NmM4NGM3ZDNiOWQxZDc4ZGQyODoPY3Jl%250AYXRlZF9hdGwrCKQvBgA%252FAQ%253D%253D--2f933caa70d01d991b43c517503f9668af293827

----------------------sDUUB6pr
Content-Disposition: form-data; name="status"

Somehow MySQL has updated their logo. But not like this:
----------------------sDUUB6pr
Content-Disposition: form-data; name="media[]"
Content-Transfer-Encoding: base64

/9j/4AAQSkZJRgABAQEBLAEsAAD/
--snip--
zmGP3rnSjiBdQywB6Ppch3Fs9xGrKkd7Tati4nmOABlB/6G/ooeeMttyV08477M3jDzHbvlY4ergU/1OMG3yMYe7XLN9amePM/5AKfDY/dwu+5V7XgpUvL/wDh/9k=
----------------------sDUUB6pr--

Here is a complete dump of the received response:

HTTP/1.1 403 Forbidden
cache-control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0
content-type: application/json; charset=utf-8
date: Sat, 01 Jun 2013 14:12:28 GMT
expires: Tue, 31 Mar 1981 05:00:00 GMT
last-modified: Sat, 01 Jun 2013 14:12:28 GMT
pragma: no-cache
server: tfe
set-cookie: dnt=; domain=.twitter.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT
status: 403 Forbidden
strict-transport-security: max-age=631138519
x-access-level: read-write-directmessages
x-frame-options: SAMEORIGIN
x-mediaratelimit-class: photos
x-mediaratelimit-limit: 100
x-mediaratelimit-remaining: 98
x-mediaratelimit-reset: 1370164313
x-mid: 86821e1aa81553fb0a14208d3e582d639fb183fa
x-runtime: 0.16406
x-transaction: a777c36ac803515f
x-transaction-mask: a6183ffa5f8ca943ff1b53b5644ef114b76befca
Content-Length: 59

{"errors":[{"code":189,"message":"Error creating status"}]}

How can this issue be fixed?

Note: When sending the same request from PHP (and without base64 encoding), it succeeds. Take a look:

Successful request with PHP:

POST https://api.twitter.com/1.1/statuses/update_with_media.json HTTP/1.1
Host: api.twitter.com
Accept: */*
Authorization: OAuth oauth_consumer_key="PNVfyHvoowa9h0Tt4fF3VQ", oauth_nonce="e434108d", oauth_signature="OFw720YH4ne%2BphnDma%2F%2BnoM9eyA%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1370094420", oauth_token="14648265-rPn8EJwfBG1FAzGmYUd1YxJB18LJwdEpzlNvEM8SZ", oauth_version="1.0"
Content-Length: 48586
Content-Type: multipart/form-data; boundary=----------------------------1fc9651dad39

------------------------------1fc9651dad39
Content-Disposition: form-data; name="status"

Somehow MySQL has updated their logo. But not like this:
------------------------------1fc9651dad39
Content-Disposition: form-data; name="media[]"

BINARY_IMAGE_DATA_HERE_DELETED_FOR_FORUM_THREAD
------------------------------1fc9651dad39--

Successful response:

HTTP/1.1 200 OK
cache-control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0
content-length: 2946
content-type: application/json; charset=utf-8
date: Sat, 01 Jun 2013 13:47:04 GMT
etag: "5f7bf1e6378dd51473a840722e2409a9"
expires: Tue, 31 Mar 1981 05:00:00 GMT
last-modified: Sat, 01 Jun 2013 13:47:03 GMT
pragma: no-cache
server: tfe
set-cookie: dnt=; domain=.twitter.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT
set-cookie: lang=de; path=/
set-cookie: lang=de; path=/
set-cookie: lang=de; path=/
set-cookie: lang=de; path=/
set-cookie: twid=u%3D14648265%7ChLcHOslbn6Y9mryuGLojr4XgDw8%3D; domain=.twitter.com; path=/; secure
set-cookie: _twitter_sess=BAh7CDoMY3NyZl9pZCIlZjM3MjllNTBmYjliZTYxZDhlN2NmNzRhZGY0ZDJh%250ANmU6B2lkIiU1ZWE4NzBlYjRhYmNkOGQxNDk1ZWFhZWUzYjc5MGQ5OToPY3Jl%250AYXRlZF9hdGwrCHXM%252Ff8%252BAQ%253D%253D--b5c590e38f120603503f0ac61065393ef9c50afe; domain=.twitter.com; path=/; HttpOnly
set-cookie: guest_id=v1%3A137009442277591021; Domain=.twitter.com; Path=/; Expires=Mon, 01-Jun-2015 13:47:04 UTC
status: 200 OK
strict-transport-security: max-age=631138519
x-access-level: read-write-directmessages
x-frame-options: SAMEORIGIN
x-mediaratelimit-class: photos
x-mediaratelimit-limit: 100
x-mediaratelimit-remaining: 98
x-mediaratelimit-reset: 1370164313
x-mid: 00414393be406ece7f0de904216581039c8790fe
x-runtime: 1.26835
x-transaction: 779ee46a92c25d37
x-transaction-mask: a6183ffa5f8ca943ff1b53b5644ef114b76befca

{"in_reply_to_status_id_str":null,"entities":{"user_mentions":[],"urls":[],"media":[{"media_url_https":"https:\/\/pbs.twimg.com\/media\/BLrcMjRCQAIi-_C.jpg","display_url":"pic.twitter.com\/9CQqwaR4M6","sizes":{"large":{"w":640,"h":427,"resize":"fit"},"small":{"w":340,"h":227,"resize":"fit"},"medium":{"w":600,"h":400,"resize":"fit"},"thumb":{"w":150,"h":150,"resize":"crop"}},"expanded_url":"http:\/\/twitter.com\/myx\/status\/340826829998931970\/photo\/1","id":340826830003126274,"id_str":"340826830003126274","indices":[57,79],"source_status_id":null,"type":"photo","url":"http:\/\/t.co\/9CQqwaR4M6","media_url":"http:\/\/pbs.twimg.com\/media\/BLrcMjRCQAIi-_C.jpg"}],"hashtags":[]},"place":null,"in_reply_to_user_id":null,"possibly_sensitive":false,"geo":null,"retweet_count":0,"in_reply_to_user_id_str":null,"source":"\u003Ca href=\"http:\/\/mynetx.net\/\" rel=\"nofollow\"\u003Emynetx\u003C\/a\u003E","coordinates":null,"retweeted":false,"id_str":"340826829998931970","id":340826829998931970,"user":{"profile_background_tile":false,"name":"J.M.","verified":false,"profile_image_url_https":"https:\/\/si0.twimg.com\/profile_images\/3005995399\/765485ad5721be61340ebbd0aa833564_normal.png","listed_count":56,"following":false,"profile_sidebar_fill_color":"B4D0EF","profile_image_url":"http:\/\/a0.twimg.com\/profile_images\/3005995399\/765485ad5721be61340ebbd0aa833564_normal.png","url":"http:\/\/t.co\/aHcqQLzDXq","followers_count":2335,"contributors_enabled":false,"id":14648265,"location":"Germany","profile_background_color":"DEEAF8","description":"Microsoft expert. Coder. #IEuseragents. Web & app developer. Localization moderator for Twitter in German. I am my own brand.","screen_name":"myx","default_profile_image":false,"profile_background_image_url":"http:\/\/a0.twimg.com\/profile_background_images\/662874303\/x8ctzxac0q9n8g3w516b.jpeg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/14648265\/1363160411","profile_link_color":"138CDD","utc_offset":3600,"time_zone":"Berlin","follow_request_sent":false,"favourites_count":164,"entities":{"description":{"urls":[]},"url":{"urls":[{"display_url":"mynetx.net","expanded_url":"http:\/\/mynetx.net\/","indices":[0,22],"url":"http:\/\/t.co\/aHcqQLzDXq"}]}},"id_str":"14648265","profile_use_background_image":true,"protected":false,"is_translator":true,"lang":"de","profile_text_color":"464646","profile_background_image_url_https":"https:\/\/si0.twimg.com\/profile_background_images\/662874303\/x8ctzxac0q9n8g3w516b.jpeg","notifications":false,"geo_enabled":true,"default_profile":false,"profile_sidebar_border_color":"FFFFFF","friends_count":87,"statuses_count":7489,"created_at":"Sun May 04 14:09:54 +0000 2008"},"in_reply_to_status_id":null,"created_at":"Sat Jun 01 13:47:04 +0000 2013","in_reply_to_screen_name":null,"truncated":false,"favorited":false,"contributors":null,"text":"Somehow MySQL has updated their logo. But not like this: http:\/\/t.co\/9CQqwaR4M6"}

Both requests use the same signing heuristic? In that they both don’t sign the request body? The only difference between the two is that the Javascript version base64-encodes the body?

Would you be able to test base64-encoding the PHP request to make sure that this is the only differentiation?

Modified codebird-php (!) to send a request with base64.
It looks like this:

POST /1.1/statuses/update_with_media.json HTTP/1.1
Host: api.twitter.com
Accept: /
Authorization: OAuth oauth_consumer_key=“PNVfyHvoowa9h0Tt4fF3VQ”, oauth_nonce=“e9931ead”, oauth_signature=“Q1tpSBHFvTl%2FCwOU3nsXSVxYEvU%3D”, oauth_signature_method=“HMAC-SHA1”, oauth_timestamp=“1371302972”, oauth_token=“14648265-rPn8EJwfBG1FAzGmYUd1YxJB18LJwdEpzlNvEM8SZ”, oauth_version=“1.0”
Content-Length: 1039
Content-Type: multipart/form-data; boundary=--------------------4076de35

----------------------4076de35
Content-Disposition: form-data; name=“status”

Test tweet (PHP version).
----------------------4076de35
Content-Disposition: form-data; name=“media
Content-Transfer-Encoding: base64

iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAB+0lEQVR42mP8//8/Ay0BEwONwagFoxZQDljI0PP8x7/Z93/e+PxXmpMpXp5dh4+ZgYHh0bd/clxYnMuINaMtfvRLgp3RVZwVU+rkuz+eRz+//wXVxcrEkKnEceXTX0dRlhoNTmKDaOvzXwHHv6x9+gtN/M9/hpjTX+GmMzAw/P7HMOnOj+ff//35x/Ds+z9iLfjPwPDt7//QE1/Sz319/RNh3PkPf+58+Yup/t7Xf9p8zFKcTMRa4CLGCrFm1v2fSjs+pJ/7uuvl7w+//yO7HRkUq3GEyrCREMk+kqy2IiyH3/xhYGD48uf/rPs/Z93/yczIwM3CiFU9Hw5xnD4ouvTt4Tf0AP37n+HTb+w+UOBmIs2CICm2R9/+EZlqGRkYzIVYSLMgRIYtUYGdSAsMBFgUuJhIy2iMDAwt2pysjAwLHv78RcgnOcrs5BQVHEyMG579Imi6Nh9zrBxZFgixMW624pXnwldYcTAzLjDhZmUit7AzE2K54c7fp8eF1QhWRobFptwmgiwkF3b//jMwMjJ8+P3/zPs/yx/9Wvr412+MgBJlZ1xsyuOOrbAibMHH3/87b32fce/nR2ypnpuFMVGevU6TQ5SdqKKeEVez5cuf/7te/j727s+9L/++/v3PzcyowM1kIcTiLs7Kz8pIfNnOONouGrVg1AIGAJ6gvN4J6V9GAAAAAElFTkSuQmCC
----------------------4076de35–

Twitter replied like this:

HTTP/1.1 403 Forbidden
cache-control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0
content-length: 59
content-type: application/json; charset=utf-8
date: Sat, 15 Jun 2013 13:31:40 GMT
expires: Tue, 31 Mar 1981 05:00:00 GMT
last-modified: Sat, 15 Jun 2013 13:31:40 GMT
pragma: no-cache
server: tfe
set-cookie: dnt=; domain=.twitter.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT
set-cookie: lang=de; path=/
set-cookie: lang=de; path=/
set-cookie: lang=de; path=/
set-cookie: lang=de; path=/
set-cookie: twid=u%3D14648265%7ChLcHOslbn6Y9mryuGLojr4XgDw8%3D; domain=.twitter.com; path=/; secure
set-cookie: _twitter_sess=BAh7CDoPY3JlYXRlZF9hdGwrCFTBCEg%252FAToHaWQiJWJiNTczNmNmMTM3Zjc4%250AOWJmYzc1ZmFlZTJmZGVkY2NiOgxjc3JmX2lkIiUyYTgxYzgzNzdlNzU5NDAz%250AM2E0NjBhZTUwNTNkYTExNw%253D%253D–04106207edd208bc92392b642bc8818205377fc7; domain=.twitter.com; path=/; HttpOnly
set-cookie: guest_id=v1%3A137130310057419027; Domain=.twitter.com; Path=/; Expires=Mon, 15-Jun-2015 13:31:41 UTC
status: 403 Forbidden
strict-transport-security: max-age=631138519
x-access-level: read-write-directmessages
x-frame-options: SAMEORIGIN
x-mediaratelimit-class: photos
x-mediaratelimit-limit: 100
x-mediaratelimit-remaining: 100
x-mediaratelimit-reset: 1371389500
x-mid: 31a4b5c84f56a6828a6807b027caa00bad738e2b
x-runtime: 0.17485
x-transaction: 9dd9cccffa15ff5e
x-transaction-mask: a6183ffa5f8ca943ff1b53b5644ef11421e83fa6

{“errors”:[{“code”:189,“message”:“Error creating status”}]}

Then the only change I made was comment the base64 lines in my code, like this:

/*
$multipart_request .=
“\r\nContent-Transfer-Encoding: base64”;
$value = base64_encode($value);
*/

When resending the request with this code edit, Twitter created the status.
See here: https://twitter.com/myx/statuses/345897034068398080

  • The “Content-Transfer-Encoding” header field is described in the IEEE RFC 2045 (http://www.ietf.org/rfc/rfc2045.txt), Section 6, Page 13.
  • Also refer to the MIME description at Wikipedia (MIME - Wikipedia). Besides base64, this page mentions other encodings as well, which I did not test with the Twitter API.

So, the problem does not depend on the scripting language used, but instead the Twitter API 1.1 method “statuses/update_with_media” does not like media files that are transfer-encoded with base64.

This issue should be resolved since JavaScript cannot properly handle binary strings, which is why they need to be encoded.

We just rolled out an update which supports this. Can you test again and confirm that the JS version works now?

Track the progress of this feature here: Don't force to send base64 media uploads via proxy · Issue #46 · jublo/codebird-js · GitHub

Success! Your update is successful, media uploads with base-64 encoded multipart are now accepted.

{ "created_at": "Sun Aug 25 19:12:23 +0000 2013", "id": 371711673885016064, "id_str": "371711673885016064", "text": "Ever been interested in seeing the full version of my avatar? Take a look! #wallpaper http:\/\/t.co\/dcKqIQvsm7", "source": "\u003ca href=\"http:\/\/mynetx.net\/\" rel=\"nofollow\"\u003emynetx\u003c\/a\u003e", "truncated": false, "in_reply_to_status_id": null, "in_reply_to_status_id_str": null, "in_reply_to_user_id": null, "in_reply_to_user_id_str": null, "in_reply_to_screen_name": null, "user": { "id": 14648265, "id_str": "14648265", "name": "J.M.", "screen_name": "myx", "location": "Germany", "description": "I am not @MYXphilippines! \r\n\u2014Microsoft expert. Coder. #IEuseragents. Web & app developer. Localization moderator for Twitter in German. I am my own brand.", "url": "http:\/\/t.co\/aHcqQLzDXq", "entities": { "url": { "urls": [{ "url": "http:\/\/t.co\/aHcqQLzDXq", "expanded_url": "http:\/\/mynetx.net\/", "display_url": "mynetx.net", "indices": [0, 22] }] }, "description": { "urls": [] } }, "protected": false, "followers_count": 2447, "friends_count": 104, "listed_count": 60, "created_at": "Sun May 04 14:09:54 +0000 2008", "favourites_count": 174, "utc_offset": 7200, "time_zone": "Berlin", "geo_enabled": true, "verified": false, "statuses_count": 7724, "lang": "de", "contributors_enabled": false, "is_translator": true, "profile_background_color": "DEEAF8", "profile_background_image_url": "http:\/\/a0.twimg.com\/profile_background_images\/662874303\/x8ctzxac0q9n8g3w516b.jpeg", "profile_background_image_url_https": "https:\/\/si0.twimg.com\/profile_background_images\/662874303\/x8ctzxac0q9n8g3w516b.jpeg", "profile_background_tile": false, "profile_image_url": "http:\/\/a0.twimg.com\/profile_images\/3005995399\/765485ad5721be61340ebbd0aa833564_normal.png", "profile_image_url_https": "https:\/\/si0.twimg.com\/profile_images\/3005995399\/765485ad5721be61340ebbd0aa833564_normal.png", "profile_banner_url": "https:\/\/pbs.twimg.com\/profile_banners\/14648265\/1372249396", "profile_link_color": "138CDD", "profile_sidebar_border_color": "FFFFFF", "profile_sidebar_fill_color": "B4D0EF", "profile_text_color": "464646", "profile_use_background_image": true, "default_profile": false, "default_profile_image": false, "following": false, "follow_request_sent": false, "notifications": false }, "geo": null, "coordinates": null, "place": null, "contributors": null, "retweet_count": 0, "favorite_count": 0, "entities": { "hashtags": [{ "text": "wallpaper", "indices": [75, 85] }], "symbols": [], "urls": [], "user_mentions": [], "media": [{ "id": 371711673759191042, "id_str": "371711673759191042", "indices": [86, 108], "media_url": "http:\/\/pbs.twimg.com\/media\/BSiVzEFIUAIZHLX.jpg", "media_url_https": "https:\/\/pbs.twimg.com\/media\/BSiVzEFIUAIZHLX.jpg", "url": "http:\/\/t.co\/dcKqIQvsm7", "display_url": "pic.twitter.com\/dcKqIQvsm7", "expanded_url": "http:\/\/twitter.com\/myx\/status\/371711673885016064\/photo\/1", "type": "photo", "sizes": { "small": { "w": 340, "h": 212, "resize": "fit" }, "large": { "w": 1024, "h": 640, "resize": "fit" }, "thumb": { "w": 150, "h": 150, "resize": "crop" }, "medium": { "w": 600, "h": 375, "resize": "fit" } } }] }, "favorited": false, "retweeted": false, "possibly_sensitive": false, "lang": "en" }