
User experience design for APIs - _ntka
https://blog.keras.io/user-experience-design-for-apis.html
======
stevencl
When I worked on the .Net framework at Microsoft we would run usability
studies on many of the APIs well before we shipped them. It was always
enlightening seeing how professional programmers use your API. We would assume
that people would approach the API with a specific mental model for example,
only to find that they thought about the API quite differently. I wrote an
article in 2004 about how we approached API usability at the time:
[http://www.drdobbs.com/windows/measuring-api-
usability/18440...](http://www.drdobbs.com/windows/measuring-api-
usability/184405654)

The great thing about doing usability studies on APIs is that it is easy to
mock up a prototype of the API that people can start to code against. The API
doesn't have to work but you get to see how people react to the names of
classes, methods, parameters etc. I used to blog about API usability and what
we were learning here: [https://blogs.msdn.microsoft.com/stevencl/tag/api-
usability/](https://blogs.msdn.microsoft.com/stevencl/tag/api-usability/)

There is a group of researchers and practitioners currently working on API
usability. Some good links here:
[http://www.cs.cmu.edu/~NatProg/apiusability.html](http://www.cs.cmu.edu/~NatProg/apiusability.html)

------
ThomPete
Before I left Square earlier this year, I lead our design efforts on the
developer API.

[https://docs.connect.squareup.com/](https://docs.connect.squareup.com/)

It was really interesting work and the team was quite committed once we found
the right team.

People always asked what does a designer do on an API. It turns out that if
the team cares, quite a lot.

Documentation is the obvious one but lots of other things like structuring the
actual information, setting up apps, working on API explorers, look at
terminology even how the endpoints are thought of and of course, how do you
easiest get up and running with the API.

It was surprisingly interesting work from a design point of view.

~~~
yeukhon
+1 Perhaps I have said this before, but UX folks have been my go-to colleagues
whenever I need their inspsirations for anything ranging from graphs (like
monitoring metrics) to debugging since the UX team is heavily involved from
the inception of a project and sometimes have more intimate knowledge of the
overall architecute of the project than the core developers do.

They are amazing (of course I am talking about the right kinds).

~~~
ThomPete
And I have it the same way with developers. They were always my go-to
colleagues more than other designers for insights and

I would generally suggest to hang out with radically different perspectives
than your own.

Luckily, in my new company both my partner feel the same way.

~~~
yeukhon
Right!

Worth sharing I am a DevOps so I do production support. One night I was at the
dinner table fixing some horrible issue. My sister and brother-in-law asked
why I was frowning. Initially I refused to explain the situation but they
convinced me to brief them the failure. They raised some questions as
outsiders (one is a doctor, another is an accountant). Immediately lightbulb
lighted up! Within minutes I was able to find the root cause, pushed a fix,
documented the issue, and closed my laptop.

~~~
ThomPete
Exactly

------
olau
You cannot build a large API effectively without using it. Programming is tons
of details, and you're going to miss some if you aren't programming with your
API yourself. It's like building a graphical user interface without ever
firing it up.

For instance, if you build a simple REST interface with a list at /object/ and
details at GET /object/1 and changes at POST /object/1, then you're good,
right? This is a well-understood way of structuring things?

Well, what if your objects are really simple and someone has to synchronize
100k of them. It's not possible to do that efficiently through such an
interface.

The API has to see some use, and it has to be able to evolve as you learn. And
you need to be ready to make some sacrifices on your side of the fence, to
make the life of the API user easier.

~~~
crdoconnor
This is essentially the argument behind BDD, except applied at a level where
the 'customer' is another developer.

Some of the worst APIs I've seen have been ones designed by a backend
developer based upon some vague specifications handed down by a non-technical
PO. They were often designed with half of the stuff that was necessary
missing, lots of stuff that wasn't necessary and some stuff which made no
sense before being thrown over a wall.

The better APIs were 'designed' by the developer who would write the code that
would consume them. Even if that just meant an email or a scrap of paper with
a few example URLs and snippets of JSON.

------
nothis
Programmers are so bad with this, this needs to be a more respected aspect of
writing software in general. "Well, I comment everything" isn't enough. If you
spent hundreds or thousands of hours on your API, but skimp on the part where
you actually look at it from the users' point of view, that's such a colossal
waste. You have to multiply the number of future users with the hours wasted
on tedious setup hoops and learning confusing naming conventions. I look at
the damage bad UX design can do to society, culturally, and I weep. A single
bad decision, there, can cause _millions_ of wasted hours.

~~~
enjo
Two decades into my career and I absolutely shudder every time I get a request
to "integrate with <xxx>'s API, it'll be easy!"

It almost never is.

Everything about the experience sucks. The documentation is usually a mess and
the code flow is usually strange as hell. But I can live with that.

It's the completely opaque error states that kill me. The article dedicated an
entire section to it and I hope people take it to heart. When I pass bad data
or something you're not expecting give me some clue as to what it might be!

I just spent 60+ hours working on an integration. 95% of that was spent
changing parameters and encodings hoping that something would magically fix
the 400 I kept receiving back from the server.

It turns out a shared secret had a typo (on my end).

The thing is this bad design is costing that company real $$. Not only was it
a frustrating experience for me, I took a couple of hours of their engineers
time trying to hunt it down. The lack of error handling made it difficult for
them to track the issue down on their side as well.

I'll never forget the first time I touched Twilio's API. Everything was so
clear. I had screwed something up, and got a clear message about what was
wrong. It was magic.. and the whole thing was up and running in less than an
hour.

Give me more of that please!

------
jamesjyu
Nit: Really verbose error messages like the last example are at risk of being
brittle. If the API changes, there may be a failure to update the error
message. This is because the messages may concern things at a distance, and
may have arbitrary prose that no longer makes sense.

For example, imagine if all your error messages were basically documentation
style prose. That would be a nightmare, and would set you up to a lot of error
message refactoring. A better strategy is somewhere in the middle.

~~~
whatismybrowser
Wow, the example in the article is an _extremely_ verbose error message! But
still, detailed and helpful error messages really do make the developer's life
easier.

When building API end points, I take a different approach by including both
messages _and_ message codes, as well as a general "result" code (to make it
easy to determine if it's a basic success/error result (also including the
right HTTP code)).

An example that I'm working on right at the moment:

    
    
        {
            "result": {
                "code": "error",
                "message_code": "too_many_user_agents",
                "message": "You have sent %s user agents; this is more than the maximum allowed per batch (%s user agents). Please send fewer in each batch."
            }
        }
    

The message describes the problem; what they did wrong, what the limit is and
how they can fix it so it doesn't happen again.

As well as this, the developer can easily check that the response _code_ was
an error, and there's the _message_code_ \- this will NEVER EVER change for
this problem, so a developer could write code to specifically deal with that
scenario.

However there's also a human readable _message_ , which may potentially change
(perhaps a grammar or punctuation fix, or if it included a URL it might change
one day...)

I've found this approach is the best sort of middle ground.

------
pmontra
I'm recommending this post to everybody I'm working with.

Many APIs are targeted to internal teams creating SPAs nowadays. Maybe the
same developer writes both the backend API and the fronted app. Those APIs
tend to be a wild west of all the mistakes the post is about. Documentation is
often sub optimal, not to talk about vulnerabilities introduced by quick and
dirty hacks because the team decides that the backend can trust the frontend
(missing validations and missing authorization checks are common in the
experience of a collegue working on penetration tests.)

Unfortunately such badly designed APIs are going to hinder the development of
new functionality for the time being. We're moving from spaghetti code to
spaghetti APIs.

------
afarrell
Folks interested in this would probably also be interested in:

* this talk by Jesse Noeller from PyTexas a few years ago: [https://www.youtube.com/watch?v=-vZ_E1OO_PY](https://www.youtube.com/watch?v=-vZ_E1OO_PY)

* the WriteTheDocs community, which is sortof a mashup of traditional technical writers and software engineers. [http://www.writethedocs.org/meetups/](http://www.writethedocs.org/meetups/)

* the mailing list GET PUT POST (shameless plug): [https://getputpost.co/overhauling-api-docs-with-gocardless-9...](https://getputpost.co/overhauling-api-docs-with-gocardless-90cc2e656750)

~~~
flaviojuvenal
Related, but shameless plug, my PyCon 2017 talk "How to make a good library
API" is also about the "API as UI" concept:
[https://www.youtube.com/watch?v=4mkFfce46zE](https://www.youtube.com/watch?v=4mkFfce46zE)

There's also a Django-specific version called "Your Django app is a User
Interface":
[https://www.youtube.com/watch?v=Mnzvjn1v1CY](https://www.youtube.com/watch?v=Mnzvjn1v1CY)

~~~
afarrell
ooh thanks!

------
iDemonix
API documentation and usability is so severely lacking, even in products you
would expect to deliver something excellent. I work for an ISP, and we've
recently acquired industry standard software for things like DDI/IPAM and so
on. The software, annual licensing and so on for some of these products is 5
to 6 figures, yet the API(s) are a mixture of ambiguous REST, undocumented
SOAP and a day-to-day nightmare.

The only breath of fresh air I've had lately is interacting with the NetBox
API which uses Swagger to sort of self-document and allow for testing.

------
te_chris
I've found that first writing the docs first using API Blueprint has been the
single most valuable thing I've done in my API development. It really makes
you think about data shape, input params, edge cases etc. and leads to much
nicer APIs in my experience. I think it's because, in my case anyway, it's a
separate project and language to the implementation, so your thinking is not
bound by whatever you're implementing with.

------
fenomas
A few more guidelines that seem obvious to me, but which I see broken _all the
time_ :

1) If an API value can be changed any time, it should probably be a plain
object property

2) If an API value can be changed but there are side effects, it should
probably be a "setFoo" method

3) If an API value can be specified, but not changed after init/instantiation,
it should probably be an argument to the constructor or init function

------
dmlittle
I really like the error messages that Joi[1] provides. You can easily use the
error.message property to give a human friendly message to the end-user and if
the default options don't serve your needs you can overwrite them with custom
messages.

Someone made a Joi playground a few months ago, Joi Tester[2], that makes it
really easy to test your Joi schemas. If you're curious, feel free to give it
a try (I'm not affiliated with the tool in any regards but I do think it's
awesome).

[1] [https://github.com/hapijs/joi](https://github.com/hapijs/joi)

[2] [http://framp.me/joi-tester/](http://framp.me/joi-tester/)

------
donmb
Everyone who has ever worked with a "proper" API (e.g. Mailchimp) with awesome
and intuitive status messages will hopefully never again fail on this. Very
bad that this is still not state of the art.

~~~
olau
I find it interesting that you hold the MailChimp API in such a high regard.

I use it frequently as an example of how not to build an API. I find it hard
to integrate efficiently with it - it got worse from version 2 to 3, although
some of the blunders have later been fixed.

You're probably right that the error messages are relatively thorough. Many of
the constraints aren't documented in the reference documentation, though, so
it requires trial-and-error.

------
deathanatos
Great, except for this…

> _(in general, always use ValueError and avoid assert)._

ValueError is for when,

> _an argument that has the right type but an inappropriate value, and the
> situation is not described by a more precise exception_

(the docs explicitly note only for builtins, but I'm frankly okay w/ people
using it in their code.)

You should _not_ be raising this when you should be raising AssertionError,
however, the two conditions are very different; the latter is for conditions
that _should_ be true but for some reason aren't. I find these usually crop up
when running through a bunch of ifs:

    
    
      if a:
      elif b:
      elif c:
      else:
        # *One* of the above should always match, in this case.
        # This branch should never be taken, and it wasn't any fault of the user we're here.
        raise AssertionError('good description of the situation')
    

Attempts to restructure the above are usually fairly bad: you can omit the
`else`, but then execution will never take any branch, and if the branches do
something like initialize a variable that the code following the branches will
make use of, you're doomed anyways, and it's better to bail in an informative
manner. You can meld the last elif and else together (i.e., the else just
handles case C above), but then it also inadvertently handles unexpected
states D, E, etc. too.

IMO, AssertionErrors should indicate bugs in the called API. ValueErrors
indicate bugs in the caller's use of the API.

(You may choose a less "destructive" method of asserting, such as logging, but
in my experience, these get lost, and if you're in an undefined state then you
still need to find some way to _repair_ that state, and in my experience most
attempts to do so are more trouble than they're worth. It's better to fail
earlier and harder and louder, s.t. bugs are discovered and swiftly fixed.)

~~~
fchollet
> IMO, AssertionErrors should indicate bugs in the called API. ValueErrors
> indicate bugs in the caller's use of the API.

Yes, I agree with this stance. `ValueError` should be used for user-provided
input validation (as well as `TypeError` in some cases). But as it happens,
many Python developers use `assert` statements to do input validation, and
generally don't provide any error messages in their `assert` statements. I'm
suggesting going with `ValueError` (and a nice message) instead.

------
js8
APIs and programming languages are UI for programmers, so yeah, they deserve
some UX thinking.

However, where is the current trend of functional programming in this piece?
(I am not disparaging FP, on the contrary, I think it's a good development.)

Shouldn't API calls be, for example, easy to test? In functional programming,
we often avoid doing actions directly, and instead give descriptions of
actions (in form of functions) that are to be made. Does your API support
that?

What about minimization of the state that the API requires to keep, and its
transparency?

I am sorry if I come off a little incoherent. Best would be if I could give
some tool a program that calls an API, say a sequence of API calls. And I
would be told, this sequence is valid for the API. So I could prove for my
program that it is accessing the API correctly.

------
tzm
Reminds me of Bryan Helmig's (Zapier) talk at APIDays SF back in 2013 about an
API consumer shift.

[http://bryanhelmig.com/your-api-consumers-arent-who-you-
thin...](http://bryanhelmig.com/your-api-consumers-arent-who-you-think-they-
are/)

------
ezekg
> Deliberately design the user onboarding process. How are complete newcomers
> going to find out the best way to solve their use case with your tool? Have
> an answer ready. Make sure your onboarding material closely maps to what
> your users care about: don't teach newcomers how your API is implemented,
> teach them how they can use it to solve their own problems.

Really good tip! Going to spend a little more time reworking my onboarding
workflow with this in mind.

------
nl
Perhaps "Don't change your API to something incompatible but with the same
names" would be a good piece of guidance too?

[https://github.com/fchollet/keras/issues/3921#issuecomment-3...](https://github.com/fchollet/keras/issues/3921#issuecomment-340626202)

------
hyperpallium
Not an "API", but git has a terrible UI... yet the error messages (and
guidance included in response to correct commands) are the best I've ever
seen. They guide the user through a typical workflow.

A similar guidance is pop-ups in games (e.g. when next to car, "F to enter").

------
pilooch
After three attempts at designing an ML rest-lile API, I ended up writing the
deepdetect's API. Never touched its fundamentals since mid 2015. Very very far
from perfect but has served great til now!

------
m_ke
pytorch with a keras like functional/layers API would be amazing. I'm guessing
we might see that in tensorflow when keras adds support for the new eager
mode.

~~~
nicolewhite
Can you elaborate? I feel like torch.nn gets you pretty close.

------
anentropic
It's telling that this is on the Keras blog, because the Tensorflow APIs are
quite a mess

