├── LICENSE.md └── README.md /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## License 2 | 3 | Copyright held by [project contributors](https://github.com/interagent/http-api-design/graphs/contributors). 4 | 5 | Released under a [Creative Commons Attribution 3.0 Unported License](http://creativecommons.org/licenses/by/3.0/). 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTTP API Design Guide 2 | 3 | ## Introduction 4 | 5 | This guide describes a set of HTTP+JSON API design practices, originally 6 | extracted from work on the [Heroku Platform API](https://devcenter.heroku.com/articles/platform-api-reference). 7 | 8 | This guide informs additions to that API and also guides new internal 9 | APIs at Heroku. We hope it’s also of interest to API designers 10 | outside of Heroku. 11 | 12 | Our goals here are consistency and focusing on business logic while 13 | avoiding design bikeshedding. We’re looking for _a good, consistent, 14 | well-documented way_ to design APIs, not necessarily _the only/ideal 15 | way_. 16 | 17 | We assume you’re familiar with the basics of HTTP+JSON APIs and won’t 18 | cover all of the fundamentals of those in this guide. 19 | 20 | ## This fork 21 | 22 | This is a fork of the original document at 23 | [interagent/http-api-design](https://github.com/interagent/http-api-design) 24 | before it changed to GitBook format. The intention is to bring it back to a 25 | single, unified document that's more easily digestible. I wrote a little more 26 | background [in blog format 27 | here](https://brandur.org/fragments/heroku-http-api-design-guide). 28 | 29 | ## Contents 30 | 31 | * [Foundations](#foundations) 32 | * [Separate Concerns](#separate-concerns) 33 | * [Require Secure Connections](#require-secure-connections) 34 | * [Require Versioning in the Accept Header](#require-versioning-in-the-accept-header) 35 | * [Support ETags for Caching](#support-etags-for-caching) 36 | * [Provide Request-Ids for Introspection](#provide-request-ids-for-introspection) 37 | * [Divide Large Responses Across Requests with Ranges](#divide-large-responses-across-requests-with-ranges) 38 | * [Requests](#requests) 39 | * [Accept serialized JSON in request bodies](#accept-serialized-json-in-request-bodies) 40 | * [Use consistent path formats](#use-consistent-path-formats) 41 | * [Downcase paths and attributes](#downcase-paths-and-attributes) 42 | * [Support non-id dereferencing for convenience](#support-non-id-dereferencing-for-convenience) 43 | * [Minimize path nesting](#minimize-path-nesting) 44 | * [Responses](#responses) 45 | * [Return appropriate status codes](#return-appropriate-status-codes) 46 | * [Provide full resources where available](#provide-full-resources-where-available) 47 | * [Provide resource (UU)IDs](#provide-resource-uuids) 48 | * [Provide standard timestamps](#provide-standard-timestamps) 49 | * [Use UTC times formatted in ISO8601](#use-utc-times-formatted-in-iso8601) 50 | * [Nest foreign key relations](#nest-foreign-key-relations) 51 | * [Generate structured errors](#generate-structured-errors) 52 | * [Show rate limit status](#show-rate-limit-status) 53 | * [Keep JSON minified in all responses](#keep-json-minified-in-all-responses) 54 | * [Artifacts](#artifacts) 55 | * [Provide machine-readable JSON schema](#provide-machine-readable-json-schema) 56 | * [Provide human-readable docs](#provide-human-readable-docs) 57 | * [Provide executable examples](#provide-executable-examples) 58 | * [Describe stability](#describe-stability) 59 | 60 | ### Foundations 61 | 62 | #### Separate Concerns 63 | 64 | Keep things simple while designing by separating the concerns between the 65 | different parts of the request and response cycle. Keeping simple rules here 66 | allows for greater focus on larger and harder problems. 67 | 68 | Requests and responses will be made to address a particular resource or 69 | collection. Use the path to indicate identity, the body to transfer the 70 | contents and headers to communicate metadata. Query params may be used as a 71 | means to pass header information also in edge cases, but headers are preferred 72 | as they are more flexible and can convey more diverse information. 73 | 74 | #### Require Secure Connections 75 | 76 | Require secure connections with TLS to access the API, without exception. 77 | It’s not worth trying to figure out or explain when it is OK to use TLS 78 | and when it’s not. Just require TLS for everything. 79 | 80 | Ideally, simply reject any non-TLS requests by not responding to requests for 81 | http or port 80 to avoid any insecure data exchange. In environments where this 82 | is not possible, respond with `403 Forbidden`. 83 | 84 | Redirects are discouraged since they allow sloppy/bad client behaviour without 85 | providing any clear gain. Clients that rely on redirects double up on 86 | server traffic and render TLS useless since sensitive data will already 87 | have been exposed during the first call. 88 | 89 | #### Require Versioning in the Accept Header 90 | 91 | Versioning and the transition between versions can be one of the more 92 | challenging aspects of designing and operating an API. As such, it is best to 93 | start with some mechanisms in place to mitigate this from the start. 94 | 95 | To prevent surprise, breaking changes to users, it is best to require a version 96 | be specified with all requests. Default versions should be avoided as they are 97 | very difficult, at best, to change in the future. 98 | 99 | It is best to provide version specification in the headers, with other 100 | metadata, using the `Accept` header with a custom content type, e.g.: 101 | 102 | ``` 103 | Accept: application/vnd.heroku+json; version=3 104 | ``` 105 | 106 | #### Support ETags for Caching 107 | 108 | Include an `ETag` header in all responses, identifying the specific 109 | version of the returned resource. This allows users to cache resources 110 | and use requests with this value in the `If-None-Match` header to determine 111 | if the cache should be updated. 112 | 113 | #### Provide Request-Ids for Introspection 114 | 115 | Include a `Request-Id` header in each API response, populated with a 116 | UUID value. By logging these values on the client, server and any backing 117 | services, it provides a mechanism to trace, diagnose and debug requests. 118 | 119 | #### Divide Large Responses Across Requests with Ranges 120 | 121 | Large responses should be broken across multiple requests using `Range` headers 122 | to specify when more data is available and how to retrieve it. See the 123 | [Heroku Platform API discussion of Ranges](https://devcenter.heroku.com/articles/platform-api-reference#ranges) 124 | for the details of request and response headers, status codes, limits, 125 | ordering, and iteration. 126 | 127 | ### Requests 128 | 129 | #### Accept serialized JSON in request bodies 130 | 131 | Accept serialized JSON on `PUT`/`PATCH`/`POST` request bodies, either 132 | instead of or in addition to form-encoded data. This creates symmetry 133 | with JSON-serialized response bodies, e.g.: 134 | 135 | ```bash 136 | $ curl -X POST https://service.com/apps \ 137 | -H "Content-Type: application/json" \ 138 | -d '{"name": "demoapp"}' 139 | 140 | { 141 | "id": "01234567-89ab-cdef-0123-456789abcdef", 142 | "name": "demoapp", 143 | "owner": { 144 | "email": "username@example.com", 145 | "id": "01234567-89ab-cdef-0123-456789abcdef" 146 | }, 147 | ... 148 | } 149 | ``` 150 | 151 | #### Use consistent path formats 152 | 153 | ##### Resource names 154 | 155 | 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. 156 | 157 | ##### Actions 158 | 159 | Prefer endpoint layouts that don’t need any special actions for 160 | individual resources. In cases where special actions are needed, place 161 | them under a standard `actions` prefix, to clearly delineate them: 162 | 163 | ``` 164 | /resources/:resource/actions/:action 165 | ``` 166 | 167 | e.g. 168 | 169 | ``` 170 | /runs/{run_id}/actions/stop 171 | ``` 172 | 173 | #### Downcase paths and attributes 174 | 175 | Use downcased and dash-separated path names, for alignment with 176 | hostnames, e.g: 177 | 178 | ``` 179 | service-api.com/users 180 | service-api.com/app-setups 181 | ``` 182 | 183 | Downcase attributes as well, but use underscore separators so that 184 | attribute names can be typed without quotes in JavaScript, e.g.: 185 | 186 | ``` 187 | service_class: "first" 188 | ``` 189 | 190 | #### Support non-id dereferencing for convenience 191 | 192 | In some cases it may be inconvenient for end-users to provide IDs to 193 | identify a resource. For example, a user may think in terms of a Heroku 194 | app name, but that app may be identified by a UUID. In these cases you 195 | may want to accept both an id or name, e.g.: 196 | 197 | ```bash 198 | $ curl https://service.com/apps/{app_id_or_name} 199 | $ curl https://service.com/apps/97addcf0-c182 200 | $ curl https://service.com/apps/www-prod 201 | ``` 202 | 203 | Do not accept only names to the exclusion of IDs. 204 | 205 | #### Minimize path nesting 206 | 207 | In data models with nested parent/child resource relationships, paths 208 | may become deeply nested, e.g.: 209 | 210 | ``` 211 | /orgs/{org_id}/apps/{app_id}/dynos/{dyno_id} 212 | ``` 213 | 214 | Limit nesting depth by preferring to locate resources at the root 215 | path. Use nesting to indicate scoped collections. For example, for the 216 | case above where a dyno belongs to an app belongs to an org: 217 | 218 | ``` 219 | /orgs/{org_id} 220 | /orgs/{org_id}/apps 221 | /apps/{app_id} 222 | /apps/{app_id}/dynos 223 | /dynos/{dyno_id} 224 | ``` 225 | 226 | ### Responses 227 | 228 | #### Return appropriate status codes 229 | 230 | Return appropriate HTTP status codes with each response. Successful 231 | responses should be coded according to this guide: 232 | 233 | * `200`: Request succeeded for a `GET`, `POST`, `DELETE`, or `PATCH` call that 234 | completed synchronously, or a `PUT` call that synchronously updated an 235 | existing resource 236 | * `201`: Request succeeded for a `POST`, or `PUT` call that synchronously 237 | created a new resource. It is also best practice to provide a `Location` 238 | header pointing to the newly created resource. This is particularly useful 239 | in the `POST` context as the new resource will have a different URL than the 240 | original request. 241 | * `202`: Request accepted for a `POST`, `PUT`, `DELETE`, or `PATCH` call that 242 | will be processed asynchronously 243 | * `206`: Request succeeded on `GET`, but only a partial response 244 | returned: see [above on ranges](#divide-large-responses-across-requests-with-ranges) 245 | 246 | Pay attention to the use of authentication and authorization error codes: 247 | 248 | * `401 Unauthorized`: Request failed because user is not authenticated 249 | * `403 Forbidden`: Request failed because user does not have authorization to access a specific resource 250 | 251 | Return suitable codes to provide additional information when there are errors: 252 | 253 | * `422 Unprocessable Entity`: Your request was understood, but contained invalid parameters 254 | * `429 Too Many Requests`: You have been rate-limited, retry later 255 | * `500 Internal Server Error`: Something went wrong on the server, check status site and/or report the issue 256 | 257 | Refer to the [HTTP response code spec](https://tools.ietf.org/html/rfc7231#section-6) 258 | for guidance on status codes for user error and server error cases. 259 | 260 | #### Provide full resources where available 261 | 262 | Provide the full resource representation (i.e. the object with all 263 | attributes) whenever possible in the response. Always provide the full 264 | resource on 200 and 201 responses, including `PUT`/`PATCH` and `DELETE` 265 | requests, e.g.: 266 | 267 | ```bash 268 | $ curl -X DELETE \ 269 | https://service.com/apps/1f9b/domains/0fd4 270 | 271 | HTTP/1.1 200 OK 272 | Content-Type: application/json;charset=utf-8 273 | ... 274 | { 275 | "created_at": "2012-01-01T12:00:00Z", 276 | "hostname": "subdomain.example.com", 277 | "id": "01234567-89ab-cdef-0123-456789abcdef", 278 | "updated_at": "2012-01-01T12:00:00Z" 279 | } 280 | ``` 281 | 282 | 202 responses will not include the full resource representation, 283 | e.g.: 284 | 285 | ```bash 286 | $ curl -X DELETE \ 287 | https://service.com/apps/1f9b/dynos/05bd 288 | 289 | HTTP/1.1 202 Accepted 290 | Content-Type: application/json;charset=utf-8 291 | ... 292 | {} 293 | ``` 294 | 295 | #### Provide resource (UU)IDs 296 | 297 | Give each resource an `id` attribute by default. Use UUIDs unless you 298 | have a very good reason not to. Don’t use IDs that won’t be globally 299 | unique across instances of the service or other resources in the 300 | service, especially auto-incrementing IDs. 301 | 302 | Render UUIDs in downcased `8-4-4-4-12` format, e.g.: 303 | 304 | ``` 305 | "id": "01234567-89ab-cdef-0123-456789abcdef" 306 | ``` 307 | 308 | #### Provide standard timestamps 309 | 310 | Provide `created_at` and `updated_at` timestamps for resources by default, 311 | e.g: 312 | 313 | ```javascript 314 | { 315 | // ... 316 | "created_at": "2012-01-01T12:00:00Z", 317 | "updated_at": "2012-01-01T13:00:00Z", 318 | // ... 319 | } 320 | ``` 321 | 322 | These timestamps may not make sense for some resources, in which case 323 | they can be omitted. 324 | 325 | #### Use UTC times formatted in ISO8601 326 | 327 | Accept and return times in UTC only. Render times in ISO8601 format, 328 | e.g.: 329 | 330 | ``` 331 | "finished_at": "2012-01-01T12:00:00Z" 332 | ``` 333 | 334 | #### Nest foreign key relations 335 | 336 | Serialize foreign key references with a nested object, e.g.: 337 | 338 | ```javascript 339 | { 340 | "name": "service-production", 341 | "owner": { 342 | "id": "5d8201b0..." 343 | }, 344 | // ... 345 | } 346 | ``` 347 | 348 | Instead of e.g.: 349 | 350 | ```javascript 351 | { 352 | "name": "service-production", 353 | "owner_id": "5d8201b0...", 354 | // ... 355 | } 356 | ``` 357 | 358 | This approach makes it possible to inline more information about the 359 | related resource without having to change the structure of the response 360 | or introduce more top-level response fields, e.g.: 361 | 362 | ```javascript 363 | { 364 | "name": "service-production", 365 | "owner": { 366 | "id": "5d8201b0...", 367 | "name": "Alice", 368 | "email": "alice@heroku.com" 369 | }, 370 | // ... 371 | } 372 | ``` 373 | 374 | #### Generate structured errors 375 | 376 | Generate consistent, structured response bodies on errors. Include a 377 | machine-readable error `id`, a human-readable error `message`, and 378 | optionally a `url` pointing the client to further information about the 379 | error and how to resolve it, e.g.: 380 | 381 | ``` 382 | HTTP/1.1 429 Too Many Requests 383 | ``` 384 | 385 | ```json 386 | { 387 | "id": "rate_limit", 388 | "message": "Account reached its API rate limit.", 389 | "url": "https://docs.service.com/rate-limits" 390 | } 391 | ``` 392 | 393 | Document your error format and the possible error `id`s that clients may 394 | encounter. 395 | 396 | #### Show rate limit status 397 | 398 | Rate limit requests from clients to protect the health of the service 399 | and maintain high service quality for other clients. You can use a 400 | [token bucket algorithm](http://en.wikipedia.org/wiki/Token_bucket) to 401 | quantify request limits. 402 | 403 | Return the remaining number of request tokens with each request in the 404 | `RateLimit-Remaining` response header. 405 | 406 | #### Keep JSON minified in all responses 407 | 408 | Extra whitespace adds needless response size to requests, and many 409 | clients for human consumption will automatically "prettify" JSON 410 | output. It is best to keep JSON responses minified e.g.: 411 | 412 | ```json 413 | {"beta":false,"email":"alice@heroku.com","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"} 414 | ``` 415 | 416 | Instead of e.g.: 417 | 418 | ```json 419 | { 420 | "beta": false, 421 | "email": "alice@heroku.com", 422 | "id": "01234567-89ab-cdef-0123-456789abcdef", 423 | "last_login": "2012-01-01T12:00:00Z", 424 | "created_at": "2012-01-01T12:00:00Z", 425 | "updated_at": "2012-01-01T12:00:00Z" 426 | } 427 | ``` 428 | 429 | You may consider optionally providing a way for clients to retrieve 430 | more verbose response, either via a query parameter (e.g. `?pretty=true`) 431 | or via an `Accept` header param (e.g. 432 | `Accept: application/vnd.heroku+json; version=3; indent=4;`). 433 | 434 | ### Artifacts 435 | 436 | #### Provide machine-readable JSON schema 437 | 438 | Provide a machine-readable schema to exactly specify your API. Use 439 | [prmd](https://github.com/interagent/prmd) to manage your schema, and ensure 440 | it validates with `prmd verify`. 441 | 442 | #### Provide human-readable docs 443 | 444 | Provide human-readable documentation that client developers can use to 445 | understand your API. 446 | 447 | If you create a schema with prmd as described above, you can easily 448 | generate Markdown docs for all endpoints with `prmd doc`. 449 | 450 | In addition to endpoint details, provide an API overview with 451 | information about: 452 | 453 | * Authentication, including acquiring and using authentication tokens. 454 | * API stability and versioning, including how to select the desired API 455 | version. 456 | * Common request and response headers. 457 | * Error serialization format. 458 | * Examples of using the API with clients in different languages. 459 | 460 | #### Provide executable examples 461 | 462 | Provide executable examples that users can type directly into their 463 | terminals to see working API calls. To the greatest extent possible, 464 | these examples should be usable verbatim, to minimize the amount of 465 | work a user needs to do to try the API, e.g.: 466 | 467 | ```bash 468 | $ export TOKEN=... # acquire from dashboard 469 | $ curl -is https://$TOKEN@service.com/users 470 | ``` 471 | 472 | If you use [prmd](https://github.com/interagent/prmd) to generate Markdown 473 | docs, you will get examples for each endpoint for free. 474 | 475 | #### Describe stability 476 | 477 | Describe the stability of your API or its various endpoints according to 478 | its maturity and stability, e.g. with prototype/development/production 479 | flags. 480 | 481 | See the [Heroku API compatibility policy](https://devcenter.heroku.com/articles/api-compatibility-policy) 482 | for a possible stability and change management approach. 483 | 484 | Once your API is declared production-ready and stable, do not make 485 | backwards incompatible changes within that API version. If you need to 486 | make backwards-incompatible changes, create a new API with an 487 | incremented version number. 488 | --------------------------------------------------------------------------------