• Stars
    star
    167
  • Rank 226,635 (Top 5 %)
  • Language
  • License
    Other
  • Created over 3 years ago
  • Updated over 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

The Heroku HTTP API Design Guide, forked to look more like its original pre-GitBook state with a more easily digestible single-page format.

HTTP API Design Guide

Introduction

This guide describes a set of HTTP+JSON API design practices, originally extracted from work on the Heroku Platform API.

This guide informs additions to that API and also guides new internal APIs at Heroku. We hope it’s also of interest to API designers outside of Heroku.

Our goals here are consistency and focusing on business logic while avoiding design bikeshedding. We’re looking for a good, consistent, well-documented way to design APIs, not necessarily the only/ideal way.

We assume you’re familiar with the basics of HTTP+JSON APIs and won’t cover all of the fundamentals of those in this guide.

This fork

This is a fork of the original document at interagent/http-api-design before it changed to GitBook format. The intention is to bring it back to a single, unified document that's more easily digestible. I wrote a little more background in blog format here.

Contents

Foundations

Separate Concerns

Keep things simple while designing by separating the concerns between the different parts of the request and response cycle. Keeping simple rules here allows for greater focus on larger and harder problems.

Requests and responses will be made to address a particular resource or collection. Use the path to indicate identity, the body to transfer the contents and headers to communicate metadata. Query params may be used as a means to pass header information also in edge cases, but headers are preferred as they are more flexible and can convey more diverse information.

Require Secure Connections

Require secure connections with TLS to access the API, without exception. It’s not worth trying to figure out or explain when it is OK to use TLS and when it’s not. Just require TLS for everything.

Ideally, simply reject any non-TLS requests by not responding to requests for http or port 80 to avoid any insecure data exchange. In environments where this is not possible, respond with 403 Forbidden.

Redirects are discouraged since they allow sloppy/bad client behaviour without providing any clear gain. Clients that rely on redirects double up on server traffic and render TLS useless since sensitive data will already have been exposed during the first call.

Require Versioning in the Accept Header

Versioning and the transition between versions can be one of the more challenging aspects of designing and operating an API. As such, it is best to start with some mechanisms in place to mitigate this from the start.

To prevent surprise, breaking changes to users, it is best to require a version be specified with all requests. Default versions should be avoided as they are very difficult, at best, to change in the future.

It is best to provide version specification in the headers, with other metadata, using the Accept header with a custom content type, e.g.:

Accept: application/vnd.heroku+json; version=3

Support ETags for Caching

Include an ETag header in all responses, identifying the specific version of the returned resource. This allows users to cache resources and use requests with this value in the If-None-Match header to determine if the cache should be updated.

Provide Request-Ids for Introspection

Include a Request-Id header in each API response, populated with a UUID value. By logging these values on the client, server and any backing services, it provides a mechanism to trace, diagnose and debug requests.

Divide Large Responses Across Requests with Ranges

Large responses should be broken across multiple requests using Range headers to specify when more data is available and how to retrieve it. See the Heroku Platform API discussion of Ranges for the details of request and response headers, status codes, limits, ordering, and iteration.

Requests

Accept serialized JSON in request bodies

Accept serialized JSON on PUT/PATCH/POST request bodies, either instead of or in addition to form-encoded data. This creates symmetry with JSON-serialized response bodies, e.g.:

$ curl -X POST https://service.com/apps \
    -H "Content-Type: application/json" \
    -d '{"name": "demoapp"}'

{
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "name": "demoapp",
  "owner": {
    "email": "[email protected]",
    "id": "01234567-89ab-cdef-0123-456789abcdef"
  },
  ...
}

Use consistent path formats

Resource names

Use the plural version of a resource name unless the resource in question is a singleton within the system (for example, in most systems a given user would only ever have one account). This keeps it consistent in the way you refer to particular resources.

Actions

Prefer endpoint layouts that don’t need any special actions for individual resources. In cases where special actions are needed, place them under a standard actions prefix, to clearly delineate them:

/resources/:resource/actions/:action

e.g.

/runs/{run_id}/actions/stop

Downcase paths and attributes

Use downcased and dash-separated path names, for alignment with hostnames, e.g:

service-api.com/users
service-api.com/app-setups

Downcase attributes as well, but use underscore separators so that attribute names can be typed without quotes in JavaScript, e.g.:

service_class: "first"

Support non-id dereferencing for convenience

In some cases it may be inconvenient for end-users to provide IDs to identify a resource. For example, a user may think in terms of a Heroku app name, but that app may be identified by a UUID. In these cases you may want to accept both an id or name, e.g.:

$ curl https://service.com/apps/{app_id_or_name}
$ curl https://service.com/apps/97addcf0-c182
$ curl https://service.com/apps/www-prod

Do not accept only names to the exclusion of IDs.

Minimize path nesting

In data models with nested parent/child resource relationships, paths may become deeply nested, e.g.:

/orgs/{org_id}/apps/{app_id}/dynos/{dyno_id}

Limit nesting depth by preferring to locate resources at the root path. Use nesting to indicate scoped collections. For example, for the case above where a dyno belongs to an app belongs to an org:

/orgs/{org_id}
/orgs/{org_id}/apps
/apps/{app_id}
/apps/{app_id}/dynos
/dynos/{dyno_id}

Responses

Return appropriate status codes

Return appropriate HTTP status codes with each response. Successful responses should be coded according to this guide:

  • 200: Request succeeded for a GET, POST, DELETE, or PATCH call that completed synchronously, or a PUT call that synchronously updated an existing resource
  • 201: Request succeeded for a POST, or PUT call that synchronously created a new resource. It is also best practice to provide a Location header pointing to the newly created resource. This is particularly useful in the POST context as the new resource will have a different URL than the original request.
  • 202: Request accepted for a POST, PUT, DELETE, or PATCH call that will be processed asynchronously
  • 206: Request succeeded on GET, but only a partial response returned: see above on ranges

Pay attention to the use of authentication and authorization error codes:

  • 401 Unauthorized: Request failed because user is not authenticated
  • 403 Forbidden: Request failed because user does not have authorization to access a specific resource

Return suitable codes to provide additional information when there are errors:

  • 422 Unprocessable Entity: Your request was understood, but contained invalid parameters
  • 429 Too Many Requests: You have been rate-limited, retry later
  • 500 Internal Server Error: Something went wrong on the server, check status site and/or report the issue

Refer to the HTTP response code spec for guidance on status codes for user error and server error cases.

Provide full resources where available

Provide the full resource representation (i.e. the object with all attributes) whenever possible in the response. Always provide the full resource on 200 and 201 responses, including PUT/PATCH and DELETE requests, e.g.:

$ curl -X DELETE \  
  https://service.com/apps/1f9b/domains/0fd4

HTTP/1.1 200 OK
Content-Type: application/json;charset=utf-8
...
{
  "created_at": "2012-01-01T12:00:00Z",
  "hostname": "subdomain.example.com",
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "updated_at": "2012-01-01T12:00:00Z"
}

202 responses will not include the full resource representation, e.g.:

$ curl -X DELETE \  
  https://service.com/apps/1f9b/dynos/05bd

HTTP/1.1 202 Accepted
Content-Type: application/json;charset=utf-8
...
{}

Provide resource (UU)IDs

Give each resource an id attribute by default. Use UUIDs unless you have a very good reason not to. Don’t use IDs that won’t be globally unique across instances of the service or other resources in the service, especially auto-incrementing IDs.

Render UUIDs in downcased 8-4-4-4-12 format, e.g.:

"id": "01234567-89ab-cdef-0123-456789abcdef"

Provide standard timestamps

Provide created_at and updated_at timestamps for resources by default, e.g:

{
  // ...
  "created_at": "2012-01-01T12:00:00Z",
  "updated_at": "2012-01-01T13:00:00Z",
  // ...
}

These timestamps may not make sense for some resources, in which case they can be omitted.

Use UTC times formatted in ISO8601

Accept and return times in UTC only. Render times in ISO8601 format, e.g.:

"finished_at": "2012-01-01T12:00:00Z"

Nest foreign key relations

Serialize foreign key references with a nested object, e.g.:

{
  "name": "service-production",
  "owner": {
    "id": "5d8201b0..."
  },
  // ...
}

Instead of e.g.:

{
  "name": "service-production",
  "owner_id": "5d8201b0...",
  // ...
}

This approach makes it possible to inline more information about the related resource without having to change the structure of the response or introduce more top-level response fields, e.g.:

{
  "name": "service-production",
  "owner": {
    "id": "5d8201b0...",
    "name": "Alice",
    "email": "[email protected]"
  },
  // ...
}

Generate structured errors

Generate consistent, structured response bodies on errors. Include a machine-readable error id, a human-readable error message, and optionally a url pointing the client to further information about the error and how to resolve it, e.g.:

HTTP/1.1 429 Too Many Requests
{
  "id":      "rate_limit",
  "message": "Account reached its API rate limit.",
  "url":     "https://docs.service.com/rate-limits"
}

Document your error format and the possible error ids that clients may encounter.

Show rate limit status

Rate limit requests from clients to protect the health of the service and maintain high service quality for other clients. You can use a token bucket algorithm to quantify request limits.

Return the remaining number of request tokens with each request in the RateLimit-Remaining response header.

Keep JSON minified in all responses

Extra whitespace adds needless response size to requests, and many clients for human consumption will automatically "prettify" JSON output. It is best to keep JSON responses minified e.g.:

{"beta":false,"email":"[email protected]","id":"01234567-89ab-cdef-0123-456789abcdef","last_login":"2012-01-01T12:00:00Z","created_at":"2012-01-01T12:00:00Z","updated_at":"2012-01-01T12:00:00Z"}

Instead of e.g.:

{
  "beta": false,
  "email": "[email protected]",
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "last_login": "2012-01-01T12:00:00Z",
  "created_at": "2012-01-01T12:00:00Z",
  "updated_at": "2012-01-01T12:00:00Z"
}

You may consider optionally providing a way for clients to retrieve more verbose response, either via a query parameter (e.g. ?pretty=true) or via an Accept header param (e.g. Accept: application/vnd.heroku+json; version=3; indent=4;).

Artifacts

Provide machine-readable JSON schema

Provide a machine-readable schema to exactly specify your API. Use prmd to manage your schema, and ensure it validates with prmd verify.

Provide human-readable docs

Provide human-readable documentation that client developers can use to understand your API.

If you create a schema with prmd as described above, you can easily generate Markdown docs for all endpoints with prmd doc.

In addition to endpoint details, provide an API overview with information about:

  • Authentication, including acquiring and using authentication tokens.
  • API stability and versioning, including how to select the desired API version.
  • Common request and response headers.
  • Error serialization format.
  • Examples of using the API with clients in different languages.

Provide executable examples

Provide executable examples that users can type directly into their terminals to see working API calls. To the greatest extent possible, these examples should be usable verbatim, to minimize the amount of work a user needs to do to try the API, e.g.:

$ export TOKEN=... # acquire from dashboard
$ curl -is https://$TOKEN@service.com/users

If you use prmd to generate Markdown docs, you will get examples for each endpoint for free.

Describe stability

Describe the stability of your API or its various endpoints according to its maturity and stability, e.g. with prototype/development/production flags.

See the Heroku API compatibility policy for a possible stability and change management approach.

Once your API is declared production-ready and stable, do not make backwards incompatible changes within that API version. If you need to make backwards-incompatible changes, create a new API with an incremented version number.

More Repositories

1

redis-cell

A Redis module that provides rate limiting in Redis as a single command.
Rust
1,189
star
2

sorg

A Go-based static site generator that compiles brandur.org.
Go
486
star
3

json_schema

A JSON Schema V4 and Hyperschema V4 parser and validator.
Ruby
230
star
4

hutils

A collection of command line utilities for working with logfmt.
Ruby
108
star
5

tmux-extra

Configuration and scripts for sane Tmux default behavior.
Shell
108
star
6

rocket-rides-atomic

Ruby
94
star
7

sinatra-router

A tiny vendorable router that makes it easy to try routes from a number of different modular Sinatra applications.
Ruby
61
star
8

rhttpserve

A tiny HTTP server that can serve files out of any rclone remote.
Go
39
star
9

rocket-rides-unified

Ruby
27
star
10

podcore

Rust
22
star
11

microservices

21
star
12

blackswan

A project designed to provide personal data ownership and display.
JavaScript
21
star
13

heroku-buildpack-mono

ASP.NET buildpack deployed on top of Mono and XSP.
Shell
18
star
14

postgres-practices

18
star
15

rocket-rides-scalable

Ruby
16
star
16

singularity

A demonstration of a very simple static site generator that deploys to S3 through Travis CI.
Go
15
star
17

dorian

A personal identity manager and aggregator written in Rails 3.1.
JavaScript
13
star
18

casseo

A Graphite dashboard for the command line.
Ruby
12
star
19

modulir

Modulir is an experimental mini-framework for static site generation.
Go
11
star
20

connections-test

Go
10
star
21

wanikaniapi

A Go client for WaniKani's API (https://docs.api.wanikani.com/).
Go
10
star
22

qself

Qself is a small tool to sync personal data from APIs down to local TOML files for easier portability and storage.
Go
10
star
23

cmark2jira

Translate good CommonMark into bad JIRA markup.
Rust
9
star
24

brandur

Go
9
star
25

hncheck

A very simple app that checks to see if something under one of your domains has been submitted to HN, and emails you if it has.
Go
9
star
26

simple-schema

9
star
27

que-degradation-test

Ruby
8
star
28

org

A now defunct project that served my personal site.
8
star
29

mutelight-v2

Content for my technical journal at mutelight.org, designed to be used with Hekla.
Ruby
8
star
30

geotools

Updated mirror for the Geotools.Net project.
C#
8
star
31

redis-haskell

Haskell bindings for Redis, a fast persistent key-value store.
Haskell
7
star
32

logfmt

logfmt parser in Rust.
Rust
7
star
33

hekla

Responsive blogging engine for ephemeral platforms.
JavaScript
7
star
34

mastodon-cross-post

A simple project that cross-posts tweets to Mastodon.
Go
6
star
35

passages-signup

A backend for the signup forms of my newsletters "Nanoglyph" and "Passages & Glass".
Go
6
star
36

heroku-agent

A lightweight process that can communicate with the Heroku CLI and hk to provide more expendient fulfillment of API requests and better convenience of use.
Go
6
star
37

simplebox

Package simplebox provides a simple, easy-to-use cryptographic API where all of the hard decisions have been made for you in advance.
Go
6
star
38

neospring

Go
6
star
39

logs

Ruby
5
star
40

perpetual

Go
5
star
41

slides

An extremely simplistic logs-as-data implementation.
Ruby
5
star
42

rack-instruments

Rack middleware providing basic instrumentation.
Ruby
4
star
43

http_accept

Simple library for HTTP Accept header parsing and ordering.
Ruby
4
star
44

composable

Slides for my talk "Post-Rails? Composable Apps with a First-class API".
JavaScript
4
star
45

middleware-rust

Rust
4
star
46

heroku-mono-build

Heroku-based build recipe for Mono and XSP.
Shell
4
star
47

rack-robots

Rack middleware that denies all robots for staging and development environments.
Ruby
4
star
48

obsidian

Web framework in Haskell to power factz.org. Deprecated in favor of the Rails Facts app.
Haskell
3
star
49

the-surf

Article content for the Surf.
Shell
3
star
50

facts-cli

Command line interface based on Thor for the Facts sever project.
Ruby
3
star
51

mutelight-v1

Nanoc source for my blog at mutelight.org. Deprecated in favor of Askja.
Ruby
3
star
52

artifice-excon

A version of Wycat's Artifice for use with Excon.
Ruby
3
star
53

facts-api

Facts API.
Ruby
3
star
54

heroku-buildpack-mono-build

Builds binaries for the herok-buildpack-mono project.
Shell
2
star
55

db-fill

PLpgSQL
2
star
56

heroku-api-blog

JavaScript
2
star
57

nanowrimo10

My current contribution for National Novel Writing Month. See http://nanowrimo.org.
2
star
58

rakenet

Command line build and testing infrastructure for a .NET project.
Ruby
2
star
59

dping

Ruby
2
star
60

cping

Go
2
star
61

mozjpeg-builder

2
star
62

zendtools.vim

Various useful functions for working in PHP with Zend.
Vim Script
2
star
63

surf

Source code for my blog 'the Surf', uses a custom (and included) static generator.
Ruby
2
star
64

sharks

A scraping and data visualization service for Global Shark Attack File records.
Ruby
2
star
65

spring83-keygen

2
star
66

gal

Go
2
star
67

brandur-old

Rails app to power brandur.org. This has been deprecated in favor of Dorian.
Ruby
2
star
68

service-stub-example

Ruby
2
star
69

imagemagick-builder

2
star
70

sequel-instruments

Basic instrumentation for Sequel.
Ruby
2
star
71

facts-nodejs

Facts database for increasing retention of general knowledge, and for winning arguments. Deprecated in favor of the Rails version.
JavaScript
2
star
72

facts

A tool for getting smarter and winning arguments.
Ruby
2
star
73

d2

A cross-version compatible shortcut for invoking a Ruby debugger.
Ruby
2
star
74

omniauth-heroku

Ruby
2
star
75

postgres-table-rename-test

Ruby
2
star
76

askja

Minimalist blogging platform written in Rails as a flexible alternative to static generators.
Ruby
2
star
77

facts-web

Frontend interface that talks to facts-api.
JavaScript
2
star
78

asp-net-sample

Sample ASP.NET MVC application that can run on Heroku.
C#
1
star
79

wgt2

Go
1
star
80

magmv

A program to rename poorly titled magazine PDF files.
Go
1
star
81

deathguild

Creates Spotify playlists for each night of Death Guild.
HTML
1
star
82

cheat-sheets-dev

A set of cheat sheets designed to centralize information to quicken the development process.
1
star
83

heroku-hyper-schema

1
star
84

umbrella-rust

Rust
1
star
85

certrotate

Go
1
star
86

php-mbstring-overload-tests

Simple test suite to demonstrate PHP's mbstring function overloading.
C
1
star
87

the-surf-old

Next generation of the Surf (journal). Deprecated in favor Hekla.
Ruby
1
star
88

schemadoc

Ruby
1
star
89

fireball

Go
1
star
90

qself-brandur

1
star
91

submodule-dud

1
star
92

archlinux-packages

My contributed PKGBUILDs for the Archlinux AUR.
1
star
93

millstone

1
star
94

neospring-bridge

Go
1
star
95

csrf

Go
1
star
96

wkunburn

Go
1
star
97

dummy

Empty repository.
1
star
98

wgt

A WGT-related playground.
Go
1
star
99

transaction-philosophy

HTML
1
star
100

tailwind-experimenting

CSS
1
star