<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/rss-style.xsl" type="text/xsl"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	     xmlns:dc="http://purl.org/dc/elements/1.1/"
	   xmlns:atom="http://www.w3.org/2005/Atom"
	     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	  xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>
<channel>
	<title>developers &#8211; Terence Eden’s Blog</title>
	<atom:link href="https://shkspr.mobi/blog/tag/developers/feed/" rel="self" type="application/rss+xml" />
	<link>https://shkspr.mobi/blog</link>
	<description>Regular nonsense about tech and its effects 🙃</description>
	<lastBuildDate>Wed, 04 Mar 2026 10:41:53 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://shkspr.mobi/blog/wp-content/uploads/2023/07/cropped-avatar-32x32.jpeg</url>
	<title>developers &#8211; Terence Eden’s Blog</title>
	<link>https://shkspr.mobi/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title><![CDATA[Adding OpenStreetMap login to Auth0]]></title>
		<link>https://shkspr.mobi/blog/2026/02/adding-openstreetmap-login-to-auth0/</link>
					<comments>https://shkspr.mobi/blog/2026/02/adding-openstreetmap-login-to-auth0/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 24 Feb 2026 12:34:21 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[Auth0]]></category>
		<category><![CDATA[developers]]></category>
		<category><![CDATA[oauth]]></category>
		<category><![CDATA[OpenStreetMap]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=67593</guid>

					<description><![CDATA[So you want to add OSM as an OAuth provider to Auth0? Here&#039;s a tip - you do not want to create a custom social connection!  Instead, you need to create an &#34;OpenID Connect&#34; provider. Here&#039;s how.  OpenSteetMap  As per the OAuth documentation you will need to:   Register a new app at https://www.openstreetmap.org/oauth2/applications/ Give it a name that users will recognise Give it a redirect of…]]></description>
										<content:encoded><![CDATA[<p>So you want to add OSM as an OAuth provider to Auth0? Here's a tip - you do <em>not</em> want to create a custom social connection!</p>

<p>Instead, you need to create an "OpenID Connect" provider. Here's how.</p>

<h2 id="opensteetmap"><a href="https://shkspr.mobi/blog/2026/02/adding-openstreetmap-login-to-auth0/#opensteetmap">OpenSteetMap</a></h2>

<p>As per <a href="https://wiki.openstreetmap.org/wiki/OAuth#Using_OpenStreetMap_as_identity_provider">the OAuth documentation</a> you will need to:</p>

<ul>
<li>Register a new app at <a href="https://www.openstreetmap.org/oauth2/applications/">https://www.openstreetmap.org/oauth2/applications/</a></li>
<li>Give it a name that users will recognise</li>
<li>Give it a redirect of <code>https://Your Auth0 Tenant.eu.auth0.com/login/callback</code></li>
<li>Tick the box for "Sign in using OpenStreetMap"</li>
</ul>

<p>Once created, you will need to securely save your Client ID and Client Secret.</p>

<h2 id="auth0"><a href="https://shkspr.mobi/blog/2026/02/adding-openstreetmap-login-to-auth0/#auth0">Auth0</a></h2>

<p>These options change frequently, so use this guide with care.</p>

<ul>
<li>Once you have logged in to your Auth0 Tennant, go to Authentication → Enterprise → OpenID Connect → Create Connection</li>
<li>Provide the new connection with the Client ID and Client Secret</li>
<li>Set the "scope" to be <code>openid</code></li>
<li>Set the OpenID Connect Discovery URL to be <code>https://www.openstreetmap.org/.well-known/openid-configuration</code></li>
<li>In the "Login Experience" tick the box for "Display connection as a button"</li>
<li>Set the favicon to be <code>https://blog.openstreetmap.org/wp-content/uploads/2022/07/osm-favicon.png</code> or other suitable graphic</li>
</ul>

<h2 id="next-steps"><a href="https://shkspr.mobi/blog/2026/02/adding-openstreetmap-login-to-auth0/#next-steps">Next Steps</a></h2>

<p>We're not quite done, sadly.</p>

<p>The details which OSM sends back to Auth0 are limited, so Auth0 is missing a few bits:</p>

<pre><code class="language-json">{
    "created_at": "2026-02-29T12:34:56.772Z",
    "identities": [
        {
            "user_id": "openstreetmap-openid|123456",
            "provider": "oidc",
            "connection": "openstreetmap-openid",
            "isSocial": false
        }
    ],
    "name": "",
    "nickname": "",
    "picture": "https://cdn.auth0.com/avatars/default.png",
    "preferred_username": "Terence Eden",
    "updated_at": "2026-02-04T12:01:33.772Z",
    "user_id": "oidc|openstreetmap-openid|123456",
    "last_ip": "12.34.56.78",
    "last_login": "2026-02-29T12:34:56.772Z",
    "logins_count": 1,
    "blocked_for": [],
    "guardian_authenticators": [],
    "passkeys": []
}
</code></pre>

<p>Annoyingly, Auth0 doesn't set a name or nickname - so you'll need to manually get the <code>preferred_username</code>, or create a "User Map":</p>

<pre><code class="language-json">{
  "mapping_mode": "use_map",
  "attributes": {
    "nickname": "${context.tokenset.preferred_username}",
    "name":     "${context.tokenset.preferred_username}"
  }
}
</code></pre>

<p>There's also no avatar image - only the default one.</p>

<h3 id="getting-the-avatar-image"><a href="https://shkspr.mobi/blog/2026/02/adding-openstreetmap-login-to-auth0/#getting-the-avatar-image">Getting the Avatar Image</a></h3>

<p>The <a href="https://wiki.openstreetmap.org/wiki/API_v0.6">OSM API</a> has a method for <a href="https://wiki.openstreetmap.org/wiki/API_v0.6#Methods_for_user_data">getting user data</a>.</p>

<p>For example, here's all my public data: <a href="https://api.openstreetmap.org/api/0.6/user/98672.json">https://api.openstreetmap.org/api/0.6/user/98672.json</a> - thankfully no authorisation required!</p>

<pre><code class="language-json">{
  "user": {
    "id": 98672,
    "display_name": "Terence Eden",
    "img": {
      "href": "https://www.gravatar.com/avatar/52cb49a66755f31abf4df9a6549f0f9c.jpg?s=100&amp;d=https%3A%2F%2Fapi.openstreetmap.org%2Fassets%2Favatar_large-54d681ddaf47c4181b05dbfae378dc0201b393bbad3ff0e68143c3d5f3880ace.png"
    }
  }
}
</code></pre>

<p>Alternatively, you can <a href="https://github.com/microlinkhq/unavatar/issues/488">use the Unavatar service</a> to get the image indirectly.</p>

<p>I hope that's helpful to someone!</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=67593&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2026/02/adding-openstreetmap-login-to-auth0/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[How to *actually* test your readme]]></title>
		<link>https://shkspr.mobi/blog/2025/10/how-to-actually-test-your-readme/</link>
					<comments>https://shkspr.mobi/blog/2025/10/how-to-actually-test-your-readme/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 07 Oct 2025 11:34:08 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[developers]]></category>
		<category><![CDATA[Free Software]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[Open Source]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=62224</guid>

					<description><![CDATA[If you&#039;ve spent any time using Linux, you&#039;ll be used to installing software like this:  The README says to download from this link. Huh, I&#039;m not sure how to unarchive .tar.xz files - guess I&#039;ll search for that. Right, it says run setup.sh hmm, that doesn&#039;t work. Oh, I need to set the permissions. What was the chmod command again? OK, that&#039;s working. Wait, it needs sudo. Let me run that again.…]]></description>
										<content:encoded><![CDATA[<p>If you've spent any time using Linux, you'll be used to installing software like this:</p>

<blockquote><p>The README says to download from this link. Huh, I'm not sure how to unarchive .tar.xz files - guess I'll search for that. Right, it says run <code>setup.sh</code> hmm, that doesn't work. Oh, I need to set the permissions. What was the <code>chmod</code> command again? OK, that's working. Wait, it needs <code>sudo</code>. Let me run that again. Hang on, am I in the right directory? Here it goes. What, it crapped out. I don't have some random library - how the hell am I meant to install that? My distro has v21 but this requires &lt;=19. Ah, I also need to upgrade something which isn't supplied by repo. Nearly there, just need to compile this obscure project from SourceForge which was inexplicably installed on the original dev's machine and then I'll be good to go. Nope. Better raise an issue on GitHub. Oh, look, it is tomorrow.</p></blockquote>

<p>As a developer, you probably don't want to answer dozens of tickets complaining that users are frustrated with your work. You thought you made the README really clear and - hey! - it works on your machine.</p>

<p>There are various solutions to this problem - developers can release AppImages, or Snaps, or FlatPaks, or Docker or whatever. But that's a bit of stretch for a solo dev who is slinging out a little tool that they coded in their spare time. And, even those don't always work as seamlessly as you'd hope.</p>

<p>There's an easier solution:</p>

<ol>
<li>Follow the steps in your README</li>
<li>See if they work.</li>
<li>…</li>
<li>That's it.</li>
</ol>

<p>OK, that's a bit reductive! There are a million variables which go into a test - so I'm going to introduce you to a secret <em>zeroth</em> step.</p>

<ol start="0">
<li>Spin up a fresh Virtual Machine with a recent-ish distro.</li>
</ol>

<p>If you are a developer, your machine probably has a billion weird configurations and obscure libraries installed on it - things which <em>definitely</em> aren't on your users' machines. Having a box-fresh VM means than you are starting with a blank-slate. If, when following your README, you discover that the app doesn't install because of a missing dependency, you can adjust your README to include <code>apt install whatever</code>.</p>

<h2 id="ok-but-how"><a href="https://shkspr.mobi/blog/2025/10/how-to-actually-test-your-readme/#ok-but-how">OK, but how?</a></h2>

<p>Personally, I like <a href="https://flathub.org/apps/org.gnome.Boxes">Boxes</a> as it gives you a simple choice of VMs - but there are plenty of other Virtual Machine managers out there.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2025/07/OS-Selection.webp" alt="List of Linux OSes." width="801" height="728" class="aligncenter size-full wp-image-62227">

<p>Pick a standard OS that you like. I think the latest Ubuntu Server is pretty lightweight and is a good baseline for what people are likely to have. But feel free to pick something with a GUI or whatever suits your audience.</p>

<p>Once your VM is installed and set up for basic use, take a snapshot.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2025/07/revert.webp" alt="Pop up showing a snapshot of a virtual machine." width="692" height="628" class="aligncenter size-full wp-image-62228">

<p>Every time you want to test or re-test a README, revert back to the <em>original</em> state of your box. That way you won't have odd half-installed packages laying about.</p>

<p>Your next step is to think about how much hand-holding do you want to do?</p>

<p>For example, the default Debian doesn't ship with git. Does your README need to tell people to <code>sudo apt install git</code> and then walk them through configuring it so that they can <code>git clone</code> your repo?</p>

<p>Possibly! Who is your audience? If you've created a tool which is likely to be used by newbies who are just getting started with their first Raspberry Pi then, yeah, you probably will need to include that. Why? Because it will save you from receiving a lot of repeated questions and frustrated emails.</p>

<p>OK, but most developers will have <code>gcc</code> installed, right? Maybe! But it doesn't do any harm to include it in a long list of <code>apt get …</code> anyway, does it? Similarly, does everyone know how to upgrade to the very latest npm?</p>

<p>If your software is designed for people who are experienced computer touchers, don't fall into the trap of thinking that they know everything you do.  I find it best to assume people are intelligent but not experienced; it doesn't hurt to give <em>slightly</em> too much detail.</p>

<p>The best way to do this is to record <em>everything</em> you do after logging into the blank VM.</p>

<ol start="0">
<li>Restore the snapshot.</li>
<li>Log in.</li>
<li>Run all the commands you need to get your software working.</li>
<li>Once done, run <code>history -w history.txt</code>

<ul>
<li>That will print out <em>every</em> command you ran.</li>
</ul></li>
<li>Copy that text into your README.</li>
</ol>

<p>Hey presto! You now have README instructions which have been tested to work. Even on the most bare-bones machine, you can say that your README will allow the user to get started with your software with the minimum amount of head-scratching.</p>

<p>Now, this isn't foolproof. Maybe the user has an ancient operating system running on obsolete hardware which is constantly bombarded by cosmic rays. But at least this way your issues won't be clogged up by people saying their install failed because <code>lib-foobar</code> wasn't available or that <code>./configure</code> had fatal errors.</p>

<p>A great example is <a href="https://github.com/xiph/opus/blob/main/README">the Opus Codec README</a>.  I went into a fresh Ubuntu machine, followed the readme, ran the above history command, and got this:</p>

<pre><code class="language-_">sudo apt-get install git autoconf automake libtool gcc make
git clone https://gitlab.xiph.org/xiph/opus.git
cd opus
./autogen.sh
./configure
make
sudo make install
</code></pre>

<p>Everything worked! There was no missing step or having to dive into another README to figure out how to bind flarg 6.9 with schnorp-unstable.</p>

<p>So that's my plea to you, dear developer friend. Make sure your README contains both the necessary <em>and</em> sufficient information required to install your software. For your sake, as much as mine!</p>

<h2 id="wait-you-didnt-follow-your-own-advice"><a href="https://shkspr.mobi/blog/2025/10/how-to-actually-test-your-readme/#wait-you-didnt-follow-your-own-advice">Wait! You didn't follow your own advice!</a></h2>

<p>You're quite right. Feel free to send a pull request to correct this post - as I shall be doing with any unhelpful READMEs I find along the way.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=62224&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2025/10/how-to-actually-test-your-readme/feed/</wfw:commentRss>
			<slash:comments>12</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Some thoughts on personal git hosting]]></title>
		<link>https://shkspr.mobi/blog/2025/09/some-thoughts-on-personal-git-hosting/</link>
					<comments>https://shkspr.mobi/blog/2025/09/some-thoughts-on-personal-git-hosting/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 07 Sep 2025 11:34:46 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[developers]]></category>
		<category><![CDATA[git]]></category>
		<category><![CDATA[ReDeCentralize]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=62821</guid>

					<description><![CDATA[As part of my ongoing (and somewhat futile) efforts to ReDeCentralise, I&#039;m looking at moving my personal projects away from GitHub.  I already have accounts with GitLab and CodeBerg - but both of those sites are run by someone else. While they&#039;re lovely now, there&#039;s nothing stopping them becoming as slow or AI-infested as GitHub.  So I want to host my own Git instance for my personal projects. …]]></description>
										<content:encoded><![CDATA[<p>As part of my ongoing (and somewhat futile) efforts to ReDeCentralise, I'm looking at moving my personal projects away from GitHub.  I already have accounts with <a href="https://gitlab.com/edent/">GitLab</a> and <a href="https://codeberg.org/edent">CodeBerg</a> - but both of those sites are run by someone else. While they're lovely now, there's nothing stopping them becoming as slow or AI-infested as GitHub.</p>

<p>So I want to host my own Git instance for my personal projects.  I'm experimenting with <a href="https://git.edent.tel/">https://git.edent.tel/</a></p>

<p>It isn't <em>quite</em> self-hosted; I'm paying <a href="http://pikapod.net/">PikaPod</a> €2/month to deal with the hassle of hosting and updating the software. I get to point my domain name at it which means I can always change the underlying service if I want. For example, it uses Gitea and I might want to switch to Forgejo later.</p>

<p>So far, it works. But there are a few significant drawbacks.</p>

<h2 id="network-effects"><a href="https://shkspr.mobi/blog/2025/09/some-thoughts-on-personal-git-hosting/#network-effects">Network Effects</a></h2>

<p>A large service like GitHub has network effects which are incredible. It feels like 90%+ of all developers have an account there. That means if someone wants to leave a comment or send a PR there is no barrier to entry.  That's huge! There are a bunch of popular FOSS projects which make me sign up for <em>yet another</em> service when all I want to do is send a simple bug report which I find deeply annoying.</p>

<p>Luckily, Gitea has built in support for various OAuth providers. So I've set up single-sign-on with Gits Hub and Lab.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2025/08/sign-in.webp" alt="An SSO screen with buttons for GitHub and GitLab." width="588" class="aligncenter size-full wp-image-62822">

<p>I <a href="https://mastodon.social/@Edent/115066706121512523">asked people how easy it was to use</a> - most people were able to use it, although a few people wanted a local-only account.</p>

<p>But is is still a bit of a faff. Even a little bit of hassle turns people away.</p>

<h2 id="forking-isnt-federated"><a href="https://shkspr.mobi/blog/2025/09/some-thoughts-on-personal-git-hosting/#forking-isnt-federated">Forking isn't Federated</a></h2>

<p>Suppose you want to make a Pull Request or just take a copy of the code. At the moment, you have to create a fork on <em>my</em> server. There's no way to easily fork something to your GitHub or personal server.</p>

<p>You can <code>git clone</code> the repo to your local machine, and you can manually move it elsewhere, but there's no way to send a PR from your repo to mine.</p>

<p>There's also no way for users to find other forks. Perhaps the <a href="https://forgefed.org/">upcoming ForgeFed proposals</a> will fix things - but it doesn't exist yet.</p>

<p>As pointed out in "<a href="https://blog.edwardloveall.com/lets-make-sure-github-doesnt-become-the-only-option">Let's Make Sure Github Doesn't Become the only Option</a>" - most of the tooling of git hosting platforms isn't adequate for modern needs.</p>

<h2 id="discoverability"><a href="https://shkspr.mobi/blog/2025/09/some-thoughts-on-personal-git-hosting/#discoverability">Discoverability</a></h2>

<p>The easiest way to find code at the moment is to search GitHub. Moving my stuff to a different site means it will only be discovered by a general search engine.</p>

<p>I want people to find and use my code. If that's hard, they won't. I can point existing users to the repo - but it still cuts down on discovery.</p>

<h2 id="admin-hassles"><a href="https://shkspr.mobi/blog/2025/09/some-thoughts-on-personal-git-hosting/#admin-hassles">Admin Hassles</a></h2>

<p>Although PikaPods takes care of all the hosting administration, there's still the faff of setting up all of Gitea's options.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2025/08/gitea-config.webp" alt="Long list of config options." width="801" height="800" class="aligncenter size-full wp-image-62823">

<p>If I get hit by <a href="https://shkspr.mobi/blog/2025/07/grinding-down-open-source-maintainers-with-ai/">an automated spam attack</a>, it'll be up to me to clean it up.</p>

<p>I'm not sure if I have the time, patience, or expertise to correctly and safely configure everything.  Time spent administrating is time not spent coding.</p>

<h2 id="sponsorship"><a href="https://shkspr.mobi/blog/2025/09/some-thoughts-on-personal-git-hosting/#sponsorship">Sponsorship</a></h2>

<p>I get a little bit of money when people <a href="https://github.com/sponsors/edent">sponsor me on GitHub</a>. There's no "sponsor" option on Gitea and, even if there was, the network effects of GitHub are substantial. Getting people to enter their credit card info into a random site isn't as convenient as clicking a button in GitHub.</p>

<h2 id="now-what"><a href="https://shkspr.mobi/blog/2025/09/some-thoughts-on-personal-git-hosting/#now-what">Now What?</a></h2>

<p>My <a href="https://github.com/edent/SuperTinyIcons">most popular Github repo</a> has around 140 contributors. I genuinely don't think I could attract that many people to OAuth onto my personal git hosting service.</p>

<p>Gitea seems to have a mixed reputation. But it's the only one offered by PikaPods.</p>

<p>There are <a href="https://github.workshops.petrichor.me/">interesting discussions about how to replace GitHub</a> but they're only in the early stages.</p>

<p>Although €2/mo isn't a huge amount, I've gotten used to having free services on GitHub / GitLab / CodeBerg.</p>

<p>So this, I think, is my plan:</p>

<ol>
<li>Leave my popular / sponsored repos on GitHub</li>
<li>Move my smaller repos to <a href="https://git.edent.tel/">https://git.edent.tel/</a></li>
<li>Create new repos in there as well</li>
</ol>

<p>I'm also going to look for a hosted Forgejo instance which lets me use my own subdomain - hopefully at a cheaper or comparable price. If you have any recommendations - please let me know!</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=62821&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2025/09/some-thoughts-on-personal-git-hosting/feed/</wfw:commentRss>
			<slash:comments>16</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[What's your API's "Time To 200"?]]></title>
		<link>https://shkspr.mobi/blog/2021/05/whats-your-apis-time-to-200/</link>
					<comments>https://shkspr.mobi/blog/2021/05/whats-your-apis-time-to-200/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Fri, 21 May 2021 11:02:07 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[developers]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=39006</guid>

					<description><![CDATA[M&#039;colleague Charles has introduced me to the most spectacular phrase - &#34;Time To 200&#34;.  That&#039;s a measurement of the length of time it takes a new user to go from signing up to your API to getting their first HTTP 200 response.  Think about the last time you started using a new API...   Fill in a tediously long registration form Set up billing in case you go over the free trial limits Wait for a…]]></description>
										<content:encoded><![CDATA[<p>M'colleague <a href="https://twitter.com/charlesbaird/status/1392098031533793284">Charles</a> has introduced me to the most spectacular phrase - "Time To 200".  That's a measurement of the length of time it takes a new user to go from signing up to your API to getting their first <code>HTTP 200</code> response.</p>

<p>Think about the last time you started using a new API...</p>

<ul>
<li>Fill in a tediously long registration form</li>
<li>Set up billing in case you go over the free trial limits</li>
<li>Wait for a confirmation email</li>
<li>Unsubscribe from all the marketing emails</li>
<li>Find the quickstart documentation</li>
<li>Realise it is outdated and consider raising an issue on the GitHub issues graveyard</li>
<li>Generate an API key and configure all its scopes</li>
<li>Install a 3rd party NPM library and a gigabyte of required packages</li>
<li>Work out how to authenticate the request - hard given the tutorial uses V1.3.4 and you're on V1.3.4.0.1b</li>
<li>Send the first request, and realise that you had to manually add your IP address to the allow-list</li>
<li>Try again, but realise you need to sign the request with a unique timestamp</li>
<li>Receive an HTTP 429 error for sending too many requests</li>
<li>Have a pint</li>
<li>Try again, get an HTTP 200! Success! You're a real developer now!</li>
</ul>

<p>The above is only a <em>minor</em> exaggeration. Every time I sign up to play with a new API, I'm grimly aware of my own mortality.  Every minute I waste doing battle with your incomplete documentation and dreadful attitude to new users, is a minute I could spend doing something more fun instead.</p>

<p>Please, I beg of you, optimise your Time To 200!</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=39006&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2021/05/whats-your-apis-time-to-200/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Working With The Twitter Videos API]]></title>
		<link>https://shkspr.mobi/blog/2015/02/working-with-the-twitter-videos-api/</link>
					<comments>https://shkspr.mobi/blog/2015/02/working-with-the-twitter-videos-api/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Wed, 18 Feb 2015 10:37:35 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[developers]]></category>
		<category><![CDATA[twitter]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=20578</guid>

					<description><![CDATA[Twitter now allows people to upload videos directly to the micro-blogging platform.  It&#039;s an attempt to bypass 3rd party sites like YouTube (owned by Google) and Instagram (owned by Facebook).  In an uncharacteristic display of openness, Twitter&#039;s API allows developers to get direct access to video.  This is a quick blog post to explain how you get access, and what you can do with the…]]></description>
										<content:encoded><![CDATA[<p>Twitter now allows people to <a href="https://blog.twitter.com/2015/now-on-twitter-group-direct-messages-and-mobile-video-capture">upload videos</a> directly to the micro-blogging platform.  It's an attempt to bypass 3rd party sites like YouTube (owned by Google) and Instagram (owned by Facebook).</p>

<p>In an uncharacteristic display of openness, Twitter's API allows developers to get direct access to video.</p>

<p>This is a quick blog post to explain how you get access, and what you can do with the information.</p>

<p>I presuppose that you're already familiar with the <a href="https://dev.twitter.com/">Twitter API</a> and know how to make basic calls.</p>

<h2 id="entities"><a href="https://shkspr.mobi/blog/2015/02/working-with-the-twitter-videos-api/#entities">Entities</a></h2>

<p>Upon requesting a status (either individually or as part of a timeline) your program will receive metadata about the post, stored in the entities node of the JSON.</p>

<p>One of these entities will be the imaginatively named "extended_entities".</p>

<p>If we request this specific Tweet:</p>

<blockquote class="social-embed" id="social-embed-567972190639022080" lang="en" itemscope="" itemtype="https://schema.org/SocialMediaPosting"><header class="social-embed-header" itemprop="author" itemscope="" itemtype="https://schema.org/Person"><a href="https://twitter.com/katiemoffat" class="social-embed-user" itemprop="url"><img class="social-embed-avatar social-embed-avatar-circle" src="data:image/webp;base64,UklGRrICAABXRUJQVlA4IKYCAACQDACdASowADAAPrVOoEsnJCMhrjgJmOAWiUATplovrG0dwe3/fyHeD6mc/jhDpTWlEylJLndLP0iRFDj8Q3c8Ku4IUUnYPouGkj7UL1QL2dTplgNu/mFzrmb1BA6R5uVPhXE+5VbULc3Zd7vQAP6qGTihtHZCXXRxRWDWTXKclrwohRErSqAbd9U/ip5OQrbYOcCz3Gv3BATur6y95m1DKtKCdAfrwQNfVQgct0A5ergLfIMzSoKpKlqLgcdmdWMq2OmTe7d7mWDxSYpgd2VO3cvyNo+Q66yjKcl+tzYkxkW5kSkv6UBj7kDMX5y7R+jFZwIBQAt5wd5FW3579s0v1AHcSoQN1zS2DRaWpEJDB8UxFaBcH4AVrvLlDt7c51iD4ax9HQs1C2q7YZ9lqIXI6CuJE8VzZVE1dVY8jJ3DkYhDZM3pNCjb6TLuIHGn1DBJ5S4rchMz1Y2hY9cg8ChoZ6jJmRrHRwBnlcWaSMqzQyahmekQM6q1MufxHlPhXpj6bb5Std/c3NTec6a9iNett+//q6jGjF7GtjH1F1ty+65MuHM1EJPiNhqEtYLwGxOrE9VHZUskG7Ne4q1T6OE4rty169MED7oOuaFfiUjmXO8bnR4BCQQYPf2K3TyOKO4suhJY9/9QQEiMsS8JOo4GIfApqqDyAb404T0j8cZmlcNIraZUnn1xInuh+LtuIqk93ZPGO87xD87jYsybaYlgfAtSIhqH9LLJeWn6rVB0pEwNvnQ1FgIy7rmi42lMX6FASLHj7dyF1bV/ZuQtxFLuVlCiUaqMY1e4c5OOKTnO4Ax/ulu7Xs5klZoC0zs5jmQ5kkohOwNyhaI2549MiasYGhyr/UCei+KwY0D1xAT8Rp8iSGcVI6/0eOq+ebJiAR3qhTsQAAA=" alt="" itemprop="image"><div class="social-embed-user-names"><p class="social-embed-user-names-name" itemprop="name">katie</p>@katiemoffat</div></a><img class="social-embed-logo" alt="Twitter" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%0Aaria-label%3D%22Twitter%22%20role%3D%22img%22%0AviewBox%3D%220%200%20512%20512%22%3E%3Cpath%0Ad%3D%22m0%200H512V512H0%22%0Afill%3D%22%23fff%22%2F%3E%3Cpath%20fill%3D%22%231d9bf0%22%20d%3D%22m458%20140q-23%2010-45%2012%2025-15%2034-43-24%2014-50%2019a79%2079%200%2000-135%2072q-101-7-163-83a80%2080%200%200024%20106q-17%200-36-10s-3%2062%2064%2079q-19%205-36%201s15%2053%2074%2055q-50%2040-117%2033a224%20224%200%2000346-200q23-16%2040-41%22%2F%3E%3C%2Fsvg%3E"></header><section class="social-embed-text" itemprop="articleBody">A dull video of my cat to test twitter videos <a href="https://x.com/katiemoffat/status/567972190639022080/video/1">pic.x.com/cGazAn7H3E</a><div class="social-embed-media-grid"><video class="social-embed-video" controls="" src="https://video.twimg.com/ext_tw_video/567972074346807296/pu/vid/720x720/njkDGgpJBpsTjQD3.mp4" poster="data:image/webp;base64,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" width="550"></video></div></section><hr class="social-embed-hr"><footer class="social-embed-footer"><a href="https://twitter.com/katiemoffat/status/567972190639022080"><span aria-label="2 likes" class="social-embed-meta">❤️ 2</span><span aria-label="1 replies" class="social-embed-meta">💬 1</span><span aria-label="0 reposts" class="social-embed-meta">🔁 0</span><time datetime="2015-02-18T09:01:37.000Z" itemprop="datePublished">09:01 - Wed 18 February 2015</time></a></footer></blockquote>

<p>The "extended_entities" looks like this :</p>

<pre><code class="language-json">"extended_entities": {
    "media": [
      {
        "id": 567972074346807300,
        "id_str": "567972074346807296",
        "indices": [
          46,
          68
        ],
        "media_url": "http://pbs.twimg.com/ext_tw_video_thumb/567972074346807296/pu/img/uz53Ap4wEah7cV50.jpg",
        "media_url_https": "https://pbs.twimg.com/ext_tw_video_thumb/567972074346807296/pu/img/uz53Ap4wEah7cV50.jpg",
        "url": "http://t.co/cGazAn7H3E",
        "display_url": "pic.twitter.com/cGazAn7H3E",
        "expanded_url": "http://twitter.com/katiemoffat/status/567972190639022080/video/1",
        "type": "video",
        "sizes": {
          "small": {
            "w": 340,
            "h": 340,
            "resize": "fit"
          },
          "thumb": {
            "w": 150,
            "h": 150,
            "resize": "crop"
          },
          "medium": {
            "w": 600,
            "h": 600,
            "resize": "fit"
          },
          "large": {
            "w": 720,
            "h": 720,
            "resize": "fit"
          }
        },
        "video_info": {
          "aspect_ratio": [
            1,
            1
          ],
          "duration_millis": 6605,
          "variants": [
            {
              "bitrate": 832000,
              "content_type": "video/mp4",
              "url": "https://video.twimg.com/ext_tw_video/567972074346807296/pu/vid/480x480/eU1s1ig_skHgeRjB.mp4"
            },
            {
              "content_type": "application/x-mpegURL",
              "url": "https://video.twimg.com/ext_tw_video/567972074346807296/pu/pl/tr7sF7aHBPOCuL8H.m3u8"
            },
            {
              "bitrate": 832000,
              "content_type": "video/webm",
              "url": "https://video.twimg.com/ext_tw_video/567972074346807296/pu/vid/480x480/eU1s1ig_skHgeRjB.webm"
            },
            {
              "bitrate": 1280000,
              "content_type": "video/mp4",
              "url": "https://video.twimg.com/ext_tw_video/567972074346807296/pu/vid/720x720/njkDGgpJBpsTjQD3.mp4"
            },
            {
              "bitrate": 320000,
              "content_type": "video/mp4",
              "url": "https://video.twimg.com/ext_tw_video/567972074346807296/pu/vid/240x240/Gye4gcWtlJq8zXhF.mp4"
            }
          ]
        }
      }
    ]
  },
</code></pre>

<p>As well as a static thumbnail, we are also given a list of direct links to the video files.
<code>extended_entities-&gt;media-&gt;video_info-&gt;variants</code></p>

<p>Here's where things get a <em>little</em> confusing...</p>

<ul>
    <li>Three of the videos are MP4 files - each with a different bitrate.</li>
    <li>There is one WEBM video.</li>
    <li>Finally, there's an <a href="http://en.wikipedia.org/wiki/M3U#M3U8">HTTP Live Streaming link</a> suitable for iOS.</li>
</ul>

<p>The videos are in an unordered list, so you'll need to parse each one carefully to pick the right format.</p>

<h2 id="one-video-to-rule-them-all"><a href="https://shkspr.mobi/blog/2015/02/working-with-the-twitter-videos-api/#one-video-to-rule-them-all">One Video To Rule Them All</a></h2>

<p>It's tricky to know which file to display to a user.  The lowest bitrate MP4 will <em>probably</em> play on most devices.  The <a href="http://caniuse.com/#feat=webm">WEBM is good for modern browsers apart from IE</a>.  The streaming link is unlikely to work with desktop machines and older phones.</p>

<p>There's no filesize information, so you can't present that to a user and allow them to make a choice.</p>

<p>There's no direct access to the resolution of the video.  If you look at the URL :
<code>https://video.twimg.com/ext_tw_video/567972074346807296/pu/vid/240x240/Gye4gcWtlJq8zXhF.mp4</code>
You should be able to extract the resolution from the string.  In the above case, it's 240*240.</p>

<p>Unless you are absolutely sure of your user's device capabilities, stick to the MP4 version.  Hopefully the lowest resolution will always be the last element of the array.</p>

<p>Ideally, I would have liked to have seen Twitter provide a canonical URL - point the user at that and let Twitter decide on the best format and directly serve it.  At the very least, it would be helpful to provide filesize and resolution in the metadata.</p>

<p>In HTML5, it's <a href="https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Using_HTML5_audio_and_video">easy to add multiple sources</a> to allow the browser to determine which one to play :</p>

<pre><code class="language-html">&lt;video controls&gt;
  &lt;source src="foo.m3u8" type="application/x-mpegURL"&gt;
  &lt;source src="foo.webm" type="video/webm"&gt;
  &lt;source src="foo.mp4"  type="video/mp4"&gt;
  Your browser does not support the &lt;code&gt;video&lt;/code&gt; element.
&lt;/video&gt;
</code></pre>

<p>That should display like this :</p>

<video controls="">
  <source src="https://video.twimg.com/ext_tw_video/567972074346807296/pu/pl/tr7sF7aHBPOCuL8H.m3u8" type="application/x-mpegURL">
  <source src="https://video.twimg.com/ext_tw_video/567972074346807296/pu/vid/480x480/eU1s1ig_skHgeRjB.webm" type="video/webm">
  <source src="https://video.twimg.com/ext_tw_video/567972074346807296/pu/vid/720x720/njkDGgpJBpsTjQD3.mp4" type="video/mp4">
  Your browser does not support the <code>video</code> element.
</video>

<p>I'm not aware of a (simple) way to advertise differing bitrates and resolutions.  You can use <a href="http://stackoverflow.com/questions/19924984/html5-video-tag-with-multiple-bitrate-videos">hacks like media queries to determine screensize</a>, or try using a video playback solution like <a href="http://www.videojs.com/">Video.js</a>;</p>

<p>If you want a fuller explanation, I advise reading <a href="https://web.archive.org/web/20150219072004/https://diveintohtml5.info/video.html#markup">Dive Into HTML5's Video section</a>.</p>

<h2 id="searching-for-videos"><a href="https://shkspr.mobi/blog/2015/02/working-with-the-twitter-videos-api/#searching-for-videos">Searching For Videos</a></h2>

<p>There's nothing in the <a href="https://dev.twitter.com/rest/public/search">search API</a> which allows you to specify that you only want results with videos.  A hacky way to get them is to search for the string "<a href="https://twitter.com/search?f=realtime&amp;q=%2Fvideo%2F1&amp;src=typd">/video/1</a>".  You might get a few false positives, but it's all that's available for now.</p>

<h2 id="where-next"><a href="https://shkspr.mobi/blog/2015/02/working-with-the-twitter-videos-api/#where-next">Where Next?</a></h2>

<p>Hopefully we'll see more 3rd party apps supporting the display of video - I'll be updating <a href="https://github.com/edent/Dabr">Dabr</a> shortly.</p>

<p>At the moment, the <a href="https://dev.twitter.com/rest/public/uploading-media">Twitter API only allows Animated GIFs to be uploaded</a> by third party clients - video is restricted to official apps.  I hope that will change in the near future.</p>

<p>I can't wait to see what interesting art and data comes from this.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=20578&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2015/02/working-with-the-twitter-videos-api/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		<enclosure url="https://video.twimg.com/ext_tw_video/567972074346807296/pu/vid/480x480/eU1s1ig_skHgeRjB.mp4" length="303404" type="video/mp4" />
<enclosure url="https://video.twimg.com/ext_tw_video/567972074346807296/pu/vid/480x480/eU1s1ig_skHgeRjB.webm" length="0" type="video/webm" />
<enclosure url="https://video.twimg.com/ext_tw_video/567972074346807296/pu/vid/720x720/njkDGgpJBpsTjQD3.mp4" length="614344" type="video/mp4" />
<enclosure url="https://video.twimg.com/ext_tw_video/567972074346807296/pu/vid/240x240/Gye4gcWtlJq8zXhF.mp4" length="132221" type="video/mp4" />

			</item>
		<item>
		<title><![CDATA[Celeb Hypocrisy on Twitter]]></title>
		<link>https://shkspr.mobi/blog/2013/08/celeb-hypocrisy-on-twitter/</link>
					<comments>https://shkspr.mobi/blog/2013/08/celeb-hypocrisy-on-twitter/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sat, 10 Aug 2013 11:00:03 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[celeb]]></category>
		<category><![CDATA[developers]]></category>
		<category><![CDATA[twitter]]></category>
		<guid isPermaLink="false">http://shkspr.mobi/blog/?p=8587</guid>

					<description><![CDATA[OMG! Some star LOVES Samsung.  Back when Twitter started, they used to advertise which Twitter clients people used.  You could see that Stephen Fry preferred Feathers, and that I used Dabr.  All was well.  Then, of course, Twitter went to war with its third party developers.  They cut their API limits, reduced their functionality, and obliterated all mention of third party clients.  Which means …]]></description>
										<content:encoded><![CDATA[<p><a href="https://twitter.com/NicoleScherzy/status/365890580426924034"><img src="https://shkspr.mobi/blog/wp-content/uploads/2013/08/Twitter-iPhone-fs8.png" alt="Twitter iPhone" width="600" height="1067" class="aligncenter size-full wp-image-8589"></a></p>

<p>OMG! Some star <strong><em>LOVES</em></strong> Samsung.</p>

<p>Back when Twitter started, they used to advertise which Twitter clients people used.  You could see that Stephen Fry preferred Feathers, and that I used Dabr.  All was well.  Then, of course, Twitter went to war with its third party developers.  They cut their API limits, reduced their functionality, and obliterated all mention of third party clients.</p>

<p>Which means that you can't see that certain "celebs" are hypocrites.</p>

<p>Looking at the underlying data of the above tweet shows that it was sent from "Twitter for iPhone".</p>

<p>Look, I don't really care that people lie on Twitter. I care that Twitter facilitates their lies.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=8587&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2013/08/celeb-hypocrisy-on-twitter/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[API Design is UI for Developers]]></title>
		<link>https://shkspr.mobi/blog/2012/03/api-design-is-ui-for-developers/</link>
					<comments>https://shkspr.mobi/blog/2012/03/api-design-is-ui-for-developers/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 04 Mar 2012 15:33:50 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[usability]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[developers]]></category>
		<category><![CDATA[hci]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[ui]]></category>
		<guid isPermaLink="false">http://shkspr.mobi/blog/?p=5382</guid>

					<description><![CDATA[I&#039;ve been thinking a lot about APIs and their design recently.  I stumbled on this fantastic quote from Greg Parker:  Greg Parker@gparkerA programming language is a user interface for developers. Language authors should learn from HCI principles.❤️ 41💬 6🔁 019:10 - Wed 22 February 2012  When I first started learning C++ (back in the bad old days) I was convinced that any 1st year student could desi…]]></description>
										<content:encoded><![CDATA[<p>I've been thinking a lot about APIs and their design recently.</p>

<p>I stumbled on this fantastic quote from Greg Parker:</p>

<blockquote class="social-embed" id="social-embed-172397794684977152" lang="en" itemscope="" itemtype="https://schema.org/SocialMediaPosting"><header class="social-embed-header" itemprop="author" itemscope="" itemtype="https://schema.org/Person"><a href="https://twitter.com/gparker" class="social-embed-user" itemprop="url"><img class="social-embed-avatar social-embed-avatar-circle" src="data:image/webp;base64,UklGRkoDAABXRUJQVlA4ID4DAAAwDgCdASowADAAPqlEnEmmJCMhNVqqqMAVCWwAnTK4EPbkO1h3Nt7duMoX8ZvAfxi/j2h/VuMDs7zL1cf65sHPCIOjPwHXXfSy7Praz4IE2MNuyG1VHdy1sJHyd6muDG3lGD0b2fo8rNBfk3YzrJ6EqPQjkDFfXWJmgAD+9WP/LPq96FERypf+7+NMAExNb//AD7EkNv0PuC9sbA4dpnCT3riDxFmYk0NeFrA8Wmv+RRfQcILX9mGSC0yo3HaH2Lub71Nh9SUIhXUulI9XNW08yrr3UjYs4VeT7RDpf4eADlOt3mFQhq55sIydn7rhLd9kXQJG+YWcFTaFdGP0Mip2KB8RLIXcXm292LheClB37dvBoGslPnZyLRifX+uviMITxyGzTGRMWssS1nFwMR3SNbnJMxOc4+Z8upbXWz2/dlwmiQDh/YTwmrD0QPYSN9Mko6ZdFnitJR9kVGStNW2jOHDxZIr/mlrL+tMAWkRSfaqDb1O+lOzOqpYzW5POGNxf0opmGh6DTgO4w9AvF11FVR84LEV4QqTcUqnGUV46pywb3VtXAPbRUqq1smP9C11FZJIONFucljPVmf5XKHYXZ+13AgHeKODlkLc4ty3LVXvAqr6h07FEiJMJ5WOz6XzDWlPX3YlMPnyFbO/L0+m3e7Fvuk9xCFOug8GYkI4sXmdUxqn+0lbyZHtSD959WW0IEsyyT9C8vxX5DEoPr5Mf6tBcQpnNus2lrtmGTn8+T8mkLSTef+dBDjMxV2FTLlx8QIEM4nr/O+ibUYGDS6iam2Iz2wJdG0ffZmHzL+PFUbrRrhQwtoYqSDRdxbq7nTjjMUdNqR4zYQjrv2ACTQz54Y6adpu0DpzdYyyCtNIg0mC5OEAjASF6mDQUDoLhcLAEq74CQyjDfc3i1B71QKebPvOhhQh4jL8J1athLJgytc9X8LhIevUYHjn6i4xh8hg3dNYT36fBuWdP8AW0mePX443MP7fRQGtqYE7DjRBTiqN0NUhfBrQiHASKafq+snBiJKTyd0s0guEBXkyEC9Hc/TQ7rHYVYI8cdsxlrK5RYYbSQgOSolBr1KazbEoU1MRm0jrg2C1sIwUqSgAAAA==" alt="" itemprop="image"><div class="social-embed-user-names"><p class="social-embed-user-names-name" itemprop="name">Greg Parker</p>@gparker</div></a><img class="social-embed-logo" alt="Twitter" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%0Aaria-label%3D%22Twitter%22%20role%3D%22img%22%0AviewBox%3D%220%200%20512%20512%22%3E%3Cpath%0Ad%3D%22m0%200H512V512H0%22%0Afill%3D%22%23fff%22%2F%3E%3Cpath%20fill%3D%22%231d9bf0%22%20d%3D%22m458%20140q-23%2010-45%2012%2025-15%2034-43-24%2014-50%2019a79%2079%200%2000-135%2072q-101-7-163-83a80%2080%200%200024%20106q-17%200-36-10s-3%2062%2064%2079q-19%205-36%201s15%2053%2074%2055q-50%2040-117%2033a224%20224%200%2000346-200q23-16%2040-41%22%2F%3E%3C%2Fsvg%3E"></header><section class="social-embed-text" itemprop="articleBody">A programming language is a user interface for developers. Language authors should learn from HCI principles.</section><hr class="social-embed-hr"><footer class="social-embed-footer"><a href="https://twitter.com/gparker/status/172397794684977152"><span aria-label="41 likes" class="social-embed-meta">❤️ 41</span><span aria-label="6 replies" class="social-embed-meta">💬 6</span><span aria-label="0 reposts" class="social-embed-meta">🔁 0</span><time datetime="2012-02-22T19:10:11.000Z" itemprop="datePublished">19:10 - Wed 22 February 2012</time></a></footer></blockquote>

<p>When I first started learning C++ (back in the bad old days) I was convinced that any 1st year student could design a better programming language.  One which behaved in a sane fashion without a lot of legacy cruft.  In many ways, PHP is that programming language.  It's simple, logical, and works without having to know lots of esoteric computer science.</p>

<p>I see API design in the same way.  Most of the APIs I come in contact with were obviously designed by the original developer <em>for</em> the original developer - with no thought of those who would come after her.  Often, they're designed for one specific internal usage, reluctantly opened to the public, and never updated to account for how people actually use them.  Oh, and you can forget about decent documentation!</p>

<p>One hackathon I went to a few weeks ago had a Developer Relations employee stand up and say:</p>

<blockquote><p>Who wants to use our API? It uses <a href="http://en.wikipedia.org/wiki/SOAP">SOAP</a> - sorry.  If you want documentation - come see me because it's not on the website. Oh, and it's read only.  Let me know as soon as possible because it takes 6 hours to approve your API key.</p></blockquote>

<p>This is madness.  Developers are human too!  They need some HCI love between them and their APIs.</p>

<p>So, here are my hastily scribbled thoughts on what an API needs at a <em>minimum</em> to entice the busy developer.</p>

<p>I don't think any of these are Earth-shattering, but it's amazing how many APIs fail to meet even these basic requirements.</p>

<h2 id="easy-access"><a href="https://shkspr.mobi/blog/2012/03/api-design-is-ui-for-developers/#easy-access">Easy Access</a></h2>

<ul>
    <li>Don't make me register, set up an account, or fill in a load of forms - I just want to see what I can do with you before I make a commitment.</li>
    <li>Do give me an Apigee console - or similar - so I can see for myself what's happening.</li>
    <li>Don't give me absurd key signing requirement - I should be able to call the API from a web browser just by typing stuff in.</li>
    <li>Do make it as easy as possible for me to get started - get your CEO (or similar) and see how long it takes her to set up an account &amp; launch her first call. If it's more than 5 minute, go back to the drawing board.</li>
</ul>

<h2 id="example-code"><a href="https://shkspr.mobi/blog/2012/03/api-design-is-ui-for-developers/#example-code">Example Code</a></h2>

<ul>
    <li>Don't tell me what your API can do - show me a few cool calls to start me off.</li>
    <li>Just because you love Ruby, doesn't mean everyone does. Show examples in a variety of languages.</li>
    <li>Provide examples of code which won't work and explain why.</li>
    <li>Comment your code. It may be obvious to you what "choes=17|L" means, but it may not be to me.</li>
</ul>

<h2 id="documentation"><a href="https://shkspr.mobi/blog/2012/03/api-design-is-ui-for-developers/#documentation">Documentation</a></h2>

<ul>
    <li>This is possibly the most important area.</li>
    <li>I need to know what I can do, how I can do it, why I should do it a certain way, and what response to expect.</li>
    <li>For. Every. Single. Function.</li>
    <li>If you can't document your API, people can't use it.</li>
</ul>

<h2 id="full-enumeration-of-responses"><a href="https://shkspr.mobi/blog/2012/03/api-design-is-ui-for-developers/#full-enumeration-of-responses">Full Enumeration of Responses</a></h2>

<ul>
    <li>Tell me the full range of response I can expect.</li>
    <li>A good example is Twitter's "retweet_count", the documentation used to imply that the response would always be an integer.  However, it would occasionally <a href="https://web.archive.org/web/20120111232055/https://gazit.me/2012/01/09/Twitter-documentation-fail.html">respond with a string of "100+"</a>. Naturally, this meant developers would write code expecting ints which would fail whenever a string was encountered.  If you can't tell me what responses to expect - how can I code something which handles those responses correctly?</li>
    <li>What error codes are you likely to throw?</li>
</ul>

<h2 id="variety-of-response-language"><a href="https://shkspr.mobi/blog/2012/03/api-design-is-ui-for-developers/#variety-of-response-language">Variety of Response Language</a></h2>

<ul>
    <li>Yes, you love XML. Guess what? I don't!</li>
    <li>The customer is always right.  If the customer (developer) wants JSON, XML, PHPobject, or just plain text - you should give it to them.</li>
    <li>It's the API designer's job to make life easy for developers - so reply in whatever formats the developer wants.</li>
</ul>

<h2 id="human-readable-response"><a href="https://shkspr.mobi/blog/2012/03/api-design-is-ui-for-developers/#human-readable-response">Human Readable Response</a></h2>

<ul>
<li>Developers are humans! Yes! It's true! And they can't all pretty-print JSON in their heads.  Give them something they can read without resorting to external tools.</li>
    <li>The Wikipedia API is a brilliant example of this.  They have a <a href="http://en.wikipedia.org/w/api.php?action=query&amp;prop=info|langlinks&amp;lllimit=200&amp;llurl&amp;titles=API&amp;redirects=">human readable response for their API calls</a>.</li>
    <li>Unless you have a very good reason not to - all your responses should be pretty-printed.  It helps with debugging and makes life just that little bit easier for a struggling developer.</li>
</ul>

<h2 id="human-readable-requests"><a href="https://shkspr.mobi/blog/2012/03/api-design-is-ui-for-developers/#human-readable-requests">Human Readable Requests</a></h2>

<ul>
<li>Developers are humans! Yes! It's true! And they can't all remember every little acronym in their heads.  Give them something they can read without resorting to documentation.</li>
    <li>What's easier to remember "gnxID" or "getNextId"?</li>
    <li>Keep your parameters consistent.</li>
</ul>

<h2 id="unchanging"><a href="https://shkspr.mobi/blog/2012/03/api-design-is-ui-for-developers/#unchanging">Unchanging</a></h2>

<ul>
    <li>Consistency is a virtue. If you have two similar APIs (say, search &amp; read) they should take the same parameters and produce identically formatted responses.</li>
    <li>If you have to change the way your API responds, or the way it accepts request, that's fine - but use versioning so that developers don't have to update their code if they don't want to.</li>
    <li>Never deprecate <em>anything</em>! Once an embedded device has had its firmware burned, it's unlikely to ever be updated.  If people or services rely on you, it's simply unacceptable to kill something off.  Remember, not every developer or end user can update their software.</li>
</ul>

<h2 id="simplicity"><a href="https://shkspr.mobi/blog/2012/03/api-design-is-ui-for-developers/#simplicity">Simplicity</a></h2>

<ul>
    <li>Perhaps the most important of all HCI commandments - Keep It Simple.</li>
    <li>Don't engage in a needless dance where developers have to take several steps to do a single action.</li>
    <li>Try to explain what you're doing in a single sentence. If you can't - it's probably too complicated.</li>
</ul>

<h2 id="libraries"><a href="https://shkspr.mobi/blog/2012/03/api-design-is-ui-for-developers/#libraries">Libraries</a></h2>

<ul>
    <li>I'm sure you're very proud that your community has created some libraries - but they're not good enough.</li>
    <li>Want people to use your API? Provide libraries in a wide variety of languages.</li>
    <li>Update and support those libraries.</li>
</ul>

<h2 id="detailed-errors"><a href="https://shkspr.mobi/blog/2012/03/api-design-is-ui-for-developers/#detailed-errors">Detailed Errors</a></h2>

<ul>
    <li>It's not enough to say an error has occurred. Say <em>why</em> it has occurred.</li>
    <li>Error codes must always be accompanied by human readable error messages.</li>
    <li>Do you really need to throw an error? Can your API take a "best" guess at what the user was trying to do? <a href="https://en.wikipedia.org/wiki/Robustness_principle">Be generous in what you accept</a>.</li>
</ul>

<h2 id="feedback"><a href="https://shkspr.mobi/blog/2012/03/api-design-is-ui-for-developers/#feedback">Feedback</a></h2>

<ul>
    <li>Your API sucks. Accept that.</li>
    <li>Provide a mechanism where people can feed back what they think is broken, poorly implemented, missing, or just plain confusing.</li>
    <li>Discuss the feedback openly. See what the rest of your community thinks.</li>
    <li>Act on feedback.  If a feature request hasn't been acted on after 6 months, you've probably failed.</li>
</ul>

<h2 id="so-what"><a href="https://shkspr.mobi/blog/2012/03/api-design-is-ui-for-developers/#so-what">So What?</a></h2>

<p>There is nothing new in this post to the seasoned developer.  But as <a href="https://web.archive.org/web/20140108122439/https://mattgemmell.com/hashing-for-privacy-in-social-apps/">Matt Gemmell reminded me recently - some people just don't know the basics</a>.</p>

<p>If you're interested in making your API useful for developers, you have to treat it like any other product.  You have to consider HCI factors, you have to do product testing, design, and planning.</p>

<p>Your API is a product.  Treat your developers as you would your most profitable users.</p>

<h2 id="further-reading"><a href="https://shkspr.mobi/blog/2012/03/api-design-is-ui-for-developers/#further-reading">Further Reading</a></h2>

<p>There are many books on this subject - the two I recommend are:
<a href="https://web.archive.org/web/20120326090638/http://www.faqs.org/docs/artu/ch01s06.html">Basics of the Unix Philosophy</a> (free).
<a href="http://www.amazon.co.uk/gp/product/0465067107/ref=as_li_ss_tl?ie=UTF8&amp;tag=shkspr-21&amp;linkCode=as2&amp;camp=1634&amp;creative=19450&amp;creativeASIN=0465067107">The Design of Everyday Things</a> (paper or ebook).</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=5382&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2012/03/api-design-is-ui-for-developers/feed/</wfw:commentRss>
			<slash:comments>20</slash:comments>
		
		
			</item>
	</channel>
</rss>
