Contents

Should you follow REST “standards”? How Apple's API surprised us!

Michał Matłoka

06 Apr 2023.7 minutes read

Should you follow REST “standards”? How Apple's API surprised us! webp image

Introduction

REST APIs are arguably the most popular communication mechanisms of public web APIs. When you want to integrate with 3rd party service it is expected that you’ll need to leverage REST. Sometimes you may find additional SDKs or Open API definitions, which makes work with that API easier, and sometimes you just find the documentation. Today let’s talk about standards, and expectations, but also about what still can be found in the real world APIs.

Recently we’ve been working on a project for one of our customers. The project includes mobile applications which will integrate with payment systems, including Apple Wallet. After the purchase, the transactions are validated on the backend side, just before granting some licenses or virtual currencies for games, to confirm that a given user really paid the money. We’ve started from the research, we found the proper docs for Apple which pointed us to the proper API endpoint. Let’s discuss its design.

Remark: we don’t have context for the technical decisions made when designing that API. Maybe it’s a form of backward compatibility, the need to integrate with some legacy systems or something else of similar significance. Maybe it’s not even considered as REST, but just as a “HTTP-API”, we can only speculate. However, an interesting fact is that other APIs are designed totally differently.

Richardson Maturity Model

During our discussion, let’s mention the Richardson Maturity Model. It’s a model which divides REST APIs maturity into four levels. Let’s sum them up very shortly :

  • Level 0 - base level for web-based API. For example you could have a POST /createProduct endpoint here.
  • Level 1 - API paths mention resources. It is expected that URIs contain nouns rather than verbs. You’d expect to see paths like /product/1 instead of the /createProduct.
  • Level 2 - HTTP verbs are being leveraged in a more standardized way - GET is used for retrieving data, POST/PUT for creation etc. For example, to retrieve a product you’d call GET /product/1 and to create a new one POST /product.
  • Level 3 - HATEOAS (Hypertext As The Engine Of Application State) - resources returned from the API don’t contain plain IDs, but URIs pointing to related resources. It is very rare to meet such APIs, they are often just too complex during the implementation without support from additional libraries. Referring to our previous example, GET /product/1 response would include links to all related entities, which you could follow to explore the whole dataset.

In most cases, we meet the Level 2 APIs.

HTTP Verb and endpoint path

Let’s go back to the Apple Verify Receipt endpoint. The first thing we can see is the API endpoint: POST https://buy.itunes.apple.com/verifyReceipt (case sensitive!). The main role of that API is to verify receipt data (encoded string passed from mobile). Could it be modeled in a more RESTful manner? Let’s check that. Path does not operate on resources (there is a verb in the path), and we’re doing a POST, but in practice we’re not creating anything. Looks a bit like Level 0 actually.

How could we design it differently? Actually, it is not so obvious. Thinking from scratch it could be a GET request with a query param. GET because actually, we’re retrieving a transaction receipt containing info about the bought product. However, the encoded receipt data, which is passed from mobile, may contain even over 6000 characters. There are some recommendations to keep query params below 2048. What are other choices? GET with body is actually allowed by the HTTP 1.1 2014 Specification. Unfortunately, specification is one thing and reality is different. Many REST client libraries simply do not support it, so it would also not be a good choice. Are there any other choices? We may consider doing a POST, which creates a verification resource. But again, probably, we don’t create anything here underneath.

What does the competition do? Let’s take a look at Google Play API. We can see there

GET https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/products/{productId}/tokens/{token}.

It definitely looks more RESTful. The provided productId and token are much shorter strings than receipt data from Apple. The real question here is what those endpoints do underneath, we could speculate that Google fetches data from DB and Apple just decodes provided data with some private key. If that’s the case, then we can’t really compare them.

Although the design choices can surprise us, the important thing to remember is that REST is not so easy to model, as it may seem. There may be no single good answer which satisfies all the best practices.

Status codes

image1

Status codes are actually what surprised me in the given Apple’s API. The analyzed endpoint always returns 200 OK. Errors are returned in the response body in the form of a status field - 0 means valid, and other values means error. So let’s say, what happens when you accidentally execute GET on that endpoint - 200 OK, body with status 21000. What happens when you provide a faulty input (e.g. input which is not a proper JSON)? 200 OK, body with status 21002. 200 OK, according to the HTTP specs means that request succeeded. There are discussions on the Internet if it should be allowed to return 200 OK with Business errors inside (since the request succeeds, just the logic fails), but I think they are totally different from the case which we have here. An illegal HTTP method definitely does not fit the 200 OK.

What implications does it have on work with the API? It’s definitely not something a developer may expect straight away. Some strongly typed REST clients assume that 200 OK is a successful situation and try to deserialize response to a given format. Of course it is possible to handle such cases, but it is a bit more complicated than usual.

Note: GraphQL query language is also served over HTTP. In its case, common practice is to return errors in the predefined body format together with the status 200 OK. However, depending on the implementation, some errors may be returned with different status codes. As you can see, depending on the communication protocol, “standards” and practices, HTTP can be used totally differently.

JSON Naming convention

There is one more thing which actually made me wonder, why? This is the potential request body:

{
   “receipt-data”: “...”, …
}

and this is the response:

{
   “status”: 0,
   “is_retryable: …,
   …
}

Do you see the difference? Requests are using kebab-case and responses snake_case. Again, it is something you may live with, but some json libraries assume that you’ll use one type of casing for your objects. There is no standard for naming keys in JSON, some companies use snake_case (e.g. GitHub or Zalando), other ones camelCase (e.g. other Apple APIs. The choice often depends on the used programming language.

Authentication

API Security is always an important topic. REST APIs can be authenticated in various ways. The most common ones would be to pass keys using headers. In older APIs you could see keys being passed in query parameters (not recommended) or the request body. That’s actually the case here. The body contains a field password. Again, not the most typical choice, but we can live with it. It is usually more problematic to handle on the backend than client side, because to authenticate the request you have to first deserialize the request body. What is more, if you choose to have consistent APIs, what would you do in case of GET endpoint, which rather should not have a body (see previous paragraph about HTTP verbs)?

Conclusions

REST API design is not standardized in most areas. Apart from the HTTP Spec, there are only good practices which you may choose to follow or not. I think that still the most important things are consistency and good quality documentation so that work with API is just easier. Companies try to write down their guidelines, but as you can see e.g. in the case of Zalando, Google Cloud or GitHub they can get awfully huge! I will say that again, REST API is not the easiest one to design!

When you create an API, you have to remember its users. Resources such as the Open API specification are super useful. It can be used with Postman or to generate client libraries for selected languages. Things like a sandbox environment or sample requests which just work out of the box are also great thing which just makes the work easier.

Apple’s verify receipt API design definitely stands out and may confuse its users. As mentioned before, we don’t know why it was designed this way, but we can only suspect there were good reasons for that.

Reviewed by: Krzysztof Ciesielski, Jacek Kunicki

Blog Comments powered by Disqus.