Error: Invalid command on chunked media upload

restapi
android
rest
media-upload

#1

I’m building a Twitter client for Android. The HTTP client I’m using is Retrofit. As of lately, I’ve started implementing the chunked media upload. When testing, I found out that the request to the media/upload (INIT) endpoint always fails with code 400 (which is bad request) and returns me the following response:

{“request”:"/1.1/media/upload.json",“error”:“Invalid command: Some(“INIT”).”}

The error says that the command (which in this request is INIT) is invalid, although I’ve checked multiple times and it seems fine to me. Also, I’ve searched the forums for similiar erros, but couldn’t find any. I don’t think this error is connected with authentication header because other requests work perfectly. Any help would be much appreciated. Code attached below.

Retrofit request:

@Multipart
@POST
public Call<MediaUploadResponse> initMediaUpload(@Header("Authorization") String authorizationHeader,
                                                 @Url String url,
                                                 @Part("command") String command,
                                                 @Part("total_bytes") long totalBytes,
                                                 @Part("media_type") String mediaType,
                                                 @Part("media_category") String mediaCategory,
                                                 @Part("additional_owners") String additionalOwners);

The function performing the request:

public static MediaUploadResponse initMediaUpload(OAuthCredentials authorizationCredentials, Media media) {
    TwitterService service = AppController.getInstance().getTwitterService();
    //creating the authorization header
    String authorizationHeader = createAuthorizationHeader(
        Method.POST,
        TWITTER_UPLOAD_MEDIA_ENDPOINT,
        authorizationCredentials.accessToken,
        authorizationCredentials.accessTokenSecret,
        "",
        null,
        false
    );
    Call<MediaUploadResponse> apiCall = service.initMediaUpload(
        authorizationHeader,
        TWITTER_UPLOAD_MEDIA_ENDPOINT,
        "INIT",
        Utils.getImageSizeFromPath(media.getMediaUrl()),
        Utils.getMimeType(media.getMediaUrl()),
        null,
        null
    );
    MediaUploadResponse mediaUploadResponse = null;
    try {
        Response<MediaUploadResponse> response = apiCall.execute();

        // Now the apiCall is executed, but the body of the response is null while the errorBody tells me about the aforementioned error
        if((response != null) && (response.isSuccessful()) && (response.body() != null)) {
            mediaUploadResponse = response.body();
        }
    } catch(IOException e) {
        if(Constants.IS_IN_DEBUG_MODE) {
            Log.e(TAG, "An error occurred while initializing media upload. Error: " + e.getLocalizedMessage());
        }
    }
    return mediaUploadResponse;
}

#2

Are you able to trace out the specific JSON body that the client is sending to the media/upload endpoint?


#3

Hello. Thanks for replying. Yes, I’m able to trace the request and see the JSON body. As far as I can tell, everything seems fine to me. Here is the full trace of the request:

–> POST https://upload.twitter.com/1.1/media/upload.json http/1.1
Content-Type: multipart/form-data; boundary=eea95237-3902-4a6b-b794-4d913055083f
Content-Length: 652
Authorization: OAuth
oauth_consumer_key=“XXXXXXXXXXXXXXXXXXXXX”,oauth_nonce=“XXXXXXXXXXXXXXXXXXXXXXXXXXXX”,oauth_signature=“XXXXXXXXXXXXXXXXXXXXXXXXXXX”,oauth_signature_method=“HMAC-SHA1”,oauth_timestamp=“1491853036”,oauth_token=“XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX”,oauth_version=“1.0”
–eea95237-3902-4a6b-b794-4d913055083f
Content-Disposition: form-data; name=“command"
Content-Transfer-Encoding: binary
Content-Type: application/json; charset=UTF-8
Content-Length: 6
"INIT”
–eea95237-3902-4a6b-b794-4d913055083f
Content-Disposition: form-data; name=“total_bytes"
Content-Transfer-Encoding: binary
Content-Type: application/json; charset=UTF-8
Content-Length: 6
149762
–eea95237-3902-4a6b-b794-4d913055083f
Content-Disposition: form-data; name=“media_type"
Content-Transfer-Encoding: binary
Content-Type: application/json; charset=UTF-8
Content-Length: 11
"image/png”
–eea95237-3902-4a6b-b794-4d913055083f–
–> END POST (652-byte body)
<-- 400 https://upload.twitter.com/1.1/media/upload.json (810ms)
cache-control: no-cache
content-disposition: attachment; filename=json.json
content-type: application/json;charset=utf-8
date: Mon, 10 Apr 2017 19:37:16 GMT
server: tsa_b
set-cookie: guest_id=v1%3A149185303660226292; Domain=.twitter.com; Path=/; Expires=Wed, 10-Apr-2019 19:37:16 UTC
status: 400 Bad Request
strict-transport-security: max-age=631138519
vary: Origin
x-access-level: read-write
x-connection-hash: 8dc8a46aeb512981f3efcc2a9d6761a2
x-frame-options: SAMEORIGIN
x-response-time: 13
x-transaction: 005f84b200093fa8
x-tsa-request-body-time: 1
x-xss-protection: 1; mode=block
{“request”:”/1.1/media/upload.json",“error”:“Invalid command: Some(“INIT”).”}
<-- END HTTP (82-byte body)


#4

Hmm. I just tried modifying the example in the documentation for the chunked upload with INIT using twurl, and tracing it out I see:

“POST /1.1/media/upload.json?command=INIT&total_bytes=285280&media_type=image%2Fpng HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: /\r\nUser-Agent: OAuth gem v0.4.7\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: OAuth oauth_consumer_key=“5Xxxxxxxbs”, oauth_nonce=“odxxxxxx2Ino”, oauth_signature=“vPxxxxxxD6m9k%2FnqMVE%3D”, oauth_signature_method=“HMAC-SHA1”, oauth_timestamp=“1491861102”, oauth_token=“xxxxxxxx”, oauth_version=“1.0”\r\nConnection: close\r\nHost: upload.twitter.com\r\nContent-Length: 0\r\n\r\n”

The bolded text is a bit different to the way your code seems to be working. You might find our python sample for chunked uploads a helpful reference.


#5

Well, I’m glad to announce that I managed to get it working by switching to the application/x-www-form-urlencoded POST format. At first, it sent me 401 code saying it could not authenticate me. After a little bit of research, I realized I forgot to include POST parameters when calculating an OAuth signature. After that, everything went fine.

So, thanks for help, anyway.


#6

Glad to hear you’re all set!