Live data from Hacker News

API Practices If You Hate Your Customers

queue.acm.org

121–130 of 258 posts

Re: API Practices If You Hate Your Customers

#121
post #5
post #2

I was expecting to see two of my pet hates - returning a null array to represent no items, and returning a single object without an array to represent one item. I also once worked with an API where you had to send the data in POST format - abc=123&def=456. After much pressure from their customers, they finally relented and added an XML version of their API... where your request could look like this: abc=123&def=456 .…

returning a null array to represent no items To be honest, that's what I'd expect. What do you dislike about that result, and what would you prefer to see returned? Jump straight to 404? returning a single object without an array to represent one item. So an array if there's multiple results, and a bare object for a single result? That's unpleasant.

>> returning a null array to represent no items

> To be honest, that's what I'd expect. What do you dislike about that result, and what would you prefer to see returned? Jump straight to 404?

I'd expect an empty array to represent no items. It's the difference between "" and "[]".

Re: API Practices If You Hate Your Customers

#122

Earlier quoted context omitted.

> XML can’t model single item arrays versus single objects. They look the same. value ... I don't really see the problem.

You need to know that “results” is an array so you need the schema. In JSON an array is an array without doubt.

I mean, sure, but if you're creating an XML API, you've almost certainly defined the schema. If I take JSON like

    [
      { "fruit": "banana" },
      { "color": "yellow" },
      [ 1 ]
    ]
...and turn it into XML like...

    
      banana
      yellow
      
        1
      
    
It's going to be fairly clear what's going on.

I'd generally prefer to work with JSON, too. But the silver lining of XML is that you can define virtually any schema you want, and there are times that's conceivably going to be clearer or even more concise than the equivalent JSON.

Re: API Practices If You Hate Your Customers

#123
post #93
post #3

This article is junk. There are many reasons for not offering an API to customers. I run a small b2b app between two completely non-technical businesses. There is absolutely no need for me to have an API available to them, they’ve never asked for it and have no desire or ability to consume it. Believe it or not, there is also an expense to offering an API! An API is a product offering like anything else, and products…

Not sure why you're being downvoted. An API is a feature just like any other features your company offers. If 1000 customers/clients ask for feature A and 0 ask for feature B, it's obvious you shouldn't waste your time on B. If B = "an API", and it doesn't change that it's a waste of time in this scenario.

That product methodology is sure to fill your roadmap with minor variations of button colors.

Re: API Practices If You Hate Your Customers

#124

> An operation is idempotent if performing it multiple times yields the same result as performing it exactly once. And then he casually offers an API that does different things on first and second call as the "good" example. If you have a "create a virtual machine" API it better create a fucking virtual machine. If I call the damn thing twice, I expect to have two VMs. If there is some sort of unique argument like cr…

I agree with the author re: returning success on retries; it lets you automate the retry process.

I work in mobile games; because someone might play in a tunnel or bad network area, I need to make sure that every request is retry-able.

To do that I generally include a GUID of some kind in the request; if the client says "create an entry for XXYY," there's a chance that the request will get to the server but the response will fail to reach the client.

If the client is able to retry the request (with the same GUID) and get a success response, then I can have the retries handled transparently in the communication layer; all the client code needs to know is "I made this request and it was a success," without any knowledge of how many tries it took.

If the second/third/etc request returned an error of some kind, I wouldn't have a good "success" response to hand back to the game code. (I'm assuming the "success" response contains some information that the game code needs.)

Re: API Practices If You Hate Your Customers

#125

Earlier quoted context omitted.

> XML can’t model single item arrays versus single objects. They look the same. value ... I don't really see the problem.

You need to know that “results” is an array so you need the schema. In JSON an array is an array without doubt.

If you don't know that "results" is an array... how do you handle the uncontroversial case of getting back an array of two results?

Re: API Practices If You Hate Your Customers

#126

Earlier quoted context omitted.

For example, suppose you want to distinguish between a missing/deleted resource /myuser/23123 and a completely invalid query /muser/23123. Both of these are 404 (or 410 for permanent caching) responses according to HTTP, though they have very different reasons for "non-existance".

No, the "completely invalid query" is 400. 404 is only for "the request makes sense but that specific resource doesn't exist".

400 is a very generic error. It is often used to complain about problems with the request body.

Considering how brief the HTTP RFC is, your interpretation is I think as good as any, if very uncommon. (Not what Django, Rails, Flask, etc. do).

Re: API Practices If You Hate Your Customers

#127
post #32

I was totally expecting to see something about using a protocol in an unexpected way, because "the protocol is not good enough". I had to work with an API where the company decided everything should return http code 200 (well, at least all 4XX errors), and give the error code in the JSON response, mixing existing 4XX errors and their own errors. When pointed out, the support answer was "we chose to give meaningful er…

I’ve heard the argument as: “HTTP errors for protocol level errors, 200 + json for application level errors”

And honestly it kinda make sense when you think about it that way. “404, wrong url” and “404, id not found” should be different errors.

Re: API Practices If You Hate Your Customers

#128
post #93

Earlier quoted context omitted.

Not sure why you're being downvoted. An API is a feature just like any other features your company offers. If 1000 customers/clients ask for feature A and 0 ask for feature B, it's obvious you shouldn't waste your time on B. If B = "an API", and it doesn't change that it's a waste of time in this scenario.

That product methodology is sure to fill your roadmap with minor variations of button colors.

[deleted]

Re: API Practices If You Hate Your Customers

#129

> An operation is idempotent if performing it multiple times yields the same result as performing it exactly once. And then he casually offers an API that does different things on first and second call as the "good" example. If you have a "create a virtual machine" API it better create a fucking virtual machine. If I call the damn thing twice, I expect to have two VMs. If there is some sort of unique argument like cr…

With an idempotent API, starting a VM and doing something with it can look like this:

    let new_id = generate_id();
    retry_with_backoff(() => api.make_vm(new_id));
    retry_with_backoff(() => api.do_thing_with_vm(new_id));
This works even if any individual API calls fail, or if the API call makes it to the API server but the response fails to make it to the client.

If the APIs aren't idempotent, then you would have to do this to get the same behavior:

    let new_id = generate_id();
    retry_with_backoff(() => {
      try {
        api.make_vm(new_id);
      } catch (e) {
        if (e.info && e.info.code === 'vm_already_exists') {
          return;
        }
        throw e;
      }
    });
    retry_with_backoff(() => {
      try {
        api.do_thing_with_vm(new_id);
      } catch (e) {
        if (e.info && e.info.code === 'thing_already_done') {
          return;
        }
        throw e;
      }
    });
This nonidempotent API is harder to use. Someone that doesn't know about these error codes or the fact that the API isn't idempotent will write code without the try-catch blocks that doesn't handle retries correctly. With the idempotent API, users fall into the pit of success where things just work without them having to know the details about each of the edge cases.

The nonidempotent API is exposing some extra data to the user, but it's not super useful. You basically always want to treat the vm_already_exists error identically to a success response. Maybe you also want to log some data about how many retries were necessary so you can figure out how spotty the network connection is, but there's no reason that couldn't work with the idempotent API either. The idempotent API could include a header about whether the action was already taken previously.

Consider how TCP connections are used by applications. Your application doesn't have to opt in to handling packets that were resent. The fact that some packets had to be resent is by default just an implementation detail. You have to opt in to get information about the resent packets; by default they're handled like regular successful packets. Idempotent APIs are about making handling retries work by default in a very similar way.

Re: API Practices If You Hate Your Customers

#130

Earlier quoted context omitted.

Totally agree that specific messaging is very convenient, but meaningful error messages and meaningful status codes aren't even remotely mutually exclusive. It's perfectly legitimate and even easy to send a 4xx or 5xx with a response body as JSON (or, for bonus points, with any other content type the client requests from possible server capabilities). And in my experience, having an out-of-body/band general indicator…

I'm curious, what tech stack are you working in where parsing json is difficult? You want to talk about inconsistent? How about a 500 error may or may not result in the standard response format you're expecting because it may be coming from the server and it may be coming from the API. I'd much rather my 500 errors be legitimate server problems.

While we're asking questions: what stack out there makes reading/writing HTTP headers hard? Because that's all it takes to work with response codes.

And yes, of course parsing JSON isn't difficult. Note that I said parsing messages -- the message field not the response body. And parsing that message field and checking conditionals to determine your client's behavior is something you'll have to write code for unless you're just relaying the error message back to the end user.

Now, if you have an HTTP error code, you already know something about why the error condition is happening before you look at any part of an error message field. For example, if it's a 4xx error, and you know that your client generated the associated request to the API using user data, then you can probably just pass the error message straight back to the user w/o parsing it and going through your own error message logic (although that depends on the quality of the API too).

> How about a 500 error may or may not result in the standard response format you're expecting

Well, in that case, relying on some message field you might have supposed would be in a specific JSON response isn't going to help you much either.

Might be better to have the HTTP error code and prepare your client to read responses based on multiple content types.

Post reply on HN