🐙

Twitter API でツイートする時に Content-Type でハマった

2021/08/22に公開

Twitter API のツイートのエンドポイント statuses/update.json にリクエストした際、以下のエラーが返ってきました。

{“message”:”Could not authenticate you”,”code”:32}

Twitter API でよく見かけるエラーレスポンスのようですが、このメッセージだけでは原因がわからなかったので、解決するまでの道のりをまとめました。

TL;DR

ヘッダーに Content-Type: application/x-www-form-urlencoded を指定して、ボディは application/x-www-form-urlencoded の形式でリクエストする。

解決までのフロー

1. axios で以下のリクエストをしたら例のエラーが返ってきた

POST /1.1/statuses/update.json HTTP/1.1
Accept: application/json, text/plain, */*
Content-Type: application/json
Authorization: OAuth ...(省略
User-Agent: axios/0.21.1
Content-Length: 17
Host: api.twitter.com
Connection: close

{"status":"test"}
post.ts
const url = "https://api.twitter.com/1.1/statuses/update.json";
const data = { status }
const configs = {
  headers: {
   Authorization: "OAuth ..." // 省略
  };
};

return await axios.post(url, data, configs);

2. node-twitter を使ってみた

node-twitter の REST API を使ってツイートできた。
→ コンシューマーキーとアクセストークンはOK

3. 署名の作成のサンプルを確認

サンプル通りに署名を作れた。
→ 署名の作り方もOK

4. リクエストの認証のサンプルを確認

サンプル通りにヘッダー文字列を作れた。
→ ヘッダーの作り方もOK

5. リクエストの認証のサンプルのリクエストを確認

リクエストヘッダー に Content-Type: application/x-www-form-urlencoded がついていたことに気がついた。

POST /1.1/statuses/update.json?include_entities=true HTTP/1.1
Accept: */*
Connection: close
User-Agent:OAuth gem v0.4.4
Content-Type: application/x-www-form-urlencoded
Content-Length:76
Host: api.twitter.com

status=Hello%20Ladies%20%2b%20Gentlemen%2c%20a%20signed%20OAuth%20request%21

6. node-twitter で成功したリクエストヘッダーを確認

Content-Type: application/x-www-form-urlencoded がついていた。

POST /1.1/statuses/update.json HTTP/1.1
Accept: */*
Connection: close
User-Agent: node-twitter/1.7.1
host: api.twitter.com
content-type: application/x-www-form-urlencoded
content-length: 11
Authorization: OAuth ...(省略

そういえば axios でリクエストするときいつも脳死で application/json の形式でリクエストしていたような・・・?🤔

7. axios で失敗したリクエストヘッダーを確認

Content-Type: application/json がついていた。(1.を参照)

やっぱり😩

8. axios の data の形式を変更

JSON形式からクエリパラメーター形式に変更した。
ツイートできた😊

post.ts
const url = "https://api.twitter.com/1.1/statuses/update.json";
// クエリパラメーター形式
const data = `status=${encodeURIComponent(status)}`; 
const configs = {
  headers: {
   Authorization: "OAuth ..." // 省略
  };
};

return await axios.post(url, data, configs);

axios で application/x-www-form-urlencoded を使う

axios の GitHub の Request Config で data に指定できるパラメーターを確認したら、以下のような記述がありました。

// `data` is the data to be sent as the request body
// Only applicable for request methods 'PUT', 'POST', 'DELETE , and 'PATCH'
// When no `transformRequest` is set, must be of one of the following types:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - Browser only: FormData, File, Blob
// - Node only: Stream, Buffer
data: {
  firstName: 'Fred'
},

// syntax alternative to send data into the body
// method post
// only the value is sent, not the key
data: 'Country=Brasil&City=Belo Horizonte',

また、Using application/x-www-form-urlencoded format には以下のような記述がありました。

By default, axios serializes JavaScript objects to JSON. To send data in the application/x-www-form-urlencoded format instead, you can use one of the following options.

上記の通り、axios では、data に指定したオブジェクトがデフォルトで JSON 形式にシリアル化されます。
このため、application/x-www-form-urlencoded の形式で POST するために、Node.js では querystring などを使い、以下のようにする必要があるようです。

const querystring = require('querystring');
axios.post('http://something.com/', querystring.stringify({ foo: 'bar' })); 
// 'foo=bar'

おわりに

Twitter API のリファレンスに Content-Type の指定が特になかったので、ほんの少しだけ不親切な気がしました。(初心者にも優しくしてほしいという気持ち)

解決するのに数時間かかりましたが、解決するために1個ずつ紐解いて行く忍耐力は大事だなと感じました。

Discussion