• Martin Thoma
  • Home
  • Categories
  • Tags
  • Archives
  • Support me

API Design

Contents

  • API Design
    • General Thoughts
      • Character Sets
      • Monitoring
      • Server Time
    • Naming
    • Versioning
    • List-returning Endpoints
    • Documentation
    • Errors
    • Security
      • HTTPS
      • Authentication
      • Input Validation
    • Other Stuff
      • HTTP Methods
      • HTTP Response Status codes
    • GraphQL
    • See also

Today, I gave API design a little bit of thought. Web APIs to be more specific. So here are some good practices I've seen. Feel free to add more in the comments 😊

General Thoughts

An API is written because you want to make a public interface to your application. You will not know who is using your service. For this reason, you have to keep things running for quite a while. Changes in an endpoint could potentialy cause another service to crash. You want your stuff to be reliable, so think about that in advance.

Oh, and JSON is likely the most common way to return information in the web. So make sure your responses are valid JSON and you include Content-Type: application/json in the header. Consequently, you can expect the payload to be valid JSON as well.

Character Sets

Use UTF8. Always. Everywhere.

See also: Should character encodings besides UTF-8 (and maybe UTF-16/UTF-32) be deprecated?

Monitoring

You offer a service, so please make sure it is available. You could use services like Runscope which ping your endpoints regularly.

To give your users a way to check if things look normal, you could create a status page. A popular choice is statuspage.io. Some examples of status pages:

  • status.aws.amazon.com
  • status.github.com
  • status.bitbucket.org

Server Time

Make sure your server time is not too off. This makes comparing logs of different systems easier. And it could be that you use the system time directly or indirectly as an API output. You can use an NTP server to sync the time.

For manual checking.

$ ntpq -pn
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
 0.ubuntu.pool.n .POOL.          16 p    -   64    0    0.000    0.000   0.000
 1.ubuntu.pool.n .POOL.          16 p    -   64    0    0.000    0.000   0.000
 2.ubuntu.pool.n .POOL.          16 p    -   64    0    0.000    0.000   0.000
 3.ubuntu.pool.n .POOL.          16 p    -   64    0    0.000    0.000   0.000
 ntp.ubuntu.com  .POOL.          16 p    -   64    0    0.000    0.000   0.000
+92.222.82.98    130.149.17.8     2 u   75  128  377   24.430   -0.043   1.522
*185.51.192.34   46.243.26.34     2 u   44  128  377   27.447   -0.319   1.120
-212.18.3.19     212.18.1.106     2 u   61  128  377   16.443   -0.317   1.454
+88.159.1.196    193.190.230.66   2 u   47  128  377   29.346    0.564   1.828
-192.33.96.102   .PPS.            1 u  113  128  377   40.525    3.008   2.11

Naming

Suppose we would have an API which lets us create / delete / receive users. I would expect the following:

  • GET domain.io/api/users: Returns a list of all users
  • GET domain.io/api/users/42: Returns the user with ID 42
  • POST domain.io/api/users: Creates a new user
  • DELETE domain.io/api/users/42: Deletes the user with ID 42

This kind of logic should be consistent through the complete API.

For the filed names, I suggest to use camelCase as it is usually done with JSON.

For the names, I suggest to use plural forms. The main point is to be consistent.

And, of course, there is the actual value of IDs. It might not be desirable that others can iterate through whatever has the ID in your system. UUID could be one alternative.

Versioning

Having an URL structure like domain.io/api/v1/foobar is really nice. This allows you to change some of the practices and be consistent. So you could follow some naming schemas vor /v1/* and others for /v2/*. Or change the authentication.

List-returning Endpoints

Please note: You might want to read Web API Pagination with the 'Timestamp_Offset_Checksum' Continuation Token - don't take the following as the best option for granted!

Always when you return lists, make it paginated. Add a default sorting and an offset parameter. You could also add an order and a limit parameter.

The endpoint should return

{"continue": 100, "data": FOOBAR}

where the value of continue is the next index to continue. So in case you decide that an limit of 100 returned elements is enough / reasonable, the the first query that has more than 100 elements would return the continue of 100. A call with an offset=142 wuld have an "continue": 242 in the response, if there are at least 242 elements.

It is done similar in the MediaWiki API.

Documentation

Add an format parameter to every GET query. If it is not specified, then you can return documentation and/or a formatted response.

See MediaWiki as an example.

Also, you should be really careful about units:

  • Prices: Always document the currency. I suggest to use cents so that you can use integers and don't have to deal with floating point precision problems.
  • Dates: There are timezones. I suggest to use the ISO 8601 with datetime as used for ECMA consistently: YYYY-MM-DDTHH:mm:ss+00:00 (UTC), e.g 2018-03-07T21:37:24+00:00.
  • SI Units: Use SI units as a default. If you don't use them, make a big warning sign.

Errors

Help your API users by making nice error messages. Something like

HTTP Status 400

{
    "errors": [
        {
            "status": 400,
            "error": "FIELDS_VALIDATION_ERROR",
            "detail": "Invalid country 'German'.",
            "fields": {
                "country": "Invalid country. See ISO 3166 ALPHA-3 for valid values."
            },
            "code": 1337,
            "links": {
                "docs": "http://domain.io/docs",
                "errors": "http://domain.io/docs/errors#1337"
            }
        }
    ]
}

Security

HTTPS

Use HTTPS.

Authentication

Give an concrete example how one should authenticate. Most likely, there will be a token system. Be absolutely clear when the tokens expire.

If loosing the token could lead to people not being able to access your service for a while, make sure they know it! This could be the case if one account may only have one active token.

See also:

  • Authentication versus Authorization
  • Introduction to JSON Web Tokens

Input Validation

Don't let users input go directly to the database / be part of the execution.

See SQL injection

Other Stuff

There are a couple of other rules, but I'm not sure how important they actually are:

HTTP Methods

Copied from developer.mozilla.org:

  • GET: [...] Requests using GET should only retrieve data.
  • POST: The POST method is used to submit an entity to the specified resource, often causing a change in state or side effects on the server
  • PUT: The PUT method replaces all current representations of the target resource with the request payload.
  • PATCH: The PATCH method is used to apply partial modifications to a resource.
  • DELETE: The DELETE method deletes the specified resource.

HTTP Response Status codes

Success: 2XX

  • 200 OK: Successful GET, PUT, PATCH
  • 201 Created: Successful POST
  • 204 No Content: For successful DELETE

Client Errors: 4XX

  • 400 Bad Request: E.g. the parameters are invalid
  • 401 Unauthorized
  • 403 Forbidden: Authenticated, but the client may not access the endpoint
  • 404 Not Found
  • 410 Gone: For deprecation

Server Errors: 5XX

  • 500 Internal Server Error
  • 503 Service Unavailable: Maintenance

GraphQL

This article was mainly about REST-like APIs. GraphQL is a data query language developed internally by Facebook which is an alternative to REST, see GraphQL vs REST.

My impression is that GraphQL is nice because you need less documentation. You buy that advantage with overhead in writing the resolver.

See also

  • RESTful API Design. Best Practices in a Nutshell.
  • jsonapi.org

Published

Mär 7, 2018
by Martin Thoma

Category

The Web

Tags

  • API 2
  • Design 8
  • Web 2

Contact

  • Martin Thoma - A blog about Code, the Web and Cyberculture
  • E-mail subscription
  • RSS-Feed
  • Privacy/Datenschutzerklärung
  • Impressum
  • Powered by Pelican. Theme: Elegant by Talha Mansoor