Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Flat HTTP API design

Peter Hilton
November 08, 2018

Flat HTTP API design

The way we write API docs is highly structured, natural to programmers, and wrong. HTTP API documentation typically looks nothing like the requests and responses it describes. This creates extra work for the reader to understand the documentation structure, and figure out how to translate that to code. It also makes it harder to spot bad API design. Instead, you need more readable documentation that doesn’t waste your time.

This presentation introduces Flat HTTP API Documentation (FHAD) - a better way to document your HTTP API. FHAD leverages HTTP’s own structure together with some layout and typography to document by example, in as much detail as you like. Attendees will learn to see API documentation in a new way, which they can use to write more effective documentation with less effort. You’ll also get a REST API design checklist that you can use to either document or avoid design mistakes.

Peter Hilton

November 08, 2018
Tweet

More Decks by Peter Hilton

Other Decks in Programming

Transcript

  1. I want two* things: 1. good HTTP API docs 2.

    good HTTP APIs * good DX !3 @PeterHilton •
  2. public Random(long seed) Создает новый генератор случайных чисел, используя сингл

    long семя. Семя является начальным значением внутреннего состояния генератора псевдослучайного числа, который сохраняется методом next(int). Параметры: seed - начальное семя См. Также: setSeed(long) Случайный
  3. MARK: Raúl, come with me and I’ll introduce you to

    a few people. RAUL: OK. MARK: Have you met Lucy? LUCY: Yes, we’ve already met. MARK: OK, in that case, meet Sam. Sam and I were at college together. Sam, this is Raúl. Raúl’s a mate of mine from Spain.
  4. introduce(subject, target) Introduces one person to someone they might not

    know. Parameters: subject - the person to introduce target - the person to introduce the subject to See also: introduce-formally Introduction
  5. public Random(long seed)   long   # "

     ! $  next(int) # ݇හ: seed -  ݚ᧗݇ᥠ: setSeed(long) Random
  6. The great HTTP takeover Then CD-ROMs e-mail IRC middleware classes


    Now web pages webmail web-based chat cloud APIs microservices !13 @PeterHilton • ➔
  7. @PeterHilton • Elsewhere: Host Authentication Content types Errors !19 Purpose

    HTTP method, URL Authorisation Parameters Request body Response status Response body
  8. raw HTTP session POST /user/repos HTTP/1.1 Accept: application/json, */* Accept-Encoding:

    gzip, deflate Authorization: Basic aGlsdG9uOm5vdG15YWN0dWFscGFzc3dvcmQ= Connection: keep-alive Content-Length: 202 Content-Type: application/json Host: api.github.com User-Agent: HTTPie/0.9.6 { "description": "Flat HTTP API Documentation", "has_issues": true, "has_projects": false, "has_wiki": false, "homepage": "https://hilton.org.uk/fhad/", "name": "FHAD", "private": false }
  9. POST /user/repos HTTP/1.1 Accept: application/json, */* Accept-Encoding: gzip, deflate Authorization:

    Basic aGlsdG9uOm5vdG15YWN0dWFscGFzc3dvcmQ= Connection: keep-alive Content-Length: 202 Content-Type: application/json Host: api.github.com User-Agent: HTTPie/0.9.6 { "description": "Flat HTTP API Documentation", "has_issues": true, "has_projects": false, "has_wiki": false, "homepage": "https://hilton.org.uk/fhad/", "name": "FHAD", "private": false } HTTP/1.1 201 Created Access-Control-Allow-Origin: * Access-Control-Expose-Headers: ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval Cache-Control: private, max-age=60, s-maxage=60 Content-Length: 4616 Content-Security-Policy: default-src 'none' Content-Type: application/json; charset=utf-8 Date: Fri, 21 Sep 2018 12:08:36 GMT ETag: "429600554274cc04f28bb186c3a993de" Location: https://api.github.com/repos/hilton/FHAD Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin Server: GitHub.com Status: 201 Created Strict-Transport-Security: max-age=31536000; includeSubdomains; preload Vary: Accept, Authorization, Cookie, X-GitHub-OTP X-Accepted-OAuth-Scopes: public_repo, repo X-Content-Type-Options: nosniff X-Frame-Options: deny X-GitHub-Media-Type: github.v3 X-GitHub-Request-Id: 6F3C:4E27:1593928:2F06A0A:5BA4DF44 X-OAuth-Scopes: gist, notifications, public_repo, read:user X-RateLimit-Limit: 5000 X-RateLimit-Remaining: 4998 X-RateLimit-Reset: 1537535112 X-Runtime-rack: 0.633194 X-XSS-Protection: 1; mode=block { "allow_merge_commit": true, "allow_rebase_merge": true, "allow_squash_merge": true, "archive_url": "https://api.github.com/repos/hilton/FHAD/{archive_format}{/ref}", "archived": false, "assignees_url": "https://api.github.com/repos/hilton/FHAD/assignees{/user}", "blobs_url": "https://api.github.com/repos/hilton/FHAD/git/blobs{/sha}", "branches_url": "https://api.github.com/repos/hilton/FHAD/branches{/branch}", "clone_url": "https://github.com/hilton/FHAD.git", "collaborators_url": "https://api.github.com/repos/hilton/FHAD/collaborators{/collaborator}", "comments_url": "https://api.github.com/repos/hilton/FHAD/comments{/number}", "commits_url": "https://api.github.com/repos/hilton/FHAD/commits{/sha}", "compare_url": "https://api.github.com/repos/hilton/FHAD/compare/{base}...{head}", "contents_url": "https://api.github.com/repos/hilton/FHAD/contents/{+path}", "contributors_url": "https://api.github.com/repos/hilton/FHAD/contributors", "created_at": "2018-09-21T12:08:36Z", "default_branch": "master", "deployments_url": "https://api.github.com/repos/hilton/FHAD/deployments", "description": "Flat HTTP API Documentation", "downloads_url": "https://api.github.com/repos/hilton/FHAD/downloads", "events_url": "https://api.github.com/repos/hilton/FHAD/events", "fork": false, "forks": 0, "forks_count": 0, "forks_url": "https://api.github.com/repos/hilton/FHAD/forks", "full_name": "hilton/FHAD", "git_commits_url": "https://api.github.com/repos/hilton/FHAD/git/commits{/sha}", "git_refs_url": "https://api.github.com/repos/hilton/FHAD/git/refs{/sha}", "git_tags_url": "https://api.github.com/repos/hilton/FHAD/git/tags{/sha}", "git_url": "git://github.com/hilton/FHAD.git", "has_downloads": true, "has_issues": true, "has_pages": false, "has_projects": false, "has_wiki": false, "homepage": "https://hilton.org.uk/fhad/", "hooks_url": "https://api.github.com/repos/hilton/FHAD/hooks", "html_url": "https://github.com/hilton/FHAD", "id": 149758160, "issue_comment_url": "https://api.github.com/repos/hilton/FHAD/issues/comments{/number}", "issue_events_url": "https://api.github.com/repos/hilton/FHAD/issues/events{/number}", "issues_url": "https://api.github.com/repos/hilton/FHAD/issues{/number}", "keys_url": "https://api.github.com/repos/hilton/FHAD/keys{/key_id}", "labels_url": "https://api.github.com/repos/hilton/FHAD/labels{/name}", "language": null, "languages_url": "https://api.github.com/repos/hilton/FHAD/languages", "license": null, "merges_url": "https://api.github.com/repos/hilton/FHAD/merges", "milestones_url": "https://api.github.com/repos/hilton/FHAD/milestones{/number}", "mirror_url": null, "name": "FHAD", "network_count": 0, "node_id": "MDEwOlJlcG9zaXRvcnkxNDk3NTgxNjA=", "notifications_url": "https://api.github.com/repos/hilton/FHAD/notifications{?since,all,participating}", "open_issues": 0, "open_issues_count": 0, "owner": { "avatar_url": "https://avatars1.githubusercontent.com/u/232614?v=4", "events_url": "https://api.github.com/users/hilton/events{/privacy}", "followers_url": "https://api.github.com/users/hilton/followers", "following_url": "https://api.github.com/users/hilton/following{/other_user}", "gists_url": "https://api.github.com/users/hilton/gists{/gist_id}", "gravatar_id": "", "html_url": "https://github.com/hilton", "id": 232614, "login": "hilton", "node_id": "MDQ6VXNlcjIzMjYxNA==", "organizations_url": "https://api.github.com/users/hilton/orgs", "received_events_url": "https://api.github.com/users/hilton/received_events", "repos_url": "https://api.github.com/users/hilton/repos", "site_admin": false, "starred_url": "https://api.github.com/users/hilton/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/hilton/subscriptions", "type": "User", "url": "https://api.github.com/users/hilton" }, "permissions": { "admin": true, "pull": true, "push": true }, "private": false, "pulls_url": "https://api.github.com/repos/hilton/FHAD/pulls{/number}", "pushed_at": "2018-09-21T12:08:36Z", "releases_url": "https://api.github.com/repos/hilton/FHAD/releases{/id}", "size": 0, "ssh_url": "[email protected]:hilton/FHAD.git", "stargazers_count": 0, "stargazers_url": "https://api.github.com/repos/hilton/FHAD/stargazers", "statuses_url": "https://api.github.com/repos/hilton/FHAD/statuses/{sha}", "subscribers_count": 1, "subscribers_url": "https://api.github.com/repos/hilton/FHAD/subscribers", "subscription_url": "https://api.github.com/repos/hilton/FHAD/subscription", "svn_url": "https://github.com/hilton/FHAD", "tags_url": "https://api.github.com/repos/hilton/FHAD/tags", "teams_url": "https://api.github.com/repos/hilton/FHAD/teams", "trees_url": "https://api.github.com/repos/hilton/FHAD/git/trees{/sha}", "updated_at": "2018-09-21T12:08:36Z", "url": "https://api.github.com/repos/hilton/FHAD", "watchers": 0, "watchers_count": 0 }
  10. POST /user/repos HTTP/1.1 Accept: application/json, */* Accept-Encoding: gzip, deflate Authorization:

    Basic aGlsdG9uOm5vdG15YWN0dWFscGFzc3dvcmQ= Connection: keep-alive Content-Length: 202 Content-Type: application/json Host: api.github.com User-Agent: HTTPie/0.9.6 HTTP/1.1 201 Created Access-Control-Allow-Origin: * Access-Control-Expose-Headers: ETag, Link, Retry-After, X-GitHub-OTP, 
 X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, 
 X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval Cache-Control: private, max-age=60, s-maxage=60 Content-Length: 4616 Content-Security-Policy: default-src 'none' Content-Type: application/json; charset=utf-8 Date: Fri, 21 Sep 2018 12:08:36 GMT ETag: "429600554274cc04f28bb186c3a993de" Location: https://api.github.com/repos/hilton/FHAD Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin Server: GitHub.com Status: 201 Created Strict-Transport-Security: max-age=31536000; includeSubdomains; preload Vary: Accept, Authorization, Cookie, X-GitHub-OTP X-Accepted-OAuth-Scopes: public_repo, repo X-Content-Type-Options: nosniff X-Frame-Options: deny X-GitHub-Media-Type: github.v3 X-GitHub-Request-Id: 6F3C:4E27:1593928:2F06A0A:5BA4DF44 X-OAuth-Scopes: gist, notifications, public_repo, read:user X-RateLimit-Limit: 5000 X-RateLimit-Remaining: 4998 X-RateLimit-Reset: 1537535112 X-Runtime-rack: 0.633194 X-XSS-Protection: 1; mode=block
 POST /user/repos HTTP/1.1 Authorization: Basic aGlsdG9uOm5vdG15YWN0dWFscGFzc3dvcmQ= Content-Type: application/json Host: api.github.com HTTP/1.1 201 Created Content-Length: 4616 Content-Type: application/json; charset=utf-8 Date: Fri, 21 Sep 2018 12:08:36 GMT ETag: "429600554274cc04f28bb186c3a993de" Location: https://api.github.com/repos/hilton/FHAD X-Accepted-OAuth-Scopes: public_repo, repo X-RateLimit-Limit: 5000 X-RateLimit-Remaining: 4998 X-RateLimit-Reset: 1537535112
  11. Create Create a new repository for the authenticated user. POST

    /user/repos HTTP/1.1 Authorization: Basic aGlsdG9uOm5vdG15YWN0dWFscGFzc3dvcmQ= Content-Type: application/json Host: api.github.com { "description": "Flat HTTP API Documentation", "has_issues": true, "has_projects": false, "has_wiki": false, "homepage": "https://hilton.org.uk/fhad/", "name": "FHAD", "private": false } HTTP/1.1 201 Created Content-Length: 4616 Content-Type: application/json; charset=utf-8 Date: Fri, 21 Sep 2018 12:08:36 GMT ETag: "429600554274cc04f28bb186c3a993de" Location: https://api.github.com/repos/hilton/FHAD X-Accepted-OAuth-Scopes: public_repo, repo X-RateLimit-Limit: 5000 X-RateLimit-Remaining: 4998 X-RateLimit-Reset: 1537535112 { "allow_merge_commit": true, "allow_rebase_merge": true, "allow_squash_merge": true, "archive_url": "https://api.github.com/repos/hilton/FHAD/{archive_format}{/ref}", "archived": false, "assignees_url": "https://api.github.com/repos/hilton/FHAD/assignees{/user}", "blobs_url": "https://api.github.com/repos/hilton/FHAD/git/blobs{/sha}", "branches_url": "https://api.github.com/repos/hilton/FHAD/branches{/branch}", "clone_url": "https://github.com/hilton/FHAD.git", "collaborators_url": "https://api.github.com/repos/hilton/FHAD/collaborators{/collaborator}", "comments_url": "https://api.github.com/repos/hilton/FHAD/comments{/number}", "commits_url": "https://api.github.com/repos/hilton/FHAD/commits{/sha}", "compare_url": "https://api.github.com/repos/hilton/FHAD/compare/{base}...{head}", "contents_url": "https://api.github.com/repos/hilton/FHAD/contents/{+path}", "contributors_url": "https://api.github.com/repos/hilton/FHAD/contributors", "created_at": "2018-09-21T12:08:36Z", "default_branch": "master", "deployments_url": "https://api.github.com/repos/hilton/FHAD/deployments", Purpose HTTP method/URL Authentication Host Parameters Response status Authorisation Rate limits Response body
  12. Create Create a new repository for the authenticated user. Request

    POST /user/repos HTTP/1.1 Authorization: Basic aGlsdG9uOm5vdWFscGFzc3dvcmQ= Content-Type: application/json Host: api.github.com { "description": "Flat HTTP API Documentation", "has_issues": true, "has_projects": false, "has_wiki": false, "homepage": "https://hilton.org.uk/fhad/", "name": "FHAD", "private": false }
  13. Type Name Purpose Value header Authorization HTTP Basic Authentication GitHub

    username and personal access token or OAuth token instead of your password Request POST /user/repos HTTP/1.1 Authorization: Basic aGlsdG9uOm5vdWFscGFzc3dvcmQ= Content-Type: application/json Host: api.github.com { "description": "Flat HTTP API Documentation", "has_issues": true, "has_projects": false, "has_wiki": false, "homepage": "https://hilton.org.uk/fhad/", "name": "FHAD", "private": false } Request parameters
  14. Random GIF Returns a random GIF, limited by tag. Excluding

    the tag parameter will return a random GIF from the GIPHY catalog. Request GET /v1/gifs/random?tag=cat&rating=g&api_key=Zz39DYlRU00XtxoCg HTTP/1.1 Accept: application/json Host: api.giphy.com Request parameters Type Name Purpose Values/example query tag Filter results by tag cat query rating MPAA rating filter y, g, pg, pg-13, r, unrated, nsfw query api_key GIPHY API Key Zz39DYlRU00XtxoCgCVGhMIT6yQSBPeG
  15. GIF by ID Returns a GIF given that GIF’s unique

    ID. Request GET /v1/gifs/heIX5HfWgEYlW?api_key=Zz39DYlRU00XtxoCg HTTP/1.1 Accept: application/json Host: api.giphy.com Request parameters Type Name Purpose Value/example path ??????? GIF ID heIX5HfWgEYlW query api_key GIPHY API Key Zz39DYlRU00XtxoCg
  16. GIF by ID Returns a GIF given that GIF’s unique

    ID. Request GET /v1/gifs/heIX5HfWgEYlW?api_key=Zz39DYlRU00XtxoCg HTTP/1.1 Accept: application/json Host: api.giphy.com Request parameters Value/example Purpose heIX5HfWgEYlW GIF ID Zz39DYlRU00XtxoCg GIPHY API Key
  17. GIPHY API Key GIF ID HTTP/1.1 200 OK Content-Type: application/json

    Date: Fri, 28 Sep 2018 13:48:08 GMT X-RateLimit-Limit-day: 1000 X-RateLimit-Limit-hour: 42 X-RateLimit-Remaining-day: 999 X-RateLimit-Remaining-hour: 41 { "data": { "bitly_gif_url": "https://gph.is/1TV6U7O", "bitly_url": "https://gph.is/1TV6U7O", GIF by ID Returns a GIF given that GIF’s unique ID. GET /v1/gifs/heIX5HfWgEYlW?api_key=Zz39DYlRU00XtxoCg HTTP/1.1 Accept: application/json Host: api.giphy.com
  18. HTTP/1.1 200 OK Content-Type: application/json Date: Fri, 28 Sep 2018

    13:48:08 GMT X-RateLimit-Limit-day: 1000 X-RateLimit-Limit-hour: 42 X-RateLimit-Remaining-day: 999 X-RateLimit-Remaining-hour: 41 { "data": { "bitly_gif_url": "https://gph.is/1TV6U7O", "bitly_url": "https://gph.is/1TV6U7O", "caption": "Cat writing API documentation", "content_url": "", "embed_url": "https://giphy.com/embed/heIX5HfWgEYlW", "fixed_height_downsampled_height": "200", "fixed_height_downsampled_url": "https://media1.giphy.com/media/heIX5HfWgEYlW "fixed_height_downsampled_width": "200", "fixed_height_small_height": "100", "fixed_height_small_still_url": "https://media1.giphy.com/media/heIX5HfWgEYlW "fixed_height_small_url": "https://media1.giphy.com/media/heIX5HfWgEYlW/100.g "fixed_height_small_width": "100", "fixed_width_downsampled_height": "200",
  19. GIPHY API Key Excluding this parameter will return a random

    GIF from the GIPHY catalog. MPAA rating filter Filter results by tag Random GIF Returns a random GIF, limited by tag. GET /v1/gifs/random?tag=cat&rating=g&api_key=Zz39DYlRU00XtxoCg HTTP/1.1 Accept: application/json Host: api.giphy.com
  20. GIPHY API Key Excluding this parameter will return a random

    GIF from the GIPHY catalog. MPAA rating filter Filter results by tag Random GIF Returns a random GIF, limited by tag. GET /v1/gifs/random?tag=cat&rating=g&api_key=Zz39DYlRU00XtxoCg HTTP/1.1 Accept: application/json Host: api.giphy.com
  21. Authentication Basic Authentication using your GitHub username and personal access

    token or OAuth token instead of your password. Create Create a new repository for the authenticated user. POST /user/repos HTTP/1.1 Authorization: Basic aGlsdG9uOm5vdWFscGFzc3dvcmQ= Content-Type: application/json Host: api.github.com { "description": "Flat HTTP API Documentation", "has_issues": true, "has_projects": false, "has_wiki": false, "homepage": "https://hilton.org.uk/fhad/", "name": "FHAD", "private": false }
  22. Image width Image height Cat API by Sara Vieira for

    ReactJSGirls Photo by Jari Hytönen on Unsplash Placeholder cats Returns an image with optional width and height. GET /placeholder/800/400 HTTP/1.1 Connection: keep-alive Host: www.catis.life HTTP/1.1 200 OK Content-Type: image/jpeg Date: Sun, 30 Sep 2018 14:32:40 GMT
  23. Flat HTTP API documentation 1. Literal request and response 2.

    Parameter annotations 3. Proper layout and typography 4. API design warnings 5. Less effort to write 6. Probably easier to consume 7. Good HTTP APIs by example !38 @PeterHilton •
  24. When you show the 
 raw HTTP session, API ugliness

    has nowhere to hide… !41 @PeterHilton •
  25. ⚠ If the request URL is not valid, the response

    status is 500 Server Error instead of 404 Not Found. Retrieve evil plan Fetches the specified world domination plan. GET /plans/world-domination/42 HTTP/1.1 Accept: text/xml HTTP/1.1 500 Internal Server Error Content-Type: text/xml <?xml version="1.0" encoding="utf-8" ?> <error> <code>666_INVALID_EVIL_PLAN_ID</code> </error>
  26. ⚠ A valid request URL with the wrong HTTP method

    returns a 500 Server Error status instead of 405 Method Not Allowed. Delete evil plan Discontinue a world domination plan. DELETE /plans/world-domination/001 HTTP/1.1 Accept: text/xml HTTP/1.1 500 Internal Server Error Content-Type: text/xml <?xml version="1.0" encoding="utf-8" ?> <error> <code>666_DELETE_EVIL_PLANS_WITH_POST</code> </error>
  27. Execute evil plan Fetches the specified world domination plan. POST

    /plans/world-domination/01 HTTP/1.1 Accept: text/xml HTTP/1.1 200 OK Content-Type: text/xml <?xml version="1.0" encoding="utf-8" ?> <error> <code>666_ERROR_ERROR_ERROR</code> </error> ⚠ Don’t expect to receive a 400 Bad Request status for an invalid request. You’re probably going to get 500 Server Error, or maybe 200 OK.
  28. API design warnings checklist - client errors 1. 404 Not

    Found for URL paths that don’t match a back-end 2. 404 Not Found for URL paths that include an invalid ID 3. 405 Method Not Allowed for a valid URL but an unsupported method, e.g. GET /p/login 4. 400 Bad Request for other request errors, e.g. the wrong request Content-type or a missing required parameter !45 @PeterHilton •
  29. " Creating a new widget returns a 200 OK status

    instead of 201 Created. The URL is in the response body instead of a Location response header. Create evil plan Publishes a new world domination plan. POST /plans/world-domination HTTP/1.1 Content-type: text/xml Accept: text/xml <?xml version="1.0" encoding="utf-8" ?> <plan> <summary>World domination</summary> </plan> HTTP/1.1 200 OK Content-Type: text/xml <?xml version="1.0" encoding="utf-8" ?> <success> <location id="/plans/world-domination/02"/> </success>
  30. # Our back-end framework is rubbish, but at least the

    right status is in the JSON Create evil plan Publishes a new world domination plan. POST /plans/world-domination HTTP/1.1 Content-type: text/xml Accept: application/json <?xml version="1.0" encoding="utf-8" ?> <plan> <summary>World domination</summary> </plan> HTTP/1.1 200 OK Content-Type: application/json { "status":201, "message":"Created", "location":"world-domination/02" }
  31. API design warnings checklist - more status codes 5. 201

    Created after creating a new object, with a Location response header that contains the new object’s URL 6. Don’t return 200 OK when the client sent a bad request, 
 or when there was a server-side error 7. If the back-end framework is rubbish, at least include the expected status text, e.g. Created, in the response !48 @PeterHilton •
  32. $ Let’s be realistic - the API is just going

    to ignore your Accept request header, and always give you XML (or worse). Want 406 Not Acceptable instead? Dream on! Create evil plan Publishes a new world domination plan. POST /plans/world-domination HTTP/1.1 Content-type: text/xml Accept: application/json <?xml version="1.0" encoding="utf-8" ?> <plan> <summary>World domination</summary> </plan> HTTP/1.1 200 OK Content-Type: text/xml <?xml version="1.0" encoding="utf-8" ?> <success> <location id="world-domination/02"/> </success>
  33. % The response body is a JSON array… in random

    order. List evil plans Lists all published plans. GET /plans HTTP/1.1 Accept: application/json HTTP/1.1 200 OK Content-Type: application/json [ {"id":"01","title":"World domination"} {"id":"07","title":"Kill Bond"} {"id":"03","title":"Make SOAP great again"} {"id":"04","title":"Teach UML in schools"} {"id":"02","title":"Defend the patriarchy"} {"id":"06","title":"Roll out SAFe"} {"id":"08","title":"Mandate open plan offices"} {"id":"05","title":"Something with blockchain"} ]
  34. & The request requires JSON embedded in form encoding. (and

    we’ve fixed most of the encoding bugs) Create evil plan Publishes a new world domination plan. POST /plans HTTP/1.1 Content-type: text/xml Accept: application/json title=World%20domination&
 plan=%7B%22summar%22%3A%22world+domination%22%2C %22scope%22%3A%22WORLD%22%2C%22owner%22%3A%22Dr+ Evil%22%7D HTTP/1.1 201 Created Location: /plans/world-domination/02
  35. API design warnings checklist - JSON 8. Always send a

    JSON response body when the request included an Accept: application/json header 9. Always sort JSON arrays in responses rather than using random order 10. Don’t mix data formats (e.g. XML and JSON) in the same HTTP resource representation !53 @PeterHilton •
  36. The future of documentation Markdown, reStructuredText and AsciiDoc remove barriers

    to actually writing the words and spaces Documentation generation pipelines help reduce the cost of layout and production … but there’s more to docs than all that. !58 @PeterHilton •