API Standards 2.0

Bbf9decfbfc2ab5b450ec503749ded28?s=47 Michael Heap
February 22, 2019

API Standards 2.0

We're all familiar with things like HTTP codes and content types, but there's so much more we can do when developing an API to make life easier for consumers. How many times have you used an API only to find out that every endpoint is slightly different – some use `snake_case`, others `camelCase`, sometimes the field is called `id`, sometimes it’s `user_id`. How about pagination? Error responses? What about API documentation? Trying to standardise on all these things can kill an engineering team. There are so many options out there it’s difficult to know where to start. Come along and learn what works for our team! We’ll cover contentious topics (should the version be in the URL or a header?), lesser-known standards that are great (RFC 7807 springs to mind) and a couple of things that aren't an issue right up until they’re a really big issue (like pagination).

Bbf9decfbfc2ab5b450ec503749ded28?s=128

Michael Heap

February 22, 2019
Tweet

Transcript

  1. API STANDARDS 2.0

  2. 1. API Deprecation Before you build it, know how you're

    going to turn it off 2
  3. API Deprecation 3 Beta Versions

  4. API Deprecation 4 Major Versions Beta Versions

  5. API Deprecation 5 Major Versions Beta Versions API
 Retirement

  6. Have a deprecation policy. It doesn't matter what it is,

    but be consistent. Deprecation Policies 6
  7. When the decision has been taken to deprecate an API:

    • For beta products the deprecation period must be at least 30 days (60 days recommended) • For GA products the deprecation period must be at least 1 year • Warning emails will be sent to the API at regular intervals before the deprecation time • A guide will be supplied to customers explaining how to migrate to the replacement API with the initial deprecation notice. Deprecation Policies: Nexmo 7
  8. Use the Sunset Header Sunset: Sat, 31 Dec 2018 23:59:59

    GMT Sunset Header 8 https://tools.ietf.org/html/draft-wilde-sunset-header-03
  9. $ composer require hskrasek/guzzle-sunset --- $stack = new \GuzzleHttp\HandlerStack(\GuzzleHttp\choose_handler()); $stack->push(new

    \HSkrasek\Sunset\SunsetMiddleware($logger)); $client = new \GuzzleHttp\Client(['handler' => $stack]); API Deprecation: Sunset Header 9
  10. 2. Versioning Header/URL, Versioning Schemes, Deprecation 10

  11. “ The reason to make a real REST API is

    to get evolvability … a "v1" is a middle finger to your API customers, indicating RPC/HTTP (not REST) https://twitter.com/fielding/status/376835835670167552
  12. “With a sufficient number of users of an interface, it

    doesn’t matter what you promised in the interface contracts, all observable behaviors of your class or function or whatnot will be depended upon by somebody. Hyrum's Law
  13. Accept: application/vnd.nexmo+json; version=3 URI vs Header 13

  14. Accept: application/vnd.nexmo+json; version=3 
 X-Nexmo-Version: 3 URI vs Header 14

  15. Accept: application/vnd.nexmo+json; version=3 
 X-Nexmo-Version: 3 https://api.nexmo.com/v3 URI vs Header

    15
  16. https://api.nexmo.com/v1/calls Global vs Endpoint versioning 16

  17. https://api.nexmo.com/v1/calls https://api.nexmo.com/v1/media Global vs Endpoint versioning 17

  18. https://api.nexmo.com/v1/calls https://api.nexmo.com/v3/media Global vs Endpoint versioning 18

  19. https://api.nexmo.com/v1/calls Versioning Scheme 19

  20. https://api.nexmo.com/v1/calls https://api.example.com/2010-04-01 Versioning Scheme 20

  21. https://api.nexmo.com/v1/calls https://api.example.com/2010-04-01 https://api.example.com/1.2.3 Versioning Scheme 21

  22. https://api.nexmo.com/v3.new- feature/calls Version Variants 22

  23. 3. HTTP Verbs GET, POST, PUT, DELETE, PATCH 23

  24. GET /users 24

  25. GET /users 25

  26. GET /users 26

  27. GET /users 27

  28. GET /users 28

  29. GET /users 29

  30. GET /users 30 200 OK 301 Moved Permanently 302 Found

    404 Not Found 406 Not Acceptable
  31. POST /users 31

  32. POST /users 32 201 Created 202 Accepted 415 Unsupported Media

    Type 422 Unprocessable Entity
  33. PUT /users/1 33 200 OK

  34. DELETE /users/1 34 204 No Content

  35. PATCH /users/1 35

  36. PATCH /users/1 36 RFC 7396 - JSON Merge Patch {

    "name":"Michael", "address": { "line_2": null } }
  37. PATCH /users/1 37 RFC 6902 - JavaScript Object Notation (JSON)

    Patch [ { "op": "replace", "path": "/name", "value": "Michael" }, { "op": "remove", "path": "/ address/line_2" } ]
  38. 4. Resource Design URLs, HTTP Requests/Responses + Webhooks 38

  39. GET /users GET /users/1 POST /users PUT /users/1 PATCH /users/1

    DELETE /users/1 URL Design 39
  40. GET /users/1 Subresources 40

  41. GET /users/1 GET /users/1/calls Subresources 41

  42. GET /users/1 GET /users/1/calls GET /users/1/calls/JSDB-1837A Subresources 42

  43. GET /users/1 GET /users/1/calls GET /users/1/calls/JSDB-1837A GET /calls/JSDB-1837A Subresources 43

  44. GET /users/1 GET /users/1/calls Subresources 44

  45. GET /users/1 GET /users/1/calls GET /calls?user=1 Subresources 45

  46. GET /users/1 GET /addresses/33-90210 GET /users/1/addresses/33-90210 Subresources 46

  47. GET /users/1 GET /addresses/33-90210 GET /users/1/addresses/33-90210 GET /user_addresses/1-33-90210 Subresources 47

  48. POST /machines/1/shutdown Actions 48

  49. POST /machines/1/shutdown POST /machines/1/actions/shutdown Actions 49

  50. POST /machines/1/shutdown POST /machines/1/actions/shutdown POST /machines/1/action:shutdown Actions 50

  51. POST /machines/1/shutdown POST /machines/1/actions/shutdown POST /machines/1/action:shutdown POST /machines/1/actions {"type": "shutdown"}

    Actions 51
  52. POST /machine-shutdown Actions as Resources 52

  53. POST /machine-shutdown PUT /article-locks/{article-id} DELETE /article-locks/{article-id} POST /balance-transfer Actions as

    Resources 53
  54. Resource Design 54 • Use snake_case for key names •

    [resource]_id - A string that identifies a specific resource • [description]_url - A url that is necessary for communication. For example, callback_url. • [description]_method - Send the callback using either GET or POST. • start - The time the communication started in https:// en.wikipedia.org/wiki/ISO_8601 format. • end - The time the communication ended in https:// en.wikipedia.org/wiki/ISO_8601 format.
  55. Webhooks 55 • Use snake_case for key names • start

    - The time the communication started in https:// en.wikipedia.org/wiki/ISO_8601 format. • end - The time the communication ended in https:// en.wikipedia.org/wiki/ISO_8601 format.
  56. 5. Errors Status Codes, RFC7807 + Extensions 56

  57. “ Errors are easy! People should just use status codes

  58. 401 Unauthorized Error Design 58

  59. 401 Unauthorized { "error": "No credentials provided" } Error Design

    59
  60. 401 Unauthorized { "error": "Invalid credentials" } Error Design 60

  61. 401 Unauthorized { "error": "Invalid credentials", "more_help": "http://example.com/ auth#invalid-credentials" }

    Error Design 61
  62. 401 Unauthorized { "msg": "Invalid credentials" } Error Design 62

  63. 401 Unauthorized { "errorCode": 118118, "errorMessage": "Invalid credentials" } Error

    Design 63
  64. 401 Unauthorized "Invalid credentials" Error Design 64

  65. RFC7807 Type A URI reference that identifies the problem type.

    When followed, it provides human-readable documentation for the problem type Title A short, human-readable summary of the problem type Detail A human- readable explanation specific to this occurrence of the problem 65 Instance A URI reference that identifies the specific occurrence of the problem
  66. RFC7807 Type A URI reference that identifies the problem type.

    When followed, it provides human-readable documentation for the problem type Title A short, human-readable summary of the problem type Detail A human- readable explanation specific to this occurrence of the problem 66 Instance A URI reference that identifies the specific occurrence of the problem Status The HTTP status code generated by the origin server for this occurrence of the problem.
  67. RFC7807 Type A URI reference that identifies the problem type.

    When followed, it provides human-readable documentation for the problem type Title A short, human-readable summary of the problem type Detail A human- readable explanation specific to this occurrence of the problem 67 Instance A URI reference that identifies the specific occurrence of the problem
  68. { "type": "https://developer.nexmo.com/api- errors#unauthorized", "title": "Invalid credentials supplied", "detail": "You

    did not provide correct credentials.", "instance": "797a8f199c45014ab7b08bfe9cc1c12c" } RFC7807 68
  69. 403 Forbidden { "type": "https://example.com/probs/out-of- credit", "title": "You do not

    have enough credit.", "detail": "Your current balance is 30, but that costs 50.", "instance": "/account/12345/msgs/abc" } Extension Members 69
  70. 403 Forbidden { "type": "https://example.com/probs/out-of-credit", "title": "You do not have

    enough credit.", "detail": "Your current balance is 30, but that costs 50.", "instance": "/account/12345/msgs/abc", "balance": 30, "accounts": ["/account/12345", "/account/67890"] } Extension Members 70
  71. 400 Bad Request { "type": "https://developer.nexmo.com/api-errors/account/secret- management#validation", "title": "Bad Request",

    "detail": "The request failed due to validation errors", "invalid_parameters": [ { "name": "secret", "reason": "must contain 1 upper case character" } ], "instance": "797a8f199c45014ab7b08bfe9cc1c12c" } Extension Members 71
  72. 401: No Credentials, Invalid Credentials 403: Feature disabled, Exceeded calls-per-second

    limit 422: Invalid product specified + more! Response Library 72
  73. “ As a consumer of the APIs, this has already

    dramatically reduced the amount of code I have to write for each endpoint, and I can't wait until all endpoints are standardised
  74. 6. Design First APIs Consistency through specifications 74

  75. '400': $ref: '#/components/responses/ InvalidPayloadError' '401': $ref: '../shared_errors.yml#/components/ responses/BadCredentialsError' '405': $ref:

    '../shared_errors.yml#/components/ responses/InvalidRequestMethod' '406': $ref: '../shared_errors.yml#/components/ responses/InvalidAcceptHeader' Design First APIs 75
  76. Design First APIs 76

  77. Design First APIs 77

  78. 7. Collection Management Sorting, Filtering, Searching, Pagination 78

  79. Sorting 79

  80. GET /users? sort_by=email&order_by=asc Sorting 80

  81. GET /users?sort_by=email.asc Sorting 81

  82. GET /users?sort_by=email.asc &sort_by=status.desc Sorting 82

  83. GET /users? sort_by=email.asc,status.desc Sorting 83

  84. GET /users?sort_by=+email,-status Sorting 84

  85. GET /users?sort_by=+email,-status GET /users? sort_by=email.asc,status.desc Sorting 85

  86. Filtering 86

  87. Filtering 87 Discrete data e.g. user role Continuous data e.g.

    subscription expiry time
  88. Discrete Data 88 /users?role=admin /orders?shipped=true /calls?status=active

  89. Discrete Data 89 /users?role=admin /orders?shipped=true /calls?status=active 403 Forbidden /users?is_fbi_informant=true

  90. Discrete Data: JSON API 90 GET /employees? filter[role]=internal&filter[title]= senior

  91. Discrete Data: JSON API 91 GET /employees? filter[role]=internal&filter[title]= senior GET

    /employees? role=internal&title=senior
  92. Continuous Data 92

  93. Continuous Data 93 GET /orders? start_date=2018-01-01&end- date=2018=01-31

  94. Continuous Data 94 GET /orders? start_date=2018-01-01&end- date=2018=01-31 GET /orders? date[gte]=2018-01-01&date[lte]=2018=

    01-31
  95. Industry Standards 95

  96. Industry Standards: SCIM 96 filter=userName eq "bjensen" filter=name.familyName co "O'Malley"

    filter=userName sw "J" filter=urn:ietf:params:scim:schemas:core: 2.0:User:userName sw "J" filter=title pr filter=meta.lastModified gt "2011-05-13T04:42:34Z" https://tools.ietf.org/html/rfc7644#section-3.4.2.2
  97. Searching 97

  98. Searching 98 GET /items?q=title:red chair AND price:[10 TO 100]

  99. Pagination 99 Offset based Cursor based

  100. Pagination: Offset 100 GET /calls?page=3&page_size=100

  101. Pagination: Cursor 101 GET /calls GET /calls? cursor=2018-01-19T12:33:51 GET /calls?

    cursor=2018-01-18T03:00:18
  102. Pagination: Links 102 { "_links": { "next": { "href": "/calls?cursor=9274"

    } } } Link: <https://api.nexmo.com/v1/calls? cursor=9274>; rel="next",
  103. 8. Hypermedia Specifically, HAL+JSON 103

  104. [ { "id": "78d335fa323d01149c3dd6f0d489", "name": "My Application", } ] GET

    /v2/applications/78d335fa323d01149c3dd6f0d489 104
  105. [ { "id": "78d335fa323d01149c3dd6f0d48968cf", "name": "My Application", "_links": { "self":

    { "href": "/v2/applications/ 78d335fa323d01149c3dd6f0d48968cf" } } } ] GET /v2/applications/78d335fa323d01149c3dd6f0d489 105
  106. [ { "id": "78d335fa323d01149c3dd6f0d48968cf", "name": "My Application", "_links": { "self":

    { "href": "/v2/applications/ 78d335fa323d01149c3dd6f0d48968cf" }, "numbers": { "href": "/v2/numbers? application=78d335fa323d01149c3dd6f0d48968cf" } } } ] GET /v2/applications/78d335fa323d01149c3dd6f0d489 106
  107. "hints": { "allow": [ "GET", "PUT", "DELETE", "PATCH" ], "formats":

    { "application/json": {} } } Hints Format 107
  108. { "numbers": { "href": "/v2/numbers? application=78d335fa323d01149c3dd6f0d48968cf", "hints": { "allow": [

    "GET", "PUT", "DELETE", "PATCH" ], "formats": { "application/json": {} } } } Hints Format 108
  109. 9. Rate Limiting Should we add rate limiting? 109

  110. Yes. Rate Limiting 110

  111. Options: • Per-server • Per-region • Per-user Rate Limiting 111

  112. Refresh Rates: • Per-second limits • Interval • Bucket Rate

    Limiting 112
  113. How about queueing? Rate Limiting 113

  114. 10. Documentation This isn't an optional task for your API

    114
  115. Open API Specification: Docs 115

  116. Open API Specification: Postman 116

  117. Open API Specification: Generated Code 117

  118. Written Docs 118

  119. Open API Specification: Generated Code 119

  120. 11. Conclusion What have we learned? 120

  121. @mheap m@michaelheap.com