The basics of API design picked up by watching the PluralSight API Design Course by Shawn Wildermuth.

# Resources

See also: HTTP Response Codes are defined in W3C RFC2616. jsonapi.org has info on standard ways of representing data.

The way APIs are designed is important because:

  • APIs are the UI layer to developers
  • Easy to use APIs are more readily adopted
  • Picking the right level of API is crucial to success
  • Don’t surprise your users
  • Balance what is good for the user with what is good for the server
  • Ultimately, users are what make a successful API

There is no real right or wrong way. Just avoid dogma and be pragmatic.

# URIs

  • Nouns are good, verbs are bad.
  • URIs should point at nouns (e.g. resources)
  • Prefer plural form
http://api/Customers
http://api/Invoices
1
2

Use identifiers to locate individual items in URIs. Doesn’t have to be internal key as long as it is unique.

http://api/Customers/123
http://api/Games/halo-3
http://api/Invoices/2014-01-01
1
2
3

# HTTP Verbs

Resource GET POST PUT DELETE
/customers Get list Create item Update batch Error
/customers/123 Get item Error Update item Delete item

What to Return

Resource GET POST PUT DELETE
/customers List New item Status code Error code
/customers/123 Item Error code Updated item Status code

# Status Codes

The HTTP specification defines the status codes. Below are some common ones:

Code Description
200 OK
201 Created
202 Accepted
302 Found
304 Not Modified
307 Temp Redirect
308 Perm Redirect
400 Bad Request
401 Not Authorized
403 Forbidden
404 Not Found
405 Method Not Allowed
409 Conflict
500 Internal Error

Limit the number of returned status codes to keep the API simple. Consider these as a bare minimum:

Code Description Real Story
200 OK It Worked
400 Bad Request Your Bad
500 Internal Error Our Bad

The following could be useful for clients:

Code Description
201 Created
304 Not Modified
401 Unauthorized
403 Forbidden
404 Not Found

Sample of Sensible Defaults:

Code Description
200 OK Successful request
204 No Content File contains no data
301 Moved Permanently The requested resource has permanently moved to another address
401 Not Authorized Athorization needed to access resource
403 Forbidden
404 Not Found The requested resource cannot be found
408 Request Timeout. The Request timed out
500 Server Error

# Associations

Using URIs

http://api/Customers/123/Invoices
http://api/Games/halo-3/Ratings
http://api/Invoices/2014-01-01/Payments
1
2
3

Returns single object or list of objects. The format should be the same as when loading associated objects as when loading them as main resources. A resource may include multiple associations:

http://api/Customers/123/Invoices
http://api/Customers/123/Payments
http://api/Customers/123/Shipments
1
2
3

For more complex needs use query params

http://api/Customers/123/Invoices?date=2014-0101&type=credit
1

# Formatting the Results

  • Single Results should be simple objects
  • Member names should not expose server details and be camelCased or at least be consistent.
  • Collections typically use an object wrapping a simple collection to hold information about the collection and not just the items.
{
    "total": 120,
    "results": [
        {"id": 1,...}, ...
    ]
}
1
2
3
4
5
6

# Response Formats

  • See JSend format.
  • Content negotiation is a best practice. Use Accept header to determine format. Accept: application/json, text/xml
  • Can use URIs to specify format but not recommended. Use only if you need to use clients that cannot use headers.
http://api/Customers?format=json
http://api/Customers.json
http://api/Customers?format=json&callback=foo
1
2
3
  • Not necessary to support lots of formats. Use a sane default, usually JSON.
Type MIME Type
JSON application/json
XML text/xml
JSONP application/javascript
RSS application/xml+rss
ATOM application/xml+atom

# Entity Tags (ETags)

  • ETags are used to support smart server caching
  • Supports both strong and weak caching and are typically returned in the response header
  • The ETag represents a version of the results returned
  • Weak ETags means results are semantically the same whereas strong ETag suggests they are byte by byte identical. Weak ETags are prefixed with W/.
HTTP/1.1 200 OK
Content-Type:application/json; charset=utf-8
Date: 20-01-2014 16:46:52 GMT
ETag: "8934675928367085"
Content-Length: 1024
1
2
3
4
5
HTTP/1.1 200 OK
Content-Type:application/json; charset=utf-8
Date: 20-01-2014 16:46:52 GMT
ETag: W/"8934675928367085"
Content-Length: 1024
1
2
3
4
5
  • Client sends along ETag with request to check for new data.
GET /api/customers/123 HTTP/1.1
Accept: application/json
Host: localhost:8863
If-None-Match: "8934675928367085"
1
2
3
4
  • If the above request would result in the same object the server returns a 304 Not Modified with an empty body.
HTTP/1.1 304 Not Modified
1
  • For PUTs you can use If-Match to check that the object you want to save hasn’t been modified.
PUT /api/customers/123 HTTP/1.1
Accept: application/json
Host: localhost:8863
If-Match: "8934675928367085"
...
1
2
3
4
5
  • If server has matching ETag for object then PUT should be allowed otherwise the PUT should return 412 Precondition Failed and client needs to GET a more recent version before trying to PUT again.

# Paging

  • Lists should always support paging for restricting the amount of data sent over the network to the client and for reducing the amount of work the server does for a single request.
  • Use query string parameters for paging information. For instance pageSize is typical for allowing the client to determine how many he wants/call.
  • Commonly use the list object wrapper for next/prev links
  • Return totalSize of the dataset so that the client can calculate how many pages there would be.
http://api/games/?page=2&pageSize=50

{
    "total": 1598,
    "next": "http://api/Invoices/?page=3",
    "prev": "http://api/Invoices/?page=1",
    "results": [...]
}
1
2
3
4
5
6
7
8
  • Set an upper limit for pageSize to something reasonable for the server to process at any one time.

# Partials

  • APIs should allow for requesting partial items. This can significantly reduce data traffic.
  • Allowing the client to specify what fields it needs in a particular case.
  • Using the query string is a common pattern http://api/invoices/123?fields=id,invoiceDate,totalAmount
  • Updating of partial items could be supported with the PATCH verb
  • Updating partials would be safer if you implement ETag support

# Non-Resource APIs

  • Handling of the functional parts of the API that cannot easily be solved by using resources and HTTP verbs.
  • Be pragmatic and document these parts properly
  • API user should be able to distingguish these types of operations from the normal usage of resources
  • Usually handled by calling get requests and functional, not resource based
  • Don’t use an an excuse to build RPC API
http:/&/api/calculateTax?state=GA&total=149.90
1

# API Versioning

  • A published API is a contract and should not be changed. Client rely on a fix API or they will break.
  • Versioning is a way of evolving the API without breaking existing clients
  • Don’t tie API versioning to product versions
  • No best way of versioning APIs but we can learn from existing ones

# Versioning Styles

  • Using URI path http://api.tumblr.com/v2/user/ like Tumblr.
  • Using a Query param http://api.netflix.com/catalog/titles/series/70023522?v=1.5 like Netflix
  • Using content negotiation: Content-Type: application/vnd.github.1.param+json like GitHub
  • Using a request header: x-ms-version:2011-08-18 like Windows Azure

# Versioning using the URI path

  • Everything after the version number is open to change
  • Allows for drastic changes
  • Simple to segregate old APIs for backwards compatibility
  • Could require lots of client changes as you version
  • Increases the size of the URI surface area you have to support
  • Can expand reach but also icrease technical debt
http://api.attracs.com/v1/Customers?type=Current&id=123
http://api.attracs.com/v2/CurrentCustomers/123
1
2

# Versioning using Query Param

  • Version can be an optional parameter. Latest version by default
  • Doesn’t impose huge client changes as versions mature.
  • You can surprise developers with unexpected changes and break clients
http://api.attracs.com/Customers
http://api.attracs.com/Customers?v=2.1
1
2

# Versioning using Content Negotiation

  • Becoming increasingly popular because the versioning is separated from the API surface area.
  • Packages API and Resource Versioning together. Versioning can be tied to specific resources
  • Client don’t have to change URIs
  • Adds complexity. Some clients may not be able to use headers
  • Can encourage increased versioning and overall complexity

Use custom MIME types in header. Standard indicates you should use “vnd.” (meaning vendor) as prefix to the MIME type

GET /Customers/123
HOST:http://api.attracs.com
Accept: application/vnd.amc.v1.customer

Accept: application/vnd.amc.v1.customer.json
1
2
3
4
5

# Versioning using Custom Header

  • Use a header value that is only recognised by the API
  • Common to use date instead of a version number
  • Separates versioning from API call signatures
  • Not tied to resource versioning
  • Adds complexity. Some clients may not be able to use headers
GET /Customers/123
HOST:http://api.attracs.com
x-AMC-Version: 2.1

x-AMC-Version: 2014-01-01
1
2
3
4
5

It’s hard to generally say which style is best. Content Negotiation and Custom Header styles are popular but adds some complexity. Versioning with URI components are still more common. These are typically easier to implement but can add more technical debt.

The API should support versioning from the first release.

# Resource Versioning

  • Versioning API calls isn’t enough. The resources themselves should be versioned too because structures and constraints change over time.
  • Versioning resources is easier using custom content types
  • Including versioning data in the resource body is an option, but it pollutes the data

# Securing the API

  • Security should be part of the original design. don’t patch it on as an afterthought.
  • Should be secure by default. Don’t return sensitive information before authorized

You need to secure the API if:

  • You’re using personalized or private data
  • Sending sensitive data across the wire
  • Using credentials of any kind
  • Protecting against overuse of servers

Threats:

  • Eavesdroppers (packet sniffers etc)
  • Hackers or even personnel (Intrusion and physical security)
  • Users/Hackers looking at source for accessing the API

Securing the server infrastructure is another topic

Secure In-Transit (protecting data when it goes across the wire). Using SSL is typically worth the expense

Securing the API consists of Cross-Origin Security and Authorization/Authentication.

# Cross Domain Security

Should you allow calls from separate domains? If internal, probably not. If public facing you might need to support cross-domain.

Two Approaches:

  • Support JSONP format
  • Enable Cross-Origin Resource Sharing (CORS)
# JSONP
function updateUser(data) {
    //use Data
}


GET /api/games?callback=updateGames HTTP/1.1
Accept: application/javascript
Host: localhost:8863

-> updateUser({total=1, results=[...]});
1
2
3
4
5
6
7
8
9
10

The data sent back from the server will be the same. It will just be wrapped in a function call.

# CORS
  • Allows Cross Site support from browser but requires handshake
  • Implementing can be difficult but many platforms support it
  • Rules implemented on server

# Authentication

Who is calling the API?

  • Server-to-Server Authentication using API keys and shared secrets
  • User Proxy Authenticaton using OAuth and similar
  • Direct User Authentication using cookies or tokens. Common for private APIs
  • Credential: A fact that can describe an identity like username and password

Authentication: Validate a set of credentials to identify an entity (whether virtual or actual).

Authorization: Verification that an entity has right to access a resource or action.

# API Keys

  • For non-user specific API usage like AWS, Google maps for monitoring usage
  • Sign up for API key
  • System returns API key and shared secret (usually for encryption)
  • Developer creates a request providing the API key.
  • A Signature is created for the requestusing the shared secret
  • Request + Signature is sent to service
  • Server looks up shared secret for user via API key and signs the request with it
  • Server verifies same signatures and within timeout
  • If all valid server executes request and returns data

# User Security/Authenticating Users

If your system has users, how do you verify which user is calling the API?

  • For 1st party solutions you could use what the web server provides like using Windows Authentication or ASP.NET forms authentications.
  • For 3rd party API developers don’t ask them to collect credentials, use OAuth

# OAuth

For allowing developer to act as a user in your system but allows you to keep control of accepting credentials

Then trust a 3rd party with a token that represents the API developer. The developer will never receive the user credentials.

# Hypermedia

Hypermedia is links for APIs helping developers to know how to use the API Links helps APIs become self-describing Links become the application state

  • Hypermedia As The Engine of Application State (HATEOAS)
  • HATEOAS add links on top of REST-ful HTTP

What kinds of links to provide:

  • Paging
  • Creating New Items
  • Retrieving Associations
  • Actions
{
    totalResults: 1000,
    links: [
        {"href": "/api/v1/games?page=1", "rel": "prevPage" },
        {"href": "/api/v1/games?page=3", "rel": "nextPage" },
        {"href": "/api/v1/games", "rel": "insert" }
    ],
    "results": []
}
1
2
3
4
5
6
7
8
9
{
    links: [
        {"href": "/api/v1/games/2", "rel": "self" },
        {"href": "/api/v1/games/2/rating", "rel": "rating" }
    ],
    "id": 2,
    "name": "Final Fantasy XIII",
    ...
}
1
2
3
4
5
6
7
8
9

Profile Media Types

  • Profiles are descriptions of data returned
  • Alternative to using custom MIME types in versioning
  • Should be included in the Accept header
  • Server can return profile for client
GET /api/order/123
HOST: http://..
Accept: appliaction/json;profile=http://api.attracs.com/orders
1
2
3

# Hypermedia Standards

The standards are emerging None are final

  • HAL
  • Collection+JSON

Based on content types with profile media type Content type defines data formatting Profile media type defines the structure of the data Keeps the format and structure/version separated

# HAL

  • Hypertext Application Language strives to be a lean hypermedia type
  • Supports standard formats like JSON and XML for including resources and links
content type application/hal+json
content type application/hal+xml
content type application/hal+json;profile=http://api.attracs.com/orders
1
2
3

Resources contains state and links plus embedded resources containing state and links

{
    "_links":{
    "self": {"href": "/games"},
    "next": {"href": "/games?page=2"},
    "find": {"href": "/games{?query}", "templated": true},
    "totalCount": 100,
    "_embedded": {
        "results": [
            {
                "_links": {
                    "self": {"href": "/games/123"},
                    ...
                },
                "price": 30.00,
                "currency": "USD",
                "name": "Halo 3"
            },
            ...
        ]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Template Links: Used when URI includes a template (e.g. RFC6570)

# Collection + JSON

  • A standard for reading and writing collections
  • Communicating lists and individual items
  • Includes UI elements in messages
  • Good for simple machine driven lists but a bit verbose for data-driven REST
  • Including UI elements sets scene for automating code
content type application/vnd.collection+json
content type application/vnd.collection+json;profile=http://foo.com/bar
1
2
{
    "collection": {
        "version": "1.0",
        "href": "http://api.attracs.com/Orders",
        "links": [
            { "rel": "feed", "href": "http://api.attracs.com/Orders/rss" }
        ],
        "items": [
            {
                "href": "http://api.attracs.com/Orders/1",
                "data": [
                    { "Id": 1, "Date": "2014-01-01"},
                    ...
                ],
                "links": [
                    {
                        "rel": "customer",
                        "href": "http://api.attracs.com/Orders/1/customer",
                        "prompt": "Show Customer"
                    },
                    ...
                ]
            }
        ]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

It also includes queries and templates for UI building:

{
    "collection": {
        ...
        "queries": [
            {
                "rel": "search",
                "href": "http://api.attracs.com/Orders/search",
                "prompt": "Find Order",
                "data": [
                    {"name": "search", "value": ""}
                ]
            }
        ],
        "template": {
            "data": [
                {"name": "id", "value": "", "prompt": "Order Id"},
                {"name": "date", "value": "", "prompt": "Order Date"},
                ...
            ]
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Updated: 8/15/2021, 1:35:39 PM