Being overly specific can lead to confusion. How SoundCloud’s API baffled me with a peculiar HTTP status code.

CACHING IS HARD, especially if you don’t have control over the data sources. Manger, a Node.js program I wrote to cache RSS feeds, encounters all kinds of things in the wild—RSS over HTTP is a zoo—but recently it failed in a particular, mildly amusing, way.

While running an integration test, a client requested Lena Dunham’s Women Of The Hour podcast and got nothing back, so it failed.

Why would my program trip over a plain redirect? My first suspicion was the protocol change, HTTP redirecting to HTTPS. Clearly, that shouldn’t be a problem either, but it was the only somewhat interesting bit about this.

422, eh? The 4xx class encompasses client errors, but I hadn’t encountered 422 yet. Apparently, it is neither part of RFC 7231, nor listed under Status code there. Instead, 422 is part of RFC 4918, a WebDAV HTTP extension.

The 422 (Unprocessable Entity) status code means the server understands the content type of the request entity (hence a 415(Unsupported Media Type) status code is inappropriate), and the syntax of the request entity is correct (thus a 400 (Bad Request) status code is inappropriate) but was unable to process the contained instructions. For example, this error condition may occur if an XML request body contains well-formed (i.e., syntactically correct), but semantically erroneous, XML instructions.

The request was valid, but one or more of the parameters looks a little screwy. It’s possible that you sent data in the wrong format. One example would be providing an array when we expected a string.

A little screwy? Come on! What’s screwy about this request?

{ headers:{'accept-encoding':'gzip'},

hostname:'feeds.soundcloud.com',

method:'GET',

path:'/users/soundcloud:users:180603351/sounds.rss',

port:80,

protocol:'http:'}

O.K., let’s compare it with curl’s request:

> GET /users/soundcloud:users:180603351/sounds.rss HTTP/1.1

> Host: feeds.soundcloud.com

> User-Agent: curl/7.43.0

> Accept: */*

Comparing the two, I spotted the obvious difference in the headers. My program didn’t sent User-Agent or Accept headers. My intuition led me to try the Acccept header first, because I thought that might be a thing.

{ headers:{'accept-encoding':'gzip', accept:'*/*'}

422, still. So, headers aren’t the issue. I had just updated to Node v6.7.0, there might be, although highly unlikely, something off with its HTTP module—hopefully not. I was about to go deep with Wireshark, when I decided to try the User-Agent header as well.

MANGER 23881: { headers:

{ 'accept-encoding': 'gzip',

accept: '*/*',

'user-agent': 'screwy' },

hostname: 'feeds.soundcloud.com',

method: 'GET',

path: '/users/soundcloud:users:180603351/sounds.rss',

port: 80,

protocol: 'http:' }

MANGER 23881: redirecting to https://rss.art19.com/women-of-the-hour

MANGER 23881: { headers:

{ 'accept-encoding': 'gzip',

accept: '*/*',

'user-agent': 'screwy' },

hostname: 'rss.art19.com',

method: 'GET',

path: '/women-of-the-hour',

port: 443,

protocol: 'https:' }

🎉 TA-DAH! In fact, the SoundCloud API demands the HTTP user-agent header being set—set to anything, really. I understand what they’re doing here, assumingly meeting a requirement of their logging, but passing 422 to a plain GET is, for want of a better word, a little screwy.