[med-svn] [r-cran-httr] 01/06: New upstream version 1.3.1

Andreas Tille tille at debian.org
Fri Sep 29 19:47:51 UTC 2017


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository r-cran-httr.

commit b3a8db74ff61395539512c47b316104d17c4d031
Author: Andreas Tille <tille at debian.org>
Date:   Fri Sep 29 21:43:09 2017 +0200

    New upstream version 1.3.1
---
 DESCRIPTION                             |  13 +-
 MD5                                     | 225 +++++----
 NAMESPACE                               |   9 +-
 NEWS.md                                 |  91 ++++
 R/body.R                                |  18 +-
 R/callback.R                            | 102 ++++
 R/http-browse.r                         |   8 +-
 R/httr.r                                |   5 +-
 R/oauth-app.r                           |  19 +-
 R/oauth-cache.R                         |  19 +-
 R/oauth-endpoint.r                      |   3 +-
 R/oauth-exchanger.r                     |  18 +-
 R/oauth-init.R                          | 181 +++++--
 R/oauth-listener.r                      |   6 +-
 R/oauth-server-side.R                   |  72 +--
 R/oauth-signature.r                     |  61 ++-
 R/oauth-token.r                         |  73 ++-
 R/progress.R                            |  22 +-
 R/request.R                             |  13 +-
 R/retry.R                               |  70 ++-
 R/safe-callback.R                       |  10 -
 R/url.r                                 |  16 +-
 R/utils.r                               |  10 +-
 R/verbose.r                             |  14 +-
 R/write-function.R                      |   3 +-
 README.md                               |   8 +-
 build/vignette.rds                      | Bin 261 -> 289 bytes
 demo/00Index                            |   1 +
 demo/oauth2-facebook.r                  |   3 +-
 demo/oauth2-github.r                    |   4 +-
 demo/oauth2-linkedin.r                  |   2 +-
 demo/oauth2-reddit.R                    |   9 +
 demo/oauth2-yelp.R                      |  43 ++
 inst/doc/api-packages.R                 |   4 -
 inst/doc/api-packages.Rmd               |  90 +---
 inst/doc/api-packages.html              | 195 +++-----
 inst/doc/quickstart.Rmd                 |  22 +-
 inst/doc/quickstart.html                | 860 +++++++++++++-------------------
 inst/doc/secrets.R                      |  90 ++++
 inst/doc/secrets.Rmd                    | 279 +++++++++++
 inst/doc/secrets.html                   | 259 ++++++++++
 man/BROWSE.Rd                           |   1 -
 man/DELETE.Rd                           |   2 +-
 man/GET.Rd                              |   2 +-
 man/HEAD.Rd                             |   2 +-
 man/PATCH.Rd                            |   1 -
 man/POST.Rd                             |   1 -
 man/PUT.Rd                              |   1 -
 man/RETRY.Rd                            |  27 +-
 man/Token-class.Rd                      |   4 +-
 man/VERB.Rd                             |   1 -
 man/add_headers.Rd                      |   1 -
 man/authenticate.Rd                     |   1 -
 man/cache_info.Rd                       |   1 -
 man/config.Rd                           |   1 -
 man/content.Rd                          |   4 +-
 man/content_type.Rd                     |   7 +-
 man/cookies.Rd                          |   1 -
 man/get_callback.Rd                     |  86 ++++
 man/guess_media.Rd                      |   1 -
 man/handle.Rd                           |   1 -
 man/handle_pool.Rd                      |   3 +-
 man/has_content.Rd                      |   1 -
 man/headers.Rd                          |   1 -
 man/hmac_sha1.Rd                        |   1 -
 man/http_condition.Rd                   |   1 -
 man/http_error.Rd                       |   3 +-
 man/http_status.Rd                      |   1 -
 man/http_type.Rd                        |   1 -
 man/{httr.Rd => httr-package.Rd}        |  19 +-
 man/httr_dr.Rd                          |   1 -
 man/httr_options.Rd                     |   3 +-
 man/init_oauth1.0.Rd                    |   3 +-
 man/init_oauth2.0.Rd                    |  22 +-
 man/insensitive.Rd                      |   1 -
 man/jwt_signature.Rd                    |  24 +-
 man/modify_url.Rd                       |   1 -
 man/oauth1.0_token.Rd                   |   1 -
 man/oauth2.0_token.Rd                   |  14 +-
 man/oauth_app.Rd                        |   7 +-
 man/oauth_callback.Rd                   |   1 -
 man/oauth_endpoint.Rd                   |   4 +-
 man/oauth_endpoints.Rd                  |   1 -
 man/oauth_exchanger.Rd                  |  10 +-
 man/oauth_listener.Rd                   |   3 +-
 man/oauth_service_token.Rd              |   6 +-
 man/oauth_signature.Rd                  |   3 +-
 man/parse_http_date.Rd                  |   3 +-
 man/parse_media.Rd                      |   1 -
 man/parse_url.Rd                        |  13 +-
 man/progress.Rd                         |   7 +-
 man/response.Rd                         |   1 -
 man/revoke_all.Rd                       |   1 -
 man/safe_callback.Rd                    |  16 -
 man/set_config.Rd                       |   3 +-
 man/set_cookies.Rd                      |   1 -
 man/sha1_hash.Rd                        |   1 -
 man/sign_oauth.Rd                       |   1 -
 man/status_code.Rd                      |   1 -
 man/stop_for_status.Rd                  |   3 +-
 man/timeout.Rd                          |   1 -
 man/upload_file.Rd                      |   1 -
 man/use_proxy.Rd                        |   1 -
 man/user_agent.Rd                       |   1 -
 man/verbose.Rd                          |   2 +-
 man/with_config.Rd                      |   1 -
 man/write_disk.Rd                       |   1 -
 man/write_function.Rd                   |   1 -
 man/write_stream.Rd                     |   3 +-
 tests/testthat/test-callback.R          |  14 +
 tests/testthat/test-config.r            |   2 +-
 tests/testthat/test-oauth-cache.R       |  42 +-
 tests/testthat/test-oauth-server-side.R |  12 +-
 tests/testthat/test-oauth.R             |  30 ++
 tests/testthat/test-oauth_signature.R   |  13 +
 tests/testthat/test-url.r               |   7 +-
 vignettes/api-packages.Rmd              |  90 +---
 vignettes/quickstart.Rmd                |  22 +-
 vignettes/secrets.Rmd                   | 279 +++++++++++
 119 files changed, 2535 insertions(+), 1272 deletions(-)

diff --git a/DESCRIPTION b/DESCRIPTION
index b3f965c..143b3a6 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,5 +1,5 @@
 Package: httr
-Version: 1.2.1
+Version: 1.3.1
 Title: Tools for Working with URLs and HTTP
 Description: Useful tools for working with HTTP organised by HTTP verbs
     (GET(), POST(), etc). Configuration functions make it easy to control
@@ -11,15 +11,16 @@ Authors at R: c(
 Depends: R (>= 3.0.0)
 Imports: jsonlite, mime, curl (>= 0.9.1), openssl (>= 0.8), R6
 Suggests: httpuv, jpeg, knitr, png, testthat (>= 0.8.0), readr, xml2,
-        rmarkdown
+        rmarkdown, covr
 VignetteBuilder: knitr
 License: MIT + file LICENSE
-URL: https://github.com/hadley/httr
-RoxygenNote: 5.0.1
+RoxygenNote: 6.0.1
+URL: https://github.com/r-lib/httr
+BugReports: https://github.com/r-lib/httr/issues
 NeedsCompilation: no
-Packaged: 2016-07-03 17:01:25 UTC; hadley
+Packaged: 2017-08-18 17:47:58 UTC; hadley
 Author: Hadley Wickham [aut, cre],
   RStudio [cph]
 Maintainer: Hadley Wickham <hadley at rstudio.com>
 Repository: CRAN
-Date/Publication: 2016-07-03 22:33:34
+Date/Publication: 2017-08-20 14:44:14 UTC
diff --git a/MD5 b/MD5
index f6fff03..62de69b 100644
--- a/MD5
+++ b/MD5
@@ -1,10 +1,11 @@
-0469ba848f54ac99f0702dc78cb66547 *DESCRIPTION
+445bf01dcc1195fbf413103ea21ccc1d *DESCRIPTION
 d8d2f9bd4836cd6c839888295d3b1da5 *LICENSE
-0e42ce7b977f0463ce599dedd8108932 *NAMESPACE
-abb7cd67ea6cd855fd7badac905021be *NEWS.md
+915536a660af0085ac4ed932f0a67523 *NAMESPACE
+79006a573e8a1efc2a842a2af2beab4c *NEWS.md
 bd92750bfa50b28c02c7b90e049e186a *R/authenticate.r
-b48786effe2ad5fb819a41cb34ddcc2f *R/body.R
+1121b77dabec9aeb328d6689bdfba3b3 *R/body.R
 aaba53b8b8faf228de81282eb510a22c *R/cache.R
+4b3546302d94c7fe16197985f258c173 *R/callback.R
 186e762d7e02335e191a9aa4275d18c3 *R/config.r
 036ef2bc6958ab6b8a49e59e897f018f *R/content-parse.r
 ae47dabf8973aa9714f60ca86dde57f3 *R/content.r
@@ -16,7 +17,7 @@ e3e79952d1831087d2354bd6c3648846 *R/handle-pool.r
 f27b306d1606ee49f65505d21ce754ef *R/handle-url.r
 c41c717ffcde8a8b1b3502f853a93c27 *R/handle.r
 394746817a32c07dae3d8a025bd70d7c *R/headers.r
-9996ec31d1f08f593cf7d7d3559090d7 *R/http-browse.r
+c02539b7c0701a71d66254a79a481ac8 *R/http-browse.r
 25186600762a002bc2169441103a7f45 *R/http-delete.r
 41340a619a0dbcfbe89284dfc16d966f *R/http-get.r
 98bd9022843c4bb5738562963a95f90b *R/http-head.r
@@ -24,145 +25,151 @@ c41c717ffcde8a8b1b3502f853a93c27 *R/handle.r
 8aaa841abc0d1a1850ef9e63d5ea8118 *R/http-post.r
 b0408bd78c3d7a391142a639199b280e *R/http-put.r
 9f4ceb2002108a4bfe90f83c72cf6cd6 *R/http-verb.R
-b879ebac436858946e832273628c272a *R/httr.r
+3b30a07f917a43dfbf2a9c51a0890820 *R/httr.r
 50c8c1f9754dabfeb57f0fb52f45abf9 *R/insensitive.r
 d600fbff459e40297d94a685c026c047 *R/media-guess.r
 db328762fdcb49c26031f84a8f657936 *R/media-parse.r
-5e37edd05896191c1312420a2ff5c311 *R/oauth-app.r
-80c4e74d44d8f269bf9591c401af7df6 *R/oauth-cache.R
-60ab655a5be1be5f0f4814adfd543e08 *R/oauth-endpoint.r
+165ce8c7b346bb7459eafd8a21bd43c1 *R/oauth-app.r
+9210663adbbef413dbe2d5a42d1c2d60 *R/oauth-cache.R
+0b321fa87268ca21612fccaf0f330f59 *R/oauth-endpoint.r
 9b91edccda60dd6f6e19ba9fe707e4af *R/oauth-error.r
-ed21a90d8f42cea7077bfbbdfb20b09e *R/oauth-exchanger.r
-cffe6a96c254fd8d14b8d06b0c6ecc8c *R/oauth-init.R
-f8135a8eb3405f696e03b38553055847 *R/oauth-listener.r
+eb24ea3fff11bf6b7a9722664e27edce *R/oauth-exchanger.r
+d6493389acf5a677f6419ff24a760a63 *R/oauth-init.R
+df27f33ced475182152b14b20b1da96d *R/oauth-listener.r
 64f4f6cc35841da3dcd6a690bb252d99 *R/oauth-refresh.R
-7c4d8f48650a3a04e90da1f676cf9fac *R/oauth-server-side.R
-386b280bbdb8ce0b010f52bd3af2a843 *R/oauth-signature.r
+58a1a1aa79b1ee27ab0d3e40f6a86951 *R/oauth-server-side.R
+2558616c7d45d6b91e379f55396d7282 *R/oauth-signature.r
 92de85f0fff3463c39d5d3d27d46cb80 *R/oauth-token-utils.R
-13c4bb1d32d822c4447527541b82dd49 *R/oauth-token.r
-4c9ff31809d82b3911940235ed9ff687 *R/progress.R
+91c48ecca7f8b1258e12d641d27de112 *R/oauth-token.r
+1d0d1e75f01b318c8aa109f7a6c1914b *R/progress.R
 d8556502db0dea0410fe50da9a96c32c *R/proxy.r
-6fa67c500f7360fc608efd66bbad2826 *R/request.R
+3501125f07bc876a39ea39e16d19e17d *R/request.R
 f3dbb44782b6008e9a33868ddca8f3b7 *R/response-status.r
 f55e739d371ee154ba74c0192c6c03b3 *R/response-type.R
 80b44155b8b5e8f39693049e16dacd5b *R/response.r
-b1f36fd2099ad765f15364e4327e9565 *R/retry.R
-e3e63290c8cae011d0557c7c61778b82 *R/safe-callback.R
+57884e53939130898dfdde92b75c3ca7 *R/retry.R
 f50da1c6a2c283f0ed7de9d3169b02cc *R/sha1.r
 1cf064907c712a08e2866864f7b68ffc *R/str.R
 c27933cf5f645f5c7468693f45bd63c1 *R/timeout.r
 5bd5207e2633fd34f9e26573f4883210 *R/upload-file.r
 4b8120b7d322cf1ef427ea5a2815e884 *R/url-query.r
-88c16965635b815ad99e8f7464e77b5c *R/url.r
+0110770879623f170ed929552d69648d *R/url.r
 824dafb1434170d307c8966f03f580c0 *R/user-agent.r
-577336a211079c5837e45dd9f5d24d56 *R/utils.r
-f9ca13c89c4014c460fe3d4272f5d54f *R/verbose.r
-f82164fab7adbbe500b3a1d5b4ddcd30 *R/write-function.R
+ab20adffd30d92cee692e3075f0fda2f *R/utils.r
+98fbd011c88867a07568f8225b54a43a *R/verbose.r
+94b50caef84c4ce828d60c7a84a2e9c6 *R/write-function.R
 ada599319a3dd7c4c2b13d0921688ca0 *R/zzz.R
-c12ca1179b8fa14b689be708fe712b77 *README.md
-1ff114b30673d19ebf5bd230cdcd06ac *build/vignette.rds
-4d3d0fc8511f908ca94f30b6c2d0d5dc *demo/00Index
+4301ecb6a5a8b49ee2217cb49f7f71d5 *README.md
+80d950c704a33991c0fb4c3373673e34 *build/vignette.rds
+e1d783c97fb8b3e46ac4737acd519ace *demo/00Index
 fc66ca80db42b950853d0b036128141b *demo/connection-sharing.r
 7cdacff15698ad17999bfe5345da6fe1 *demo/oauth1-twitter.r
 8a9d5260225541930dfc05b080e51d51 *demo/oauth1-vimeo.r
 947b50917afbe6ce5326ad004a79ac06 *demo/oauth1-withings.r
 3a82d7f7f92a6ce4c4b15e1f171074b8 *demo/oauth1-yahoo.r
 f46460f846bd679d70b740777eae5c78 *demo/oauth2-azure.r
-9ac5cedb8edf2b9caceb5d27752266d0 *demo/oauth2-facebook.r
-5a974f25c29ef1da20486a8ccd701c71 *demo/oauth2-github.r
+ff9d9ca1c28c9f69e47fc4fe10837761 *demo/oauth2-facebook.r
+8178fe363473dbe607e476bedb7472fe *demo/oauth2-github.r
 8911d6fd881354e1208eaf810e48a01d *demo/oauth2-google.r
-328191c10a2aeaa4521937b1cac34312 *demo/oauth2-linkedin.r
-c2544c2fe736898cb88e98d5617cf814 *demo/oauth2-reddit.R
+a8d44f62b83806847fab08e598c51f46 *demo/oauth2-linkedin.r
+3983858eb314575fdbeb5e42a5209e70 *demo/oauth2-reddit.R
+d99932e43e85f3d3666ed54b75d5fdb9 *demo/oauth2-yelp.R
 56b0c883a1b838991459f7eecc14bbc5 *demo/service-account.R
-87d51740a72639b47b511b1e65483341 *inst/doc/api-packages.R
-f6864e8c7578d7f38b36afd4cc526ba6 *inst/doc/api-packages.Rmd
-5e030c82fad4dcd6db4af986bb2a1026 *inst/doc/api-packages.html
+76d3c415e63c23cd74d89dc6448f5d40 *inst/doc/api-packages.R
+ae1c07481fc6d84990b99a04b6e24a8c *inst/doc/api-packages.Rmd
+be8db65a050943812497c6bc313345d4 *inst/doc/api-packages.html
 18601f685970936f29efcc7180d9833b *inst/doc/quickstart.R
-0b42e0a3138e6decc52e833e2be51b36 *inst/doc/quickstart.Rmd
-0e70146a9176e6e0130a6e0fdc342640 *inst/doc/quickstart.html
-4ce63cb50b0e51b00c0b7d55fff8ed03 *man/BROWSE.Rd
-5bc314a058d7989f19d508af8956bec9 *man/DELETE.Rd
-504166acbdb669867cc25a41d094ec62 *man/GET.Rd
-e6033a1ddb084222b15188384ecbec5a *man/HEAD.Rd
-f920f4af0bcb90fcd0d95af6d22b9399 *man/PATCH.Rd
-7a6c4b101dbbbd65f6b53ec7461f67d2 *man/POST.Rd
-8af5afe973d13961ff4e4ac93376c1db *man/PUT.Rd
-afbd54e66f30e6011b5d31f88f4f7a2c *man/RETRY.Rd
-0c48c73110d51b97bea50dfeb36ef733 *man/Token-class.Rd
-50fca7c44f6f7cce73271fc8e83ddf6c *man/VERB.Rd
-dabe1819e7fcd41a1053cf40e761b98a *man/add_headers.Rd
-2368c678a054cc0d8a2a4bc93458a2ef *man/authenticate.Rd
-ba9c12af5069debb480c4c0ad1b48a86 *man/cache_info.Rd
-c25539c73dcd1067f5a8e0fb6737e839 *man/config.Rd
-f55c550b46b918cdc14a0108abe87274 *man/content.Rd
-3e1b14f7a31342c290202f8f29cfe474 *man/content_type.Rd
-e52dcdc759b1450803739c63226edefa *man/cookies.Rd
-3bd98071566314acc174eb469003036d *man/guess_media.Rd
-0964052d75054ab082b0135de58a55c7 *man/handle.Rd
-8f88da3c4e76650dfadb2aa992436b9f *man/handle_pool.Rd
-3556f0196233074a73c462af0f141282 *man/has_content.Rd
-41ece222eab8883f9320c1ce4bd41bd4 *man/headers.Rd
-71b5f0bab83e74803dd83d4c6a9d4f3c *man/hmac_sha1.Rd
-4f32d37c8399712ecf4ac8d9cef3a153 *man/http_condition.Rd
-a1d04030297bf356f9b1b493abf0fa77 *man/http_error.Rd
-8c39280ac940496c23260ade4c759b36 *man/http_status.Rd
-b748089a0b8af1bda99b76f896deaa01 *man/http_type.Rd
-ab770adb0fc75faae597b61d6f861a7a *man/httr.Rd
-c381ef53e7d3508efd16d53223cb958b *man/httr_dr.Rd
-c746c303e13fcbbcade3dcc2b83ba4f5 *man/httr_options.Rd
-3b96f7d1aa42de44eb20748538717cd1 *man/init_oauth1.0.Rd
-065c981a0d75b877d2404a485b8b0515 *man/init_oauth2.0.Rd
-84d7be3d72063c2dabf654041d16875e *man/insensitive.Rd
-bdfc5a61474425e744a1d8fd8411fc34 *man/jwt_signature.Rd
-9dd2f4e14ce47e4cb77a75f0348040b0 *man/modify_url.Rd
-ec6d70ecefe1e997ed03e063e6a3e15f *man/oauth1.0_token.Rd
-2b1e8733fbe1261dc565488cf1719630 *man/oauth2.0_token.Rd
-1e2c94af3120d45a67a19ca175448f47 *man/oauth_app.Rd
-561fa1aa1556297a100222d88654fa7f *man/oauth_callback.Rd
-6688a309e83d52a5351adddde6ac851c *man/oauth_endpoint.Rd
-c79c6445aff9c54395bb0e1b7eccdced *man/oauth_endpoints.Rd
-048b0684b3f9977801d33bb4f71ecbfc *man/oauth_exchanger.Rd
-061149a5aff45c8d572d59e10acf06f3 *man/oauth_listener.Rd
-ab1b4687dd3fd6d96b6ac0c1a774fe1a *man/oauth_service_token.Rd
-68b619d7741bb24172504d108b1da47f *man/oauth_signature.Rd
-34d32b43df6f19b136ad15db88d141c8 *man/parse_http_date.Rd
-37a2d6f36d6355d446551d74508c5554 *man/parse_media.Rd
-e3d5c74f6ada861804097d6a0a2e04a0 *man/parse_url.Rd
-8dc81980505c5b2ccc920d6d51079e9d *man/progress.Rd
-df6ab262a10e8d249bb4f42220903039 *man/response.Rd
-d34717c68ab3cb596ed1e65ca84d381d *man/revoke_all.Rd
-7166648637b10c7cdcba2a060e2ab67a *man/safe_callback.Rd
-d4e739b72d9d4cf6dc54012878a8b087 *man/set_config.Rd
-a9ee6a77a8037f2a144b451f8b7cd1f9 *man/set_cookies.Rd
-6165b0829150f6c16c4cc809c72f9fe8 *man/sha1_hash.Rd
-ea2a1a4990e4e1d29325e53f127239a6 *man/sign_oauth.Rd
-b618be30c07bfbb98cdc5e93492fd0de *man/status_code.Rd
-2f93e7581eab65200588096b45ff8c07 *man/stop_for_status.Rd
-788002fb737ab0a0693fc9f07c47f5a5 *man/timeout.Rd
-dcc6fbfc9845e17f65733c138fb4efd8 *man/upload_file.Rd
-bd50f8e60bfaee5898767c199fac5c7b *man/use_proxy.Rd
-666d54eed089d5f71d3ba7e4d81b9314 *man/user_agent.Rd
-5a91123b7cd4653d323261c81fc4ce43 *man/verbose.Rd
-e82221d439847e09b18a2f67e05250ba *man/with_config.Rd
-c73238298e0ebbc4282ea0420caaf0ac *man/write_disk.Rd
-bae74ffe21191454355d08ce616ad9e0 *man/write_function.Rd
-b8200aea09f347b5db487103841d3dff *man/write_stream.Rd
+fc892815cee67ed566f68e0751d244a3 *inst/doc/quickstart.Rmd
+3b427dbd8a32af3035ae483435715e8c *inst/doc/quickstart.html
+41c9a71eb50601e92976731000ee67c3 *inst/doc/secrets.R
+cdba13e9901895ea94f0dad3e902471c *inst/doc/secrets.Rmd
+c6cbb1c1f34e0c745d3da4faca326cd8 *inst/doc/secrets.html
+028ab749089fc5d7c27b9eb2cc9dfa3f *man/BROWSE.Rd
+4e49b59759aae59d6c0c31fd17706e04 *man/DELETE.Rd
+5baa979071e4e52abfa0f7767ae11993 *man/GET.Rd
+a52460d1b0f77edbf401668524d64816 *man/HEAD.Rd
+a58674a8d451f44b28a46b654603cf19 *man/PATCH.Rd
+d2a87e3d75669363ff5f63ff844d6360 *man/POST.Rd
+0098fc2313cc8f13ffb55182ceea4d76 *man/PUT.Rd
+411f5bf5172ea2a9dab647df1a1e1b69 *man/RETRY.Rd
+96adee358da5dc4f7a94265410d072a0 *man/Token-class.Rd
+0b5fca8c493dae6bcb90c39df0a49f2c *man/VERB.Rd
+81a22abf94f8ffd11884c340cd8b1cfc *man/add_headers.Rd
+8b4d3e1272553a8b6d8e4aafc4831cff *man/authenticate.Rd
+562421c2c203be5971b6ceb3ee40f1e8 *man/cache_info.Rd
+dab17879a3f0bd3e87b0ab2777fb2856 *man/config.Rd
+3e5be8879b161a83ffd17d36451c8ba8 *man/content.Rd
+0db2ef5b370bbdccf3a2612ba035d1c7 *man/content_type.Rd
+c30076306c657a613ea0919606be6f31 *man/cookies.Rd
+1eada51bcd1480272516e0f16d65dff7 *man/get_callback.Rd
+ad1faa46b07c9a66cc38908fcdf1b7c6 *man/guess_media.Rd
+487f9b437b3d054c0a25d77176c47047 *man/handle.Rd
+8ba5cde89845df95cf4156c9dc2ed8b4 *man/handle_pool.Rd
+caca67bf710251fecc3244ed46fecc7b *man/has_content.Rd
+d4e019ff70f1c79e1c8f92822fc09852 *man/headers.Rd
+300c19506c669d0d8c3101a7b919f3ae *man/hmac_sha1.Rd
+9c03d0ba23b4378418865932fe0b9751 *man/http_condition.Rd
+d291c88e3cd3a90c8180589fb356a548 *man/http_error.Rd
+9a640339fc6c6174807c11ed1a1d72f6 *man/http_status.Rd
+84fa93c3bd833769ea6dd3792a83f3e6 *man/http_type.Rd
+676a452b5785b21b1c4f47ba7fa0a9b0 *man/httr-package.Rd
+30dffc7b44899d9e080ff819162771aa *man/httr_dr.Rd
+d16c3bbcd38e4e677ad9b7a40330fdb5 *man/httr_options.Rd
+10db8cffeeb5c34d0c6952d25d0c668f *man/init_oauth1.0.Rd
+7f8b1d067ebc903ccd35db87aebcdbb9 *man/init_oauth2.0.Rd
+1ba4167747a3d89733e2cb1ab33eef0e *man/insensitive.Rd
+82b40c57f442b4e116a999083d48a198 *man/jwt_signature.Rd
+e3bee35191682ddf090bbd73d9cd9592 *man/modify_url.Rd
+11d5e05b297a8768ac0e38cc7570974f *man/oauth1.0_token.Rd
+bfd961a5f5f715d0b6b620f738fce9ba *man/oauth2.0_token.Rd
+58b0546de39086e7ec7e4cb5e6192613 *man/oauth_app.Rd
+9b9fe6bb82fa6274712e9136b482649a *man/oauth_callback.Rd
+722e8b4df52fa6eba05b8ac9b05070df *man/oauth_endpoint.Rd
+7555dfc425fc22adfee7f8e330006434 *man/oauth_endpoints.Rd
+79ea6c92708fde82e844cfc7996cfc4a *man/oauth_exchanger.Rd
+321f702590c5b8ea6811027422d68ea8 *man/oauth_listener.Rd
+b66e7e4611fcf4d4e3675dd0152048b8 *man/oauth_service_token.Rd
+b6dec3cf695d96180fbce47241e33ace *man/oauth_signature.Rd
+6a185a4ccec67d75564c153fc3086c01 *man/parse_http_date.Rd
+5e8127a123ad143acedbc79eaddd11c5 *man/parse_media.Rd
+4fe3672f20514d7c99347b1d66ab5657 *man/parse_url.Rd
+bf9e2d108f1c635bca0114b6177f36cc *man/progress.Rd
+43c769bcdc2dd01c068758d826004c2f *man/response.Rd
+189323e1e16fd2b5f9adea68bf22d9a5 *man/revoke_all.Rd
+31d6fe21340ae511a2dfb6f56702b5cd *man/set_config.Rd
+ab59af469f26b04583f649a21624243a *man/set_cookies.Rd
+fe7d33df28253b823a835c602d26fbb3 *man/sha1_hash.Rd
+bb3c257ca803c6b9821cb95af81ebb16 *man/sign_oauth.Rd
+a7df331a37986e730a86a03f1001f72d *man/status_code.Rd
+8017c7e4f80a178d578f393c8b72b09e *man/stop_for_status.Rd
+4bd325d297a093429ffd55efe09b7a49 *man/timeout.Rd
+e2c7336a50729ba895409d01e6a5596f *man/upload_file.Rd
+37dc1f870ac342b2596b82bf7090de90 *man/use_proxy.Rd
+97786a3601f7f6cc7b03e9280d86af58 *man/user_agent.Rd
+ebd8a23e932723dbed3ce9c8157a08a2 *man/verbose.Rd
+b693a1eb48fe4004d8722ef74081a8e9 *man/with_config.Rd
+25f3f984eab9536dee6ee3f36a0de3bc *man/write_disk.Rd
+10a960266264d74c53d32ee662df802d *man/write_function.Rd
+6a0b730632c2f51731bebd6ca5b52046 *man/write_stream.Rd
 24f3564620367449ffc825c5bafb0964 *tests/testthat.R
 7fe4b98fb68caf83fa31905435f08da0 *tests/testthat/data.txt
 a0b8fa3b6d09d21a6834f93f678e9d67 *tests/testthat/test-body.r
-0b30ee69b1e565aba653220cce46bd13 *tests/testthat/test-config.r
+ab59d0a852911dcf3f36676ddcb4580d *tests/testthat/test-callback.R
+a6dd96fe4697c3bff347ae2b5a9d1a71 *tests/testthat/test-config.r
 fffa5d36bd98d50813a328cb5def6008 *tests/testthat/test-content.R
 838c524ae18e025498ae6d40ee82c696 *tests/testthat/test-encoding.R
 dbb6ca6ecd7835bce66ffb3af68bcddb *tests/testthat/test-header.r
 d43ac011e6e59c4472fc733e5892066d *tests/testthat/test-http-condition.R
 aa1471215f9852500a963cd11b8dfdec *tests/testthat/test-http-error.R
-ea6398221aa38f6f4a9c377095f91cda *tests/testthat/test-oauth-cache.R
-1bf1e458091b159d93d75518c9939bb7 *tests/testthat/test-oauth-server-side.R
-8a332374e13cb21ad434ef1a6ae02780 *tests/testthat/test-oauth.R
+f476707e215eabc8c3537e68e1737e29 *tests/testthat/test-oauth-cache.R
+804cabd5b736d494ed5e0ed7876980d1 *tests/testthat/test-oauth-server-side.R
+b1a47cde8e20787ef956f75e5711bd00 *tests/testthat/test-oauth.R
+3c599f3ebfdccb3206bf73013a3804dd *tests/testthat/test-oauth_signature.R
 bf515db445fb665849227fe5ff91aaba *tests/testthat/test-parse_media.R
 1d47e0820dc326332f4659e9212ac7b9 *tests/testthat/test-request.r
 cd528971a375e9a62afcd3cf58a82172 *tests/testthat/test-response.r
 f645a25ca4d13891743ea5e4104bad91 *tests/testthat/test-ssl.R
-1f5e9436928209f5acebc02cbf207602 *tests/testthat/test-url.r
-f6864e8c7578d7f38b36afd4cc526ba6 *vignettes/api-packages.Rmd
-0b42e0a3138e6decc52e833e2be51b36 *vignettes/quickstart.Rmd
+77c89656a4a413ef823d7adcf3679fd5 *tests/testthat/test-url.r
+ae1c07481fc6d84990b99a04b6e24a8c *vignettes/api-packages.Rmd
+fc892815cee67ed566f68e0751d244a3 *vignettes/quickstart.Rmd
+cdba13e9901895ea94f0dad3e902471c *vignettes/secrets.Rmd
diff --git a/NAMESPACE b/NAMESPACE
index eb9cab5..e0ff844 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -5,6 +5,9 @@ S3method("[",insensitive)
 S3method("[[",insensitive)
 S3method(as.character,form_file)
 S3method(as.character,response)
+S3method(as_field,default)
+S3method(as_field,logical)
+S3method(as_field,numeric)
 S3method(c,request)
 S3method(cookies,handle)
 S3method(cookies,response)
@@ -12,7 +15,6 @@ S3method(headers,response)
 S3method(http_error,character)
 S3method(http_error,integer)
 S3method(http_error,response)
-S3method(length,path)
 S3method(print,cache_info)
 S3method(print,handle)
 S3method(print,oauth_app)
@@ -49,6 +51,7 @@ export(content_type_json)
 export(content_type_xml)
 export(cookies)
 export(curl_docs)
+export(get_callback)
 export(guess_media)
 export(handle)
 export(handle_find)
@@ -69,6 +72,8 @@ export(insensitive)
 export(message_for_status)
 export(modify_url)
 export(oauth1.0_token)
+export(oauth2.0_access_token)
+export(oauth2.0_authorize_url)
 export(oauth2.0_token)
 export(oauth_app)
 export(oauth_callback)
@@ -87,7 +92,7 @@ export(progress)
 export(rerequest)
 export(reset_config)
 export(revoke_all)
-export(safe_callback)
+export(set_callback)
 export(set_config)
 export(set_cookies)
 export(sha1_hash)
diff --git a/NEWS.md b/NEWS.md
index 4356c06..183ab9d 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,94 @@
+# httr 1.3.1
+
+* Re-enable on-disk caching (accidentally disabled in #457) (#475)
+
+# httr 1.3.0
+
+## API changes
+
+* Deprecated `safe_callback()` has been removed.
+
+* `is_interactive` argument to `init_oauth1.0()`, `init_oauth2.0()` and 
+  `oauth_listener()` has been deprecated, as the R session does not actually
+  need to be interactive.
+
+## New features
+
+* New `set_callback()` and `get_callback()` set and query callback functions 
+  that are called right before and after performing an HTTP request 
+  (@gaborcsardi, #409)
+
+* `RETRY()` now retries if an error occurs during the request (@asieira, #404),
+  and gains two new arguments:
+
+  * `terminate_on` gives you greater control over which status codes should
+     it stop retrying. (@asieira, #404) 
+
+  * `pause_min` allows for sub-second delays. (Use with caution! Generally the 
+    default is preferred.) (@r2evans)
+    
+  * If the server returns HTTP status code 429 and specifies a `retry-after` 
+    value, that value will now be used instead of exponential backoff with 
+    jitter, unless it's smaller than `pause_min`. (@nielsoledam, #472)
+
+## OAuth
+
+* New oauth cache files are always added to `.gitignore` and, if it exists, 
+  `.Rbuildignore`. Specifically, this now happens when option 
+  `httr_oauth_cache = TRUE` or user specifies cache file name explicitly. 
+  (@jennybc, #436)
+
+* `oauth_encode()` now handles UTF-8 characters correctly. 
+  (@yutannihilation, #424)
+
+* `oauth_app()` allows you to specify the `redirect_url` if you need to 
+  customise it. 
+  
+* `oauth_service_token()` gains a `sub` parameter so you can request
+  access on behalf of another user (#410), and accepts a character vector 
+  of `scopes` as was described in the documentation (#389).
+
+* `oauth_signature()` now normalises the URL as described in the OAuth1.0a
+  spec (@leeper, #435)
+
+* New `oauth2.0_authorize_url()` and `oauth2.0_access_token()` functions 
+  pull out parts of the OAuth process for reuse elsewhere (#457).
+
+* `oauth2.0_token()` gains three new arguments:
+
+    * `config_init` allows you to supply additional config for the initial 
+      request. This is needed for some APIs (e.g. reddit) which rate limit 
+      based on `user_agent` (@muschellij2, #363).
+      
+    * `client_credentials`, allows you to use the OAauth2 *Client Credential 
+      Grant*. See [RFC 6749](https://tools.ietf.org/html/rfc6749#section-4)
+      for details. (@cderv, #384)
+
+    * A `credentials` argument that allows you to customise the auth flow. 
+      For advanced used only (#457)
+
+* `is_interactive` argument to `init_oauth1.0()`, `init_oauth2.0()` and 
+  `oauth_listener()` has been deprecated, as the R session does not need
+  to be interactive.
+
+## Minor bug fixes and improvements
+  
+* `BROWSER()` prints a message telling you to browse to the URL if called
+  in a non-interactive session.
+
+* `find_cert_bundle()` will now correctly find cert bundle in "R_HOME/etc" 
+  (@jiwalker-usgs, #386).
+
+* You can now send lists containing `curl::form_data()` in the `body` of
+  requests with `encoding = "multipart". This makes it possible to specify the 
+  mime-type of individual components (#430).
+
+* `modify_url()` recognises more forms of empty queries. This eliminates a 
+  source of spurious trailing `?` and `?=` (@jennybc, #452).
+  
+* The `length()` method of the internal `path` class is no longer exported 
+  (#395).
+
 # httr 1.2.1
 
 * Fix bug with new cache creation code: need to check that 
diff --git a/R/body.R b/R/body.R
index 8fbb1f5..9985eb9 100644
--- a/R/body.R
+++ b/R/body.R
@@ -5,10 +5,6 @@ body_config <- function(body = NULL,
   if (identical(body, FALSE))
     return(config(post = TRUE, nobody = TRUE))
 
-  # For character/raw, send raw bytes
-  if (is.character(body) || is.raw(body))
-    return(body_raw(body, type = type))
-
   # Send single file lazily
   if (inherits(body, "form_file")) {
     con <- file(body$path, "rb")
@@ -33,6 +29,10 @@ body_config <- function(body = NULL,
     ))
   }
 
+  # For character/raw, send raw bytes
+  if (is.character(body) || is.raw(body))
+    return(body_raw(body, type = type))
+
   # Post with empty body
   if (is.null(body))
     return(body_raw(raw()))
@@ -55,10 +55,18 @@ body_config <- function(body = NULL,
   } else if (encode == "multipart") {
     if (!all(has_name(body)))
       stop("All components of body must be named", call. = FALSE)
-    request(fields = lapply(body, as.character))
+    request(fields = lapply(body, as_field))
   }
 }
 
+as_field <- function(x) UseMethod("as_field")
+#' @export
+as_field.numeric <- function(x) as.character(x)
+#' @export
+as_field.logical <- function(x) as.character(x)
+#' @export
+as_field.default <- function(x) x # assume curl will handle
+
 body_raw <- function(body, type = NULL) {
   if (is.character(body)) {
     body <- charToRaw(paste(body, collapse = "\n"))
diff --git a/R/callback.R b/R/callback.R
new file mode 100644
index 0000000..f02203a
--- /dev/null
+++ b/R/callback.R
@@ -0,0 +1,102 @@
+
+callback_env <- new.env(parent = emptyenv())
+callback_env$request <- NULL
+callback_env$response <- NULL
+
+#' Install or uninstall a callback function
+#'
+#' Supported callback functions: \describe{
+#' \item{\sQuote{request}}{This callback is called before an HTTP request
+#'   is performed, with the \code{request} object as an argument.
+#'   If the callback returns a value other than \code{NULL}, the HTTP
+#'   request is not performed at all, and the return value of the callback
+#'   is returned. This mechanism can be used to replay previously
+#'   recorded HTTP responses.
+#' }
+#' \item{\sQuote{response}}{This callback is called after an HTTP request
+#'   is performed. The callback is called with two arguments: the
+#'   \code{request} object and the \code{response} object of the HTTP
+#'   request. If this callback returns a value other than \code{NULL},
+#'   then this value is returned by \code{httr}.}
+#' }
+#'
+#' Note that it is not possible to install multiple callbacks of the same
+#' type. The installed callback overwrites the previously intalled one.
+#' To uninstall a callback function, set it to \code{NULL} with
+#' \code{set_callback()}.
+#'
+#' See the \code{httrmock} package for a proper example that uses
+#' callbacks.
+#'
+#' @param name Character scalar, name of the callback to query or set.
+#' @param new_callback The callback function to install, a function object;
+#'   or \code{NULL} to remove the currently installed callback (if any).
+#'
+#' @return \code{get_callback} returns the currently installed
+#'   callback, or \code{NULL} if none is installed.
+#'
+#'   \code{set_callback} returns the previously installed callback,
+#'   or \code{NULL} if none was installed.
+#'
+#' @export
+#' @examples
+#' \dontrun{
+#' ## Log all HTTP requests to the screeen
+#' req_logger <- function(req) {
+#'   cat("HTTP request to", sQuote(req$url), "\n")
+#' }
+#'
+#' old <- set_callback("request", req_logger)
+#' g1 <- GET("https://httpbin.org")
+#' g2 <- GET("https://httpbin.org/ip")
+#' set_callback("request", old)
+#'
+#' ## Log all HTTP requests and response status codes as well
+#' req_logger2 <- function(req) {
+#'   cat("HTTP request to", sQuote(req$url), "... ")
+#' }
+#' res_logger <- function(req, res) {
+#'   cat(res$status_code, "\n")
+#' }
+#'
+#' old_req <- set_callback("request", req_logger2)
+#' old_res <- set_callback("response", res_logger)
+#' g3 <- GET("https://httpbin.org")
+#' g4 <- GET("https://httpbin.org/ip")
+#' set_callback("request", old_req)
+#' set_callback("response", old_res)
+#'
+#' ## Return a recorded response, without performing the HTTP request
+#' replay <- function(req) {
+#'   if (req$url == "https://httpbin.org") g3
+#' }
+#' old_req <- set_callback("request", replay)
+#' grec <- GET("https://httpbin.org")
+#' grec$date == g3$date
+#' set_callback("request", old_req)
+#' }
+
+get_callback <- function(name) {
+  stopifnot(is.character(name), length(name) == 1, !is.na(name))
+  if (!name %in% ls(callback_env)) stop("Unknown httr callback: ", name)
+  callback_env[[name]]
+}
+
+#' @export
+#' @rdname get_callback
+
+set_callback <- function(name, new_callback = NULL) {
+  stopifnot(is.character(name), length(name) == 1, !is.na(name))
+  if (!name %in% ls(callback_env)) stop("Unknown httr callback: ", name)
+
+  old <- callback_env[[name]]
+  stopifnot(is.null(new_callback) || is.function(new_callback))
+  callback_env[[name]] <- new_callback
+  invisible(old)
+}
+
+perform_callback <- function(name, ...) {
+  if (!is.null(callback <- get_callback(name))) {
+    callback(...)
+  }
+}
diff --git a/R/http-browse.r b/R/http-browse.r
index 117def6..6c22732 100644
--- a/R/http-browse.r
+++ b/R/http-browse.r
@@ -13,7 +13,11 @@
 #' BROWSE("http://google.com")
 #' BROWSE("http://had.co.nz")
 BROWSE <- function(url = NULL, config = list(), ..., handle = NULL) {
-  if (!interactive()) return()
   hu <- handle_url(handle, url, ...)
-  utils::browseURL(hu$url)
+  if (interactive()) {
+    utils::browseURL(hu$url)
+  } else {
+    message("Please point your browser to the following url: ")
+    message(hu$url)
+  }
 }
diff --git a/R/httr.r b/R/httr.r
index d1aad16..d580659 100644
--- a/R/httr.r
+++ b/R/httr.r
@@ -23,6 +23,5 @@
 #' requests. The demos directory has six demos of using OAuth: three for 1.0
 #' (linkedin, twitter and vimeo) and three for 2.0 (facebook, github, google).
 #'
-#' @name httr
-#' @docType package
-NULL
+#' @keywords internal
+"_PACKAGE"
diff --git a/R/oauth-app.r b/R/oauth-app.r
index 2932d6a..29e3b4b 100644
--- a/R/oauth-app.r
+++ b/R/oauth-app.r
@@ -15,6 +15,9 @@
 #'
 #'   Use \code{NULL} to not store a secret: this is useful if you're relying on
 #'   cached OAuth tokens.
+#' @param redirect_uri The URL that user will be redirected to after
+#'   authorisation is complete. You should generally leave this as the default
+#'   unless you're using a non-standard auth flow (like with shiny).
 #' @export
 #' @family OAuth
 #' @examples
@@ -28,7 +31,10 @@
 #' # suppress the warning message
 #' oauth_app("my_app", "mykey")
 #' oauth_app("my_app", "mykey", NULL)
-oauth_app <- function(appname, key, secret = NULL) {
+oauth_app <- function(appname,
+                      key,
+                      secret = NULL,
+                      redirect_uri = oauth_callback()) {
   if (missing(secret)) {
     env_name <- paste0(toupper(appname), "_CONSUMER_SECRET")
     secret <- Sys.getenv(env_name)
@@ -40,8 +46,15 @@ oauth_app <- function(appname, key, secret = NULL) {
       message("Using secret stored in environment variable ", env_name)
     }
   }
-  structure(list(appname = appname, secret = secret, key = key),
-    class = "oauth_app")
+  structure(
+    list(
+      appname = appname,
+      secret = secret,
+      key = key,
+      redirect_uri = redirect_uri
+    ),
+    class = "oauth_app"
+  )
 }
 
 is.oauth_app <- function(x) inherits(x, "oauth_app")
diff --git a/R/oauth-cache.R b/R/oauth-cache.R
index 9b76377..82fc259 100644
--- a/R/oauth-cache.R
+++ b/R/oauth-cache.R
@@ -6,21 +6,30 @@ use_cache <- function(cache = getOption("httr_oauth_cache")) {
     stop("Cache must either be logical or string (file path)")
   }
 
-  # If it's a character, then it's a file path, so use it
-  if (is.character(cache)) return(cache)
-
   # If missing, see if it's ok to use one, and cache the results of
   # that check in a global option.
   if (is.na(cache)) {
     cache <- can_use_cache()
     options("httr_oauth_cache" = cache)
   }
+  ## cache is now TRUE, FALSE or path
+
+  if (isFALSE(cache)) {
+    return(NULL)
+  }
 
-  if (cache) ".httr-oauth" else NULL
+  if (isTRUE(cache)) {
+    cache <- ".httr-oauth"
+  }
+
+  if (!file.exists(cache)) {
+    create_cache(cache)
+  }
+  return(cache)
 }
 
 can_use_cache <- function(path = ".httr-oauth") {
-  file.exists(path) || (should_cache(path) && create_cache(path))
+  file.exists(path) || should_cache(path)
 }
 
 should_cache <- function(path = ".httr-oauth") {
diff --git a/R/oauth-endpoint.r b/R/oauth-endpoint.r
index b3edf91..a5cffb3 100644
--- a/R/oauth-endpoint.r
+++ b/R/oauth-endpoint.r
@@ -5,7 +5,8 @@
 #'
 #' @param request url used to request initial (unauthenticated) token.
 #'   If using OAuth2.0, leave as \code{NULL}.
-#' @param authorize url to send client to for authorisation
+#' @param authorize url to send client to for authorisation. Set to \code{NULL}
+#'   if not needed
 #' @param access url used to exchange unauthenticated for authenticated token.
 #' @param ... other additional endpoints.
 #' @param base_url option url to use as base for \code{request},
diff --git a/R/oauth-exchanger.r b/R/oauth-exchanger.r
index 9ba175d..d3ae5eb 100644
--- a/R/oauth-exchanger.r
+++ b/R/oauth-exchanger.r
@@ -1,11 +1,10 @@
 #' Walk the user through the OAuth2 dance without a local webserver.
 #'
 #' This performs a similar function to \code{\link{oauth_listener}},
-#' but without trying do open a browser or start a local webserver.
-#' This manual process can be useful in situations where the user is
-#' remotely accessing the machine outside a browser (say via ssh) or
-#' when it's not possible to successfully receive a callback (such as
-#' when behind a firewall).
+#' but without running a local webserver.  This manual process can be useful
+#' in situations where the user is remotely accessing the machine outside a
+#' browser (say via ssh) or when it's not possible to successfully receive a
+#' callback (such as when behind a firewall).
 #'
 #' This function should generally not be called directly by the user.
 #'
@@ -13,11 +12,8 @@
 #' @export
 #' @keywords internal
 oauth_exchanger <- function(request_url) {
-  message("Please point your browser to the following url: ")
-  message("")
-  message("  ", request_url)
-  message("")
+  BROWSE(request_url)
+
   authorization_code <- str_trim(readline("Enter authorization code: "))
-  info <- list(code = authorization_code)
-  info
+  list(code = authorization_code)
 }
diff --git a/R/oauth-init.R b/R/oauth-init.R
index 2376ed3..973ff13 100644
--- a/R/oauth-init.R
+++ b/R/oauth-init.R
@@ -6,7 +6,7 @@
 #' @param app An OAuth consumer application, created by
 #'    \code{\link{oauth_app}}
 #' @param permission optional, a string of permissions to ask for.
-#' @param is_interactive Is the current environment interactive?
+#' @param is_interactive DEPRECATED
 #' @param private_key Optional, a key provided by \code{\link[openssl]{read_key}}.
 #'   Used for signed OAuth 1.0.
 #' @export
@@ -48,76 +48,177 @@ init_oauth1.0 <- function(endpoint, app, permission = NULL,
 #' See demos for use.
 #'
 #' @inheritParams init_oauth1.0
-#' @param type content type used to override incorrect server response
 #' @param scope a character vector of scopes to request.
 #' @param user_params Named list holding endpoint specific parameters to pass to
-#'     the server when posting the request for obtaining or refreshing the
-#'     access token.
+#'   the server when posting the request for obtaining or refreshing the
+#'   access token.
+#' @param type content type used to override incorrect server response
 #' @param use_oob if FALSE, use a local webserver for the OAuth dance.
-#'     Otherwise, provide a URL to the user and prompt for a validation
-#'     code. Defaults to the of the \code{"httr_oob_default"} default,
-#'     or \code{TRUE} if \code{httpuv} is not installed.
-#' @param is_interactive Is the current environment interactive?
+#'   Otherwise, provide a URL to the user and prompt for a validation
+#'   code. Defaults to the of the \code{"httr_oob_default"} default,
+#'   or \code{TRUE} if \code{httpuv} is not installed.
 #' @param use_basic_auth if \code{TRUE} use http basic authentication to
-#'     retrieve the token. Some authorization servers require this.
-#'     If \code{FALSE}, the default, retrieve the token by including the
-#'     app key and secret in the request body.
+#'   retrieve the token. Some authorization servers require this.
+#'   If \code{FALSE}, the default, retrieve the token by including the
+#'   app key and secret in the request body.
+#' @param config_init Additional configuration settings sent to
+#'   \code{\link{POST}}, e.g. \code{\link{user_agent}}.
+#' @param client_credentials Default to \code{FALSE}. Set to \code{TRUE} to use
+#'   \emph{Client Credentials Grant} instead of \emph{Authorization
+#'   Code Grant}. See \url{https://tools.ietf.org/html/rfc6749#section-4.4}.
 #' @export
 #' @keywords internal
-init_oauth2.0 <- function(endpoint, app, scope = NULL, user_params = NULL,
-                          type = NULL, use_oob = getOption("httr_oob_default"),
+init_oauth2.0 <- function(endpoint, app, scope = NULL,
+                          user_params = NULL,
+                          type = NULL,
+                          use_oob = getOption("httr_oob_default"),
                           is_interactive = interactive(),
-                          use_basic_auth = FALSE) {
-  if (!use_oob && !is_installed("httpuv")) {
-    message("httpuv not installed, defaulting to out-of-band authentication")
-    use_oob <- TRUE
-  }
+                          use_basic_auth = FALSE,
+                          config_init = list(),
+                          client_credentials = FALSE
+                         ) {
+
+  scope <- check_scope(scope)
+  use_oob <- check_oob(use_oob)
 
-  if (isTRUE(use_oob)) {
-    stopifnot(interactive())
+  if (use_oob) {
     redirect_uri <- "urn:ietf:wg:oauth:2.0:oob"
     state <- NULL
   } else {
-    redirect_uri <- oauth_callback()
+    redirect_uri <- app$redirect_uri
     state <- nonce()
   }
 
-  scope_arg <- paste(scope, collapse = ' ')
-
-  authorize_url <- modify_url(endpoint$authorize, query = compact(list(
-    client_id = app$key,
-    scope = scope_arg,
-    redirect_uri = redirect_uri,
-    response_type = "code",
-    state = state)))
-  if (isTRUE(use_oob)) {
-    code <- oauth_exchanger(authorize_url)$code
+  # Some Oauth2 grant type not required an authorization request and code
+  # (see https://tools.ietf.org/html/rfc6749#section-4.4)
+  if (client_credentials) {
+    code <- NULL
   } else {
-    code <- oauth_listener(authorize_url, is_interactive)$code
+    authorize_url <- oauth2.0_authorize_url(
+      endpoint,
+      app,
+      scope = scope,
+      redirect_uri = redirect_uri,
+      state = state
+    )
+    code <- oauth_authorize(authorize_url, use_oob)
   }
 
   # Use authorisation code to get (temporary) access token
+  oauth2.0_access_token(
+    endpoint,
+    app,
+    code = code,
+    user_params = user_params,
+    type = type,
+    redirect_uri = redirect_uri,
+    client_credentials = client_credentials,
+    config = config_init
+  )
+}
 
-  # Send credentials using HTTP Basic or as parameters in the request body
-  # See https://tools.ietf.org/html/rfc6749#section-2.3 (Client Authentication)
-  req_params <- list(
+#' @export
+#' @rdname init_oauth2.0
+oauth2.0_authorize_url <- function(endpoint, app, scope,
+                                   redirect_uri = app$redirect_uri,
+                                   state = nonce()
+                                   ) {
+  modify_url(endpoint$authorize, query = compact(list(
     client_id = app$key,
+    scope = scope,
     redirect_uri = redirect_uri,
-    grant_type = "authorization_code",
-    code = code)
+    response_type = "code",
+    state = state)
+  ))
+}
+
+#' @export
+#' @rdname init_oauth2.0
+oauth2.0_access_token <- function(endpoint,
+                                  app,
+                                  code,
+                                  user_params = NULL,
+                                  type = NULL,
+                                  use_basic_auth = FALSE,
+                                  redirect_uri = app$redirect_uri,
+                                  client_credentials = FALSE,
+                                  config = list()
+                                  ) {
+
+  req_params <- compact(list(
+    client_id = app$key,
+    redirect_uri = if (client_credentials) NULL else redirect_uri,
+    grant_type = if (client_credentials) "client_credentials" else "authorization_code",
+    code = code
+  ))
 
   if (!is.null(user_params)) {
     req_params <- utils::modifyList(user_params, req_params)
   }
 
+  # Send credentials using HTTP Basic or as parameters in the request body
+  # See https://tools.ietf.org/html/rfc6749#section-2.3 (Client Authentication)
   if (isTRUE(use_basic_auth)) {
-    req <- POST(endpoint$access, encode = "form", body = req_params,
-      authenticate(app$key, app$secret, type = "basic"))
+    req <- POST(endpoint$access,
+      encode = "form",
+      body = req_params,
+      authenticate(app$key, app$secret, type = "basic"),
+      config = config
+    )
   } else {
     req_params$client_secret <- app$secret
-    req <- POST(endpoint$access, encode = "form", body = req_params)
+    req <- POST(endpoint$access,
+      encode = "form",
+      body = req_params,
+      config = config
+    )
   }
 
   stop_for_status(req, task = "get an access token")
   content(req, type = type)
 }
+
+
+# Parameter checking ------------------------------------------------------
+
+check_scope <- function(x) {
+  if (is.null(x)) {
+    return(NULL)
+  }
+
+  if (!is.character(x)) {
+    stop("`scope` must be a character vector", call. = FALSE)
+  }
+  paste(x, collapse = ' ')
+}
+
+check_oob <- function(x) {
+  if (!is.logical(x) || length(x) != 1) {
+    stop("`use_oob` must be a length-1 logical vector", call. = FALSE)
+  }
+
+  if (!x && !is_installed("httpuv")) {
+    message("httpuv not installed, defaulting to out-of-band authentication")
+    x <- TRUE
+  }
+
+  if (x) {
+    if (!interactive()) {
+      stop(
+        "Can only use oob authentication in an interactive session",
+        call. = FALSE
+      )
+    }
+  }
+
+  x
+}
+
+
+oauth_authorize <- function(url, oob = FALSE) {
+  if (oob) {
+    oauth_exchanger(url)$code
+  } else {
+    oauth_listener(url)$code
+  }
+}
diff --git a/R/oauth-listener.r b/R/oauth-listener.r
index 39e87eb..f52f542 100644
--- a/R/oauth-listener.r
+++ b/R/oauth-listener.r
@@ -9,7 +9,7 @@
 #' This function should not normally be called directly by the user.
 #'
 #' @param request_url the url to send the browser to
-#' @param is_interactive Is an interactive environment available?
+#' @param is_interactive DEPRECATED
 #' @param host ip address for the listener
 #' @param port for the listener
 #' @export
@@ -19,10 +19,6 @@ oauth_listener <- function(request_url, is_interactive = interactive()) {
     stop("httpuv package required to capture OAuth credentials.")
   }
 
-  if (!is_interactive) {
-    stop("oauth_listener() needs an interactive environment.", call. = FALSE)
-  }
-
   info <- NULL
   listen <- function(env) {
     if (!identical(env$PATH_INFO, "/")) {
diff --git a/R/oauth-server-side.R b/R/oauth-server-side.R
index e0714ad..9adedce 100644
--- a/R/oauth-server-side.R
+++ b/R/oauth-server-side.R
@@ -1,6 +1,11 @@
 
-init_oauth_service_account <- function(secrets, scope = NULL) {
-  signature <- jwt_signature(secrets, aud = secrets$token_uri, scope = scope)
+init_oauth_service_account <- function(secrets, scope = NULL, sub = NULL) {
+  signature <- jwt_signature(
+    secrets,
+    aud = secrets$token_uri,
+    scope = scope,
+    sub = sub
+  )
 
   res <- POST(
     secrets$token_uri,
@@ -23,6 +28,19 @@ init_oauth_service_account <- function(secrets, scope = NULL) {
 #' @param credentials Parsed contents of the credentials file.
 #' @param scope A space-delimited list of the permissions that the application
 #'    requests.
+#' @param iss Email address of the client_id of the application making the
+#'   access token request.
+#' @param scope A space-delimited list of the permissions that the application
+#'    requests.
+#' @param sub The email address of the user for which the application is
+#'    requesting delegated access.
+#' @param aud A descriptor of the intended target of the assertion. This
+#'    typically comes from the service auth file.
+#' @param iat The time the assertion was issued, measured in seconds since
+#'    00:00:00 UTC, January 1, 1970.
+#' @param exp The expiration time of the assertion, measured in seconds since
+#'   00:00:00 UTC, January 1, 1970. This value has a maximum of 1 hour from
+#'   the issued time.
 #' @param duration Duration of token, in seconds.
 #' @keywords internal
 #' @examples
@@ -30,10 +48,23 @@ init_oauth_service_account <- function(secrets, scope = NULL) {
 #' cred <- jsonlite::fromJSON("~/Desktop/httrtest-45693cbfac92.json")
 #' jwt_signature(cred, "https://www.googleapis.com/auth/userinfo.profile")
 #' }
-jwt_signature <- function(credentials, scope, aud, duration = 60L * 60L) {
-  now <- as.integer(Sys.time())
-  cs <- jwt_claimset(credentials$client_email, scope, aud = aud, iat = now,
-    exp = now + duration)
+jwt_signature <- function(credentials,
+                          scope,
+                          aud,
+                          sub = NULL,
+                          iat = as.integer(Sys.time()),
+                          exp = iat + duration,
+                          duration = 60L * 60L
+                          ) {
+  cs <- compact(list(
+    iss = credentials$client_email,
+    scope = scope,
+    aud = aud,
+    sub = sub,
+    iat = iat,
+    exp = exp
+  ))
+
   jwt_sign(cs, credentials$private_key)
 }
 
@@ -54,35 +85,6 @@ jwt_header <- function() {
   )
 }
 
-#' @param iss Email address of the client_id of the application making the
-#'   access token request.
-#' @param scope A space-delimited list of the permissions that the application
-#'    requests.
-#' @param aud A descriptor of the intended target of the assertion. This
-#'    typically comes from the service auth file.
-#' @param exp The expiration time of the assertion, measured in seconds since
-#'   00:00:00 UTC, January 1, 1970. This value has a maximum of 1 hour from
-#'   the issued time.
-#' @param iat The time the assertion was issued, measured in seconds since
-#'  00:00:00 UTC, January 1, 1970.
-#' @noRd
-jwt_claimset <- function(iss, scope, aud, exp = NULL, iat = NULL) {
-
-  if (is.null(iat)) {
-    iat <- as.integer(Sys.time())
-  }
-  if (is.null(exp)) {
-    exp <- iat + 60L * 60L
-  }
-
-  list(
-    iss = iss,
-    scope = scope,
-    aud = aud,
-    exp = exp,
-    iat = iat
-  )
-}
 
 jwt_base64 <- function(x) base64url(jwt_json(x))
 jwt_json <- function(x) jsonlite::toJSON(x, auto_unbox = TRUE)
diff --git a/R/oauth-signature.r b/R/oauth-signature.r
index 031ccce..67c4804 100644
--- a/R/oauth-signature.r
+++ b/R/oauth-signature.r
@@ -46,12 +46,8 @@ oauth_signature <- function(url, method = "GET", app, token = NULL,
   } else {
     signature_method <- "HMAC-SHA1"
   }
-  method <- toupper(method)
 
-  url <- parse_url(url)
-  base_url <- build_url(url[c("scheme", "hostname", "port", "url", "path")])
-
-  oauth <- compact(list(
+  oauth_params <- compact(list(
     oauth_consumer_key = app$key,
     oauth_nonce = nonce(),
     oauth_signature_method = signature_method,
@@ -59,27 +55,26 @@ oauth_signature <- function(url, method = "GET", app, token = NULL,
     oauth_version = "1.0",
     oauth_token = token
   ))
-
   if (length(other_params) > 0) {
-    oauth <- c(oauth, other_params)
+    oauth_params <- c(oauth_params, other_params)
   }
 
-  # Collect params, oauth_encode, sort and concatenated into a single string
-  params <- c(url$query, oauth)
-  params_esc <- stats::setNames(oauth_encode(params), oauth_encode(names(params)))
-  params_srt <- sort_names(params_esc)
-  params_str <- paste0(names(params_srt), "=", params_srt, collapse = "&")
-
-  base_string <- paste0(method, "&", oauth_encode(base_url), "&",
-   oauth_encode(params_str))
+  norm_method <- toupper(method)
+  norm_url <- oauth_normalise_url(url)
+  norm_params <- oauth_normalize_params(url, oauth_params)
+  norm_req <- paste0(
+    norm_method, "&",
+    oauth_encode(norm_url), "&",
+    oauth_encode(norm_params)
+  )
 
   # Generate signature
   if (signature_method == "HMAC-SHA1") {
     private_key <- paste0(oauth_encode(app$secret), "&", oauth_encode(token_secret))
   }
-  oauth$oauth_signature <- sha1_hash(private_key, base_string, signature_method)
+  oauth_params$oauth_signature <- sha1_hash(private_key, norm_req, signature_method)
 
-  sort_names(oauth)
+  sort_names(oauth_params)
 }
 
 #' @rdname oauth_signature
@@ -94,7 +89,7 @@ oauth_encode <- function(x) vapply(x, oauth_encode1, character(1))
 
 # As described in http://tools.ietf.org/html/rfc5849#page-29
 oauth_encode1 <- function(x) {
-  encode <- function(x) paste0("%", toupper(as.character(charToRaw(x))))
+  encode <- function(x) paste0("%", toupper(as.character(charToRaw(x))), collapse = "")
 
   x <- as.character(x)
   chars <- strsplit(x, "")[[1]]
@@ -105,3 +100,33 @@ oauth_encode1 <- function(x) {
   chars[!ok] <- unlist(lapply(chars[!ok], encode))
   paste0(chars, collapse = "")
 }
+
+oauth_normalise_url <- function(url) {
+  url <- parse_url(url)
+
+  # > Unless specified, URL scheme and authority MUST be lowercase and include
+  # > the port number; http default port 80 and https default port 443 MUST be
+  # > excluded.
+  # > --- https://oauth.net/core/1.0/#anchor14
+  url$scheme <- tolower(url$scheme)
+
+  if (url$scheme == "http" && identical(url$port, "80")) {
+    url$port <- NULL
+  } else if (url$scheme == "https" && identical(url$port, "443")) {
+    url$port <- NULL
+  }
+
+  build_url(url[c("scheme", "hostname", "port", "url", "path")])
+}
+
+oauth_normalize_params <- function(url, extra) {
+  # Collect params, encode, sort and concatenate into a single string
+  url <- parse_url(url)
+
+  params <- c(url$query, extra)
+  params_esc <- stats::setNames(oauth_encode(params), oauth_encode(names(params)))
+  params_srt <- sort_names(params_esc)
+  params_str <- paste0(names(params_srt), "=", params_srt, collapse = "&")
+
+  params_str
+}
diff --git a/R/oauth-token.r b/R/oauth-token.r
index 470eab3..6cac121 100644
--- a/R/oauth-token.r
+++ b/R/oauth-token.r
@@ -142,18 +142,29 @@ oauth1.0_token <- function(endpoint, app, permission = NULL,
                            as_header = TRUE,
                            private_key = NULL,
                            cache = getOption("httr_oauth_cache")) {
-  params <- list(permission = permission, as_header = as_header)
+  params <- list(
+    permission = permission,
+    as_header = as_header
+  )
 
-  Token1.0$new(app = app, endpoint = endpoint, params = params,
-    private_key = private_key, cache_path = cache)
+  Token1.0$new(
+    app = app,
+    endpoint = endpoint,
+    params = params,
+    private_key = private_key,
+    cache_path = cache
+  )
 }
 
 #' @export
 #' @rdname Token-class
 Token1.0 <- R6::R6Class("Token1.0", inherit = Token, list(
   init_credentials = function(force = FALSE) {
-    self$credentials <- init_oauth1.0(self$endpoint, self$app,
-      permission = self$params$permission, private_key = self$private_key)
+    self$credentials <- init_oauth1.0(
+      self$endpoint, self$app,
+      permission = self$params$permission,
+      private_key = self$private_key
+    )
   },
   can_refresh = function() {
     FALSE
@@ -189,21 +200,40 @@ Token1.0 <- R6::R6Class("Token1.0", inherit = Token, list(
 #'   itself to the bearer header of subsequent requests. If \code{FALSE},
 #'   configures the token to add itself as a url parameter of subsequent
 #'   requests.
+#' @param credentials Advanced use only: allows you to completely customise
+#'   token generation.
 #' @inheritParams oauth1.0_token
 #' @return A \code{Token2.0} reference class (RC) object.
 #' @family OAuth
 #' @export
 oauth2.0_token <- function(endpoint, app, scope = NULL, user_params = NULL,
-                           type = NULL, use_oob = getOption("httr_oob_default"),
+                           type = NULL,
+                           use_oob = getOption("httr_oob_default"),
                            as_header = TRUE,
                            use_basic_auth = FALSE,
-                           cache = getOption("httr_oauth_cache")) {
-  params <- list(scope = scope, user_params = user_params, type = type,
-      use_oob = use_oob, as_header = as_header,
-      use_basic_auth = use_basic_auth)
+                           cache = getOption("httr_oauth_cache"),
+                           config_init = list(),
+                           client_credentials = FALSE,
+                           credentials = NULL
+                          ) {
+  params <- list(
+    scope = scope,
+    user_params = user_params,
+    type = type,
+    use_oob = use_oob,
+    as_header = as_header,
+    use_basic_auth = use_basic_auth,
+    config_init = config_init,
+    client_credentials = client_credentials
+  )
 
-  Token2.0$new(app = app, endpoint = endpoint, params = params,
-    cache_path = cache)
+  Token2.0$new(
+    app = app,
+    endpoint = endpoint,
+    params = params,
+    credentials = credentials,
+    cache_path = if (is.null(credentials)) cache else FALSE
+  )
 }
 
 #' @export
@@ -213,7 +243,9 @@ Token2.0 <- R6::R6Class("Token2.0", inherit = Token, list(
     self$credentials <- init_oauth2.0(self$endpoint, self$app,
       scope = self$params$scope, user_params = self$params$user_params,
       type = self$params$type, use_oob = self$params$use_oob,
-      use_basic_auth = self$params$use_basic_auth)
+      use_basic_auth = self$params$use_basic_auth,
+      config_init = self$params$config_init,
+      client_credentials = self$params$client_credentials)
   },
   can_refresh = function() {
     !is.null(self$credentials$refresh_token)
@@ -257,6 +289,7 @@ Token2.0 <- R6::R6Class("Token2.0", inherit = Token, list(
 #' that information is embedded in the account itself.
 #'
 #' @inheritParams oauth2.0_token
+#' @inheritParams jwt_signature
 #' @param secrets Secrets loaded from JSON file, downloaded from console.
 #' @family OAuth
 #' @export
@@ -268,18 +301,18 @@ Token2.0 <- R6::R6Class("Token2.0", inherit = Token, list(
 #'
 #' token <- oauth_service_token(endpoint, secrets, scope)
 #' }
-oauth_service_token <- function(endpoint, secrets, scope = NULL) {
+oauth_service_token <- function(endpoint, secrets, scope = NULL, sub = NULL) {
   if (!is.oauth_endpoint(endpoint))
     stop("`endpoint` must be an OAuth endpoint", call. = FALSE)
   if (!is.list(secrets))
     stop("`secrets` must be a list.", call. = FALSE)
-  if (!is.null(scope) && !(is.character(scope) && length(scope) == 1))
-    stop("`scope` must be a length 1 character vector.", call. = FALSE)
+
+  scope <- check_scope(scope)
 
   TokenServiceAccount$new(
     endpoint = endpoint,
     secrets = secrets,
-    params = list(scope = scope)
+    params = list(scope = scope, sub = sub)
   )
 }
 
@@ -298,7 +331,11 @@ TokenServiceAccount <- R6::R6Class("TokenServiceAccount", inherit = Token2.0, li
     TRUE
   },
   refresh = function() {
-    self$credentials <- init_oauth_service_account(self$secrets, self$params$scope)
+    self$credentials <- init_oauth_service_account(
+      self$secrets,
+      scope = self$params$scope,
+      sub = self$params$sub
+    )
     self
   },
   sign = function(method, url) {
diff --git a/R/progress.R b/R/progress.R
index 8e44eb1..01df046 100644
--- a/R/progress.R
+++ b/R/progress.R
@@ -6,11 +6,13 @@
 #'    \code{stderr}.
 #' @export
 #' @examples
+#' cap_speed <- config(max_recv_speed_large = 10000)
+#'
 #' \donttest{
 #' # If file size is known, you get a progress bar:
-#' x <- GET("http://courses.had.co.nz/12-oscon/slides.zip", progress())
+#' x <- GET("http://httpbin.org/bytes/102400", progress(), cap_speed)
 #' # Otherwise you get the number of bytes downloaded:
-#' x <- GET("http://httpbin.org/drip?numbytes=4000&duration=3", progress())
+#' x <- GET("http://httpbin.org/stream-bytes/102400", progress(), cap_speed)
 #' }
 progress <- function(type = c("down", "up"), con = stdout()) {
   type <- match.arg(type)
@@ -23,7 +25,6 @@ progress <- function(type = c("down", "up"), con = stdout()) {
 
 progress_bar <- function(type, con) {
   bar <- NULL
-  first <- TRUE
 
   show_progress <- function(down, up) {
     if (type == "down") {
@@ -34,25 +35,20 @@ progress_bar <- function(type, con) {
       now <- up[[2]]
     }
 
-    # First progress request on new file
     if (total == 0 && now == 0) {
+      # Reset progress bar when seeing first byte
       bar <<- NULL
-      first <<- TRUE
-      return(TRUE)
-    }
-
-    if (total == 0) {
-      if (first) {
-        first <<- FALSE
-      }
+    } else if (total == 0) {
       cat("\rDownloading: ", bytes(now, digits = 2), "     ", sep = "", file = con)
-      if (now == total) cat("\n", file = con)
       utils::flush.console()
+      # Can't automatically add newline on completion because there's no
+      # way to tell when then the file has finished downloading
     } else {
       if (is.null(bar)) {
         bar <<- utils::txtProgressBar(max = total, style = 3, file = con)
       }
       utils::setTxtProgressBar(bar, now)
+      if (now == total) close(bar)
     }
 
     TRUE
diff --git a/R/request.R b/R/request.R
index ff98084..111a6de 100644
--- a/R/request.R
+++ b/R/request.R
@@ -122,8 +122,12 @@ request_prepare <- function(req) {
 
 request_perform <- function(req, handle, refresh = TRUE) {
   stopifnot(is.request(req), inherits(handle, "curl_handle"))
+
   req <- request_prepare(req)
 
+  ## This callback can cancel the request
+  if (!is.null(res <- perform_callback("request", req = req))) return(res)
+
   curl::handle_setopt(handle, .list = req$options)
   if (!is.null(req$fields))
     curl::handle_setform(handle, .list = req$fields)
@@ -149,7 +153,7 @@ request_perform <- function(req, handle, refresh = TRUE) {
     date <- Sys.time()
   }
 
-  response(
+  res <- response(
     url = resp$url,
     status_code = resp$status_code,
     headers = headers,
@@ -161,5 +165,12 @@ request_perform <- function(req, handle, refresh = TRUE) {
     request = req,
     handle = handle
   )
+
+  ## If the callback provides a result, we return that
+  if (!is.null(cbres <- perform_callback("response", req, res))) {
+    return(cbres)
+  }
+
+  res
 }
 
diff --git a/R/retry.R b/R/retry.R
index c26663c..766fab8 100644
--- a/R/retry.R
+++ b/R/retry.R
@@ -1,10 +1,16 @@
 #' Retry a request until it succeeds.
 #'
-#' Safely retry a request until it succeeds (returns an HTTP status code
-#' below 400). It is designed to be kind to the server: after each failure
+#' Safely retry a request until it succeeds, as defined by the \code{terminate_on}
+#' parameter, which by default means a response for which \code{\link{http_error}()}
+#' is \code{FALSE}. Will also retry on error conditions raised by the underlying curl code,
+#' but if the last retry still raises one, \code{RETRY} will raise it again with
+#' \code{\link{stop}()}.
+#' It is designed to be kind to the server: after each failure
 #' randomly waits up to twice as long. (Technically it uses exponential
 #' backoff with jitter, using the approach outlined in
 #' \url{https://www.awsarchitectureblog.com/2015/03/backoff.html}.)
+#' If the server returns status code 429 and specifies a \code{retry-after} value, that
+#' value will be used instead, unless it's smaller than \code{pause_min}.
 #'
 #' @inheritParams VERB
 #' @inheritParams GET
@@ -14,8 +20,14 @@
 #'   full jitter - this means that each request will randomly wait between 0
 #'   and \code{pause_base * 2 ^ attempt} seconds, up to a maximum of
 #'   \code{pause_cap} seconds.
+#' @param pause_min Minimum time to wait in the backoff; generally
+#'   only necessary if you need pauses less than one second (which may
+#'   not be kind to the server, use with caution!).
 #' @param quiet If \code{FALSE}, will print a message displaying how long
 #'   until the next request.
+#' @param terminate_on Optional vector of numeric HTTP status codes that if found
+#'   on the response will terminate the retry process. If \code{NULL}, will keep
+#'   retrying while \code{\link{http_error}()} is \code{TRUE} for the response.
 #' @return The last response. Note that if the request doesn't succeed after
 #'   \code{times} times this will be a failed request, i.e. you still need
 #'   to use \code{\link{stop_for_status}()}.
@@ -25,33 +37,69 @@
 #' RETRY("GET", "http://httpbin.org/status/200")
 #' # Never succeeds
 #' RETRY("GET", "http://httpbin.org/status/500")
+#' \donttest{
+#' # Invalid hostname generates curl error condition and is retried but eventually
+#' # raises an error condition.
+#' RETRY("GET", "http://invalidhostname/")
+#' }
 RETRY <- function(verb, url = NULL, config = list(), ...,
                   body = NULL, encode = c("multipart", "form", "json", "raw"),
-                  times = 3, pause_base = 1, pause_cap = 60,
-                  handle = NULL, quiet = FALSE) {
+                  times = 3, pause_base = 1, pause_cap = 60, pause_min = 1,
+                  handle = NULL, quiet = FALSE, terminate_on = NULL) {
   stopifnot(is.numeric(times), length(times) == 1L)
   stopifnot(is.numeric(pause_base), length(pause_base) == 1L)
   stopifnot(is.numeric(pause_cap), length(pause_cap) == 1L)
+  stopifnot(is.numeric(terminate_on) || is.null(terminate_on))
 
   hu <- handle_url(handle, url, ...)
   req <- request_build(verb, hu$url, body_config(body, match.arg(encode)), config, ...)
-  resp <- request_perform(req, hu$handle$handle)
+  resp <- tryCatch(request_perform(req, hu$handle$handle), error = function(e) e)
 
   i <- 1
-  while (i < times && http_error(resp)) {
-    backoff_full_jitter(i, status_code(resp), pause_base, pause_cap, quiet = quiet)
+  while (!retry_should_terminate(i, times, resp, terminate_on)) {
+    backoff_full_jitter(i, resp, pause_base, pause_cap, pause_min, quiet = quiet)
 
     i <- i + 1
-    resp <- request_perform(req, hu$handle$handle)
+    resp <- tryCatch(request_perform(req, hu$handle$handle), error = function(e) e)
+  }
+
+  if (inherits(resp, "error")) {
+    stop(resp)
   }
 
   resp
 }
 
-backoff_full_jitter <- function(i, status, pause_base = 1, pause_cap = 60, quiet = FALSE) {
-  length <- ceiling(stats::runif(1, max = min(pause_cap, pause_base * (2 ^ i))))
+retry_should_terminate <- function(i, times, resp, terminate_on) {
+  if (i >= times) {
+    TRUE
+  } else if (inherits(resp, "error")) {
+    FALSE
+  } else if (!is.null(terminate_on)) {
+    status_code(resp) %in% terminate_on
+  } else {
+    !http_error(resp)
+  }
+}
+
+backoff_full_jitter <- function(i, resp, pause_base = 1, pause_cap = 60,
+                                pause_min = 1, quiet = FALSE) {
+  length <- max(pause_min, stats::runif(1, max = min(pause_cap, pause_base * (2 ^ i))))
   if (!quiet) {
-    message("Request failed [", status, "]. Retrying in ", length, " seconds...")
+    if (inherits(resp, "error")) {
+      error_description <- gsub("[\n\r]*$", "\n", as.character(resp))
+      status <- "ERROR"
+    } else {
+      error_description <- ""
+      status <- status_code(resp)
+    }
+    if (status == 429) {
+      retry_after <- resp$headers[["retry-after"]]
+      if (!is.null(retry_after)) {
+        length <- max(pause_min, as.numeric(retry_after))
+      }
+    }
+    message(error_description, "Request failed [", status, "]. Retrying in ", round(length, 1), " seconds...")
   }
   Sys.sleep(length)
 }
diff --git a/R/safe-callback.R b/R/safe-callback.R
deleted file mode 100644
index 8969b99..0000000
--- a/R/safe-callback.R
+++ /dev/null
@@ -1,10 +0,0 @@
-#' Generate a safe R callback.
-#'
-#' @param f A function.
-#' @keywords deprecated
-#' @export
-safe_callback <- function(f) {
-  warning("`safe_callback()` is no longer needed and will be removed in a ",
-    "future version", call. = FALSE)
-  f
-}
diff --git a/R/url.r b/R/url.r
index c89d87f..6b7f177 100644
--- a/R/url.r
+++ b/R/url.r
@@ -6,8 +6,9 @@
 #' See \url{http://tools.ietf.org/html/rfc1808.html} for details of parsing
 #' algorithm.
 #'
-#' @param url a character vector (of length 1) to parse into components,
-#'   or for \code{build_url} a url to turn back into a string.
+#' @param url For \code{parse_url} a character vector (of length 1) to parse
+#'   into components; for \code{build_url} a list of components to turn back
+#'   into a string.
 #' @return a list containing: \itemize{
 #'  \item scheme
 #'  \item hostname
@@ -25,7 +26,10 @@
 #' parse_url("http://google.com:80/")
 #' parse_url("http://google.com:80/?a=1&b=2")
 #'
-#' build_url(parse_url("http://google.com/"))
+#' url <- parse_url("http://google.com/")
+#' url$scheme <- "https"
+#' url$query <- list(q = "hello")
+#' build_url(url)
 parse_url <- function(url) {
   if (is.url(url)) return(url)
 
@@ -120,11 +124,11 @@ build_url <- function(url) {
   }
 
   if (is.list(url$query)) {
-    query <- compose_query(url$query)
+    query <- compose_query(compact(url$query))
   } else {
     query <- url$query
   }
-  if (!is.null(query)) {
+  if (!is.null(query) && nzchar(query)) {
     stopifnot(is.character(query), length(query) == 1)
     query <- paste0("?", query)
   }
@@ -161,5 +165,3 @@ modify_url <- function(url, scheme = NULL, hostname = NULL, port = NULL,
 
   build_url(utils::modifyList(old, new))
 }
-
-compact <- function(x) Filter(Negate(is.null), x)
diff --git a/R/utils.r b/R/utils.r
index 6c81a7e..78afe83 100644
--- a/R/utils.r
+++ b/R/utils.r
@@ -47,10 +47,12 @@ last <- function(x) {
 }
 
 compact <- function(x) {
-  null <- vapply(x, is.null, logical(1))
-  x[!null]
+  empty <- vapply(x, is_empty, logical(1))
+  x[!empty]
 }
 
+is_empty <- function(x) length(x) == 0
+
 keep_last <- function(...) {
   x <- c(...)
   x[!duplicated(names(x), fromLast = TRUE)]
@@ -79,8 +81,10 @@ find_cert_bundle <- function() {
 
   bundled <- file.path(R.home("etc"), "curl-ca-bundle.crt")
   if (file.exists(bundled))
-    bundled
+    return(bundled)
 
   # Fall back to certificate bundle in openssl
   system.file("cacert.pem", package = "openssl")
 }
+
+isFALSE <- function(x) identical(x, FALSE)
diff --git a/R/verbose.r b/R/verbose.r
index 27fc7f7..3b68114 100644
--- a/R/verbose.r
+++ b/R/verbose.r
@@ -53,13 +53,13 @@
 verbose <- function(data_out = TRUE, data_in = FALSE, info = FALSE, ssl = FALSE) {
   debug <- function(type, msg) {
     switch(type + 1,
-      text =       if (info) prefix_message("*  ", msg),
-      headerIn =   prefix_message("<- ", msg),
-      headerOut =  prefix_message("-> ", msg),
-      dataIn =     if (data_in) prefix_message("<<  ", msg, TRUE),
-      dataOut =    if (data_out) prefix_message(">> ", msg, TRUE),
-      sslDataIn =  if (data_in && ssl) prefix_message("*< ", msg, TRUE),
-      sslDataOut = if (data_out && ssl) prefix_message("*> ", msg, TRUE)
+      text =       if (info)            prefix_message("*  ", msg),
+      headerIn =                        prefix_message("<- ", msg),
+      headerOut =                       prefix_message("-> ", msg),
+      dataIn =     if (data_in)         prefix_message("<<  ", msg, TRUE),
+      dataOut =    if (data_out)        prefix_message(">> ", msg, TRUE),
+      sslDataIn =  if (ssl && data_in)  prefix_message("*< ", msg, TRUE),
+      sslDataOut = if (ssl && data_out) prefix_message("*> ", msg, TRUE)
     )
   }
   config(debugfunction = debug, verbose = TRUE)
diff --git a/R/write-function.R b/R/write-function.R
index ac9144e..08587f0 100644
--- a/R/write-function.R
+++ b/R/write-function.R
@@ -58,7 +58,7 @@ write_memory <- function() {
 #'   vector containing the bytes recieved from the server. This will usually
 #'   be 16k or less. The return value of the function is ignored.
 #' @examples
-#' GET("https://jeroenooms.github.io/data/diamonds.json",
+#' GET("https://github.com/jeroen/data/raw/gh-pages/diamonds.json",
 #'   write_stream(function(x) {
 #'    print(length(x))
 #'    length(x)
@@ -85,6 +85,5 @@ request_fetch.write_stream <- function(x, url, handle) {
 }
 
 path <- function(x) structure(x, class = "path")
-#' @export
 length.path <- function(x) file.info(x)$size
 is.path <- function(x) inherits(x, "path")
diff --git a/README.md b/README.md
index bff7fec..4d64c17 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
 # httr
 
-[![Build Status](https://travis-ci.org/hadley/httr.png?branch=master)](https://travis-ci.org/hadley/httr)
-[![Coverage Status](https://img.shields.io/codecov/c/github/hadley/httr/master.svg)](https://codecov.io/github/hadley/httr?branch=master)
-[![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/httr)](http://cran.r-project.org/package=httr)
+[![Build Status](https://travis-ci.org/r-lib/httr.svg?branch=master)](https://travis-ci.org/r-lib/httr)
+[![Coverage Status](https://img.shields.io/codecov/c/github/r-lib/httr/master.svg)](https://codecov.io/github/r-lib/httr?branch=master)
+[![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/httr)](https://cran.r-project.org/package=httr)
 
 The aim of httr is to provide a wrapper for the [curl](https://cran.r-project.org/package=curl) package, customised to the demands of modern web APIs.
 
@@ -47,5 +47,5 @@ To get the current development version from github:
 
 ```R
 # install.packages("devtools")
-devtools::install_github("hadley/httr")
+devtools::install_github("r-lib/httr")
 ```
diff --git a/build/vignette.rds b/build/vignette.rds
index 74676b6..6d9a089 100644
Binary files a/build/vignette.rds and b/build/vignette.rds differ
diff --git a/demo/00Index b/demo/00Index
index 0f1e26a..462a8e2 100644
--- a/demo/00Index
+++ b/demo/00Index
@@ -9,4 +9,5 @@ oauth2-github       Using the github api with OAuth 2.0
 oauth2-google       Using the google api with OAuth 2.0
 oauth2-reddit       Using the reddit api with OAuth 2.0
 oauth2-linkedin     Using linkedin api with OAuth 1.0
+oauth2-yelp         Using yelp api with OAuth 2.0 and Client Credentials Grant
 service-account     Using Google service account
diff --git a/demo/oauth2-facebook.r b/demo/oauth2-facebook.r
index f679e62..7ea8312 100644
--- a/demo/oauth2-facebook.r
+++ b/demo/oauth2-facebook.r
@@ -12,8 +12,7 @@ myapp <- oauth_app("facebook", "353609681364760", "1777c63343eba28359537764fab99
 # 3. Get OAuth credentials
 facebook_token <- oauth2.0_token(
   oauth_endpoints("facebook"),
-  myapp,
-  type = "application/x-www-form-urlencoded"
+  myapp
 )
 
 # 4. Use API
diff --git a/demo/oauth2-github.r b/demo/oauth2-github.r
index 42d5823..69c38b4 100644
--- a/demo/oauth2-github.r
+++ b/demo/oauth2-github.r
@@ -4,8 +4,8 @@ library(httr)
 #    http://developer.github.com/v3/oauth/
 oauth_endpoints("github")
 
-# 2. To make your own application, register at at
-#    https://github.com/settings/applications. Use any URL for the homepage URL
+# 2. To make your own application, register at 
+#    https://github.com/settings/developers. Use any URL for the homepage URL
 #    (http://github.com is fine) and  http://localhost:1410 as the callback url
 #
 #    Replace your key and secret below.
diff --git a/demo/oauth2-linkedin.r b/demo/oauth2-linkedin.r
index 5b9ba35..8975e7d 100644
--- a/demo/oauth2-linkedin.r
+++ b/demo/oauth2-linkedin.r
@@ -21,7 +21,7 @@ TokenLinkedIn <- R6::R6Class("TokenLinkedIn", inherit = Token2.0, list(
   sign = function(method, url) {
     url <- parse_url(url)
     url$query$oauth2_access_token <- self$credentials$access_token
-    list(url = build_url(url), config = config())
+    request(url = build_url(url))
   },
   can_refresh = function() {
     TRUE
diff --git a/demo/oauth2-reddit.R b/demo/oauth2-reddit.R
index 1c6cc8e..249c843 100644
--- a/demo/oauth2-reddit.R
+++ b/demo/oauth2-reddit.R
@@ -15,3 +15,12 @@ token <- oauth2.0_token(reddit, app,
   scope = c("read", "modposts"),
   use_basic_auth = TRUE
 )
+
+# 3b. If get 429 too many requests, the default user_agent is overloaded.
+# If you have an application on Reddit then you can pass that using:
+token <- oauth2.0_token(
+  reddit, app,
+  scope = c("read", "modposts"),
+  use_basic_auth = TRUE,
+  config_init = user_agent("YOUR_USER_AGENT")
+)
diff --git a/demo/oauth2-yelp.R b/demo/oauth2-yelp.R
new file mode 100644
index 0000000..d807ca0
--- /dev/null
+++ b/demo/oauth2-yelp.R
@@ -0,0 +1,43 @@
+library(httr)
+
+# This example demonstrate the use of client credentials grant
+
+# 1. Find OAuth settings for yelp:
+# https://www.yelp.ca/developers/documentation/v3/authentication
+# Set authorize url to NULL as we are not using Authorization code grant
+# but client credential grant
+yelp_endpoint <- oauth_endpoint(
+  authorize = NULL,
+  access    = "https://api.yelp.com/oauth2/token"
+)
+
+# 2. Register an application at https://www.yelp.com/developers/v3/manage_app
+#    Replace key and secret below.
+yelp_app <- oauth_app(
+  appname = "yelp",
+  key = "bvmjj2EOBvOknQ",
+  secret = "n8ueSvTNdlE0BDDJpLljvmgUGUw"
+)
+
+# 3. Get OAuth credentials using client credential grant
+#    Yelp do not use basic auth. Use `use_basic_auth = T` otherwise
+yelp_token <- oauth2.0_token(
+  endpoint = yelp_endpoint,
+  app = yelp_app,
+  client_credentials = T
+)
+
+# 4. Use API
+url <- modify_url(
+  url = "https://api.yelp.com",
+  path = c("v3", "businesses", "search"),
+  query = list(
+    term = "coffee",
+    location = "Vancouver, BC",
+    limit = 3
+  )
+)
+
+req <- GET(url, config(token = token))
+stop_for_status(req)
+content(req)
diff --git a/inst/doc/api-packages.R b/inst/doc/api-packages.R
index db79b0e..dfaaa24 100644
--- a/inst/doc/api-packages.R
+++ b/inst/doc/api-packages.R
@@ -193,7 +193,3 @@ rate_limit <- function() {
 
 rate_limit()
 
-## ------------------------------------------------------------------------
-NOT_CRAN <- identical(tolower(Sys.getenv("NOT_CRAN")), "true")
-knitr::opts_chunk$set(purl = NOT_CRAN)
-
diff --git a/inst/doc/api-packages.Rmd b/inst/doc/api-packages.Rmd
index c44033c..ac0cee6 100644
--- a/inst/doc/api-packages.Rmd
+++ b/inst/doc/api-packages.Rmd
@@ -1,10 +1,8 @@
 ---
-title: "Best practices for writing an API package"
-author: "Hadley Wickham"
-date: "`r Sys.Date()`"
+title: "Best practices for API packages"
 output: rmarkdown::html_vignette
 vignette: >
-  %\VignetteIndexEntry{Best practices for writing an API package}
+  %\VignetteIndexEntry{Best practices for API packages}
   %\VignetteEngine{knitr::rmarkdown}
   %\VignetteEncoding{UTF-8}
 ---
@@ -100,7 +98,7 @@ resp
 
 ### Parse the response
 
-Next, you need to take the response returned by the API and turn it into a useful object. Any API will return an HTTP response that consists of headers and a body. While the response can come in a multiple a forms (see above), two of the most common structured formats are XML and JSON. 
+Next, you need to take the response returned by the API and turn it into a useful object. Any API will return an HTTP response that consists of headers and a body. While the response can come in multiple forms (see above), two of the most common structured formats are XML and JSON. 
 
 Note that while most APIs will return only one or the other, some, like the colour lovers API, allow you to choose which one with a url parameter:
 
@@ -109,7 +107,7 @@ GET("http://www.colourlovers.com/api/color/6B4106?format=xml")
 GET("http://www.colourlovers.com/api/color/6B4106?format=json")
 ```
 
-Others use [content negotiation](http://en.wikipedia.org/wiki/Content_negotiation) to determine whats sort of data to send back. If the API you're wrapping does this, then you'll need to include one of `accept_json()` and `accept_xml()` in your request. 
+Others use [content negotiation](http://en.wikipedia.org/wiki/Content_negotiation) to determine what sort of data to send back. If the API you're wrapping does this, then you'll need to include one of `accept_json()` and `accept_xml()` in your request. 
 
 If you have a choice, choose json: it's usually much easier to work with than xml.
 
@@ -192,7 +190,7 @@ The API might return invalid data, but this should be rare, so you can just rely
 
 ### Turn API errors into R errors
 
-Next, you need to make sure that your API wrapper throws an error if the request failed. Using a web API introduces additional possible points of failure into R code aside from those occuring in R itself. These include:
+Next, you need to make sure that your API wrapper throws an error if the request failed. Using a web API introduces additional possible points of failure into R code aside from those occurring in R itself. These include:
 
 - Client-side exceptions
 - Network / communication exceptions
@@ -200,7 +198,7 @@ Next, you need to make sure that your API wrapper throws an error if the request
 
 You need to make sure these are all converted into regular R errors. You can figure out if there's a problem with `http_error()`, which checks the HTTP status code. Status codes in the 400 range usually mean that you've done something wrong. Status codes in the 500 range typically mean that something has gone wrong on the server side.
 
-Often the API will information about the error in the body of the response: you should use this where available. If the API returns special errors for common problems, you might want to provide more detail in the error. For example, if you run out of requests and are [rate limited](https://developer.github.com/v3/#rate-limiting) you might want to tell the user how long to wait until they can make the next request (or even automatically wait that long!).
+Often the API will provide information about the error in the body of the response: you should use this where available. If the API returns special errors for common problems, you might want to provide more detail in the error. For example, if you run out of requests and are [rate limited](https://developer.github.com/v3/#rate-limiting) you might want to tell the user how long to wait until they can make the next request (or even automatically wait that long!).
 
 ```{r, error = TRUE}
 github_api <- function(path) {
@@ -296,7 +294,7 @@ Most APIs work by executing an HTTP method on a specified URL with some addition
 3. HTTP headers: `add_headers()` 
 4. Request body: The `body` argument to `GET()`, `POST()`, etc.
 
-[RESTful APIs](https://en.wikipedia.org/wiki/Representational_state_transfer) also use the HTTP verb to communicate arguments (e.g,., `GET` retrieves a file, `POST` adds a file, `DELETE` removes a file, etc.). We can use the helpful [httpbin service](http://httpbin.org) to show how to send arguments in each of these ways.
+[RESTful APIs](https://en.wikipedia.org/wiki/Representational_state_transfer) also use the HTTP verb to communicate arguments (e.g., `GET` retrieves a file, `POST` adds a file, `DELETE` removes a file, etc.). We can use the helpful [httpbin service](http://httpbin.org) to show how to send arguments in each of these ways.
 
 ```{r, eval = FALSE}
 # modify_url
@@ -317,7 +315,7 @@ POST("http://httpbin.org/post", body = list(foo = "bar"), encode = "json")
 
 Many APIs will use just one of these forms of argument passing, but others will use multiple of them in combination. Best practice is to insulate the user from how and where the various arguments are used by the API and instead simply expose relevant arguments via R function arguments, some of which might be used in the URL, in the headers, in the body, etc.
 
-If a parameter has a small fixed set of possible values that are allowed by the API, you can use list them in the default arguments and then use  `match.arg()` to ensure that the caller only supplies one of those values. (This also allows the user to supply the short unique prefixes.)
+If a parameter has a small fixed set of possible values that are allowed by the API, you can use list them in the default arguments and then use `match.arg()` to ensure that the caller only supplies one of those values. (This also allows the user to supply the short unique prefixes.)
 
 ```{r}
 f <- function(x = c("apple", "banana", "orange")) {
@@ -332,7 +330,7 @@ It is good practice to explicitly set default values for arguments that are not
 
 Many APIs can be called without any authentication (just as if you called them in a web browser). However, others require authentication to perform particular requests or to avoid rate limits and other limitations. The most common forms of authentication are OAuth and HTTP basic authentication:
 
-1. *"Basic" authentication*: This  requires a username and password (or 
+1. *"Basic" authentication*: This requires a username and password (or 
     sometimes just a username). This is passed as part of the HTTP request. 
     In httr, you can do: 
     `GET("http://httpbin.org", authenticate("username", "password"))`
@@ -387,7 +385,7 @@ When a response is paginated, the API response will typically respond with a hea
 These values can then be used to make further requests. This will either involve specifying a specific page of responses or specifying a "next page token" that returns the next page of results. How to deal with pagination is a difficult question and a client could implement any of the following:
 
 1. Return one page only by default with an option to return additional specific pages
-2. Return a specified page (deafulting to 1) and require the end user to handle pagination
+2. Return a specified page (defaulting to 1) and require the end user to handle pagination
 3. Return all pages by writing an internal process of checking for further pages and combining the results
 
 The choice of which to use depends on your needs and goals and the rate limits of the API.
@@ -405,7 +403,7 @@ rate_limit <- function() {
 rate_limit()
 ```
 
-After getting the first version getting working, you'll often want to polish the output to be more user friendly. For this example, we can parse the unix timestamps into more useful date types.
+After getting the first version working, you'll often want to polish the output to be more user friendly. For this example, we can parse the unix timestamps into more useful date types.
 
 ```{r}
 rate_limit <- function() {
@@ -419,69 +417,3 @@ rate_limit <- function() {
 
 rate_limit()
 ```
-
-## Packaging youe code
-
-### Documentation
-
-Like any R package, an API client needs clear and complete documentation of all functions. Examples are particularly useful but may need to be wrapped in `\donttest{}` to avoid challenges of authentication, rate limiting, lack of network access, or occasional API server down time.
-
-One other challenge unique to web API packages is the need to have documentation that clarifies any distinctions between the R implementation and the API generally (e.g., in the "details" section). It can be useful to add reference links to the API documentation to clarify the mapping between R functions and API endpoints.
-
-### Vignettes
-
-Vignettes pose additional challenges when an API requires authentication, because you don't want to bundle your own credentials with the package! However, you can take advantage of the fact that the vignette is built locally, and only checked by CRAN with this idea from Jenny Bryan:
-
-In an setup chunk, do:
-
-```{r}
-NOT_CRAN <- identical(tolower(Sys.getenv("NOT_CRAN")), "true")
-knitr::opts_chunk$set(purl = NOT_CRAN)
-```
-
-And then use `eval = NOT_CRAN` as needed as a chunk option.
-
-### Testing
-
-The same issues and solutions can also be applied to test suites for API clients that require authenticated credentials. For these packages, you may want to have a test suite that is only run locally but not on CRAN. Another solution is to use a continuous integration service such as Travis-CI or Appveyor, which allows the inclusion of encrypted environment variables or encrypted token files. This enables continuous testing in a relatively secure way.
-
-## Appendix: Storing API Authentication Keys/Tokens
-
-If your package supports an authentication workflow based on an API key or token, encourage users to store it in an environment variable. We illustrate this using the [`github` R package](https://github.com/cscheid/rgithub), which wraps the Github v3 API. Tailor this template to your API + package and include in `README.md` or a vignette.
-
-1.  Create a personal access token in the  [Personal access tokens area](https://github.com/settings/tokens) of your GitHub personal settings. Copy token to the clipboard.
-2.  Identify your home directory. Not sure? Enter `normalizePath("~/")` in  the R console. 
-3.  Create a new text file. If in RStudio, do *File > New File > Text file.*
-4.  Create a line like this:
-
-```bash
-GITHUB_PAT=blahblahblahblahblahblah
-```
-
-where the name `GITHUB_PAT` reminds you which API this is for and  `blahblahblahblahblahblah` is your personal access token, pasted from the clipboard. 
-
-1.  Make sure the last line in the file is empty (if it isn't R will 
-    __silently__ fail to load the file. If you're using an editor that shows
-    line numbers, there should be two lines, where the second one is empty. 
-
-1.  Save in your home directory with the filename `.Renviron`. If questioned,  
-    YES you do want to use a filename that begins with a dot `.`.
-
-    Note that by default [dotfiles](http://linux.about.com/cs/linux101/g/dot_file.htm)
-    are usually hidden. But within RStudio, the file browser will make
-    `.Renviron` visible and therefore easy to edit in the future. 
-
-1.  Restart R. `.Renviron` is processed only [at the start of an R session](http://stat.ethz.ch/R-manual/R-patched/library/base/html/Startup.html). 
-
-1.  Use `Sys.getenv()` to access your token. For example, here's how to use
-    your `GITHUB_PAT` with the `github` package: 
-
-    ```R
-    library(github)
-    ctx <- create.github.context(access_token = Sys.getenv("GITHUB_PAT"))
-    # ... proceed to use other package functions to open issues, etc.
-    ```
-
-FAQ: Why define this environment variable via `.Renviron` instead of in `.bash_profile` or `.bashrc`?
-
-Because there are many combinations of OS and ways of running R where the `.Renviron` approach "just works" and the bash stuff does not. When R is a child process of, say, Emacs or RStudio, you can't always count on environment variables being passed to R. Put them in an R-specific start-up file and save yourself some grief.
diff --git a/inst/doc/api-packages.html b/inst/doc/api-packages.html
index 7d540a1..f31f651 100644
--- a/inst/doc/api-packages.html
+++ b/inst/doc/api-packages.html
@@ -4,17 +4,15 @@
 
 <head>
 
-<meta charset="utf-8">
+<meta charset="utf-8" />
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 <meta name="generator" content="pandoc" />
 
 <meta name="viewport" content="width=device-width, initial-scale=1">
 
-<meta name="author" content="Hadley Wickham" />
 
-<meta name="date" content="2016-07-03" />
 
-<title>Best practices for writing an API package</title>
+<title>Best practices for API packages</title>
 
 
 
@@ -68,9 +66,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
 
 
 
-<h1 class="title toc-ignore">Best practices for writing an API package</h1>
-<h4 class="author"><em>Hadley Wickham</em></h4>
-<h4 class="date"><em>2016-07-03</em></h4>
+<h1 class="title toc-ignore">Best practices for API packages</h1>
 
 
 
@@ -134,68 +130,68 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
 <h3>Send a simple request</h3>
 <p>First, find a simple API endpoint that doesn’t require authentication: this lets you get the basics working before tackling the complexities of authentication. For this example, we’ll use the list of httr issues which requires sending a GET request to <code>repos/hadley/httr</code>:</p>
 <div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">library</span>(httr)
-github_api <-<span class="st"> </span>function(path) {
+github_api <-<span class="st"> </span><span class="cf">function</span>(path) {
   url <-<span class="st"> </span><span class="kw">modify_url</span>(<span class="st">"https://api.github.com"</span>, <span class="dt">path =</span> path)
   <span class="kw">GET</span>(url)
 }
 
 resp <-<span class="st"> </span><span class="kw">github_api</span>(<span class="st">"/repos/hadley/httr"</span>)
 resp
-<span class="co">#> Response [https://api.github.com/repos/hadley/httr]</span>
-<span class="co">#>   Date: 2016-07-03 17:01</span>
+<span class="co">#> Response [https://api.github.com/repositories/2756403]</span>
+<span class="co">#>   Date: 2017-08-18 17:47</span>
 <span class="co">#>   Status: 200</span>
 <span class="co">#>   Content-Type: application/json; charset=utf-8</span>
-<span class="co">#>   Size: 4.73 kB</span>
+<span class="co">#>   Size: 5.71 kB</span>
 <span class="co">#> {</span>
 <span class="co">#>   "id": 2756403,</span>
 <span class="co">#>   "name": "httr",</span>
-<span class="co">#>   "full_name": "hadley/httr",</span>
+<span class="co">#>   "full_name": "r-lib/httr",</span>
 <span class="co">#>   "owner": {</span>
-<span class="co">#>     "login": "hadley",</span>
-<span class="co">#>     "id": 4196,</span>
-<span class="co">#>     "avatar_url": "https://avatars.githubusercontent.com/u/4196?v=3",</span>
+<span class="co">#>     "login": "r-lib",</span>
+<span class="co">#>     "id": 22618716,</span>
+<span class="co">#>     "avatar_url": "https://avatars0.githubusercontent.com/u/22618716?v=4",</span>
 <span class="co">#>     "gravatar_id": "",</span>
-<span class="co">#>     "url": "https://api.github.com/users/hadley",</span>
+<span class="co">#>     "url": "https://api.github.com/users/r-lib",</span>
 <span class="co">#> ...</span></code></pre></div>
 </div>
 <div id="parse-the-response" class="section level3">
 <h3>Parse the response</h3>
-<p>Next, you need to take the response returned by the API and turn it into a useful object. Any API will return an HTTP response that consists of headers and a body. While the response can come in a multiple a forms (see above), two of the most common structured formats are XML and JSON.</p>
+<p>Next, you need to take the response returned by the API and turn it into a useful object. Any API will return an HTTP response that consists of headers and a body. While the response can come in multiple forms (see above), two of the most common structured formats are XML and JSON.</p>
 <p>Note that while most APIs will return only one or the other, some, like the colour lovers API, allow you to choose which one with a url parameter:</p>
 <div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">GET</span>(<span class="st">"http://www.colourlovers.com/api/color/6B4106?format=xml"</span>)
 <span class="co">#> Response [http://www.colourlovers.com/api/color/6B4106?format=xml]</span>
-<span class="co">#>   Date: 2016-07-03 17:01</span>
+<span class="co">#>   Date: 2017-08-18 17:47</span>
 <span class="co">#>   Status: 200</span>
 <span class="co">#>   Content-Type: text/xml; charset=utf-8</span>
 <span class="co">#>   Size: 970 B</span>
 <span class="co">#> <?xml version="1.0" encoding="UTF-8" standalone="yes"?></span>
-<span class="co">#> <colors numResults="1" totalResults="9565103"></span>
+<span class="co">#> <colors numResults="1" totalResults="9853929"></span>
 <span class="co">#>  <color></span>
 <span class="co">#>      <id>903893</id></span>
 <span class="co">#>      <title><![CDATA[wet dirt]]></title></span>
 <span class="co">#>      <userName><![CDATA[jessicabrown]]></userName></span>
-<span class="co">#>      <numViews>434</numViews></span>
+<span class="co">#>      <numViews>480</numViews></span>
 <span class="co">#>      <numVotes>1</numVotes></span>
 <span class="co">#>      <numComments>0</numComments></span>
 <span class="co">#>      <numHearts>0</numHearts></span>
 <span class="co">#> ...</span>
 <span class="kw">GET</span>(<span class="st">"http://www.colourlovers.com/api/color/6B4106?format=json"</span>)
 <span class="co">#> Response [http://www.colourlovers.com/api/color/6B4106?format=json]</span>
-<span class="co">#>   Date: 2016-07-03 17:01</span>
+<span class="co">#>   Date: 2017-08-18 17:47</span>
 <span class="co">#>   Status: 200</span>
 <span class="co">#>   Content-Type: application/json; charset=utf-8</span>
 <span class="co">#>   Size: 569 B</span></code></pre></div>
-<p>Others use <a href="http://en.wikipedia.org/wiki/Content_negotiation">content negotiation</a> to determine whats sort of data to send back. If the API you’re wrapping does this, then you’ll need to include one of <code>accept_json()</code> and <code>accept_xml()</code> in your request.</p>
+<p>Others use <a href="http://en.wikipedia.org/wiki/Content_negotiation">content negotiation</a> to determine what sort of data to send back. If the API you’re wrapping does this, then you’ll need to include one of <code>accept_json()</code> and <code>accept_xml()</code> in your request.</p>
 <p>If you have a choice, choose json: it’s usually much easier to work with than xml.</p>
 <p>Most APIs will return most or all useful information in the response body, which can be accessed using <code>content()</code>. To determine what type of information is returned, you can use <code>http_type()</code></p>
 <div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">http_type</span>(resp)
 <span class="co">#> [1] "application/json"</span></code></pre></div>
 <p>I recommend checking that the type is as you expect in your helper function. This will ensure that you get a clear error message if the API changes:</p>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">github_api <-<span class="st"> </span>function(path) {
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">github_api <-<span class="st"> </span><span class="cf">function</span>(path) {
   url <-<span class="st"> </span><span class="kw">modify_url</span>(<span class="st">"https://api.github.com"</span>, <span class="dt">path =</span> path)
   
   resp <-<span class="st"> </span><span class="kw">GET</span>(url)
-  if (<span class="kw">http_type</span>(resp) !=<span class="st"> "application/json"</span>) {
+  <span class="cf">if</span> (<span class="kw">http_type</span>(resp) <span class="op">!=</span><span class="st"> "application/json"</span>) {
     <span class="kw">stop</span>(<span class="st">"API did not return json"</span>, <span class="dt">call. =</span> <span class="ot">FALSE</span>)
   }
   
@@ -207,29 +203,29 @@ resp
 <li>To parse json, use <code>jsonlite</code> package.</li>
 <li>To parse xml, use the <code>xml2</code> package.</li>
 </ol>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">github_api <-<span class="st"> </span>function(path) {
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">github_api <-<span class="st"> </span><span class="cf">function</span>(path) {
   url <-<span class="st"> </span><span class="kw">modify_url</span>(<span class="st">"https://api.github.com"</span>, <span class="dt">path =</span> path)
   
   resp <-<span class="st"> </span><span class="kw">GET</span>(url)
-  if (<span class="kw">http_type</span>(resp) !=<span class="st"> "application/json"</span>) {
+  <span class="cf">if</span> (<span class="kw">http_type</span>(resp) <span class="op">!=</span><span class="st"> "application/json"</span>) {
     <span class="kw">stop</span>(<span class="st">"API did not return json"</span>, <span class="dt">call. =</span> <span class="ot">FALSE</span>)
   }
   
-  jsonlite::<span class="kw">fromJSON</span>(<span class="kw">content</span>(resp, <span class="st">"text"</span>), <span class="dt">simplifyVector =</span> <span class="ot">FALSE</span>)
+  jsonlite<span class="op">::</span><span class="kw">fromJSON</span>(<span class="kw">content</span>(resp, <span class="st">"text"</span>), <span class="dt">simplifyVector =</span> <span class="ot">FALSE</span>)
 }</code></pre></div>
 </div>
 <div id="return-a-helpful-object" class="section level3">
 <h3>Return a helpful object</h3>
 <p>Rather than simply returning the response as a list, I think it’s a good practice to make a simple S3 object. That way you can return the response and parsed object, and provide a nice print method. This will make debugging later on much much much more pleasant.</p>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">github_api <-<span class="st"> </span>function(path) {
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">github_api <-<span class="st"> </span><span class="cf">function</span>(path) {
   url <-<span class="st"> </span><span class="kw">modify_url</span>(<span class="st">"https://api.github.com"</span>, <span class="dt">path =</span> path)
   
   resp <-<span class="st"> </span><span class="kw">GET</span>(url)
-  if (<span class="kw">http_type</span>(resp) !=<span class="st"> "application/json"</span>) {
+  <span class="cf">if</span> (<span class="kw">http_type</span>(resp) <span class="op">!=</span><span class="st"> "application/json"</span>) {
     <span class="kw">stop</span>(<span class="st">"API did not return json"</span>, <span class="dt">call. =</span> <span class="ot">FALSE</span>)
   }
   
-  parsed <-<span class="st"> </span>jsonlite::<span class="kw">fromJSON</span>(<span class="kw">content</span>(resp, <span class="st">"text"</span>), <span class="dt">simplifyVector =</span> <span class="ot">FALSE</span>)
+  parsed <-<span class="st"> </span>jsonlite<span class="op">::</span><span class="kw">fromJSON</span>(<span class="kw">content</span>(resp, <span class="st">"text"</span>), <span class="dt">simplifyVector =</span> <span class="ot">FALSE</span>)
   
   <span class="kw">structure</span>(
     <span class="kw">list</span>(
@@ -241,9 +237,9 @@ resp
   )
 }
 
-print.github_api <-<span class="st"> </span>function(x, ...) {
-  <span class="kw">cat</span>(<span class="st">"<GitHub "</span>, x$path, <span class="st">"></span><span class="ch">\n</span><span class="st">"</span>, <span class="dt">sep =</span> <span class="st">""</span>)
-  <span class="kw">str</span>(x$content)
+print.github_api <-<span class="st"> </span><span class="cf">function</span>(x, ...) {
+  <span class="kw">cat</span>(<span class="st">"<GitHub "</span>, x<span class="op">$</span>path, <span class="st">"></span><span class="ch">\n</span><span class="st">"</span>, <span class="dt">sep =</span> <span class="st">""</span>)
+  <span class="kw">str</span>(x<span class="op">$</span>content)
   <span class="kw">invisible</span>(x)
 }
 
@@ -252,7 +248,7 @@ print.github_api <-<span class="st"> </span>function(x, ...) {
 <span class="co">#> List of 30</span>
 <span class="co">#>  $ login              : chr "hadley"</span>
 <span class="co">#>  $ id                 : int 4196</span>
-<span class="co">#>  $ avatar_url         : chr "https://avatars.githubusercontent.com/u/4196?v=3"</span>
+<span class="co">#>  $ avatar_url         : chr "https://avatars3.githubusercontent.com/u/4196?v=4"</span>
 <span class="co">#>  $ gravatar_id        : chr ""</span>
 <span class="co">#>  $ url                : chr "https://api.github.com/users/hadley"</span>
 <span class="co">#>  $ html_url           : chr "https://github.com/hadley"</span>
@@ -271,44 +267,44 @@ print.github_api <-<span class="st"> </span>function(x, ...) {
 <span class="co">#>  $ company            : chr "@rstudio "</span>
 <span class="co">#>  $ blog               : chr "http://hadley.nz"</span>
 <span class="co">#>  $ location           : chr "Houston, TX"</span>
-<span class="co">#>  $ email              : chr "h.wickham at gmail.com"</span>
+<span class="co">#>  $ email              : NULL</span>
 <span class="co">#>  $ hireable           : NULL</span>
-<span class="co">#>  $ bio                : NULL</span>
-<span class="co">#>  $ public_repos       : int 194</span>
-<span class="co">#>  $ public_gists       : int 148</span>
-<span class="co">#>  $ followers          : int 6577</span>
+<span class="co">#>  $ bio                : chr "Chief Scientist at @RStudio"</span>
+<span class="co">#>  $ public_repos       : int 206</span>
+<span class="co">#>  $ public_gists       : int 160</span>
+<span class="co">#>  $ followers          : int 10515</span>
 <span class="co">#>  $ following          : int 7</span>
 <span class="co">#>  $ created_at         : chr "2008-04-01T14:47:36Z"</span>
-<span class="co">#>  $ updated_at         : chr "2016-07-01T20:50:53Z"</span></code></pre></div>
+<span class="co">#>  $ updated_at         : chr "2017-08-14T19:03:52Z"</span></code></pre></div>
 <p>The API might return invalid data, but this should be rare, so you can just rely on the parser to provide a useful error message.</p>
 </div>
 <div id="turn-api-errors-into-r-errors" class="section level3">
 <h3>Turn API errors into R errors</h3>
-<p>Next, you need to make sure that your API wrapper throws an error if the request failed. Using a web API introduces additional possible points of failure into R code aside from those occuring in R itself. These include:</p>
+<p>Next, you need to make sure that your API wrapper throws an error if the request failed. Using a web API introduces additional possible points of failure into R code aside from those occurring in R itself. These include:</p>
 <ul>
 <li>Client-side exceptions</li>
 <li>Network / communication exceptions</li>
 <li>Server-side exceptions</li>
 </ul>
 <p>You need to make sure these are all converted into regular R errors. You can figure out if there’s a problem with <code>http_error()</code>, which checks the HTTP status code. Status codes in the 400 range usually mean that you’ve done something wrong. Status codes in the 500 range typically mean that something has gone wrong on the server side.</p>
-<p>Often the API will information about the error in the body of the response: you should use this where available. If the API returns special errors for common problems, you might want to provide more detail in the error. For example, if you run out of requests and are <a href="https://developer.github.com/v3/#rate-limiting">rate limited</a> you might want to tell the user how long to wait until they can make the next request (or even automatically wait that long!).</p>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">github_api <-<span class="st"> </span>function(path) {
+<p>Often the API will provide information about the error in the body of the response: you should use this where available. If the API returns special errors for common problems, you might want to provide more detail in the error. For example, if you run out of requests and are <a href="https://developer.github.com/v3/#rate-limiting">rate limited</a> you might want to tell the user how long to wait until they can make the next request (or even automatically wait that long!).</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">github_api <-<span class="st"> </span><span class="cf">function</span>(path) {
   url <-<span class="st"> </span><span class="kw">modify_url</span>(<span class="st">"https://api.github.com"</span>, <span class="dt">path =</span> path)
   
   resp <-<span class="st"> </span><span class="kw">GET</span>(url)
-  if (<span class="kw">http_type</span>(resp) !=<span class="st"> "application/json"</span>) {
+  <span class="cf">if</span> (<span class="kw">http_type</span>(resp) <span class="op">!=</span><span class="st"> "application/json"</span>) {
     <span class="kw">stop</span>(<span class="st">"API did not return json"</span>, <span class="dt">call. =</span> <span class="ot">FALSE</span>)
   }
   
-  parsed <-<span class="st"> </span>jsonlite::<span class="kw">fromJSON</span>(<span class="kw">content</span>(resp, <span class="st">"text"</span>), <span class="dt">simplifyVector =</span> <span class="ot">FALSE</span>)
+  parsed <-<span class="st"> </span>jsonlite<span class="op">::</span><span class="kw">fromJSON</span>(<span class="kw">content</span>(resp, <span class="st">"text"</span>), <span class="dt">simplifyVector =</span> <span class="ot">FALSE</span>)
   
-  if (<span class="kw">http_error</span>(resp)) {
+  <span class="cf">if</span> (<span class="kw">http_error</span>(resp)) {
     <span class="kw">stop</span>(
       <span class="kw">sprintf</span>(
         <span class="st">"GitHub API request failed [%s]</span><span class="ch">\n</span><span class="st">%s</span><span class="ch">\n</span><span class="st"><%s>"</span>, 
         <span class="kw">status_code</span>(resp),
-        parsed$message,
-        parsed$documentation_url
+        parsed<span class="op">$</span>message,
+        parsed<span class="op">$</span>documentation_url
       ),
       <span class="dt">call. =</span> <span class="ot">FALSE</span>
     )
@@ -342,23 +338,23 @@ ua
 <span class="co">#> Options:</span>
 <span class="co">#> * useragent: http://github.com/hadley/httr</span>
 
-github_api <-<span class="st"> </span>function(path) {
+github_api <-<span class="st"> </span><span class="cf">function</span>(path) {
   url <-<span class="st"> </span><span class="kw">modify_url</span>(<span class="st">"https://api.github.com"</span>, <span class="dt">path =</span> path)
   
   resp <-<span class="st"> </span><span class="kw">GET</span>(url, ua)
-  if (<span class="kw">http_type</span>(resp) !=<span class="st"> "application/json"</span>) {
+  <span class="cf">if</span> (<span class="kw">http_type</span>(resp) <span class="op">!=</span><span class="st"> "application/json"</span>) {
     <span class="kw">stop</span>(<span class="st">"API did not return json"</span>, <span class="dt">call. =</span> <span class="ot">FALSE</span>)
   }
   
-  parsed <-<span class="st"> </span>jsonlite::<span class="kw">fromJSON</span>(<span class="kw">content</span>(resp, <span class="st">"text"</span>), <span class="dt">simplifyVector =</span> <span class="ot">FALSE</span>)
+  parsed <-<span class="st"> </span>jsonlite<span class="op">::</span><span class="kw">fromJSON</span>(<span class="kw">content</span>(resp, <span class="st">"text"</span>), <span class="dt">simplifyVector =</span> <span class="ot">FALSE</span>)
   
-  if (<span class="kw">status_code</span>(resp) !=<span class="st"> </span><span class="dv">200</span>) {
+  <span class="cf">if</span> (<span class="kw">status_code</span>(resp) <span class="op">!=</span><span class="st"> </span><span class="dv">200</span>) {
     <span class="kw">stop</span>(
       <span class="kw">sprintf</span>(
         <span class="st">"GitHub API request failed [%s]</span><span class="ch">\n</span><span class="st">%s</span><span class="ch">\n</span><span class="st"><%s>"</span>, 
         <span class="kw">status_code</span>(resp),
-        parsed$message,
-        parsed$documentation_url
+        parsed<span class="op">$</span>message,
+        parsed<span class="op">$</span>documentation_url
       ),
       <span class="dt">call. =</span> <span class="ot">FALSE</span>
     )
@@ -383,7 +379,7 @@ github_api <-<span class="st"> </span>function(path) {
 <li>HTTP headers: <code>add_headers()</code></li>
 <li>Request body: The <code>body</code> argument to <code>GET()</code>, <code>POST()</code>, etc.</li>
 </ol>
-<p><a href="https://en.wikipedia.org/wiki/Representational_state_transfer">RESTful APIs</a> also use the HTTP verb to communicate arguments (e.g,., <code>GET</code> retrieves a file, <code>POST</code> adds a file, <code>DELETE</code> removes a file, etc.). We can use the helpful <a href="http://httpbin.org">httpbin service</a> to show how to send arguments in each of these ways.</p>
+<p><a href="https://en.wikipedia.org/wiki/Representational_state_transfer">RESTful APIs</a> also use the HTTP verb to communicate arguments (e.g., <code>GET</code> retrieves a file, <code>POST</code> adds a file, <code>DELETE</code> removes a file, etc.). We can use the helpful <a href="http://httpbin.org">httpbin service</a> to show how to send arguments in each of these ways.</p>
 <div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="co"># modify_url</span>
 <span class="kw">POST</span>(<span class="kw">modify_url</span>(<span class="st">"https://httpbin.org"</span>, <span class="dt">path =</span> <span class="st">"/post"</span>))
 
@@ -400,7 +396,7 @@ github_api <-<span class="st"> </span>function(path) {
 <span class="kw">POST</span>(<span class="st">"http://httpbin.org/post"</span>, <span class="dt">body =</span> <span class="kw">list</span>(<span class="dt">foo =</span> <span class="st">"bar"</span>), <span class="dt">encode =</span> <span class="st">"json"</span>)</code></pre></div>
 <p>Many APIs will use just one of these forms of argument passing, but others will use multiple of them in combination. Best practice is to insulate the user from how and where the various arguments are used by the API and instead simply expose relevant arguments via R function arguments, some of which might be used in the URL, in the headers, in the body, etc.</p>
 <p>If a parameter has a small fixed set of possible values that are allowed by the API, you can use list them in the default arguments and then use <code>match.arg()</code> to ensure that the caller only supplies one of those values. (This also allows the user to supply the short unique prefixes.)</p>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">f <-<span class="st"> </span>function(<span class="dt">x =</span> <span class="kw">c</span>(<span class="st">"apple"</span>, <span class="st">"banana"</span>, <span class="st">"orange"</span>)) {
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">f <-<span class="st"> </span><span class="cf">function</span>(<span class="dt">x =</span> <span class="kw">c</span>(<span class="st">"apple"</span>, <span class="st">"banana"</span>, <span class="st">"orange"</span>)) {
   <span class="kw">match.arg</span>(x)
 }
 <span class="kw">f</span>(<span class="st">"a"</span>)
@@ -421,9 +417,9 @@ github_api <-<span class="st"> </span>function(path) {
 <p>Some APIs describe their authentication processes inaccurately, so care needs to be taken to understand the true authentication mechanism regardless of the label used in the API docs.</p>
 </blockquote>
 <p>It is possible to specify the key(s) or token(s) required for basic or OAuth authentication in a number of different ways (see Appendix for a detailed discussion). You may also need some way to preserve user credentials between function calls so that end users do not need to specify them each time. A good start is to use an environment variable. Here is an example of how to write a function that checks for the presence of a GitHub personal access token and errors otherwise:</p>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">github_pat <-<span class="st"> </span>function() {
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">github_pat <-<span class="st"> </span><span class="cf">function</span>() {
   pat <-<span class="st"> </span><span class="kw">Sys.getenv</span>(<span class="st">'GITHUB_PAT'</span>)
-  if (<span class="kw">identical</span>(pat, <span class="st">""</span>)) {
+  <span class="cf">if</span> (<span class="kw">identical</span>(pat, <span class="st">""</span>)) {
     <span class="kw">stop</span>(<span class="st">"Please set env var GITHUB_PAT to your github personal access token"</span>,
       <span class="dt">call. =</span> <span class="ot">FALSE</span>)
   }
@@ -444,7 +440,7 @@ github_api <-<span class="st"> </span>function(path) {
 <p>These values can then be used to make further requests. This will either involve specifying a specific page of responses or specifying a “next page token” that returns the next page of results. How to deal with pagination is a difficult question and a client could implement any of the following:</p>
 <ol style="list-style-type: decimal">
 <li>Return one page only by default with an option to return additional specific pages</li>
-<li>Return a specified page (deafulting to 1) and require the end user to handle pagination</li>
+<li>Return a specified page (defaulting to 1) and require the end user to handle pagination</li>
 <li>Return all pages by writing an internal process of checking for further pages and combining the results</li>
 </ol>
 <p>The choice of which to use depends on your needs and goals and the rate limits of the API.</p>
@@ -452,84 +448,43 @@ github_api <-<span class="st"> </span>function(path) {
 <h3>Rate limiting</h3>
 <p>Many APIs are rate limited, which means that you can only send a certain number of requests per hour. Often if your request is rate limited, the error message will tell you how long you should wait before performing another request. You might want to expose this to the user, or even include a wall to <code>Sys.sleep()</code> that waits long enough.</p>
 <p>For example, we could implement a <code>rate_limit()</code> function that tells you how many calls against the github API are available to you.</p>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">rate_limit <-<span class="st"> </span>function() {
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">rate_limit <-<span class="st"> </span><span class="cf">function</span>() {
   <span class="kw">github_api</span>(<span class="st">"/rate_limit"</span>)
 }
 <span class="kw">rate_limit</span>()
 <span class="co">#> <GitHub /rate_limit></span>
 <span class="co">#> List of 2</span>
-<span class="co">#>  $ resources:List of 2</span>
-<span class="co">#>   ..$ core  :List of 3</span>
+<span class="co">#>  $ resources:List of 3</span>
+<span class="co">#>   ..$ core   :List of 3</span>
 <span class="co">#>   .. ..$ limit    : int 60</span>
-<span class="co">#>   .. ..$ remaining: int 50</span>
-<span class="co">#>   .. ..$ reset    : int 1467567028</span>
-<span class="co">#>   ..$ search:List of 3</span>
+<span class="co">#>   .. ..$ remaining: int 56</span>
+<span class="co">#>   .. ..$ reset    : int 1503082049</span>
+<span class="co">#>   ..$ search :List of 3</span>
 <span class="co">#>   .. ..$ limit    : int 10</span>
 <span class="co">#>   .. ..$ remaining: int 10</span>
-<span class="co">#>   .. ..$ reset    : int 1467565342</span>
+<span class="co">#>   .. ..$ reset    : int 1503078516</span>
+<span class="co">#>   ..$ graphql:List of 3</span>
+<span class="co">#>   .. ..$ limit    : int 0</span>
+<span class="co">#>   .. ..$ remaining: int 0</span>
+<span class="co">#>   .. ..$ reset    : int 1503082056</span>
 <span class="co">#>  $ rate     :List of 3</span>
 <span class="co">#>   ..$ limit    : int 60</span>
-<span class="co">#>   ..$ remaining: int 50</span>
-<span class="co">#>   ..$ reset    : int 1467567028</span></code></pre></div>
-<p>After getting the first version getting working, you’ll often want to polish the output to be more user friendly. For this example, we can parse the unix timestamps into more useful date types.</p>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">rate_limit <-<span class="st"> </span>function() {
+<span class="co">#>   ..$ remaining: int 56</span>
+<span class="co">#>   ..$ reset    : int 1503082049</span></code></pre></div>
+<p>After getting the first version working, you’ll often want to polish the output to be more user friendly. For this example, we can parse the unix timestamps into more useful date types.</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">rate_limit <-<span class="st"> </span><span class="cf">function</span>() {
   req <-<span class="st"> </span><span class="kw">github_api</span>(<span class="st">"/rate_limit"</span>)
-  core <-<span class="st"> </span>req$content$resources$core
+  core <-<span class="st"> </span>req<span class="op">$</span>content<span class="op">$</span>resources<span class="op">$</span>core
 
-  reset <-<span class="st"> </span><span class="kw">as.POSIXct</span>(core$reset, <span class="dt">origin =</span> <span class="st">"1970-01-01"</span>)
-  <span class="kw">cat</span>(core$remaining, <span class="st">" / "</span>, core$limit,
+  reset <-<span class="st"> </span><span class="kw">as.POSIXct</span>(core<span class="op">$</span>reset, <span class="dt">origin =</span> <span class="st">"1970-01-01"</span>)
+  <span class="kw">cat</span>(core<span class="op">$</span>remaining, <span class="st">" / "</span>, core<span class="op">$</span>limit,
     <span class="st">" (Resets at "</span>, <span class="kw">strftime</span>(reset, <span class="st">"%H:%M:%S"</span>), <span class="st">")</span><span class="ch">\n</span><span class="st">"</span>, <span class="dt">sep =</span> <span class="st">""</span>)
 }
 
 <span class="kw">rate_limit</span>()
-<span class="co">#> 50 / 60 (Resets at 10:30:28)</span></code></pre></div>
+<span class="co">#> 56 / 60 (Resets at 13:47:29)</span></code></pre></div>
 </div>
 </div>
-<div id="packaging-youe-code" class="section level2">
-<h2>Packaging youe code</h2>
-<div id="documentation" class="section level3">
-<h3>Documentation</h3>
-<p>Like any R package, an API client needs clear and complete documentation of all functions. Examples are particularly useful but may need to be wrapped in <code>\donttest{}</code> to avoid challenges of authentication, rate limiting, lack of network access, or occasional API server down time.</p>
-<p>One other challenge unique to web API packages is the need to have documentation that clarifies any distinctions between the R implementation and the API generally (e.g., in the “details” section). It can be useful to add reference links to the API documentation to clarify the mapping between R functions and API endpoints.</p>
-</div>
-<div id="vignettes" class="section level3">
-<h3>Vignettes</h3>
-<p>Vignettes pose additional challenges when an API requires authentication, because you don’t want to bundle your own credentials with the package! However, you can take advantage of the fact that the vignette is built locally, and only checked by CRAN with this idea from Jenny Bryan:</p>
-<p>In an setup chunk, do:</p>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">NOT_CRAN <-<span class="st"> </span><span class="kw">identical</span>(<span class="kw">tolower</span>(<span class="kw">Sys.getenv</span>(<span class="st">"NOT_CRAN"</span>)), <span class="st">"true"</span>)
-knitr::opts_chunk$<span class="kw">set</span>(<span class="dt">purl =</span> NOT_CRAN)</code></pre></div>
-<p>And then use <code>eval = NOT_CRAN</code> as needed as a chunk option.</p>
-</div>
-<div id="testing" class="section level3">
-<h3>Testing</h3>
-<p>The same issues and solutions can also be applied to test suites for API clients that require authenticated credentials. For these packages, you may want to have a test suite that is only run locally but not on CRAN. Another solution is to use a continuous integration service such as Travis-CI or Appveyor, which allows the inclusion of encrypted environment variables or encrypted token files. This enables continuous testing in a relatively secure way.</p>
-</div>
-</div>
-<div id="appendix-storing-api-authentication-keystokens" class="section level2">
-<h2>Appendix: Storing API Authentication Keys/Tokens</h2>
-<p>If your package supports an authentication workflow based on an API key or token, encourage users to store it in an environment variable. We illustrate this using the <a href="https://github.com/cscheid/rgithub"><code>github</code> R package</a>, which wraps the Github v3 API. Tailor this template to your API + package and include in <code>README.md</code> or a vignette.</p>
-<ol style="list-style-type: decimal">
-<li>Create a personal access token in the <a href="https://github.com/settings/tokens">Personal access tokens area</a> of your GitHub personal settings. Copy token to the clipboard.</li>
-<li>Identify your home directory. Not sure? Enter <code>normalizePath("~/")</code> in the R console.</li>
-<li>Create a new text file. If in RStudio, do <em>File > New File > Text file.</em></li>
-<li>Create a line like this:</li>
-</ol>
-<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ot">GITHUB_PAT=</span>blahblahblahblahblahblah</code></pre></div>
-<p>where the name <code>GITHUB_PAT</code> reminds you which API this is for and <code>blahblahblahblahblahblah</code> is your personal access token, pasted from the clipboard.</p>
-<ol style="list-style-type: decimal">
-<li><p>Make sure the last line in the file is empty (if it isn’t R will <strong>silently</strong> fail to load the file. If you’re using an editor that shows line numbers, there should be two lines, where the second one is empty.</p></li>
-<li><p>Save in your home directory with the filename <code>.Renviron</code>. If questioned,<br />
-YES you do want to use a filename that begins with a dot <code>.</code>.</p>
-<p>Note that by default <a href="http://linux.about.com/cs/linux101/g/dot_file.htm">dotfiles</a> are usually hidden. But within RStudio, the file browser will make <code>.Renviron</code> visible and therefore easy to edit in the future.</p></li>
-<li><p>Restart R. <code>.Renviron</code> is processed only <a href="http://stat.ethz.ch/R-manual/R-patched/library/base/html/Startup.html">at the start of an R session</a>.</p></li>
-<li><p>Use <code>Sys.getenv()</code> to access your token. For example, here’s how to use your <code>GITHUB_PAT</code> with the <code>github</code> package:</p>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">library</span>(github)
-ctx <-<span class="st"> </span><span class="kw">create.github.context</span>(<span class="dt">access_token =</span> <span class="kw">Sys.getenv</span>(<span class="st">"GITHUB_PAT"</span>))
-<span class="co"># ... proceed to use other package functions to open issues, etc.</span></code></pre></div></li>
-</ol>
-<p>FAQ: Why define this environment variable via <code>.Renviron</code> instead of in <code>.bash_profile</code> or <code>.bashrc</code>?</p>
-<p>Because there are many combinations of OS and ways of running R where the <code>.Renviron</code> approach “just works” and the bash stuff does not. When R is a child process of, say, Emacs or RStudio, you can’t always count on environment variables being passed to R. Put them in an R-specific start-up file and save yourself some grief.</p>
-</div>
 
 
 
@@ -538,7 +493,7 @@ ctx <-<span class="st"> </span><span class="kw">create.github.context</span>(
   (function () {
     var script = document.createElement("script");
     script.type = "text/javascript";
-    script.src  = "https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
+    script.src  = "https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
     document.getElementsByTagName("head")[0].appendChild(script);
   })();
 </script>
diff --git a/inst/doc/quickstart.Rmd b/inst/doc/quickstart.Rmd
index 66f42fe..76ddc3b 100644
--- a/inst/doc/quickstart.Rmd
+++ b/inst/doc/quickstart.Rmd
@@ -1,7 +1,11 @@
-<!--
-%\VignetteEngine{knitr::knitr}
-%\VignetteIndexEntry{httr quickstart guide}
--->
+---
+title: "Getting started with httr"
+output: rmarkdown::html_vignette
+vignette: >
+  %\VignetteIndexEntry{Getting started with httr}
+  %\VignetteEngine{knitr::rmarkdown}
+  %\VignetteEncoding{UTF-8}
+---
 
 ```{r, echo = FALSE}
 library(httr)
@@ -14,7 +18,7 @@ The goal of this document is to get you up and running with httr as quickly as p
 
 This vignette (and parts of the httr API) derived from the excellent "[Requests quickstart guide](http://docs.python-requests.org/en/latest/user/quickstart/)" by Kenneth Reitz. Requests is a python library similar in spirit to httr.  
 
-There are two important parts to http: the __request__, the data sent to the server, and the __response__, the data sent back from the server. In the first section, you'll learn about the basics of constructing a requests and accessing the response. In the second and third sections, you'll dive into more details of each.
+There are two important parts to http: the __request__, the data sent to the server, and the __response__, the data sent back from the server. In the first section, you'll learn about the basics of constructing a request and accessing the response. In the second and third sections, you'll dive into more details of each.
 
 ## httr basics
 
@@ -39,7 +43,7 @@ headers(r)
 str(content(r))
 ```
 
-I'll use `httpbin.org` throughout this introduction. It accepts many types of http request and returns json that describes the data that it recieved. This makes it easy to see what httr is doing.
+I'll use `httpbin.org` throughout this introduction. It accepts many types of http request and returns json that describes the data that it received. This makes it easy to see what httr is doing.
 
 As well as `GET()`, you can also use the `HEAD()`, `POST()`, `PATCH()`, `PUT()` and `DELETE()` verbs. You're probably most familiar with `GET()` and `POST()`: `GET()` is used by your browser when requesting a page, and `POST()` is (usually) used when submitting a form to a server. `PUT()`, `PATCH()` and `DELETE()` are used most often by web APIs.
 
@@ -49,7 +53,7 @@ The data sent back from the server consists of three parts: the status line, the
 
 ### The status code
 
-The status code is a three digit number that summarises whether or not the request was succesful (as defined by the server that you're talking to). You can access the status code along with a descriptive message using `http_status()`:
+The status code is a three digit number that summarises whether or not the request was successful (as defined by the server that you're talking to). You can access the status code along with a descriptive message using `http_status()`:
 
 ```{r}
 r <- GET("http://httpbin.org/get")
@@ -60,7 +64,7 @@ http_status(r)
 r$status_code
 ```
 
-A succesful request always returns a status of 200. Common errors are 404 (file not found) and 403 (permission denied). If you're talking to web APIs you might also see 500, which is a generic failure code (and thus not very helpful). If you'd like to learn more, the most memorable guides are the [http status cats](https://www.flickr.com/photos/girliemac/sets/72157628409467125).
+A successful request always returns a status of 200. Common errors are 404 (file not found) and 403 (permission denied). If you're talking to web APIs you might also see 500, which is a generic failure code (and thus not very helpful). If you'd like to learn more, the most memorable guides are the [http status cats](https://www.flickr.com/photos/girliemac/sets/72157628409467125).
 
 You can automatically throw a warning or raise an error if a request did not succeed:
 
@@ -227,7 +231,7 @@ POST(url, body = body, encode = "form", verbose())
 POST(url, body = body, encode = "json", verbose())
 ```
 
-`PUT()` and `PATCH()` can also have request bodies, and they identically to `POST()`.
+`PUT()` and `PATCH()` can also have request bodies, and they take arguments identically to `POST()`.
 
 You can also send files off disk:
 
diff --git a/inst/doc/quickstart.html b/inst/doc/quickstart.html
index f80398a..fce10b6 100644
--- a/inst/doc/quickstart.html
+++ b/inst/doc/quickstart.html
@@ -1,584 +1,408 @@
 <!DOCTYPE html>
-<html>
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-
-<title>httr quickstart guide</title>
 
-<script type="text/javascript">
-window.onload = function() {
-  var imgs = document.getElementsByTagName('img'), i, img;
-  for (i = 0; i < imgs.length; i++) {
-    img = imgs[i];
-    // center an image if it is the only element of its parent
-    if (img.parentElement.childElementCount === 1)
-      img.parentElement.style.textAlign = 'center';
-  }
-};
-</script>
+<html xmlns="http://www.w3.org/1999/xhtml">
 
-<!-- Styles for R syntax highlighter -->
-<style type="text/css">
-   pre .operator,
-   pre .paren {
-     color: rgb(104, 118, 135)
-   }
-
-   pre .literal {
-     color: #990073
-   }
-
-   pre .number {
-     color: #099;
-   }
+<head>
 
-   pre .comment {
-     color: #998;
-     font-style: italic
-   }
+<meta charset="utf-8" />
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="generator" content="pandoc" />
 
-   pre .keyword {
-     color: #900;
-     font-weight: bold
-   }
+<meta name="viewport" content="width=device-width, initial-scale=1">
 
-   pre .identifier {
-     color: rgb(0, 0, 0);
-   }
 
-   pre .string {
-     color: #d14;
-   }
-</style>
 
-<!-- R syntax highlighter -->
-<script type="text/javascript">
-var hljs=new function(){function m(p){return p.replace(/&/gm,"&").replace(/</gm,"<")}function f(r,q,p){return RegExp(q,"m"+(r.cI?"i":"")+(p?"g":""))}function b(r){for(var p=0;p<r.childNodes.length;p++){var q=r.childNodes[p];if(q.nodeName=="CODE"){return q}if(!(q.nodeType==3&&q.nodeValue.match(/\s+/))){break}}}function h(t,s){var p="";for(var r=0;r<t.childNodes.length;r++){if(t.childNodes[r].nodeType==3){var q=t.childNodes[r].nodeValue;if(s){q=q.replace(/\n/g,"")}p+=q}else{if(t.chi [...]
-hljs.initHighlightingOnLoad();
-</script>
+<title>Getting started with httr</title>
 
 
 
+<style type="text/css">code{white-space: pre;}</style>
 <style type="text/css">
-body, td {
-   font-family: sans-serif;
-   background-color: white;
-   font-size: 13px;
-}
-
-body {
-  max-width: 800px;
-  margin: auto;
-  padding: 1em;
-  line-height: 20px;
-}
-
-tt, code, pre {
-   font-family: 'DejaVu Sans Mono', 'Droid Sans Mono', 'Lucida Console', Consolas, Monaco, monospace;
-}
-
-h1 {
-   font-size:2.2em;
-}
-
-h2 {
-   font-size:1.8em;
-}
-
-h3 {
-   font-size:1.4em;
-}
-
-h4 {
-   font-size:1.0em;
-}
-
-h5 {
-   font-size:0.9em;
-}
-
-h6 {
-   font-size:0.8em;
-}
-
-a:visited {
-   color: rgb(50%, 0%, 50%);
-}
-
-pre, img {
-  max-width: 100%;
-}
-pre {
-  overflow-x: auto;
-}
-pre code {
-   display: block; padding: 0.5em;
-}
-
-code {
-  font-size: 92%;
-  border: 1px solid #ccc;
-}
-
-code[class] {
-  background-color: #F8F8F8;
-}
-
-table, td, th {
-  border: none;
-}
-
-blockquote {
-   color:#666666;
-   margin:0;
-   padding-left: 1em;
-   border-left: 0.5em #EEE solid;
-}
-
-hr {
-   height: 0px;
-   border-bottom: none;
-   border-top-width: thin;
-   border-top-style: dotted;
-   border-top-color: #999999;
-}
-
- at media print {
-   * {
-      background: transparent !important;
-      color: black !important;
-      filter:none !important;
-      -ms-filter: none !important;
-   }
-
-   body {
-      font-size:12pt;
-      max-width:100%;
-   }
-
-   a, a:visited {
-      text-decoration: underline;
-   }
-
-   hr {
-      visibility: hidden;
-      page-break-before: always;
-   }
+div.sourceCode { overflow-x: auto; }
+table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode {
+  margin: 0; padding: 0; vertical-align: baseline; border: none; }
+table.sourceCode { width: 100%; line-height: 100%; }
+td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; }
+td.sourceCode { padding-left: 5px; }
+code > span.kw { color: #007020; font-weight: bold; } /* Keyword */
+code > span.dt { color: #902000; } /* DataType */
+code > span.dv { color: #40a070; } /* DecVal */
+code > span.bn { color: #40a070; } /* BaseN */
+code > span.fl { color: #40a070; } /* Float */
+code > span.ch { color: #4070a0; } /* Char */
+code > span.st { color: #4070a0; } /* String */
+code > span.co { color: #60a0b0; font-style: italic; } /* Comment */
+code > span.ot { color: #007020; } /* Other */
+code > span.al { color: #ff0000; font-weight: bold; } /* Alert */
+code > span.fu { color: #06287e; } /* Function */
+code > span.er { color: #ff0000; font-weight: bold; } /* Error */
+code > span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
+code > span.cn { color: #880000; } /* Constant */
+code > span.sc { color: #4070a0; } /* SpecialChar */
+code > span.vs { color: #4070a0; } /* VerbatimString */
+code > span.ss { color: #bb6688; } /* SpecialString */
+code > span.im { } /* Import */
+code > span.va { color: #19177c; } /* Variable */
+code > span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
+code > span.op { color: #666666; } /* Operator */
+code > span.bu { } /* BuiltIn */
+code > span.ex { } /* Extension */
+code > span.pp { color: #bc7a00; } /* Preprocessor */
+code > span.at { color: #7d9029; } /* Attribute */
+code > span.do { color: #ba2121; font-style: italic; } /* Documentation */
+code > span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
+code > span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
+code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
+</style>
 
-   pre, blockquote {
-      padding-right: 1em;
-      page-break-inside: avoid;
-   }
 
-   tr, img {
-      page-break-inside: avoid;
-   }
 
-   img {
-      max-width: 100% !important;
-   }
+<link href="data:text/css;charset=utf-8,body%20%7B%0Abackground%2Dcolor%3A%20%23fff%3B%0Amargin%3A%201em%20auto%3B%0Amax%2Dwidth%3A%20700px%3B%0Aoverflow%3A%20visible%3B%0Apadding%2Dleft%3A%202em%3B%0Apadding%2Dright%3A%202em%3B%0Afont%2Dfamily%3A%20%22Open%20Sans%22%2C%20%22Helvetica%20Neue%22%2C%20Helvetica%2C%20Arial%2C%20sans%2Dserif%3B%0Afont%2Dsize%3A%2014px%3B%0Aline%2Dheight%3A%201%2E35%3B%0A%7D%0A%23header%20%7B%0Atext%2Dalign%3A%20center%3B%0A%7D%0A%23TOC%20%7B%0Aclear%3A%20bot [...]
 
-   @page :left {
-      margin: 15mm 20mm 15mm 10mm;
-   }
+</head>
 
-   @page :right {
-      margin: 15mm 10mm 15mm 20mm;
-   }
+<body>
 
-   p, h2, h3 {
-      orphans: 3; widows: 3;
-   }
 
-   h2, h3 {
-      page-break-after: avoid;
-   }
-}
-</style>
 
 
+<h1 class="title toc-ignore">Getting started with httr</h1>
 
-</head>
 
-<body>
-<!--
-%\VignetteEngine{knitr::knitr}
-%\VignetteIndexEntry{httr quickstart guide}
--->
 
+<div id="httr-quickstart-guide" class="section level1">
 <h1>httr quickstart guide</h1>
-
-<p>The goal of this document is to get you up and running with httr as quickly as possible. httr is designed to map closely to the underlying http protocol. I'll try and explain the basics in this intro, but I'd also recommend “<a href="http://code.tutsplus.com/tutorials/http-the-protocol-every-web-developer-must-know-part-1--net-31177">HTTP: The Protocol Every Web Developer Must Know</a>” or “<a href="http://www.jmarshall.com/easy/http/">HTTP made really easy</ [...]
-
-<p>This vignette (and parts of the httr API) derived from the excellent “<a href="http://docs.python-requests.org/en/latest/user/quickstart/">Requests quickstart guide</a>” by Kenneth Reitz. Requests is a python library similar in spirit to httr.  </p>
-
-<p>There are two important parts to http: the <strong>request</strong>, the data sent to the server, and the <strong>response</strong>, the data sent back from the server. In the first section, you'll learn about the basics of constructing a requests and accessing the response. In the second and third sections, you'll dive into more details of each.</p>
-
+<p>The goal of this document is to get you up and running with httr as quickly as possible. httr is designed to map closely to the underlying http protocol. I’ll try and explain the basics in this intro, but I’d also recommend “<a href="http://code.tutsplus.com/tutorials/http-the-protocol-every-web-developer-must-know-part-1--net-31177">HTTP: The Protocol Every Web Developer Must Know</a>” or “<a href="http://www.jmarshall.com/easy/http/">HTTP made really easy</a>”.</p>
+<p>This vignette (and parts of the httr API) derived from the excellent “<a href="http://docs.python-requests.org/en/latest/user/quickstart/">Requests quickstart guide</a>” by Kenneth Reitz. Requests is a python library similar in spirit to httr.</p>
+<p>There are two important parts to http: the <strong>request</strong>, the data sent to the server, and the <strong>response</strong>, the data sent back from the server. In the first section, you’ll learn about the basics of constructing a request and accessing the response. In the second and third sections, you’ll dive into more details of each.</p>
+<div id="httr-basics" class="section level2">
 <h2>httr basics</h2>
-
 <p>To make a request, first load httr, then call <code>GET()</code> with a url:</p>
-
-<pre><code class="r">library(httr)
-r <- GET("http://httpbin.org/get")
-</code></pre>
-
-<p>This gives you a response object. Printing a response object gives you some useful information: the actual url used (after any redirects), the http status, the file (content) type, the size, and if it's a text file, the first few lines of output.</p>
-
-<pre><code class="r">r
-#> Response [http://httpbin.org/get]
-#>   Date: 2016-07-03 17:01
-#>   Status: 200
-#>   Content-Type: application/json
-#>   Size: 299 B
-#> {
-#>   "args": {}, 
-#>   "headers": {
-#>     "Accept": "application/json, text/xml, application/xml, */*", 
-#>     "Accept-Encoding": "gzip, deflate", 
-#>     "Host": "httpbin.org", 
-#>     "User-Agent": "libcurl/7.43.0 r-curl/0.9.7 httr/1.2.1"
-#>   }, 
-#>   "origin": "68.65.174.224", 
-#>   "url": "http://httpbin.org/get"
-#> ...
-</code></pre>
-
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">library</span>(httr)
+r <-<span class="st"> </span><span class="kw">GET</span>(<span class="st">"http://httpbin.org/get"</span>)</code></pre></div>
+<p>This gives you a response object. Printing a response object gives you some useful information: the actual url used (after any redirects), the http status, the file (content) type, the size, and if it’s a text file, the first few lines of output.</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">r
+<span class="co">#> Response [http://httpbin.org/get]</span>
+<span class="co">#>   Date: 2017-08-18 17:47</span>
+<span class="co">#>   Status: 200</span>
+<span class="co">#>   Content-Type: application/json</span>
+<span class="co">#>   Size: 329 B</span>
+<span class="co">#> {</span>
+<span class="co">#>   "args": {}, </span>
+<span class="co">#>   "headers": {</span>
+<span class="co">#>     "Accept": "application/json, text/xml, application/xml, */*", </span>
+<span class="co">#>     "Accept-Encoding": "gzip, deflate", </span>
+<span class="co">#>     "Connection": "close", </span>
+<span class="co">#>     "Host": "httpbin.org", </span>
+<span class="co">#>     "User-Agent": "libcurl/7.54.0 r-curl/2.8.1 httr/1.3.1"</span>
+<span class="co">#>   }, </span>
+<span class="co">#>   "origin": "104.153.224.166", </span>
+<span class="co">#> ...</span></code></pre></div>
 <p>You can pull out important parts of the response with various helper methods, or dig directly into the object:</p>
-
-<pre><code class="r">status_code(r)
-#> [1] 200
-headers(r)
-#> $server
-#> [1] "nginx"
-#> 
-#> $date
-#> [1] "Sun, 03 Jul 2016 17:01:22 GMT"
-#> 
-#> $`content-type`
-#> [1] "application/json"
-#> 
-#> $`content-length`
-#> [1] "299"
-#> 
-#> $connection
-#> [1] "keep-alive"
-#> 
-#> $`access-control-allow-origin`
-#> [1] "*"
-#> 
-#> $`access-control-allow-credentials`
-#> [1] "true"
-#> 
-#> attr(,"class")
-#> [1] "insensitive" "list"
-str(content(r))
-#> List of 4
-#>  $ args   : Named list()
-#>  $ headers:List of 4
-#>   ..$ Accept         : chr "application/json, text/xml, application/xml, */*"
-#>   ..$ Accept-Encoding: chr "gzip, deflate"
-#>   ..$ Host           : chr "httpbin.org"
-#>   ..$ User-Agent     : chr "libcurl/7.43.0 r-curl/0.9.7 httr/1.2.1"
-#>  $ origin : chr "68.65.174.224"
-#>  $ url    : chr "http://httpbin.org/get"
-</code></pre>
-
-<p>I'll use <code>httpbin.org</code> throughout this introduction. It accepts many types of http request and returns json that describes the data that it recieved. This makes it easy to see what httr is doing.</p>
-
-<p>As well as <code>GET()</code>, you can also use the <code>HEAD()</code>, <code>POST()</code>, <code>PATCH()</code>, <code>PUT()</code> and <code>DELETE()</code> verbs. You're probably most familiar with <code>GET()</code> and <code>POST()</code>: <code>GET()</code> is used by your browser when requesting a page, and <code>POST()</code> is (usually) used when submitting a form to a server. <code>PUT()</code>, <code>PATCH()</code> and <code>DELETE()</code> are used most often by web [...]
-
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">status_code</span>(r)
+<span class="co">#> [1] 200</span>
+<span class="kw">headers</span>(r)
+<span class="co">#> $connection</span>
+<span class="co">#> [1] "keep-alive"</span>
+<span class="co">#> </span>
+<span class="co">#> $server</span>
+<span class="co">#> [1] "meinheld/0.6.1"</span>
+<span class="co">#> </span>
+<span class="co">#> $date</span>
+<span class="co">#> [1] "Fri, 18 Aug 2017 17:47:39 GMT"</span>
+<span class="co">#> </span>
+<span class="co">#> $`content-type`</span>
+<span class="co">#> [1] "application/json"</span>
+<span class="co">#> </span>
+<span class="co">#> $`access-control-allow-origin`</span>
+<span class="co">#> [1] "*"</span>
+<span class="co">#> </span>
+<span class="co">#> $`access-control-allow-credentials`</span>
+<span class="co">#> [1] "true"</span>
+<span class="co">#> </span>
+<span class="co">#> $`x-powered-by`</span>
+<span class="co">#> [1] "Flask"</span>
+<span class="co">#> </span>
+<span class="co">#> $`x-processed-time`</span>
+<span class="co">#> [1] "0.00117087364197"</span>
+<span class="co">#> </span>
+<span class="co">#> $`content-length`</span>
+<span class="co">#> [1] "329"</span>
+<span class="co">#> </span>
+<span class="co">#> $via</span>
+<span class="co">#> [1] "1.1 vegur"</span>
+<span class="co">#> </span>
+<span class="co">#> attr(,"class")</span>
+<span class="co">#> [1] "insensitive" "list"</span>
+<span class="kw">str</span>(<span class="kw">content</span>(r))
+<span class="co">#> List of 4</span>
+<span class="co">#>  $ args   : Named list()</span>
+<span class="co">#>  $ headers:List of 5</span>
+<span class="co">#>   ..$ Accept         : chr "application/json, text/xml, application/xml, */*"</span>
+<span class="co">#>   ..$ Accept-Encoding: chr "gzip, deflate"</span>
+<span class="co">#>   ..$ Connection     : chr "close"</span>
+<span class="co">#>   ..$ Host           : chr "httpbin.org"</span>
+<span class="co">#>   ..$ User-Agent     : chr "libcurl/7.54.0 r-curl/2.8.1 httr/1.3.1"</span>
+<span class="co">#>  $ origin : chr "104.153.224.166"</span>
+<span class="co">#>  $ url    : chr "http://httpbin.org/get"</span></code></pre></div>
+<p>I’ll use <code>httpbin.org</code> throughout this introduction. It accepts many types of http request and returns json that describes the data that it received. This makes it easy to see what httr is doing.</p>
+<p>As well as <code>GET()</code>, you can also use the <code>HEAD()</code>, <code>POST()</code>, <code>PATCH()</code>, <code>PUT()</code> and <code>DELETE()</code> verbs. You’re probably most familiar with <code>GET()</code> and <code>POST()</code>: <code>GET()</code> is used by your browser when requesting a page, and <code>POST()</code> is (usually) used when submitting a form to a server. <code>PUT()</code>, <code>PATCH()</code> and <code>DELETE()</code> are used most often by web APIs.</p>
+</div>
+<div id="the-response" class="section level2">
 <h2>The response</h2>
-
-<p>The data sent back from the server consists of three parts: the status line, the headers and the body. The most important part of the status line is the http status code: it tells you whether or not the request was successful. I'll show you how to access that data, then how to access the body and headers.</p>
-
+<p>The data sent back from the server consists of three parts: the status line, the headers and the body. The most important part of the status line is the http status code: it tells you whether or not the request was successful. I’ll show you how to access that data, then how to access the body and headers.</p>
+<div id="the-status-code" class="section level3">
 <h3>The status code</h3>
-
-<p>The status code is a three digit number that summarises whether or not the request was succesful (as defined by the server that you're talking to). You can access the status code along with a descriptive message using <code>http_status()</code>:</p>
-
-<pre><code class="r">r <- GET("http://httpbin.org/get")
-# Get an informative description:
-http_status(r)
-#> $category
-#> [1] "Success"
-#> 
-#> $reason
-#> [1] "OK"
-#> 
-#> $message
-#> [1] "Success: (200) OK"
-
-# Or just access the raw code:
-r$status_code
-#> [1] 200
-</code></pre>
-
-<p>A succesful request always returns a status of 200. Common errors are 404 (file not found) and 403 (permission denied). If you're talking to web APIs you might also see 500, which is a generic failure code (and thus not very helpful). If you'd like to learn more, the most memorable guides are the <a href="https://www.flickr.com/photos/girliemac/sets/72157628409467125">http status cats</a>.</p>
-
+<p>The status code is a three digit number that summarises whether or not the request was successful (as defined by the server that you’re talking to). You can access the status code along with a descriptive message using <code>http_status()</code>:</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">r <-<span class="st"> </span><span class="kw">GET</span>(<span class="st">"http://httpbin.org/get"</span>)
+<span class="co"># Get an informative description:</span>
+<span class="kw">http_status</span>(r)
+<span class="co">#> $category</span>
+<span class="co">#> [1] "Success"</span>
+<span class="co">#> </span>
+<span class="co">#> $reason</span>
+<span class="co">#> [1] "OK"</span>
+<span class="co">#> </span>
+<span class="co">#> $message</span>
+<span class="co">#> [1] "Success: (200) OK"</span>
+
+<span class="co"># Or just access the raw code:</span>
+r<span class="op">$</span>status_code
+<span class="co">#> [1] 200</span></code></pre></div>
+<p>A successful request always returns a status of 200. Common errors are 404 (file not found) and 403 (permission denied). If you’re talking to web APIs you might also see 500, which is a generic failure code (and thus not very helpful). If you’d like to learn more, the most memorable guides are the <a href="https://www.flickr.com/photos/girliemac/sets/72157628409467125">http status cats</a>.</p>
 <p>You can automatically throw a warning or raise an error if a request did not succeed:</p>
-
-<pre><code class="r">warn_for_status(r)
-stop_for_status(r)
-</code></pre>
-
-<p>I highly recommend using one of these functions whenever you're using httr inside a function (i.e. not interactively) to make sure you find out about errors as soon as possible.</p>
-
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">warn_for_status</span>(r)
+<span class="kw">stop_for_status</span>(r)</code></pre></div>
+<p>I highly recommend using one of these functions whenever you’re using httr inside a function (i.e. not interactively) to make sure you find out about errors as soon as possible.</p>
+</div>
+<div id="the-body" class="section level3">
 <h3>The body</h3>
-
 <p>There are three ways to access the body of the request, all using <code>content()</code>:</p>
-
 <ul>
 <li><p><code>content(r, "text")</code> accesses the body as a character vector:</p>
-
-<pre><code class="r">r <- GET("http://httpbin.org/get")
-content(r, "text")
-#> No encoding supplied: defaulting to UTF-8.
-#> [1] "{\n  \"args\": {}, \n  \"headers\": {\n    \"Accept\": \"application/json, text/xml, application/xml, */*\", \n    \"Accept-Encoding\": \"gzip, deflate\", \n    \"Host\": \"httpbin.org\", \n    \"User-Agent\": \"libcurl/7.43.0 r-curl/0.9.7 httr/1.2.1\"\n  }, \n  \"origin\": \"68.65.174.224\", \n  \"url\": \"http://httpbin.org/get\"\n} [...]
-</code></pre>
-
-<p>httr will automatically decode content from the server using the encoding 
-supplied in the <code>content-type</code> HTTP header. Unfortunately you can't always 
-trust what the server tells you, so you can override encoding if needed:</p>
-
-<pre><code class="r">content(r, "text", encoding = "ISO-8859-1")
-</code></pre>
-
-<p>If you're having problems figuring out what the correct encoding 
-should be, try <code>stringi::stri_enc_detect(content(r, "raw"))</code>.</p></li>
-<li><p>For non-text requests, you can access the body of the request as a 
-raw vector:</p>
-
-<pre><code class="r">content(r, "raw")
-#>   [1] 7b 0a 20 20 22 61 72 67 73 22 3a 20 7b 7d 2c 20 0a 20 20 22 68 65 61
-#>  [24] 64 65 72 73 22 3a 20 7b 0a 20 20 20 20 22 41 63 63 65 70 74 22 3a 20
-#>  [47] 22 61 70 70 6c 69 63 61 74 69 6f 6e 2f 6a 73 6f 6e 2c 20 74 65 78 74
-#>  [70] 2f 78 6d 6c 2c 20 61 70 70 6c 69 63 61 74 69 6f 6e 2f 78 6d 6c 2c 20
-#>  [93] 2a 2f 2a 22 2c 20 0a 20 20 20 20 22 41 63 63 65 70 74 2d 45 6e 63 6f
-#> [116] 64 69 6e 67 22 3a 20 22 67 7a 69 70 2c 20 64 65 66 6c 61 74 65 22 2c
-#> [139] 20 0a 20 20 20 20 22 48 6f 73 74 22 3a 20 22 68 74 74 70 62 69 6e 2e
-#> [162] 6f 72 67 22 2c 20 0a 20 20 20 20 22 55 73 65 72 2d 41 67 65 6e 74 22
-#> [185] 3a 20 22 6c 69 62 63 75 72 6c 2f 37 2e 34 33 2e 30 20 72 2d 63 75 72
-#> [208] 6c 2f 30 2e 39 2e 37 20 68 74 74 72 2f 31 2e 32 2e 31 22 0a 20 20 7d
-#> [231] 2c 20 0a 20 20 22 6f 72 69 67 69 6e 22 3a 20 22 36 38 2e 36 35 2e 31
-#> [254] 37 34 2e 32 32 34 22 2c 20 0a 20 20 22 75 72 6c 22 3a 20 22 68 74 74
-#> [277] 70 3a 2f 2f 68 74 74 70 62 69 6e 2e 6f 72 67 2f 67 65 74 22 0a 7d 0a
-</code></pre>
-
-<p>This is exactly the sequence of bytes that the web server sent, so this is
-the highest fidelity way of saving files to disk:</p>
-
-<pre><code class="r">bin <- content(r, "raw")
-writeBin(bin, "myfile.txt")
-</code></pre></li>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">r <-<span class="st"> </span><span class="kw">GET</span>(<span class="st">"http://httpbin.org/get"</span>)
+<span class="kw">content</span>(r, <span class="st">"text"</span>)
+<span class="co">#> No encoding supplied: defaulting to UTF-8.</span>
+<span class="co">#> [1] "{\n  \"args\": {}, \n  \"headers\": {\n    \"Accept\": \"application/json, text/xml, application/xml, */*\", \n    \"Accept-Encoding\": \"gzip, deflate\", \n    \"Connection\": \"close\", \n    \"Host\": \"httpbin.org\", \n    \"User-Agent\": \"libcurl/7.54.0 r-curl/2.8.1 httr/1.3.1\"\n  }, \n  \"origin\": \"104.153.224.16 [...]
+<p>httr will automatically decode content from the server using the encoding supplied in the <code>content-type</code> HTTP header. Unfortunately you can’t always trust what the server tells you, so you can override encoding if needed:</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">content</span>(r, <span class="st">"text"</span>, <span class="dt">encoding =</span> <span class="st">"ISO-8859-1"</span>)</code></pre></div>
+<p>If you’re having problems figuring out what the correct encoding should be, try <code>stringi::stri_enc_detect(content(r, "raw"))</code>.</p></li>
+<li><p>For non-text requests, you can access the body of the request as a raw vector:</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">content</span>(r, <span class="st">"raw"</span>)
+<span class="co">#>   [1] 7b 0a 20 20 22 61 72 67 73 22 3a 20 7b 7d 2c 20 0a 20 20 22 68 65 61</span>
+<span class="co">#>  [24] 64 65 72 73 22 3a 20 7b 0a 20 20 20 20 22 41 63 63 65 70 74 22 3a 20</span>
+<span class="co">#>  [47] 22 61 70 70 6c 69 63 61 74 69 6f 6e 2f 6a 73 6f 6e 2c 20 74 65 78 74</span>
+<span class="co">#>  [70] 2f 78 6d 6c 2c 20 61 70 70 6c 69 63 61 74 69 6f 6e 2f 78 6d 6c 2c 20</span>
+<span class="co">#>  [93] 2a 2f 2a 22 2c 20 0a 20 20 20 20 22 41 63 63 65 70 74 2d 45 6e 63 6f</span>
+<span class="co">#> [116] 64 69 6e 67 22 3a 20 22 67 7a 69 70 2c 20 64 65 66 6c 61 74 65 22 2c</span>
+<span class="co">#> [139] 20 0a 20 20 20 20 22 43 6f 6e 6e 65 63 74 69 6f 6e 22 3a 20 22 63 6c</span>
+<span class="co">#> [162] 6f 73 65 22 2c 20 0a 20 20 20 20 22 48 6f 73 74 22 3a 20 22 68 74 74</span>
+<span class="co">#> [185] 70 62 69 6e 2e 6f 72 67 22 2c 20 0a 20 20 20 20 22 55 73 65 72 2d 41</span>
+<span class="co">#> [208] 67 65 6e 74 22 3a 20 22 6c 69 62 63 75 72 6c 2f 37 2e 35 34 2e 30 20</span>
+<span class="co">#> [231] 72 2d 63 75 72 6c 2f 32 2e 38 2e 31 20 68 74 74 72 2f 31 2e 33 2e 31</span>
+<span class="co">#> [254] 22 0a 20 20 7d 2c 20 0a 20 20 22 6f 72 69 67 69 6e 22 3a 20 22 31 30</span>
+<span class="co">#> [277] 34 2e 31 35 33 2e 32 32 34 2e 31 36 36 22 2c 20 0a 20 20 22 75 72 6c</span>
+<span class="co">#> [300] 22 3a 20 22 68 74 74 70 3a 2f 2f 68 74 74 70 62 69 6e 2e 6f 72 67 2f</span>
+<span class="co">#> [323] 67 65 74 22 0a 7d 0a</span></code></pre></div>
+<p>This is exactly the sequence of bytes that the web server sent, so this is the highest fidelity way of saving files to disk:</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">bin <-<span class="st"> </span><span class="kw">content</span>(r, <span class="st">"raw"</span>)
+<span class="kw">writeBin</span>(bin, <span class="st">"myfile.txt"</span>)</code></pre></div></li>
 <li><p>httr provides a number of default parsers for common file types:</p>
-
-<pre><code class="r"># JSON automatically parsed into named list
-str(content(r, "parsed"))
-#> List of 4
-#>  $ args   : Named list()
-#>  $ headers:List of 4
-#>   ..$ Accept         : chr "application/json, text/xml, application/xml, */*"
-#>   ..$ Accept-Encoding: chr "gzip, deflate"
-#>   ..$ Host           : chr "httpbin.org"
-#>   ..$ User-Agent     : chr "libcurl/7.43.0 r-curl/0.9.7 httr/1.2.1"
-#>  $ origin : chr "68.65.174.224"
-#>  $ url    : chr "http://httpbin.org/get"
-</code></pre>
-
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="co"># JSON automatically parsed into named list</span>
+<span class="kw">str</span>(<span class="kw">content</span>(r, <span class="st">"parsed"</span>))
+<span class="co">#> List of 4</span>
+<span class="co">#>  $ args   : Named list()</span>
+<span class="co">#>  $ headers:List of 5</span>
+<span class="co">#>   ..$ Accept         : chr "application/json, text/xml, application/xml, */*"</span>
+<span class="co">#>   ..$ Accept-Encoding: chr "gzip, deflate"</span>
+<span class="co">#>   ..$ Connection     : chr "close"</span>
+<span class="co">#>   ..$ Host           : chr "httpbin.org"</span>
+<span class="co">#>   ..$ User-Agent     : chr "libcurl/7.54.0 r-curl/2.8.1 httr/1.3.1"</span>
+<span class="co">#>  $ origin : chr "104.153.224.166"</span>
+<span class="co">#>  $ url    : chr "http://httpbin.org/get"</span></code></pre></div>
 <p>See <code>?content</code> for a complete list.</p>
-
-<p>These are convenient for interactive usage, but if you're writing an API
-wrapper, it's best to parse the text or raw content yourself and check it
-is as you expect. See the API wrappers vignette for more details.</p></li>
+<p>These are convenient for interactive usage, but if you’re writing an API wrapper, it’s best to parse the text or raw content yourself and check it is as you expect. See the API wrappers vignette for more details.</p></li>
 </ul>
-
+</div>
+<div id="the-headers" class="section level3">
 <h3>The headers</h3>
-
 <p>Access response headers with <code>headers()</code>:</p>
-
-<pre><code class="r">headers(r)
-#> $server
-#> [1] "nginx"
-#> 
-#> $date
-#> [1] "Sun, 03 Jul 2016 17:01:23 GMT"
-#> 
-#> $`content-type`
-#> [1] "application/json"
-#> 
-#> $`content-length`
-#> [1] "299"
-#> 
-#> $connection
-#> [1] "keep-alive"
-#> 
-#> $`access-control-allow-origin`
-#> [1] "*"
-#> 
-#> $`access-control-allow-credentials`
-#> [1] "true"
-#> 
-#> attr(,"class")
-#> [1] "insensitive" "list"
-</code></pre>
-
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">headers</span>(r)
+<span class="co">#> $connection</span>
+<span class="co">#> [1] "keep-alive"</span>
+<span class="co">#> </span>
+<span class="co">#> $server</span>
+<span class="co">#> [1] "meinheld/0.6.1"</span>
+<span class="co">#> </span>
+<span class="co">#> $date</span>
+<span class="co">#> [1] "Fri, 18 Aug 2017 17:47:42 GMT"</span>
+<span class="co">#> </span>
+<span class="co">#> $`content-type`</span>
+<span class="co">#> [1] "application/json"</span>
+<span class="co">#> </span>
+<span class="co">#> $`access-control-allow-origin`</span>
+<span class="co">#> [1] "*"</span>
+<span class="co">#> </span>
+<span class="co">#> $`access-control-allow-credentials`</span>
+<span class="co">#> [1] "true"</span>
+<span class="co">#> </span>
+<span class="co">#> $`x-powered-by`</span>
+<span class="co">#> [1] "Flask"</span>
+<span class="co">#> </span>
+<span class="co">#> $`x-processed-time`</span>
+<span class="co">#> [1] "0.00101518630981"</span>
+<span class="co">#> </span>
+<span class="co">#> $`content-length`</span>
+<span class="co">#> [1] "329"</span>
+<span class="co">#> </span>
+<span class="co">#> $via</span>
+<span class="co">#> [1] "1.1 vegur"</span>
+<span class="co">#> </span>
+<span class="co">#> attr(,"class")</span>
+<span class="co">#> [1] "insensitive" "list"</span></code></pre></div>
 <p>This is basically a named list, but because http headers are case insensitive, indexing this object ignores case:</p>
-
-<pre><code class="r">headers(r)$date
-#> [1] "Sun, 03 Jul 2016 17:01:23 GMT"
-headers(r)$DATE
-#> [1] "Sun, 03 Jul 2016 17:01:23 GMT"
-</code></pre>
-
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">headers</span>(r)<span class="op">$</span>date
+<span class="co">#> [1] "Fri, 18 Aug 2017 17:47:42 GMT"</span>
+<span class="kw">headers</span>(r)<span class="op">$</span>DATE
+<span class="co">#> [1] "Fri, 18 Aug 2017 17:47:42 GMT"</span></code></pre></div>
+</div>
+<div id="cookies" class="section level3">
 <h3>Cookies</h3>
-
 <p>You can access cookies in a similar way:</p>
-
-<pre><code class="r">r <- GET("http://httpbin.org/cookies/set", query = list(a = 1))
-cookies(r)
-#>        domain  flag path secure expiration name value
-#> 1 httpbin.org FALSE    /  FALSE       <NA>    a     1
-</code></pre>
-
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">r <-<span class="st"> </span><span class="kw">GET</span>(<span class="st">"http://httpbin.org/cookies/set"</span>, <span class="dt">query =</span> <span class="kw">list</span>(<span class="dt">a =</span> <span class="dv">1</span>))
+<span class="kw">cookies</span>(r)
+<span class="co">#>        domain  flag path secure expiration name value</span>
+<span class="co">#> 1 httpbin.org FALSE    /  FALSE       <NA>    a     1</span></code></pre></div>
 <p>Cookies are automatically persisted between requests to the same domain:</p>
-
-<pre><code class="r">r <- GET("http://httpbin.org/cookies/set", query = list(b = 1))
-cookies(r)
-#>        domain  flag path secure expiration name value
-#> 1 httpbin.org FALSE    /  FALSE       <NA>    a     1
-#> 2 httpbin.org FALSE    /  FALSE       <NA>    b     1
-</code></pre>
-
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">r <-<span class="st"> </span><span class="kw">GET</span>(<span class="st">"http://httpbin.org/cookies/set"</span>, <span class="dt">query =</span> <span class="kw">list</span>(<span class="dt">b =</span> <span class="dv">1</span>))
+<span class="kw">cookies</span>(r)
+<span class="co">#>        domain  flag path secure expiration name value</span>
+<span class="co">#> 1 httpbin.org FALSE    /  FALSE       <NA>    a     1</span>
+<span class="co">#> 2 httpbin.org FALSE    /  FALSE       <NA>    b     1</span></code></pre></div>
+</div>
+</div>
+<div id="the-request" class="section level2">
 <h2>The request</h2>
-
 <p>Like the response, the request consists of three pieces: a status line, headers and a body. The status line defines the http method (GET, POST, DELETE, etc) and the url. You can send additional data to the server in the url (with the query string), in the headers (including cookies) and in the body of <code>POST()</code>, <code>PUT()</code> and <code>PATCH()</code> requests.</p>
-
+<div id="the-url-query-string" class="section level3">
 <h3>The url query string</h3>
-
 <p>A common way of sending simple key-value pairs to the server is the query string: e.g. <code>http://httpbin.org/get?key=val</code>. httr allows you to provide these arguments as a named list with the <code>query</code> argument. For example, if you wanted to pass <code>key1=value1</code> and <code>key2=value2</code> to <code>http://httpbin.org/get</code> you could do:</p>
-
-<pre><code class="r">r <- GET("http://httpbin.org/get", 
-  query = list(key1 = "value1", key2 = "value2")
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">r <-<span class="st"> </span><span class="kw">GET</span>(<span class="st">"http://httpbin.org/get"</span>, 
+  <span class="dt">query =</span> <span class="kw">list</span>(<span class="dt">key1 =</span> <span class="st">"value1"</span>, <span class="dt">key2 =</span> <span class="st">"value2"</span>)
 )
-content(r)$args
-#> $key1
-#> [1] "value1"
-#> 
-#> $key2
-#> [1] "value2"
-</code></pre>
-
+<span class="kw">content</span>(r)<span class="op">$</span>args
+<span class="co">#> $key1</span>
+<span class="co">#> [1] "value1"</span>
+<span class="co">#> </span>
+<span class="co">#> $key2</span>
+<span class="co">#> [1] "value2"</span></code></pre></div>
 <p>Any <code>NULL</code> elements are automatically dropped from the list, and both keys and values are escaped automatically.</p>
-
-<pre><code class="r">r <- GET("http://httpbin.org/get", 
-  query = list(key1 = "value 1", "key 2" = "value2", key2 = NULL))
-content(r)$args
-#> $`key 2`
-#> [1] "value2"
-#> 
-#> $key1
-#> [1] "value 1"
-</code></pre>
-
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">r <-<span class="st"> </span><span class="kw">GET</span>(<span class="st">"http://httpbin.org/get"</span>, 
+  <span class="dt">query =</span> <span class="kw">list</span>(<span class="dt">key1 =</span> <span class="st">"value 1"</span>, <span class="st">"key 2"</span> =<span class="st"> "value2"</span>, <span class="dt">key2 =</span> <span class="ot">NULL</span>))
+<span class="kw">content</span>(r)<span class="op">$</span>args
+<span class="co">#> $`key 2`</span>
+<span class="co">#> [1] "value2"</span>
+<span class="co">#> </span>
+<span class="co">#> $key1</span>
+<span class="co">#> [1] "value 1"</span></code></pre></div>
+</div>
+<div id="custom-headers" class="section level3">
 <h3>Custom headers</h3>
-
 <p>You can add custom headers to a request with <code>add_headers()</code>:</p>
-
-<pre><code class="r">r <- GET("http://httpbin.org/get", add_headers(Name = "Hadley"))
-str(content(r)$headers)
-#> List of 6
-#>  $ Accept         : chr "application/json, text/xml, application/xml, */*"
-#>  $ Accept-Encoding: chr "gzip, deflate"
-#>  $ Cookie         : chr "a=1; b=1"
-#>  $ Host           : chr "httpbin.org"
-#>  $ Name           : chr "Hadley"
-#>  $ User-Agent     : chr "libcurl/7.43.0 r-curl/0.9.7 httr/1.2.1"
-</code></pre>
-
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">r <-<span class="st"> </span><span class="kw">GET</span>(<span class="st">"http://httpbin.org/get"</span>, <span class="kw">add_headers</span>(<span class="dt">Name =</span> <span class="st">"Hadley"</span>))
+<span class="kw">str</span>(<span class="kw">content</span>(r)<span class="op">$</span>headers)
+<span class="co">#> List of 7</span>
+<span class="co">#>  $ Accept         : chr "application/json, text/xml, application/xml, */*"</span>
+<span class="co">#>  $ Accept-Encoding: chr "gzip, deflate"</span>
+<span class="co">#>  $ Connection     : chr "close"</span>
+<span class="co">#>  $ Cookie         : chr "a=1; b=1"</span>
+<span class="co">#>  $ Host           : chr "httpbin.org"</span>
+<span class="co">#>  $ Name           : chr "Hadley"</span>
+<span class="co">#>  $ User-Agent     : chr "libcurl/7.54.0 r-curl/2.8.1 httr/1.3.1"</span></code></pre></div>
 <p>(Note that <code>content(r)$header</code> retrieves the headers that httpbin received. <code>headers(r)</code> gives the headers that it sent back in its response.)</p>
-
+</div>
+</div>
+<div id="cookies-1" class="section level2">
 <h2>Cookies</h2>
-
-<p>Cookies are simple key-value pairs like the query string, but they persist across multiple requests in a session (because they're sent back and forth every time). To send your own cookies to the server, use <code>set_cookies()</code>:</p>
-
-<pre><code class="r">r <- GET("http://httpbin.org/cookies", set_cookies("MeWant" = "cookies"))
-content(r)$cookies
-#> $MeWant
-#> [1] "cookies"
-#> 
-#> $a
-#> [1] "1"
-#> 
-#> $b
-#> [1] "1"
-</code></pre>
-
+<p>Cookies are simple key-value pairs like the query string, but they persist across multiple requests in a session (because they’re sent back and forth every time). To send your own cookies to the server, use <code>set_cookies()</code>:</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">r <-<span class="st"> </span><span class="kw">GET</span>(<span class="st">"http://httpbin.org/cookies"</span>, <span class="kw">set_cookies</span>(<span class="st">"MeWant"</span> =<span class="st"> "cookies"</span>))
+<span class="kw">content</span>(r)<span class="op">$</span>cookies
+<span class="co">#> $MeWant</span>
+<span class="co">#> [1] "cookies"</span>
+<span class="co">#> </span>
+<span class="co">#> $a</span>
+<span class="co">#> [1] "1"</span>
+<span class="co">#> </span>
+<span class="co">#> $b</span>
+<span class="co">#> [1] "1"</span></code></pre></div>
 <p>Note that this response includes the <code>a</code> and <code>b</code> cookies that were added by the server earlier.</p>
-
+<div id="request-body" class="section level3">
 <h3>Request body</h3>
-
 <p>When <code>POST()</code>ing, you can include data in the <code>body</code> of the request. httr allows you to supply this in a number of different ways. The most common way is a named list:</p>
-
-<pre><code class="r">r <- POST("http://httpbin.org/post", body = list(a = 1, b = 2, c = 3))
-</code></pre>
-
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">r <-<span class="st"> </span><span class="kw">POST</span>(<span class="st">"http://httpbin.org/post"</span>, <span class="dt">body =</span> <span class="kw">list</span>(<span class="dt">a =</span> <span class="dv">1</span>, <span class="dt">b =</span> <span class="dv">2</span>, <span class="dt">c =</span> <span class="dv">3</span>))</code></pre></div>
 <p>You can use the <code>encode</code> argument to determine how this data is sent to the server:</p>
-
-<pre><code class="r">url <- "http://httpbin.org/post"
-body <- list(a = 1, b = 2, c = 3)
-
-# Form encoded
-r <- POST(url, body = body, encode = "form")
-# Multipart encoded
-r <- POST(url, body = body, encode = "multipart")
-# JSON encoded
-r <- POST(url, body = body, encode = "json")
-</code></pre>
-
-<p>To see exactly what's being sent to the server, use <code>verbose()</code>. Unfortunately due to the way that <code>verbose()</code> works, knitr can't capture the messages, so you'll need to run these from an interactive console to see what's going on.</p>
-
-<pre><code class="r">POST(url, body = body, encode = "multipart", verbose()) # the default
-POST(url, body = body, encode = "form", verbose())
-POST(url, body = body, encode = "json", verbose())
-</code></pre>
-
-<p><code>PUT()</code> and <code>PATCH()</code> can also have request bodies, and they identically to <code>POST()</code>.</p>
-
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">url <-<span class="st"> "http://httpbin.org/post"</span>
+body <-<span class="st"> </span><span class="kw">list</span>(<span class="dt">a =</span> <span class="dv">1</span>, <span class="dt">b =</span> <span class="dv">2</span>, <span class="dt">c =</span> <span class="dv">3</span>)
+
+<span class="co"># Form encoded</span>
+r <-<span class="st"> </span><span class="kw">POST</span>(url, <span class="dt">body =</span> body, <span class="dt">encode =</span> <span class="st">"form"</span>)
+<span class="co"># Multipart encoded</span>
+r <-<span class="st"> </span><span class="kw">POST</span>(url, <span class="dt">body =</span> body, <span class="dt">encode =</span> <span class="st">"multipart"</span>)
+<span class="co"># JSON encoded</span>
+r <-<span class="st"> </span><span class="kw">POST</span>(url, <span class="dt">body =</span> body, <span class="dt">encode =</span> <span class="st">"json"</span>)</code></pre></div>
+<p>To see exactly what’s being sent to the server, use <code>verbose()</code>. Unfortunately due to the way that <code>verbose()</code> works, knitr can’t capture the messages, so you’ll need to run these from an interactive console to see what’s going on.</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">POST</span>(url, <span class="dt">body =</span> body, <span class="dt">encode =</span> <span class="st">"multipart"</span>, <span class="kw">verbose</span>()) <span class="co"># the default</span>
+<span class="kw">POST</span>(url, <span class="dt">body =</span> body, <span class="dt">encode =</span> <span class="st">"form"</span>, <span class="kw">verbose</span>())
+<span class="kw">POST</span>(url, <span class="dt">body =</span> body, <span class="dt">encode =</span> <span class="st">"json"</span>, <span class="kw">verbose</span>())</code></pre></div>
+<p><code>PUT()</code> and <code>PATCH()</code> can also have request bodies, and they take arguments identically to <code>POST()</code>.</p>
 <p>You can also send files off disk:</p>
-
-<pre><code class="r">POST(url, body = upload_file("mypath.txt"))
-POST(url, body = list(x = upload_file("mypath.txt")))
-</code></pre>
-
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">POST</span>(url, <span class="dt">body =</span> <span class="kw">upload_file</span>(<span class="st">"mypath.txt"</span>))
+<span class="kw">POST</span>(url, <span class="dt">body =</span> <span class="kw">list</span>(<span class="dt">x =</span> <span class="kw">upload_file</span>(<span class="st">"mypath.txt"</span>)))</code></pre></div>
 <p>(<code>upload_file()</code> will guess the mime-type from the extension - using the <code>type</code> argument to override/supply yourself.)</p>
-
 <p>These uploads stream the data to the server: the data will be loaded in R in chunks then sent to the remote server. This means that you can upload files that are larger than memory.</p>
-
 <p>See <code>POST()</code> for more details on the other types of thing that you can send: no body, empty body, and character and raw vectors.</p>
-
+<div id="built-with" class="section level5">
 <h5>Built with</h5>
-
-<pre><code class="r">sessionInfo()
-#> R version 3.3.0 (2016-05-03)
-#> Platform: x86_64-apple-darwin13.4.0 (64-bit)
-#> Running under: OS X 10.11.5 (El Capitan)
-#> 
-#> locale:
-#> [1] C/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
-#> 
-#> attached base packages:
-#> [1] stats     graphics  grDevices utils     datasets  methods   base     
-#> 
-#> other attached packages:
-#> [1] httr_1.2.1
-#> 
-#> loaded via a namespace (and not attached):
-#>  [1] R6_2.1.2           magrittr_1.5       formatR_1.4       
-#>  [4] htmltools_0.3.5    tools_3.3.0        curl_0.9.7        
-#>  [7] yaml_2.1.13        Rcpp_0.12.5        rmarkdown_0.9.6.14
-#> [10] stringi_1.1.1      knitr_1.13         jsonlite_0.9.21   
-#> [13] digest_0.6.9       stringr_1.0.0.9000 evaluate_0.9
-</code></pre>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">sessionInfo</span>()
+<span class="co">#> R version 3.4.1 (2017-06-30)</span>
+<span class="co">#> Platform: x86_64-apple-darwin15.6.0 (64-bit)</span>
+<span class="co">#> Running under: macOS Sierra 10.12.6</span>
+<span class="co">#> </span>
+<span class="co">#> Matrix products: default</span>
+<span class="co">#> BLAS: /Library/Frameworks/R.framework/Versions/3.4/Resources/lib/libRblas.0.dylib</span>
+<span class="co">#> LAPACK: /Library/Frameworks/R.framework/Versions/3.4/Resources/lib/libRlapack.dylib</span>
+<span class="co">#> </span>
+<span class="co">#> locale:</span>
+<span class="co">#> [1] C/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8</span>
+<span class="co">#> </span>
+<span class="co">#> attached base packages:</span>
+<span class="co">#> [1] stats     graphics  grDevices utils     datasets  methods   base     </span>
+<span class="co">#> </span>
+<span class="co">#> other attached packages:</span>
+<span class="co">#> [1] httr_1.3.1</span>
+<span class="co">#> </span>
+<span class="co">#> loaded via a namespace (and not attached):</span>
+<span class="co">#>  [1] Rcpp_0.12.12    digest_0.6.12   rprojroot_1.2   R6_2.2.2       </span>
+<span class="co">#>  [5] jsonlite_1.5    backports_1.1.0 magrittr_1.5    evaluate_0.10.1</span>
+<span class="co">#>  [9] stringi_1.1.5   curl_2.8.1      rmarkdown_1.6   tools_3.4.1    </span>
+<span class="co">#> [13] stringr_1.2.0   yaml_2.1.14     compiler_3.4.1  htmltools_0.3.6</span>
+<span class="co">#> [17] knitr_1.16</span></code></pre></div>
+</div>
+</div>
+</div>
+</div>
+
+
+
+<!-- dynamically load mathjax for compatibility with self-contained -->
+<script>
+  (function () {
+    var script = document.createElement("script");
+    script.type = "text/javascript";
+    script.src  = "https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
+    document.getElementsByTagName("head")[0].appendChild(script);
+  })();
+</script>
 
 </body>
-
 </html>
diff --git a/inst/doc/secrets.R b/inst/doc/secrets.R
new file mode 100644
index 0000000..ff7b0d4
--- /dev/null
+++ b/inst/doc/secrets.R
@@ -0,0 +1,90 @@
+## ---- echo = FALSE-------------------------------------------------------
+library(httr)
+knitr::opts_chunk$set(comment = "#>", collapse = TRUE)
+
+## ---- eval = FALSE-------------------------------------------------------
+#  Sys.chmod("secret.file", mode = "0400")
+
+## ------------------------------------------------------------------------
+my_secrets <- function() {
+  path <- "~/secrets/secret.json"
+  if (!file.exists(path)) {
+    stop("Can't find secret file: '", path, "'")
+  }
+  
+  jsonlite::read_json(path)
+}
+
+## ---- eval = FALSE-------------------------------------------------------
+#  password <- rstudioapi::askForPassword()
+
+## ---- eval = FALSE-------------------------------------------------------
+#  file.edit("~/.Renviron")
+
+## ---- include = FALSE----------------------------------------------------
+Sys.setenv("VAR1" = "value1")
+
+## ------------------------------------------------------------------------
+Sys.getenv("VAR1")
+
+## ---- eval = FALSE-------------------------------------------------------
+#  keyring::key_set("MY_SECRET")
+#  keyring::key_get("MY_SECRET")
+
+## ---- eval = FALSE-------------------------------------------------------
+#  keyring::keyring_create("httr")
+#  keyring::key_set("MY_SECRET", keyring = "httr")
+
+## ---- eval = FALSE-------------------------------------------------------
+#  keyring::keyring_lock("httr")
+
+## ---- eval = FALSE-------------------------------------------------------
+#  library(openssl)
+#  library(jsonlite)
+#  library(curl)
+#  
+#  encrypt <- function(secret, username) {
+#    url <- paste("https://api.github.com/users", username, "keys", sep = "/")
+#  
+#    resp <- httr::GET(url)
+#    httr::stop_for_status(resp)
+#    pubkey <- httr::content(resp)[[1]]$key
+#  
+#    opubkey <- openssl::read_pubkey(pubkey)
+#    cipher <- openssl::rsa_encrypt(charToRaw(secret), opubkey)
+#    jsonlite::base64_enc(cipher)
+#  }
+#  
+#  cipher <- encrypt("<username>\n<password>", "hadley")
+#  cat(cipher)
+
+## ---- eval = FALSE-------------------------------------------------------
+#  decrypt <- function(cipher, key = openssl::my_key()) {
+#    cipherraw <- jsonlite::base64_dec(cipher)
+#    rawToChar(openssl::rsa_decrypt(cipherraw, key = key))
+#  }
+#  
+#  decrypt(cipher)
+#  #> username
+#  #> password
+
+## ------------------------------------------------------------------------
+my_secret <- function() {
+  val <- Sys.getenv("SECRET")
+  if (identical(val, "")) {
+    stop("`SECRET` env var has not been set")
+  }
+  val
+}
+
+## ------------------------------------------------------------------------
+NOT_CRAN <- identical(tolower(Sys.getenv("NOT_CRAN")), "true")
+knitr::opts_chunk$set(purl = NOT_CRAN)
+
+## ------------------------------------------------------------------------
+skip_if_no_auth <- function() {
+  if (identical(Sys.getenv("MY_SECRET"), "")) {
+    skip("No authentication available")
+  }
+}
+
diff --git a/inst/doc/secrets.Rmd b/inst/doc/secrets.Rmd
new file mode 100644
index 0000000..e4b7362
--- /dev/null
+++ b/inst/doc/secrets.Rmd
@@ -0,0 +1,279 @@
+---
+title: "Managing secrets"
+author: "Hadley Wickham"
+output: rmarkdown::html_vignette
+vignette: >
+  %\VignetteIndexEntry{Managing secrets}
+  %\VignetteEngine{knitr::rmarkdown}
+  %\VignetteEncoding{UTF-8}
+---
+
+```{r, echo = FALSE}
+library(httr)
+knitr::opts_chunk$set(comment = "#>", collapse = TRUE)
+```
+
+## Introduction
+
+This document gives you the basics on securely managing secrets. Most of this document is not directly related to httr, but it's common to have some secrets to manage whenever you are using an API.
+
+What is a secret? Some secrets are short alphanumeric sequences:
+
+* Passwords are clearly secrets, e.g. the second argument to `authenticate()`.
+  Passwords are particularly important because people (ill-advisedly) often 
+  use the same password in multiple places.
+
+* Personal access tokens (e.g. [github][github-token]) should be kept secret:
+  they are basically equivalent to a user name password combination, but 
+  are slightly safer because you can have multiple tokens for different 
+  purposes and it's easy to invalidate one token without affecting the others.
+
+Surprisingly, the "client secret" in an `oauth_app()` is __not__ a secret. It's not equivalent to a password, and if you are writing an API wrapper package, it should be included in the package. (If you don't believe me, here are [google's comments on the topic][google-secret].)
+
+Other secrets are files:
+
+* The JSON web token (jwt) used for server-to-server OAuth 
+  (e.g. [google][google-server]) is a secret because it's equivalent to a
+  personal access token.
+
+* The `.httr-oauth` file is a secret because it stores OAuth access tokens.
+
+The goal of this vignette is to give you the tools to manage these secrets in a secure way. We'll start with best practices for managing secrets locally, then talk about sharing secrets with selected others (including travis), and finish with the challenges that CRAN presents.
+
+Here, I assume that the main threat is accidentally sharing your secrets when you don't want to. Protecting against a committed attacker is much harder. And if someone has already hacked your computer to the point where they can run code, there's almost nothing you can do. If you're concerned about those scenarios, you'll need to take a more comprehensive approach that's outside the scope of this document.
+
+## Locally
+
+Working with secret files locally is straightforward because it's ok to store them in your project directory as long as you take three precautions:
+
+* Ensure the file is only readable by you, not by any other user on the 
+  system. You can use the R function `Sys.chmod()` to do so:
+
+    ```{r, eval = FALSE}
+    Sys.chmod("secret.file", mode = "0400")
+    ```
+    
+    It's good practice to verify this setting by examining the file metadata 
+    with your local filesystem GUI tools or commands.
+
+* If you use git: make sure the files are listed in `.gitignore` so they don't
+  accidentally get included in a public repository.
+  
+* If you're making a package: make sure they are listed in `.Rbuildignore` 
+  so they don't accidentally get included in a public R package.
+  
+httr proactively takes all of these steps for you whenever it creates a `.httr-oauth` file.
+
+The main remaining risk is that you might zip up the entire directory and share it. If you're worried about this scenario, store your secret files outside of the project directory. If you do this, make sure to provide a helper function to locate the file and provide an informative message if it's missing.
+
+```{r}
+my_secrets <- function() {
+  path <- "~/secrets/secret.json"
+  if (!file.exists(path)) {
+    stop("Can't find secret file: '", path, "'")
+  }
+  
+  jsonlite::read_json(path)
+}
+```
+
+Storing short secrets is harder because it's tempting to record them as a variable in your R script. This is a bad idea, because you end up with a file that contains a mix of secret and public code. Instead, you have three options:
+
+* Ask for the secret each time.
+* Store in an environment variable.
+* Use the keyring package. 
+
+Regardless of how you store them, to use your secrets you will still need to read them into R variables. Be careful not to expose them by printing them or saving them to a file.
+
+### Ask each time
+
+For scripts that you only use every now and then, a simple solution is to simply ask for the password each time the script is run. If you use RStudio an easy and secure way to request a password is with the rstudioapi package:
+
+```{r, eval = FALSE}
+password <- rstudioapi::askForPassword()
+```
+
+If you don't use RStudio, use a more general solution like the [getPass](https://github.com/wrathematics/getPass) package.
+
+You should __never__ type your password into the R console: this will typically be stored in the `.Rhistory` file, and it's easy to accidentally share without realising it.
+
+### Environment variables
+
+Asking each time is a hassle, so you might want to store the secret across sessions. One easy way to do that is with environment variables. Environment variables, or __envvars__ for short, are a cross platform way of passing information to processes.
+
+For passing envvars to R, you can list name-value pairs in a file called `.Renviron` in your home directory. The easiest way to edit it is to run:
+
+```{r, eval = FALSE}
+file.edit("~/.Renviron")
+```
+
+The file looks something like
+
+```
+VAR1 = value1
+VAR2 = value2
+```
+
+```{r, include = FALSE}
+Sys.setenv("VAR1" = "value1")
+```
+
+And you can access the values in R using `Sys.getenv()`:
+
+```{r}
+Sys.getenv("VAR1")
+```
+
+Note that `.Renviron` is only processed on startup, so you'll need to restart R to see changes.
+
+These environment variables will be available in every running R process, and can easily be read by any other program on your computer to access that file directly. For more security, use the keyring package. 
+
+### Keyring
+
+The [keyring](https://github.com/r-lib/keyring) package provides a way to store (and retrieve) data in your OS's secure secret store. Keyring has a simple API:
+
+```{r, eval = FALSE}
+keyring::key_set("MY_SECRET")
+keyring::key_get("MY_SECRET")
+```
+
+By default, keyring will use the system keyring. This is unlocked by default when you log in, which means while the password is stored securely pretty much any process can access it.
+
+If you want to be even more secure, you can create custom keyring and keep it locked. That will require you to enter a password every time you want to access your secret. 
+
+```{r, eval = FALSE}
+keyring::keyring_create("httr")
+keyring::key_set("MY_SECRET", keyring = "httr")
+```
+
+Note that accessing the key always unlocks the keyring, so if you're being really careful, make sure to lock it again afterwards.
+
+```{r, eval = FALSE}
+keyring::keyring_lock("httr")
+```
+
+You might wonder if we've actually achieved anything here because we still need to enter a password! However, that one password lets you access every secret, and you can control how often you need to re-enter it by manually locking and unlocking the keyring.
+
+## Sharing with others
+
+By and large, managing secrets on your own computer is straightforward. The challenge comes when you need to share them with selected others:
+
+* You may need to share a secret with me so that I can run your reprex
+  and figure out what is wrong with httr.
+  
+* You might want to share a secret amongst a group of developers all working
+  on the same GitHub project.
+  
+* You might want to automatically run authenticated tests on travis.
+
+To make this work, all the techniques in this section rely on __public key cryptography__. This is a type of asymmetric encryption where you use a public key to produce content that can only be decrypted by the holder of the matching private key.
+
+### Reprexes
+
+The most common place you might need to share a secret is to generate a reprex. First, do everything you can do eliminate the need to share a secret:
+
+* If it is an http problem, make sure to run all requests with `verbose()`.
+* If you get an R error, make sure to include `traceback()`.
+
+If you're lucky, that will be sufficient information to fix the problem.
+
+Otherwise, you'll need to encrypt the secret so you can share it with me. The easiest way to do so is with the following snippet:
+
+```{r, eval = FALSE}
+library(openssl)
+library(jsonlite)
+library(curl)
+
+encrypt <- function(secret, username) {
+  url <- paste("https://api.github.com/users", username, "keys", sep = "/")
+
+  resp <- httr::GET(url)
+  httr::stop_for_status(resp)
+  pubkey <- httr::content(resp)[[1]]$key
+
+  opubkey <- openssl::read_pubkey(pubkey)
+  cipher <- openssl::rsa_encrypt(charToRaw(secret), opubkey)
+  jsonlite::base64_enc(cipher)
+}
+  
+cipher <- encrypt("<username>\n<password>", "hadley")
+cat(cipher)
+```
+
+Then I can run the following code on my computer to access it:
+
+```{r, eval = FALSE}
+decrypt <- function(cipher, key = openssl::my_key()) {
+  cipherraw <- jsonlite::base64_dec(cipher)
+  rawToChar(openssl::rsa_decrypt(cipherraw, key = key))
+}
+
+decrypt(cipher)
+#> username
+#> password
+```
+
+Change your password before and after you share it with me or anyone else. 
+
+### GitHub
+
+If you want to share secrets with a group of other people on GitHub, use the [secret](https://github.com/gaborcsardi/secret) or [cyphr](https://github.com/richfitz/cyphr) packages.
+
+### Travis
+
+The easiest way to handle short secrets is to use environment variables. You'll set in your `.Renviron` locally and in the settings pane on travis. That way you can use `Sys.getenv()` to access in both places. It's also possible to set encrypted env vars in your `.travis.yml`: see [the documentation][travis-envvar] for details.
+
+Regardless of how you set it, make sure you have a helper to retrieve the value. A good error message will save you a lot of time when debugging problems!
+
+```{r}
+my_secret <- function() {
+  val <- Sys.getenv("SECRET")
+  if (identical(val, "")) {
+    stop("`SECRET` env var has not been set")
+  }
+  val
+}
+```
+
+Note that encrypted data is not available in pull requests in forks. Typically you'll need to check PRs locally once you've confirmed that the code isn't actively malicious.
+
+To share secret files on travis, see <https://docs.travis-ci.com/user/encrypting-files/>. Basically you will encrypt the file locally and check it in to git. Then you'll add a decryption step to your `.travis.yml` which makes it decrypts it for each run. See [bigquery][travis-bigrquery] for an example.
+
+Be careful to not accidentally expose the secret on travis. An easy way to accidentally expose the secret is to print it out so that it's captured in the log. Don't do that!
+
+## CRAN 
+
+There is no way to securely share information with arbitrary R users, including CRAN. That means that if you're developing a package, you need to make sure that `R CMD check` passes cleanly even when authentication is not available. This tends to primarily affect the documentation, vignettes, and tests.
+
+### Documentation
+
+Like any R package, an API client needs clear and complete documentation of all functions. Examples are particularly useful but may need to be wrapped in `\donttest{}` to avoid challenges of authentication, rate limiting, lack of network access, or occasional API server down time.
+
+### Vignettes
+
+Vignettes pose additional challenges when an API requires authentication, because you don't want to bundle your own credentials with the package! However, you can take advantage of the fact that the vignette is built locally, and only checked by CRAN. In a setup chunk, do:
+
+```{r}
+NOT_CRAN <- identical(tolower(Sys.getenv("NOT_CRAN")), "true")
+knitr::opts_chunk$set(purl = NOT_CRAN)
+```
+
+And then use `eval = NOT_CRAN` in any chunk that requires access to a secret.
+
+### Testing
+
+Use `testthat::skip()` to automatically skip tests that require authentication. I typically will wrap this into a little helper function that I call at the start of every test requiring auth.
+
+```{r}
+skip_if_no_auth <- function() {
+  if (identical(Sys.getenv("MY_SECRET"), "")) {
+    skip("No authentication available")
+  }
+}
+```
+
+[google-secret]: https://developers.google.com/identity/protocols/OAuth2#installed
+[google-server]: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
+[github-token]: https://github.com/blog/1509-personal-api-tokens
+[travis-envvar]: https://docs.travis-ci.com/user/environment-variables/
+[travis-bigrquery]: https://github.com/rstats-db/bigrquery/blob/master/.travis.yml
diff --git a/inst/doc/secrets.html b/inst/doc/secrets.html
new file mode 100644
index 0000000..fb4bdfd
--- /dev/null
+++ b/inst/doc/secrets.html
@@ -0,0 +1,259 @@
+<!DOCTYPE html>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+
+<meta charset="utf-8" />
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="generator" content="pandoc" />
+
+<meta name="viewport" content="width=device-width, initial-scale=1">
+
+<meta name="author" content="Hadley Wickham" />
+
+
+<title>Managing secrets</title>
+
+
+
+<style type="text/css">code{white-space: pre;}</style>
+<style type="text/css">
+div.sourceCode { overflow-x: auto; }
+table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode {
+  margin: 0; padding: 0; vertical-align: baseline; border: none; }
+table.sourceCode { width: 100%; line-height: 100%; }
+td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; }
+td.sourceCode { padding-left: 5px; }
+code > span.kw { color: #007020; font-weight: bold; } /* Keyword */
+code > span.dt { color: #902000; } /* DataType */
+code > span.dv { color: #40a070; } /* DecVal */
+code > span.bn { color: #40a070; } /* BaseN */
+code > span.fl { color: #40a070; } /* Float */
+code > span.ch { color: #4070a0; } /* Char */
+code > span.st { color: #4070a0; } /* String */
+code > span.co { color: #60a0b0; font-style: italic; } /* Comment */
+code > span.ot { color: #007020; } /* Other */
+code > span.al { color: #ff0000; font-weight: bold; } /* Alert */
+code > span.fu { color: #06287e; } /* Function */
+code > span.er { color: #ff0000; font-weight: bold; } /* Error */
+code > span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
+code > span.cn { color: #880000; } /* Constant */
+code > span.sc { color: #4070a0; } /* SpecialChar */
+code > span.vs { color: #4070a0; } /* VerbatimString */
+code > span.ss { color: #bb6688; } /* SpecialString */
+code > span.im { } /* Import */
+code > span.va { color: #19177c; } /* Variable */
+code > span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
+code > span.op { color: #666666; } /* Operator */
+code > span.bu { } /* BuiltIn */
+code > span.ex { } /* Extension */
+code > span.pp { color: #bc7a00; } /* Preprocessor */
+code > span.at { color: #7d9029; } /* Attribute */
+code > span.do { color: #ba2121; font-style: italic; } /* Documentation */
+code > span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
+code > span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
+code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
+</style>
+
+
+
+<link href="data:text/css;charset=utf-8,body%20%7B%0Abackground%2Dcolor%3A%20%23fff%3B%0Amargin%3A%201em%20auto%3B%0Amax%2Dwidth%3A%20700px%3B%0Aoverflow%3A%20visible%3B%0Apadding%2Dleft%3A%202em%3B%0Apadding%2Dright%3A%202em%3B%0Afont%2Dfamily%3A%20%22Open%20Sans%22%2C%20%22Helvetica%20Neue%22%2C%20Helvetica%2C%20Arial%2C%20sans%2Dserif%3B%0Afont%2Dsize%3A%2014px%3B%0Aline%2Dheight%3A%201%2E35%3B%0A%7D%0A%23header%20%7B%0Atext%2Dalign%3A%20center%3B%0A%7D%0A%23TOC%20%7B%0Aclear%3A%20bot [...]
+
+</head>
+
+<body>
+
+
+
+
+<h1 class="title toc-ignore">Managing secrets</h1>
+<h4 class="author"><em>Hadley Wickham</em></h4>
+
+
+
+<div id="introduction" class="section level2">
+<h2>Introduction</h2>
+<p>This document gives you the basics on securely managing secrets. Most of this document is not directly related to httr, but it’s common to have some secrets to manage whenever you are using an API.</p>
+<p>What is a secret? Some secrets are short alphanumeric sequences:</p>
+<ul>
+<li><p>Passwords are clearly secrets, e.g. the second argument to <code>authenticate()</code>. Passwords are particularly important because people (ill-advisedly) often use the same password in multiple places.</p></li>
+<li><p>Personal access tokens (e.g. <a href="https://github.com/blog/1509-personal-api-tokens">github</a>) should be kept secret: they are basically equivalent to a user name password combination, but are slightly safer because you can have multiple tokens for different purposes and it’s easy to invalidate one token without affecting the others.</p></li>
+</ul>
+<p>Surprisingly, the “client secret” in an <code>oauth_app()</code> is <strong>not</strong> a secret. It’s not equivalent to a password, and if you are writing an API wrapper package, it should be included in the package. (If you don’t believe me, here are <a href="https://developers.google.com/identity/protocols/OAuth2#installed">google’s comments on the topic</a>.)</p>
+<p>Other secrets are files:</p>
+<ul>
+<li><p>The JSON web token (jwt) used for server-to-server OAuth (e.g. <a href="https://developers.google.com/identity/protocols/OAuth2ServiceAccount">google</a>) is a secret because it’s equivalent to a personal access token.</p></li>
+<li><p>The <code>.httr-oauth</code> file is a secret because it stores OAuth access tokens.</p></li>
+</ul>
+<p>The goal of this vignette is to give you the tools to manage these secrets in a secure way. We’ll start with best practices for managing secrets locally, then talk about sharing secrets with selected others (including travis), and finish with the challenges that CRAN presents.</p>
+<p>Here, I assume that the main threat is accidentally sharing your secrets when you don’t want to. Protecting against a committed attacker is much harder. And if someone has already hacked your computer to the point where they can run code, there’s almost nothing you can do. If you’re concerned about those scenarios, you’ll need to take a more comprehensive approach that’s outside the scope of this document.</p>
+</div>
+<div id="locally" class="section level2">
+<h2>Locally</h2>
+<p>Working with secret files locally is straightforward because it’s ok to store them in your project directory as long as you take three precautions:</p>
+<ul>
+<li><p>Ensure the file is only readable by you, not by any other user on the system. You can use the R function <code>Sys.chmod()</code> to do so:</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">Sys.chmod</span>(<span class="st">"secret.file"</span>, <span class="dt">mode =</span> <span class="st">"0400"</span>)</code></pre></div>
+<p>It’s good practice to verify this setting by examining the file metadata with your local filesystem GUI tools or commands.</p></li>
+<li><p>If you use git: make sure the files are listed in <code>.gitignore</code> so they don’t accidentally get included in a public repository.</p></li>
+<li><p>If you’re making a package: make sure they are listed in <code>.Rbuildignore</code> so they don’t accidentally get included in a public R package.</p></li>
+</ul>
+<p>httr proactively takes all of these steps for you whenever it creates a <code>.httr-oauth</code> file.</p>
+<p>The main remaining risk is that you might zip up the entire directory and share it. If you’re worried about this scenario, store your secret files outside of the project directory. If you do this, make sure to provide a helper function to locate the file and provide an informative message if it’s missing.</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">my_secrets <-<span class="st"> </span><span class="cf">function</span>() {
+  path <-<span class="st"> "~/secrets/secret.json"</span>
+  <span class="cf">if</span> (<span class="op">!</span><span class="kw">file.exists</span>(path)) {
+    <span class="kw">stop</span>(<span class="st">"Can't find secret file: '"</span>, path, <span class="st">"'"</span>)
+  }
+  
+  jsonlite<span class="op">::</span><span class="kw">read_json</span>(path)
+}</code></pre></div>
+<p>Storing short secrets is harder because it’s tempting to record them as a variable in your R script. This is a bad idea, because you end up with a file that contains a mix of secret and public code. Instead, you have three options:</p>
+<ul>
+<li>Ask for the secret each time.</li>
+<li>Store in an environment variable.</li>
+<li>Use the keyring package.</li>
+</ul>
+<p>Regardless of how you store them, to use your secrets you will still need to read them into R variables. Be careful not to expose them by printing them or saving them to a file.</p>
+<div id="ask-each-time" class="section level3">
+<h3>Ask each time</h3>
+<p>For scripts that you only use every now and then, a simple solution is to simply ask for the password each time the script is run. If you use RStudio an easy and secure way to request a password is with the rstudioapi package:</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">password <-<span class="st"> </span>rstudioapi<span class="op">::</span><span class="kw">askForPassword</span>()</code></pre></div>
+<p>If you don’t use RStudio, use a more general solution like the <a href="https://github.com/wrathematics/getPass">getPass</a> package.</p>
+<p>You should <strong>never</strong> type your password into the R console: this will typically be stored in the <code>.Rhistory</code> file, and it’s easy to accidentally share without realising it.</p>
+</div>
+<div id="environment-variables" class="section level3">
+<h3>Environment variables</h3>
+<p>Asking each time is a hassle, so you might want to store the secret across sessions. One easy way to do that is with environment variables. Environment variables, or <strong>envvars</strong> for short, are a cross platform way of passing information to processes.</p>
+<p>For passing envvars to R, you can list name-value pairs in a file called <code>.Renviron</code> in your home directory. The easiest way to edit it is to run:</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">file.edit</span>(<span class="st">"~/.Renviron"</span>)</code></pre></div>
+<p>The file looks something like</p>
+<pre><code>VAR1 = value1
+VAR2 = value2</code></pre>
+<p>And you can access the values in R using <code>Sys.getenv()</code>:</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">Sys.getenv</span>(<span class="st">"VAR1"</span>)
+<span class="co">#> [1] "value1"</span></code></pre></div>
+<p>Note that <code>.Renviron</code> is only processed on startup, so you’ll need to restart R to see changes.</p>
+<p>These environment variables will be available in every running R process, and can easily be read by any other program on your computer to access that file directly. For more security, use the keyring package.</p>
+</div>
+<div id="keyring" class="section level3">
+<h3>Keyring</h3>
+<p>The <a href="https://github.com/r-lib/keyring">keyring</a> package provides a way to store (and retrieve) data in your OS’s secure secret store. Keyring has a simple API:</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">keyring<span class="op">::</span><span class="kw">key_set</span>(<span class="st">"MY_SECRET"</span>)
+keyring<span class="op">::</span><span class="kw">key_get</span>(<span class="st">"MY_SECRET"</span>)</code></pre></div>
+<p>By default, keyring will use the system keyring. This is unlocked by default when you log in, which means while the password is stored securely pretty much any process can access it.</p>
+<p>If you want to be even more secure, you can create custom keyring and keep it locked. That will require you to enter a password every time you want to access your secret.</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">keyring<span class="op">::</span><span class="kw">keyring_create</span>(<span class="st">"httr"</span>)
+keyring<span class="op">::</span><span class="kw">key_set</span>(<span class="st">"MY_SECRET"</span>, <span class="dt">keyring =</span> <span class="st">"httr"</span>)</code></pre></div>
+<p>Note that accessing the key always unlocks the keyring, so if you’re being really careful, make sure to lock it again afterwards.</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">keyring<span class="op">::</span><span class="kw">keyring_lock</span>(<span class="st">"httr"</span>)</code></pre></div>
+<p>You might wonder if we’ve actually achieved anything here because we still need to enter a password! However, that one password lets you access every secret, and you can control how often you need to re-enter it by manually locking and unlocking the keyring.</p>
+</div>
+</div>
+<div id="sharing-with-others" class="section level2">
+<h2>Sharing with others</h2>
+<p>By and large, managing secrets on your own computer is straightforward. The challenge comes when you need to share them with selected others:</p>
+<ul>
+<li><p>You may need to share a secret with me so that I can run your reprex and figure out what is wrong with httr.</p></li>
+<li><p>You might want to share a secret amongst a group of developers all working on the same GitHub project.</p></li>
+<li><p>You might want to automatically run authenticated tests on travis.</p></li>
+</ul>
+<p>To make this work, all the techniques in this section rely on <strong>public key cryptography</strong>. This is a type of asymmetric encryption where you use a public key to produce content that can only be decrypted by the holder of the matching private key.</p>
+<div id="reprexes" class="section level3">
+<h3>Reprexes</h3>
+<p>The most common place you might need to share a secret is to generate a reprex. First, do everything you can do eliminate the need to share a secret:</p>
+<ul>
+<li>If it is an http problem, make sure to run all requests with <code>verbose()</code>.</li>
+<li>If you get an R error, make sure to include <code>traceback()</code>.</li>
+</ul>
+<p>If you’re lucky, that will be sufficient information to fix the problem.</p>
+<p>Otherwise, you’ll need to encrypt the secret so you can share it with me. The easiest way to do so is with the following snippet:</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">library</span>(openssl)
+<span class="kw">library</span>(jsonlite)
+<span class="kw">library</span>(curl)
+
+encrypt <-<span class="st"> </span><span class="cf">function</span>(secret, username) {
+  url <-<span class="st"> </span><span class="kw">paste</span>(<span class="st">"https://api.github.com/users"</span>, username, <span class="st">"keys"</span>, <span class="dt">sep =</span> <span class="st">"/"</span>)
+
+  resp <-<span class="st"> </span>httr<span class="op">::</span><span class="kw">GET</span>(url)
+  httr<span class="op">::</span><span class="kw">stop_for_status</span>(resp)
+  pubkey <-<span class="st"> </span>httr<span class="op">::</span><span class="kw">content</span>(resp)[[<span class="dv">1</span>]]<span class="op">$</span>key
+
+  opubkey <-<span class="st"> </span>openssl<span class="op">::</span><span class="kw">read_pubkey</span>(pubkey)
+  cipher <-<span class="st"> </span>openssl<span class="op">::</span><span class="kw">rsa_encrypt</span>(<span class="kw">charToRaw</span>(secret), opubkey)
+  jsonlite<span class="op">::</span><span class="kw">base64_enc</span>(cipher)
+}
+  
+cipher <-<span class="st"> </span><span class="kw">encrypt</span>(<span class="st">"<username></span><span class="ch">\n</span><span class="st"><password>"</span>, <span class="st">"hadley"</span>)
+<span class="kw">cat</span>(cipher)</code></pre></div>
+<p>Then I can run the following code on my computer to access it:</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">decrypt <-<span class="st"> </span><span class="cf">function</span>(cipher, <span class="dt">key =</span> openssl<span class="op">::</span><span class="kw">my_key</span>()) {
+  cipherraw <-<span class="st"> </span>jsonlite<span class="op">::</span><span class="kw">base64_dec</span>(cipher)
+  <span class="kw">rawToChar</span>(openssl<span class="op">::</span><span class="kw">rsa_decrypt</span>(cipherraw, <span class="dt">key =</span> key))
+}
+
+<span class="kw">decrypt</span>(cipher)
+<span class="co">#> username</span>
+<span class="co">#> password</span></code></pre></div>
+<p>Change your password before and after you share it with me or anyone else.</p>
+</div>
+<div id="github" class="section level3">
+<h3>GitHub</h3>
+<p>If you want to share secrets with a group of other people on GitHub, use the <a href="https://github.com/gaborcsardi/secret">secret</a> or <a href="https://github.com/richfitz/cyphr">cyphr</a> packages.</p>
+</div>
+<div id="travis" class="section level3">
+<h3>Travis</h3>
+<p>The easiest way to handle short secrets is to use environment variables. You’ll set in your <code>.Renviron</code> locally and in the settings pane on travis. That way you can use <code>Sys.getenv()</code> to access in both places. It’s also possible to set encrypted env vars in your <code>.travis.yml</code>: see <a href="https://docs.travis-ci.com/user/environment-variables/">the documentation</a> for details.</p>
+<p>Regardless of how you set it, make sure you have a helper to retrieve the value. A good error message will save you a lot of time when debugging problems!</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">my_secret <-<span class="st"> </span><span class="cf">function</span>() {
+  val <-<span class="st"> </span><span class="kw">Sys.getenv</span>(<span class="st">"SECRET"</span>)
+  <span class="cf">if</span> (<span class="kw">identical</span>(val, <span class="st">""</span>)) {
+    <span class="kw">stop</span>(<span class="st">"`SECRET` env var has not been set"</span>)
+  }
+  val
+}</code></pre></div>
+<p>Note that encrypted data is not available in pull requests in forks. Typically you’ll need to check PRs locally once you’ve confirmed that the code isn’t actively malicious.</p>
+<p>To share secret files on travis, see <a href="https://docs.travis-ci.com/user/encrypting-files/" class="uri">https://docs.travis-ci.com/user/encrypting-files/</a>. Basically you will encrypt the file locally and check it in to git. Then you’ll add a decryption step to your <code>.travis.yml</code> which makes it decrypts it for each run. See <a href="https://github.com/rstats-db/bigrquery/blob/master/.travis.yml">bigquery</a> for an example.</p>
+<p>Be careful to not accidentally expose the secret on travis. An easy way to accidentally expose the secret is to print it out so that it’s captured in the log. Don’t do that!</p>
+</div>
+</div>
+<div id="cran" class="section level2">
+<h2>CRAN</h2>
+<p>There is no way to securely share information with arbitrary R users, including CRAN. That means that if you’re developing a package, you need to make sure that <code>R CMD check</code> passes cleanly even when authentication is not available. This tends to primarily affect the documentation, vignettes, and tests.</p>
+<div id="documentation" class="section level3">
+<h3>Documentation</h3>
+<p>Like any R package, an API client needs clear and complete documentation of all functions. Examples are particularly useful but may need to be wrapped in <code>\donttest{}</code> to avoid challenges of authentication, rate limiting, lack of network access, or occasional API server down time.</p>
+</div>
+<div id="vignettes" class="section level3">
+<h3>Vignettes</h3>
+<p>Vignettes pose additional challenges when an API requires authentication, because you don’t want to bundle your own credentials with the package! However, you can take advantage of the fact that the vignette is built locally, and only checked by CRAN. In a setup chunk, do:</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">NOT_CRAN <-<span class="st"> </span><span class="kw">identical</span>(<span class="kw">tolower</span>(<span class="kw">Sys.getenv</span>(<span class="st">"NOT_CRAN"</span>)), <span class="st">"true"</span>)
+knitr<span class="op">::</span>opts_chunk<span class="op">$</span><span class="kw">set</span>(<span class="dt">purl =</span> NOT_CRAN)</code></pre></div>
+<p>And then use <code>eval = NOT_CRAN</code> in any chunk that requires access to a secret.</p>
+</div>
+<div id="testing" class="section level3">
+<h3>Testing</h3>
+<p>Use <code>testthat::skip()</code> to automatically skip tests that require authentication. I typically will wrap this into a little helper function that I call at the start of every test requiring auth.</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">skip_if_no_auth <-<span class="st"> </span><span class="cf">function</span>() {
+  <span class="cf">if</span> (<span class="kw">identical</span>(<span class="kw">Sys.getenv</span>(<span class="st">"MY_SECRET"</span>), <span class="st">""</span>)) {
+    <span class="kw">skip</span>(<span class="st">"No authentication available"</span>)
+  }
+}</code></pre></div>
+</div>
+</div>
+
+
+
+<!-- dynamically load mathjax for compatibility with self-contained -->
+<script>
+  (function () {
+    var script = document.createElement("script");
+    script.type = "text/javascript";
+    script.src  = "https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
+    document.getElementsByTagName("head")[0].appendChild(script);
+  })();
+</script>
+
+</body>
+</html>
diff --git a/man/BROWSE.Rd b/man/BROWSE.Rd
index 642fdea..2f43e5e 100644
--- a/man/BROWSE.Rd
+++ b/man/BROWSE.Rd
@@ -40,4 +40,3 @@ Other http methods: \code{\link{DELETE}},
   \code{\link{PATCH}}, \code{\link{POST}},
   \code{\link{PUT}}, \code{\link{VERB}}
 }
-
diff --git a/man/DELETE.Rd b/man/DELETE.Rd
index 2801ac2..67e2a28 100644
--- a/man/DELETE.Rd
+++ b/man/DELETE.Rd
@@ -79,6 +79,7 @@ If the request passes through a cache and the Request-URI identifies one or
 more currently cached entities, those entries SHOULD be treated as stale.
 Responses to this method are not cacheable.
 }
+
 \examples{
 DELETE("http://httpbin.org/delete")
 POST("http://httpbin.org/delete")
@@ -89,4 +90,3 @@ Other http methods: \code{\link{BROWSE}},
   \code{\link{PATCH}}, \code{\link{POST}},
   \code{\link{PUT}}, \code{\link{VERB}}
 }
-
diff --git a/man/GET.Rd b/man/GET.Rd
index 42ce67d..bd7d910 100644
--- a/man/GET.Rd
+++ b/man/GET.Rd
@@ -53,6 +53,7 @@ The partial GET method is intended to reduce unnecessary network usage by
 allowing partially-retrieved entities to be completed without transferring
 data already held by the client.
 }
+
 \examples{
 GET("http://google.com/")
 GET("http://google.com/", path = "search")
@@ -79,4 +80,3 @@ Other http methods: \code{\link{BROWSE}},
   \code{\link{PATCH}}, \code{\link{POST}},
   \code{\link{PUT}}, \code{\link{VERB}}
 }
-
diff --git a/man/HEAD.Rd b/man/HEAD.Rd
index 6f42eb9..e1f9f50 100644
--- a/man/HEAD.Rd
+++ b/man/HEAD.Rd
@@ -46,6 +46,7 @@ cached entity differs from the current entity (as would be indicated by a
 change in Content-Length, Content-MD5, ETag or Last-Modified), then the
 cache MUST treat the cache entry as stale.
 }
+
 \examples{
 HEAD("http://google.com")
 headers(HEAD("http://google.com"))
@@ -56,4 +57,3 @@ Other http methods: \code{\link{BROWSE}},
   \code{\link{PATCH}}, \code{\link{POST}},
   \code{\link{PUT}}, \code{\link{VERB}}
 }
-
diff --git a/man/PATCH.Rd b/man/PATCH.Rd
index a214b7a..6f0889d 100644
--- a/man/PATCH.Rd
+++ b/man/PATCH.Rd
@@ -64,4 +64,3 @@ Other http methods: \code{\link{BROWSE}},
   \code{\link{HEAD}}, \code{\link{POST}},
   \code{\link{PUT}}, \code{\link{VERB}}
 }
-
diff --git a/man/POST.Rd b/man/POST.Rd
index 32144ff..8c2a074 100644
--- a/man/POST.Rd
+++ b/man/POST.Rd
@@ -76,4 +76,3 @@ Other http methods: \code{\link{BROWSE}},
   \code{\link{HEAD}}, \code{\link{PATCH}},
   \code{\link{PUT}}, \code{\link{VERB}}
 }
-
diff --git a/man/PUT.Rd b/man/PUT.Rd
index f6f2c38..d9ce165 100644
--- a/man/PUT.Rd
+++ b/man/PUT.Rd
@@ -74,4 +74,3 @@ Other http methods: \code{\link{BROWSE}},
   \code{\link{HEAD}}, \code{\link{PATCH}},
   \code{\link{POST}}, \code{\link{VERB}}
 }
-
diff --git a/man/RETRY.Rd b/man/RETRY.Rd
index 301b761..3f3aaab 100644
--- a/man/RETRY.Rd
+++ b/man/RETRY.Rd
@@ -6,7 +6,8 @@
 \usage{
 RETRY(verb, url = NULL, config = list(), ..., body = NULL,
   encode = c("multipart", "form", "json", "raw"), times = 3,
-  pause_base = 1, pause_cap = 60, handle = NULL, quiet = FALSE)
+  pause_base = 1, pause_cap = 60, pause_min = 1, handle = NULL,
+  quiet = FALSE, terminate_on = NULL)
 }
 \arguments{
 \item{verb}{Name of verb to use.}
@@ -57,6 +58,10 @@ full jitter - this means that each request will randomly wait between 0
 and \code{pause_base * 2 ^ attempt} seconds, up to a maximum of
 \code{pause_cap} seconds.}
 
+\item{pause_min}{Minimum time to wait in the backoff; generally
+only necessary if you need pauses less than one second (which may
+not be kind to the server, use with caution!).}
+
 \item{handle}{The handle to use with this request. If not
 supplied, will be retrieved and reused from the \code{\link{handle_pool}}
 based on the scheme, hostname and port of the url. By default \pkg{httr}
@@ -67,6 +72,10 @@ details.}
 
 \item{quiet}{If \code{FALSE}, will print a message displaying how long
 until the next request.}
+
+\item{terminate_on}{Optional vector of numeric HTTP status codes that if found
+on the response will terminate the retry process. If \code{NULL}, will keep
+retrying while \code{\link{http_error}()} is \code{TRUE} for the response.}
 }
 \value{
 The last response. Note that if the request doesn't succeed after
@@ -74,16 +83,26 @@ The last response. Note that if the request doesn't succeed after
   to use \code{\link{stop_for_status}()}.
 }
 \description{
-Safely retry a request until it succeeds (returns an HTTP status code
-below 400). It is designed to be kind to the server: after each failure
+Safely retry a request until it succeeds, as defined by the \code{terminate_on}
+parameter, which by default means a response for which \code{\link{http_error}()}
+is \code{FALSE}. Will also retry on error conditions raised by the underlying curl code,
+but if the last retry still raises one, \code{RETRY} will raise it again with
+\code{\link{stop}()}.
+It is designed to be kind to the server: after each failure
 randomly waits up to twice as long. (Technically it uses exponential
 backoff with jitter, using the approach outlined in
 \url{https://www.awsarchitectureblog.com/2015/03/backoff.html}.)
+If the server returns status code 429 and specifies a \code{retry-after} value, that
+value will be used instead, unless it's smaller than \code{pause_min}.
 }
 \examples{
 # Succeeds straight away
 RETRY("GET", "http://httpbin.org/status/200")
 # Never succeeds
 RETRY("GET", "http://httpbin.org/status/500")
+\donttest{
+# Invalid hostname generates curl error condition and is retried but eventually
+# raises an error condition.
+RETRY("GET", "http://invalidhostname/")
+}
 }
-
diff --git a/man/Token-class.Rd b/man/Token-class.Rd
index f463c7a..dde0f45 100644
--- a/man/Token-class.Rd
+++ b/man/Token-class.Rd
@@ -2,8 +2,8 @@
 % Please edit documentation in R/oauth-token.r
 \docType{class}
 \name{Token-class}
-\alias{Token}
 \alias{Token-class}
+\alias{Token}
 \alias{Token1.0}
 \alias{Token2.0}
 \alias{TokenServiceAccount}
@@ -54,6 +54,6 @@ The cache file should not be included in source code control or R packages
 (because it contains private information), so httr will automatically add
 the appropriate entries to `.gitignore` and `.Rbuildignore` if needed.
 }
+
 \keyword{datasets}
 \keyword{internal}
-
diff --git a/man/VERB.Rd b/man/VERB.Rd
index f0ddd81..22c2325 100644
--- a/man/VERB.Rd
+++ b/man/VERB.Rd
@@ -75,4 +75,3 @@ Other http methods: \code{\link{BROWSE}},
   \code{\link{HEAD}}, \code{\link{PATCH}},
   \code{\link{POST}}, \code{\link{PUT}}
 }
-
diff --git a/man/add_headers.Rd b/man/add_headers.Rd
index dcae56c..1887085 100644
--- a/man/add_headers.Rd
+++ b/man/add_headers.Rd
@@ -38,4 +38,3 @@ Other config: \code{\link{authenticate}},
   \code{\link{timeout}}, \code{\link{use_proxy}},
   \code{\link{user_agent}}, \code{\link{verbose}}
 }
-
diff --git a/man/authenticate.Rd b/man/authenticate.Rd
index 2985923..1c90774 100644
--- a/man/authenticate.Rd
+++ b/man/authenticate.Rd
@@ -30,4 +30,3 @@ Other config: \code{\link{add_headers}},
   \code{\link{timeout}}, \code{\link{use_proxy}},
   \code{\link{user_agent}}, \code{\link{verbose}}
 }
-
diff --git a/man/cache_info.Rd b/man/cache_info.Rd
index 160f1cb..75303ff 100644
--- a/man/cache_info.Rd
+++ b/man/cache_info.Rd
@@ -48,4 +48,3 @@ cache_info(r4)
 rerequest(r4)$date
 }
 }
-
diff --git a/man/config.Rd b/man/config.Rd
index 578a79e..6f083f8 100644
--- a/man/config.Rd
+++ b/man/config.Rd
@@ -61,4 +61,3 @@ Other config: \code{\link{add_headers}},
 Other ways to set configuration: \code{\link{set_config}},
   \code{\link{with_config}}
 }
-
diff --git a/man/content.Rd b/man/content.Rd
index 36c9695..656db4d 100644
--- a/man/content.Rd
+++ b/man/content.Rd
@@ -2,8 +2,8 @@
 % Please edit documentation in R/content.r
 \name{content}
 \alias{content}
-\alias{parsed_content}
 \alias{text_content}
+\alias{parsed_content}
 \title{Extract content from a request.}
 \usage{
 content(x, as = NULL, type = NULL, encoding = NULL, ...)
@@ -69,6 +69,7 @@ Instead, check the mime-type is what you expect, and then parse yourself.
 This is safer, as you will fail informatively if the API changes, and
 you will protect yourself against changes to httr.
 }
+
 \examples{
 r <- POST("http://httpbin.org/post", body = list(a = 1, b = 2))
 content(r) # automatically parses JSON
@@ -84,4 +85,3 @@ Other response methods: \code{\link{http_error}},
   \code{\link{http_status}}, \code{\link{response}},
   \code{\link{stop_for_status}}
 }
-
diff --git a/man/content_type.Rd b/man/content_type.Rd
index 6ec3384..3adacb9 100644
--- a/man/content_type.Rd
+++ b/man/content_type.Rd
@@ -1,12 +1,12 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/headers.r
 \name{content_type}
-\alias{accept}
-\alias{accept_json}
-\alias{accept_xml}
 \alias{content_type}
 \alias{content_type_json}
 \alias{content_type_xml}
+\alias{accept}
+\alias{accept_json}
+\alias{accept_xml}
 \title{Set content-type and accept headers.}
 \usage{
 content_type(type)
@@ -44,4 +44,3 @@ GET("http://httpbin.org/headers", content_type_xml())
 GET("http://httpbin.org/headers", content_type("text/csv"))
 GET("http://httpbin.org/headers", content_type(".xml"))
 }
-
diff --git a/man/cookies.Rd b/man/cookies.Rd
index 91ce6ff..7600844 100644
--- a/man/cookies.Rd
+++ b/man/cookies.Rd
@@ -19,4 +19,3 @@ cookies(r)
 \seealso{
 \code{\link{set_cookies}()} to send cookies in request.
 }
-
diff --git a/man/get_callback.Rd b/man/get_callback.Rd
new file mode 100644
index 0000000..9beea84
--- /dev/null
+++ b/man/get_callback.Rd
@@ -0,0 +1,86 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/callback.R
+\name{get_callback}
+\alias{get_callback}
+\alias{set_callback}
+\title{Install or uninstall a callback function}
+\usage{
+get_callback(name)
+
+set_callback(name, new_callback = NULL)
+}
+\arguments{
+\item{name}{Character scalar, name of the callback to query or set.}
+
+\item{new_callback}{The callback function to install, a function object;
+or \code{NULL} to remove the currently installed callback (if any).}
+}
+\value{
+\code{get_callback} returns the currently installed
+  callback, or \code{NULL} if none is installed.
+
+  \code{set_callback} returns the previously installed callback,
+  or \code{NULL} if none was installed.
+}
+\description{
+Supported callback functions: \describe{
+\item{\sQuote{request}}{This callback is called before an HTTP request
+  is performed, with the \code{request} object as an argument.
+  If the callback returns a value other than \code{NULL}, the HTTP
+  request is not performed at all, and the return value of the callback
+  is returned. This mechanism can be used to replay previously
+  recorded HTTP responses.
+}
+\item{\sQuote{response}}{This callback is called after an HTTP request
+  is performed. The callback is called with two arguments: the
+  \code{request} object and the \code{response} object of the HTTP
+  request. If this callback returns a value other than \code{NULL},
+  then this value is returned by \code{httr}.}
+}
+}
+\details{
+Note that it is not possible to install multiple callbacks of the same
+type. The installed callback overwrites the previously intalled one.
+To uninstall a callback function, set it to \code{NULL} with
+\code{set_callback()}.
+
+See the \code{httrmock} package for a proper example that uses
+callbacks.
+}
+\examples{
+\dontrun{
+## Log all HTTP requests to the screeen
+req_logger <- function(req) {
+  cat("HTTP request to", sQuote(req$url), "\\n")
+}
+
+old <- set_callback("request", req_logger)
+g1 <- GET("https://httpbin.org")
+g2 <- GET("https://httpbin.org/ip")
+set_callback("request", old)
+
+## Log all HTTP requests and response status codes as well
+req_logger2 <- function(req) {
+  cat("HTTP request to", sQuote(req$url), "... ")
+}
+res_logger <- function(req, res) {
+  cat(res$status_code, "\\n")
+}
+
+old_req <- set_callback("request", req_logger2)
+old_res <- set_callback("response", res_logger)
+g3 <- GET("https://httpbin.org")
+g4 <- GET("https://httpbin.org/ip")
+set_callback("request", old_req)
+set_callback("response", old_res)
+
+## Return a recorded response, without performing the HTTP request
+replay <- function(req) {
+  if (req$url == "https://httpbin.org") g3
+}
+old_req <- set_callback("request", replay)
+grec <- GET("https://httpbin.org")
+grec$date == g3$date
+set_callback("request", old_req)
+}
+}
diff --git a/man/guess_media.Rd b/man/guess_media.Rd
index e5938f5..7f67a6f 100644
--- a/man/guess_media.Rd
+++ b/man/guess_media.Rd
@@ -13,4 +13,3 @@ guess_media(x)
 DEPRECATED: please use \code{mime::guess_type} instead.
 }
 \keyword{internal}
-
diff --git a/man/handle.Rd b/man/handle.Rd
index 4006956..451930f 100644
--- a/man/handle.Rd
+++ b/man/handle.Rd
@@ -45,4 +45,3 @@ GET(handle = h, config = list(timeout(10), add_headers(Accept = "")))
 }
 
 }
-
diff --git a/man/handle_pool.Rd b/man/handle_pool.Rd
index 510597c..19850f5 100644
--- a/man/handle_pool.Rd
+++ b/man/handle_pool.Rd
@@ -2,8 +2,8 @@
 % Please edit documentation in R/handle-pool.r
 \docType{data}
 \name{handle_pool}
-\alias{handle_find}
 \alias{handle_pool}
+\alias{handle_find}
 \alias{handle_reset}
 \title{Maintain a pool of handles.}
 \format{An environment.}
@@ -21,4 +21,3 @@ automatically reused, and cookies are maintained across requests to a site
 without user intervention.
 }
 \keyword{internal}
-
diff --git a/man/has_content.Rd b/man/has_content.Rd
index babcadf..639be13 100644
--- a/man/has_content.Rd
+++ b/man/has_content.Rd
@@ -14,4 +14,3 @@ has_content(POST("http://httpbin.org/post", body = FALSE))
 has_content(HEAD("http://httpbin.org/headers"))
 }
 \keyword{internal}
-
diff --git a/man/headers.Rd b/man/headers.Rd
index d7be9f6..456081a 100644
--- a/man/headers.Rd
+++ b/man/headers.Rd
@@ -20,4 +20,3 @@ headers(r)
 \code{\link{add_headers}()} to send additional headers in a
   request
 }
-
diff --git a/man/hmac_sha1.Rd b/man/hmac_sha1.Rd
index 32358cc..69cd583 100644
--- a/man/hmac_sha1.Rd
+++ b/man/hmac_sha1.Rd
@@ -15,4 +15,3 @@ hmac_sha1(key, string)
 As described in \url{http://datatracker.ietf.org/doc/rfc2104/}.
 }
 \keyword{internal}
-
diff --git a/man/http_condition.Rd b/man/http_condition.Rd
index 4700b81..9aac8dc 100644
--- a/man/http_condition.Rd
+++ b/man/http_condition.Rd
@@ -51,4 +51,3 @@ f("http://httpbin.org/status/505")
   for more details about R's condition handling model
 }
 \keyword{internal}
-
diff --git a/man/http_error.Rd b/man/http_error.Rd
index 649d8f6..83cb69b 100644
--- a/man/http_error.Rd
+++ b/man/http_error.Rd
@@ -2,8 +2,8 @@
 % Please edit documentation in R/response-status.r
 \name{http_error}
 \alias{http_error}
-\alias{url_ok}
 \alias{url_success}
+\alias{url_ok}
 \title{Check for an http error.}
 \usage{
 http_error(x, ...)
@@ -40,4 +40,3 @@ Other response methods: \code{\link{content}},
   \code{\link{http_status}}, \code{\link{response}},
   \code{\link{stop_for_status}}
 }
-
diff --git a/man/http_status.Rd b/man/http_status.Rd
index 66310c3..6b63089 100644
--- a/man/http_status.Rd
+++ b/man/http_status.Rd
@@ -47,4 +47,3 @@ Other response methods: \code{\link{content}},
   \code{\link{http_error}}, \code{\link{response}},
   \code{\link{stop_for_status}}
 }
-
diff --git a/man/http_type.Rd b/man/http_type.Rd
index 96f5e81..bbfbf1e 100644
--- a/man/http_type.Rd
+++ b/man/http_type.Rd
@@ -25,4 +25,3 @@ r2 <- GET("http://httpbin.org/ip")
 http_type(r2)
 headers(r2)[["Content-Type"]]
 }
-
diff --git a/man/httr.Rd b/man/httr-package.Rd
similarity index 80%
rename from man/httr.Rd
rename to man/httr-package.Rd
index e63b018..e5e9e81 100644
--- a/man/httr.Rd
+++ b/man/httr-package.Rd
@@ -1,7 +1,7 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/httr.r
 \docType{package}
-\name{httr}
+\name{httr-package}
 \alias{httr}
 \alias{httr-package}
 \title{\pkg{httr} makes http easy.}
@@ -30,4 +30,21 @@ httr supports OAuth 1.0 and 2.0. Use \code{\link{oauth1.0_token}} and
 requests. The demos directory has six demos of using OAuth: three for 1.0
 (linkedin, twitter and vimeo) and three for 2.0 (facebook, github, google).
 }
+\seealso{
+Useful links:
+\itemize{
+  \item \url{https://github.com/r-lib/httr}
+  \item Report bugs at \url{https://github.com/r-lib/httr/issues}
+}
+
+}
+\author{
+\strong{Maintainer}: Hadley Wickham \email{hadley at rstudio.com}
 
+Other contributors:
+\itemize{
+  \item RStudio [copyright holder]
+}
+
+}
+\keyword{internal}
diff --git a/man/httr_dr.Rd b/man/httr_dr.Rd
index 965c382..3e4ceb2 100644
--- a/man/httr_dr.Rd
+++ b/man/httr_dr.Rd
@@ -9,4 +9,3 @@ httr_dr()
 \description{
 Currently one check: that curl uses nss.
 }
-
diff --git a/man/httr_options.Rd b/man/httr_options.Rd
index a64998e..220c3e1 100644
--- a/man/httr_options.Rd
+++ b/man/httr_options.Rd
@@ -1,8 +1,8 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/config.r
 \name{httr_options}
-\alias{curl_docs}
 \alias{httr_options}
+\alias{curl_docs}
 \title{List available options.}
 \usage{
 httr_options(matches)
@@ -42,4 +42,3 @@ httr_options("post")
 curl_docs("userpwd")
 curl_docs("CURLOPT_USERPWD")
 }
-
diff --git a/man/init_oauth1.0.Rd b/man/init_oauth1.0.Rd
index 5847eff..c79db18 100644
--- a/man/init_oauth1.0.Rd
+++ b/man/init_oauth1.0.Rd
@@ -15,7 +15,7 @@ init_oauth1.0(endpoint, app, permission = NULL,
 
 \item{permission}{optional, a string of permissions to ask for.}
 
-\item{is_interactive}{Is the current environment interactive?}
+\item{is_interactive}{DEPRECATED}
 
 \item{private_key}{Optional, a key provided by \code{\link[openssl]{read_key}}.
 Used for signed OAuth 1.0.}
@@ -24,4 +24,3 @@ Used for signed OAuth 1.0.}
 See demos for use.
 }
 \keyword{internal}
-
diff --git a/man/init_oauth2.0.Rd b/man/init_oauth2.0.Rd
index c08cadd..f46f097 100644
--- a/man/init_oauth2.0.Rd
+++ b/man/init_oauth2.0.Rd
@@ -2,11 +2,21 @@
 % Please edit documentation in R/oauth-init.R
 \name{init_oauth2.0}
 \alias{init_oauth2.0}
+\alias{oauth2.0_authorize_url}
+\alias{oauth2.0_access_token}
 \title{Retrieve OAuth 2.0 access token.}
 \usage{
 init_oauth2.0(endpoint, app, scope = NULL, user_params = NULL,
   type = NULL, use_oob = getOption("httr_oob_default"),
-  is_interactive = interactive(), use_basic_auth = FALSE)
+  is_interactive = interactive(), use_basic_auth = FALSE,
+  config_init = list(), client_credentials = FALSE)
+
+oauth2.0_authorize_url(endpoint, app, scope, redirect_uri = app$redirect_uri,
+  state = nonce())
+
+oauth2.0_access_token(endpoint, app, code, user_params = NULL, type = NULL,
+  use_basic_auth = FALSE, redirect_uri = app$redirect_uri,
+  client_credentials = FALSE, config = list())
 }
 \arguments{
 \item{endpoint}{An OAuth endpoint, created by \code{\link{oauth_endpoint}}}
@@ -27,15 +37,21 @@ Otherwise, provide a URL to the user and prompt for a validation
 code. Defaults to the of the \code{"httr_oob_default"} default,
 or \code{TRUE} if \code{httpuv} is not installed.}
 
-\item{is_interactive}{Is the current environment interactive?}
+\item{is_interactive}{DEPRECATED}
 
 \item{use_basic_auth}{if \code{TRUE} use http basic authentication to
 retrieve the token. Some authorization servers require this.
 If \code{FALSE}, the default, retrieve the token by including the
 app key and secret in the request body.}
+
+\item{config_init}{Additional configuration settings sent to
+\code{\link{POST}}, e.g. \code{\link{user_agent}}.}
+
+\item{client_credentials}{Default to \code{FALSE}. Set to \code{TRUE} to use
+\emph{Client Credentials Grant} instead of \emph{Authorization
+Code Grant}. See \url{https://tools.ietf.org/html/rfc6749#section-4.4}.}
 }
 \description{
 See demos for use.
 }
 \keyword{internal}
-
diff --git a/man/insensitive.Rd b/man/insensitive.Rd
index 17490b3..833bc0c 100644
--- a/man/insensitive.Rd
+++ b/man/insensitive.Rd
@@ -20,4 +20,3 @@ y["ABC"]
 y[["ABC"]]
 }
 \keyword{internal}
-
diff --git a/man/jwt_signature.Rd b/man/jwt_signature.Rd
index fc15c13..5762bc4 100644
--- a/man/jwt_signature.Rd
+++ b/man/jwt_signature.Rd
@@ -4,7 +4,9 @@
 \alias{jwt_signature}
 \title{Generate a JWT signature given credentials.}
 \usage{
-jwt_signature(credentials, scope, aud, duration = 60L * 60L)
+jwt_signature(credentials, scope, aud, sub = NULL,
+  iat = as.integer(Sys.time()), exp = iat + duration, duration = 60L *
+  60L)
 }
 \arguments{
 \item{credentials}{Parsed contents of the credentials file.}
@@ -12,7 +14,26 @@ jwt_signature(credentials, scope, aud, duration = 60L * 60L)
 \item{scope}{A space-delimited list of the permissions that the application
 requests.}
 
+\item{aud}{A descriptor of the intended target of the assertion. This
+typically comes from the service auth file.}
+
+\item{sub}{The email address of the user for which the application is
+requesting delegated access.}
+
+\item{iat}{The time the assertion was issued, measured in seconds since
+00:00:00 UTC, January 1, 1970.}
+
+\item{exp}{The expiration time of the assertion, measured in seconds since
+00:00:00 UTC, January 1, 1970. This value has a maximum of 1 hour from
+the issued time.}
+
 \item{duration}{Duration of token, in seconds.}
+
+\item{iss}{Email address of the client_id of the application making the
+access token request.}
+
+\item{scope}{A space-delimited list of the permissions that the application
+requests.}
 }
 \description{
 As described in
@@ -25,4 +46,3 @@ jwt_signature(cred, "https://www.googleapis.com/auth/userinfo.profile")
 }
 }
 \keyword{internal}
-
diff --git a/man/modify_url.Rd b/man/modify_url.Rd
index 9102f8e..918b5aa 100644
--- a/man/modify_url.Rd
+++ b/man/modify_url.Rd
@@ -17,4 +17,3 @@ modify_url(url, scheme = NULL, hostname = NULL, port = NULL,
 Modify a url by first parsing it and then replacing components with
 the non-NULL arguments of this function.
 }
-
diff --git a/man/oauth1.0_token.Rd b/man/oauth1.0_token.Rd
index a5d4d28..6e00fa2 100644
--- a/man/oauth1.0_token.Rd
+++ b/man/oauth1.0_token.Rd
@@ -42,4 +42,3 @@ Other OAuth: \code{\link{oauth2.0_token}},
   \code{\link{oauth_app}}, \code{\link{oauth_endpoint}},
   \code{\link{oauth_service_token}}
 }
-
diff --git a/man/oauth2.0_token.Rd b/man/oauth2.0_token.Rd
index a051a38..abb89ca 100644
--- a/man/oauth2.0_token.Rd
+++ b/man/oauth2.0_token.Rd
@@ -6,7 +6,8 @@
 \usage{
 oauth2.0_token(endpoint, app, scope = NULL, user_params = NULL,
   type = NULL, use_oob = getOption("httr_oob_default"), as_header = TRUE,
-  use_basic_auth = FALSE, cache = getOption("httr_oauth_cache"))
+  use_basic_auth = FALSE, cache = getOption("httr_oauth_cache"),
+  config_init = list(), client_credentials = FALSE, credentials = NULL)
 }
 \arguments{
 \item{endpoint}{An OAuth endpoint, created by \code{\link{oauth_endpoint}}}
@@ -41,6 +42,16 @@ app key and secret in the request body.}
 using the default cache file \code{.httr-oauth}, \code{FALSE} means
 don't cache, and \code{NA} means to guess using some sensible heuristics.
 A string mean use the specified path as the cache file.}
+
+\item{config_init}{Additional configuration settings sent to
+\code{\link{POST}}, e.g. \code{\link{user_agent}}.}
+
+\item{client_credentials}{Default to \code{FALSE}. Set to \code{TRUE} to use
+\emph{Client Credentials Grant} instead of \emph{Authorization
+Code Grant}. See \url{https://tools.ietf.org/html/rfc6749#section-4.4}.}
+
+\item{credentials}{Advanced use only: allows you to completely customise
+token generation.}
 }
 \value{
 A \code{Token2.0} reference class (RC) object.
@@ -60,4 +71,3 @@ Other OAuth: \code{\link{oauth1.0_token}},
   \code{\link{oauth_app}}, \code{\link{oauth_endpoint}},
   \code{\link{oauth_service_token}}
 }
-
diff --git a/man/oauth_app.Rd b/man/oauth_app.Rd
index a49479f..855064a 100644
--- a/man/oauth_app.Rd
+++ b/man/oauth_app.Rd
@@ -4,7 +4,7 @@
 \alias{oauth_app}
 \title{Create an OAuth application.}
 \usage{
-oauth_app(appname, key, secret = NULL)
+oauth_app(appname, key, secret = NULL, redirect_uri = oauth_callback())
 }
 \arguments{
 \item{appname}{name of the application.  This is not used for OAuth, but
@@ -19,6 +19,10 @@ provide a consistent way of storing secrets in environment variables.}
 
   Use \code{NULL} to not store a secret: this is useful if you're relying on
   cached OAuth tokens.}
+
+\item{redirect_uri}{The URL that user will be redirected to after
+authorisation is complete. You should generally leave this as the default
+unless you're using a non-standard auth flow (like with shiny).}
 }
 \description{
 The OAuth framework doesn't match perfectly to use from R. Each user of the
@@ -44,4 +48,3 @@ Other OAuth: \code{\link{oauth1.0_token}},
   \code{\link{oauth_endpoint}},
   \code{\link{oauth_service_token}}
 }
-
diff --git a/man/oauth_callback.Rd b/man/oauth_callback.Rd
index 6281c61..54e131c 100644
--- a/man/oauth_callback.Rd
+++ b/man/oauth_callback.Rd
@@ -11,4 +11,3 @@ The url that \code{\link{oauth_listener}} expects that the client be
 referred to.
 }
 \keyword{internal}
-
diff --git a/man/oauth_endpoint.Rd b/man/oauth_endpoint.Rd
index 3366e2f..dddb77a 100644
--- a/man/oauth_endpoint.Rd
+++ b/man/oauth_endpoint.Rd
@@ -10,7 +10,8 @@ oauth_endpoint(request = NULL, authorize, access, ..., base_url = NULL)
 \item{request}{url used to request initial (unauthenticated) token.
 If using OAuth2.0, leave as \code{NULL}.}
 
-\item{authorize}{url to send client to for authorisation}
+\item{authorize}{url to send client to for authorisation. Set to \code{NULL}
+if not needed}
 
 \item{access}{url used to exchange unauthenticated for authenticated token.}
 
@@ -39,4 +40,3 @@ Other OAuth: \code{\link{oauth1.0_token}},
   \code{\link{oauth2.0_token}}, \code{\link{oauth_app}},
   \code{\link{oauth_service_token}}
 }
-
diff --git a/man/oauth_endpoints.Rd b/man/oauth_endpoints.Rd
index 00c4a20..3450ed2 100644
--- a/man/oauth_endpoints.Rd
+++ b/man/oauth_endpoints.Rd
@@ -16,4 +16,3 @@ Provides some common OAuth endpoints.
 \examples{
 oauth_endpoints("twitter")
 }
-
diff --git a/man/oauth_exchanger.Rd b/man/oauth_exchanger.Rd
index 56fbe37..22bfab0 100644
--- a/man/oauth_exchanger.Rd
+++ b/man/oauth_exchanger.Rd
@@ -11,14 +11,12 @@ oauth_exchanger(request_url)
 }
 \description{
 This performs a similar function to \code{\link{oauth_listener}},
-but without trying do open a browser or start a local webserver.
-This manual process can be useful in situations where the user is
-remotely accessing the machine outside a browser (say via ssh) or
-when it's not possible to successfully receive a callback (such as
-when behind a firewall).
+but without running a local webserver.  This manual process can be useful
+in situations where the user is remotely accessing the machine outside a
+browser (say via ssh) or when it's not possible to successfully receive a
+callback (such as when behind a firewall).
 }
 \details{
 This function should generally not be called directly by the user.
 }
 \keyword{internal}
-
diff --git a/man/oauth_listener.Rd b/man/oauth_listener.Rd
index 65dd73d..cff10d4 100644
--- a/man/oauth_listener.Rd
+++ b/man/oauth_listener.Rd
@@ -9,7 +9,7 @@ oauth_listener(request_url, is_interactive = interactive())
 \arguments{
 \item{request_url}{the url to send the browser to}
 
-\item{is_interactive}{Is an interactive environment available?}
+\item{is_interactive}{DEPRECATED}
 
 \item{host}{ip address for the listener}
 
@@ -26,4 +26,3 @@ and \code{\link{oauth2.0_token}} for examples of both techniques.
 This function should not normally be called directly by the user.
 }
 \keyword{internal}
-
diff --git a/man/oauth_service_token.Rd b/man/oauth_service_token.Rd
index fb27110..149ac6e 100644
--- a/man/oauth_service_token.Rd
+++ b/man/oauth_service_token.Rd
@@ -4,7 +4,7 @@
 \alias{oauth_service_token}
 \title{Generate OAuth token for service accounts.}
 \usage{
-oauth_service_token(endpoint, secrets, scope = NULL)
+oauth_service_token(endpoint, secrets, scope = NULL, sub = NULL)
 }
 \arguments{
 \item{endpoint}{An OAuth endpoint, created by \code{\link{oauth_endpoint}}}
@@ -12,6 +12,9 @@ oauth_service_token(endpoint, secrets, scope = NULL)
 \item{secrets}{Secrets loaded from JSON file, downloaded from console.}
 
 \item{scope}{a character vector of scopes to request.}
+
+\item{sub}{The email address of the user for which the application is
+requesting delegated access.}
 }
 \description{
 Service accounts provide a way of using OAuth2 without user intervention.
@@ -33,4 +36,3 @@ Other OAuth: \code{\link{oauth1.0_token}},
   \code{\link{oauth2.0_token}}, \code{\link{oauth_app}},
   \code{\link{oauth_endpoint}}
 }
-
diff --git a/man/oauth_signature.Rd b/man/oauth_signature.Rd
index 8570953..f083d1d 100644
--- a/man/oauth_signature.Rd
+++ b/man/oauth_signature.Rd
@@ -1,8 +1,8 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/oauth-signature.r
 \name{oauth_signature}
-\alias{oauth_header}
 \alias{oauth_signature}
+\alias{oauth_header}
 \title{Generate oauth signature.}
 \usage{
 oauth_signature(url, method = "GET", app, token = NULL,
@@ -28,4 +28,3 @@ For advanced use only. Occassionally needed for sites that use some
 components of the OAuth spec, but not all of them (e.g. 2-legged oauth)
 }
 \keyword{internal}
-
diff --git a/man/parse_http_date.Rd b/man/parse_http_date.Rd
index d970fc5..c5bf5c6 100644
--- a/man/parse_http_date.Rd
+++ b/man/parse_http_date.Rd
@@ -1,8 +1,8 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/date.R
 \name{parse_http_date}
-\alias{http_date}
 \alias{parse_http_date}
+\alias{http_date}
 \title{Parse and print http dates.}
 \usage{
 parse_http_date(x, failure = NA)
@@ -37,4 +37,3 @@ parse_http_date("Sun Nov  6 08:49:37 1994")
 
 http_date(Sys.time())
 }
-
diff --git a/man/parse_media.Rd b/man/parse_media.Rd
index dbfcb16..b290abd 100644
--- a/man/parse_media.Rd
+++ b/man/parse_media.Rd
@@ -31,4 +31,3 @@ parse_media("text/plain; charset=\\"utf-8\\"")
 parse_media("text/plain; randomparam=\\";=;=\\"")
 }
 \keyword{internal}
-
diff --git a/man/parse_url.Rd b/man/parse_url.Rd
index f815054..d8c7f04 100644
--- a/man/parse_url.Rd
+++ b/man/parse_url.Rd
@@ -1,8 +1,8 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/url.r
 \name{parse_url}
-\alias{build_url}
 \alias{parse_url}
+\alias{build_url}
 \title{Parse and build urls according to RFC1808.}
 \usage{
 parse_url(url)
@@ -10,8 +10,9 @@ parse_url(url)
 build_url(url)
 }
 \arguments{
-\item{url}{a character vector (of length 1) to parse into components,
-or for \code{build_url} a url to turn back into a string.}
+\item{url}{For \code{parse_url} a character vector (of length 1) to parse
+into components; for \code{build_url} a list of components to turn back
+into a string.}
 }
 \value{
 a list containing: \itemize{
@@ -35,6 +36,8 @@ parse_url("http://google.com/")
 parse_url("http://google.com:80/")
 parse_url("http://google.com:80/?a=1&b=2")
 
-build_url(parse_url("http://google.com/"))
+url <- parse_url("http://google.com/")
+url$scheme <- "https"
+url$query <- list(q = "hello")
+build_url(url)
 }
-
diff --git a/man/progress.Rd b/man/progress.Rd
index 7189d3f..e82a188 100644
--- a/man/progress.Rd
+++ b/man/progress.Rd
@@ -17,11 +17,12 @@ or downloaded.}
 Add a progress bar.
 }
 \examples{
+cap_speed <- config(max_recv_speed_large = 10000)
+
 \donttest{
 # If file size is known, you get a progress bar:
-x <- GET("http://courses.had.co.nz/12-oscon/slides.zip", progress())
+x <- GET("http://httpbin.org/bytes/102400", progress(), cap_speed)
 # Otherwise you get the number of bytes downloaded:
-x <- GET("http://httpbin.org/drip?numbytes=4000&duration=3", progress())
+x <- GET("http://httpbin.org/stream-bytes/102400", progress(), cap_speed)
 }
 }
-
diff --git a/man/response.Rd b/man/response.Rd
index 336d4b9..96a53b4 100644
--- a/man/response.Rd
+++ b/man/response.Rd
@@ -26,4 +26,3 @@ Other response methods: \code{\link{content}},
   \code{\link{http_error}}, \code{\link{http_status}},
   \code{\link{stop_for_status}}
 }
-
diff --git a/man/revoke_all.Rd b/man/revoke_all.Rd
index 4b77388..117c2b4 100644
--- a/man/revoke_all.Rd
+++ b/man/revoke_all.Rd
@@ -15,4 +15,3 @@ Use this function if you think that your token may have been compromised,
 e.g. you accidentally uploaded the cache file to github. It's not possible
 to automatically revoke all tokens - this function will warn when it can't.
 }
-
diff --git a/man/safe_callback.Rd b/man/safe_callback.Rd
deleted file mode 100644
index 95869d8..0000000
--- a/man/safe_callback.Rd
+++ /dev/null
@@ -1,16 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/safe-callback.R
-\name{safe_callback}
-\alias{safe_callback}
-\title{Generate a safe R callback.}
-\usage{
-safe_callback(f)
-}
-\arguments{
-\item{f}{A function.}
-}
-\description{
-Generate a safe R callback.
-}
-\keyword{deprecated}
-
diff --git a/man/set_config.Rd b/man/set_config.Rd
index 0f185cf..81f0b35 100644
--- a/man/set_config.Rd
+++ b/man/set_config.Rd
@@ -1,8 +1,8 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/config.r
 \name{set_config}
-\alias{reset_config}
 \alias{set_config}
+\alias{reset_config}
 \title{Set (and reset) global httr configuration.}
 \usage{
 set_config(config, override = FALSE)
@@ -33,4 +33,3 @@ GET("http://google.com")
 Other ways to set configuration: \code{\link{config}},
   \code{\link{with_config}}
 }
-
diff --git a/man/set_cookies.Rd b/man/set_cookies.Rd
index f17b05c..9f7007d 100644
--- a/man/set_cookies.Rd
+++ b/man/set_cookies.Rd
@@ -29,4 +29,3 @@ Other config: \code{\link{add_headers}},
   \code{\link{timeout}}, \code{\link{use_proxy}},
   \code{\link{user_agent}}, \code{\link{verbose}}
 }
-
diff --git a/man/sha1_hash.Rd b/man/sha1_hash.Rd
index a4ebe8d..8447c11 100644
--- a/man/sha1_hash.Rd
+++ b/man/sha1_hash.Rd
@@ -17,4 +17,3 @@ sha1_hash(key, string, method = "HMAC-SHA1")
 Creates a SHA1 hash of data using either HMAC or RSA.
 }
 \keyword{internal}
-
diff --git a/man/sign_oauth.Rd b/man/sign_oauth.Rd
index 3c6d675..a3c692e 100644
--- a/man/sign_oauth.Rd
+++ b/man/sign_oauth.Rd
@@ -16,4 +16,3 @@ Deprecated. Instead create a config object directly using
 \code{config(token = my_token)}.
 }
 \keyword{internal}
-
diff --git a/man/status_code.Rd b/man/status_code.Rd
index b0a68b6..e1b167c 100644
--- a/man/status_code.Rd
+++ b/man/status_code.Rd
@@ -12,4 +12,3 @@ status_code(x)
 \description{
 Extract status code from response.
 }
-
diff --git a/man/stop_for_status.Rd b/man/stop_for_status.Rd
index 2e44c5f..5ade766 100644
--- a/man/stop_for_status.Rd
+++ b/man/stop_for_status.Rd
@@ -1,9 +1,9 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/response-status.r
 \name{stop_for_status}
-\alias{message_for_status}
 \alias{stop_for_status}
 \alias{warn_for_status}
+\alias{message_for_status}
 \title{Take action on http error.}
 \usage{
 stop_for_status(x, task = NULL)
@@ -60,4 +60,3 @@ Other response methods: \code{\link{content}},
   \code{\link{http_error}}, \code{\link{http_status}},
   \code{\link{response}}
 }
-
diff --git a/man/timeout.Rd b/man/timeout.Rd
index aeb5edc..71360a0 100644
--- a/man/timeout.Rd
+++ b/man/timeout.Rd
@@ -25,4 +25,3 @@ Other config: \code{\link{add_headers}},
   \code{\link{set_cookies}}, \code{\link{use_proxy}},
   \code{\link{user_agent}}, \code{\link{verbose}}
 }
-
diff --git a/man/upload_file.Rd b/man/upload_file.Rd
index f978eac..925b068 100644
--- a/man/upload_file.Rd
+++ b/man/upload_file.Rd
@@ -20,4 +20,3 @@ citation <- upload_file(system.file("CITATION"))
 POST("http://httpbin.org/post", body = citation)
 POST("http://httpbin.org/post", body = list(y = citation))
 }
-
diff --git a/man/use_proxy.Rd b/man/use_proxy.Rd
index f99f3de..e89a61c 100644
--- a/man/use_proxy.Rd
+++ b/man/use_proxy.Rd
@@ -29,4 +29,3 @@ Other config: \code{\link{add_headers}},
   \code{\link{set_cookies}}, \code{\link{timeout}},
   \code{\link{user_agent}}, \code{\link{verbose}}
 }
-
diff --git a/man/user_agent.Rd b/man/user_agent.Rd
index 5e08fcf..23febf6 100644
--- a/man/user_agent.Rd
+++ b/man/user_agent.Rd
@@ -22,4 +22,3 @@ Other config: \code{\link{add_headers}},
   \code{\link{set_cookies}}, \code{\link{timeout}},
   \code{\link{use_proxy}}, \code{\link{verbose}}
 }
-
diff --git a/man/verbose.Rd b/man/verbose.Rd
index c93b348..7fa315d 100644
--- a/man/verbose.Rd
+++ b/man/verbose.Rd
@@ -38,6 +38,7 @@ different components of the http messages:
   \item \code{<*} ssl data received (in)
 }
 }
+
 \examples{
 GET("http://httpbin.org", verbose())
 GET("http://httpbin.org", verbose(info = TRUE))
@@ -69,4 +70,3 @@ Other config: \code{\link{add_headers}},
   \code{\link{set_cookies}}, \code{\link{timeout}},
   \code{\link{use_proxy}}, \code{\link{user_agent}}
 }
-
diff --git a/man/with_config.Rd b/man/with_config.Rd
index d15a2cd..45729bc 100644
--- a/man/with_config.Rd
+++ b/man/with_config.Rd
@@ -36,4 +36,3 @@ with_verbose(GET("http://google.com"))
 Other ways to set configuration: \code{\link{config}},
   \code{\link{set_config}}
 }
-
diff --git a/man/write_disk.Rd b/man/write_disk.Rd
index a7c0f2f..ddf875e 100644
--- a/man/write_disk.Rd
+++ b/man/write_disk.Rd
@@ -34,4 +34,3 @@ GET("http://www2.census.gov/acs2011_5yr/pums/csv_pus.zip",
   write_disk("csv_pus.zip"), progress())
 }
 }
-
diff --git a/man/write_function.Rd b/man/write_function.Rd
index 2a22395..08e591b 100644
--- a/man/write_function.Rd
+++ b/man/write_function.Rd
@@ -15,4 +15,3 @@ write_function(subclass, ...)
 This S3 object allows you to control how the response body is saved.
 }
 \keyword{internal}
-
diff --git a/man/write_stream.Rd b/man/write_stream.Rd
index 2377a16..89e214a 100644
--- a/man/write_stream.Rd
+++ b/man/write_stream.Rd
@@ -17,11 +17,10 @@ you receive the raw bytes as they come in, and you can do whatever you want
 with them.
 }
 \examples{
-GET("https://jeroenooms.github.io/data/diamonds.json",
+GET("https://github.com/jeroen/data/raw/gh-pages/diamonds.json",
   write_stream(function(x) {
    print(length(x))
    length(x)
  })
 )
 }
-
diff --git a/tests/testthat/test-callback.R b/tests/testthat/test-callback.R
new file mode 100644
index 0000000..91eff30
--- /dev/null
+++ b/tests/testthat/test-callback.R
@@ -0,0 +1,14 @@
+context("Callback")
+
+test_that("request callback", {
+
+  f <- function(req) req$url
+  old <- set_callback("request", f)
+  on.exit(set_callback("request", old))
+
+  expect_identical(GET("foo.bar"), "foo.bar")
+
+  expect_identical(f, get_callback("request"))
+  expect_identical(f, set_callback("request", old))
+  expect_identical(old, get_callback("request"))
+})
diff --git a/tests/testthat/test-config.r b/tests/testthat/test-config.r
index 1e3b540..a3460ba 100644
--- a/tests/testthat/test-config.r
+++ b/tests/testthat/test-config.r
@@ -32,6 +32,6 @@ test_that("digest authentication works", {
 test_that("timeout enforced", {
   skip_on_cran()
   expect_error(GET("http://httpbin.org/delay/1", timeout(0.5)),
-    "Timeout was reached")
+    "(Timeout was reached)|(timed out)")
 })
 
diff --git a/tests/testthat/test-oauth-cache.R b/tests/testthat/test-oauth-cache.R
index 03df708..ecdc2d4 100644
--- a/tests/testthat/test-oauth-cache.R
+++ b/tests/testthat/test-oauth-cache.R
@@ -1,6 +1,17 @@
 context("OAuth cache")
 
+tmp_dir <- function() {
+  x <- tempfile()
+  dir.create(x)
+  x
+}
+
 test_that("use_cache() returns NULL or filepath", {
+  old <- options()
+  on.exit(options(old))
+  owd <- setwd(tmp_dir())
+  on.exit(setwd(owd), add = TRUE)
+
   expect_equal(use_cache(FALSE), NULL)
   expect_equal(use_cache(TRUE), ".httr-oauth")
   expect_equal(use_cache("xyz"), "xyz")
@@ -9,6 +20,8 @@ test_that("use_cache() returns NULL or filepath", {
 test_that("use_cache() respects options", {
   old <- options()
   on.exit(options(old))
+  owd <- setwd(tmp_dir())
+  on.exit(setwd(owd), add = TRUE)
 
   options(httr_oauth_cache = FALSE)
   expect_equal(use_cache(), NULL)
@@ -18,7 +31,8 @@ test_that("use_cache() respects options", {
 })
 
 test_that("token saved to and restored from cache", {
-  on.exit(unlink(".tmp-cache"))
+  owd <- setwd(tmp_dir())
+  on.exit(setwd(owd))
 
   token_a <- Token2.0$new(
     app = oauth_app("x", "y", "z"),
@@ -39,3 +53,29 @@ test_that("token saved to and restored from cache", {
   expect_equal(token_b$endpoint, token_a$endpoint)
   expect_equal(token_b$credentials, token_a$credentials)
 })
+
+test_that("new caches are Rbuildignored and gitignored", {
+  owd <- setwd(tmp_dir())
+  on.exit(setwd(owd))
+  file.create("DESCRIPTION")
+
+  ## default: options("httr_oauth_cache" = NA)
+  ## not tested
+  ## if cache does not exist, will not be created if !interactive()
+
+  old <- options("httr_oauth_cache" = TRUE)
+  on.exit(options(old), add = TRUE)
+  use_cache()
+  expect_true(file.exists(".Rbuildignore"))
+  expect_identical(readLines(".Rbuildignore"), "^\\.httr-oauth$")
+  expect_true(file.exists(".gitignore"))
+  expect_identical(readLines(".gitignore"), ".httr-oauth")
+
+  unlink(c(".httr-oauth", ".Rbuildignore", ".gitignore"))
+
+  use_cache("xyz")
+  expect_true(file.exists(".Rbuildignore"))
+  expect_identical(readLines(".Rbuildignore"), "^xyz$")
+  expect_true(file.exists(".gitignore"))
+  expect_identical(readLines(".gitignore"), "xyz")
+})
diff --git a/tests/testthat/test-oauth-server-side.R b/tests/testthat/test-oauth-server-side.R
index a6adf9d..f00196c 100644
--- a/tests/testthat/test-oauth-server-side.R
+++ b/tests/testthat/test-oauth-server-side.R
@@ -8,12 +8,12 @@ test_that("reference header yields expected base64", {
 })
 
 test_that("reference claimset yields expected base64", {
-  cs <- jwt_claimset(
-    "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5 at developer.gserviceaccount.com",
-    "https://www.googleapis.com/auth/prediction",
-    "https://accounts.google.com/o/oauth2/token",
-    1328554385,
-    1328550785
+  cs <- list(
+    iss = "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5 at developer.gserviceaccount.com",
+    scope = "https://www.googleapis.com/auth/prediction",
+    aud = "https://accounts.google.com/o/oauth2/token",
+    exp = 1328554385,
+    iat = 1328550785
   )
   expected <- "eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU1NDM4NSwiaWF0IjoxMzI4NTUwNzg1fQ"
 
diff --git a/tests/testthat/test-oauth.R b/tests/testthat/test-oauth.R
index 137cd14..f8b7cb6 100644
--- a/tests/testthat/test-oauth.R
+++ b/tests/testthat/test-oauth.R
@@ -44,3 +44,33 @@ test_that("partial OAuth1 flow works", {
   expect_equal(status_code(r), 200)
 })
 
+test_that("oauth_encode1 works", {
+  # chinese characters for "xaringan"
+  orig_string <- "\u5199\u8f6e\u773c"
+  restored_string <- URLdecode(oauth_encode1(orig_string))
+  Encoding(restored_string) <- "UTF-8"
+
+  expect_equal(orig_string, restored_string)
+})
+
+
+# Parameter checking ------------------------------------------------------
+
+test_that("scope must be character or NULL", {
+  expect_equal(check_scope("a"), "a")
+  expect_equal(check_scope(NULL), NULL)
+  expect_error(check_scope(1), "character vector")
+})
+
+test_that("multiple scopes collapsed with space", {
+  expect_equal(check_scope(c("a", "b")), "a b")
+})
+
+test_that("oob must be a flag", {
+  expect_error(check_oob("a"), "logical vector")
+  expect_error(check_oob(c(FALSE, FALSE)), "logical vector")
+})
+
+test_that("can not use oob in non-interactive session", {
+  expect_error(check_oob(TRUE), "interactive")
+})
diff --git a/tests/testthat/test-oauth_signature.R b/tests/testthat/test-oauth_signature.R
new file mode 100644
index 0000000..7c46143
--- /dev/null
+++ b/tests/testthat/test-oauth_signature.R
@@ -0,0 +1,13 @@
+context("oauth_signature")
+
+
+# Url normalisation -------------------------------------------------------
+
+test_that("protocol is lower-cased", {
+  expect_equal(oauth_normalise_url("HTTP://google.com"), "http://google.com/")
+})
+
+test_that("default ports are stripped", {
+  expect_equal(oauth_normalise_url("http://google.com:80"), "http://google.com/")
+  expect_equal(oauth_normalise_url("https://google.com:443"), "https://google.com/")
+})
diff --git a/tests/testthat/test-url.r b/tests/testthat/test-url.r
index d5d2fa5..4f47516 100644
--- a/tests/testthat/test-url.r
+++ b/tests/testthat/test-url.r
@@ -66,10 +66,13 @@ test_that("build_url drops leading / in path", {
   expect_equal(url, "http://google.com/one")
 })
 
-test_that("build_url drops null query", {
+test_that("build_url drops null or empty query", {
   url <- modify_url("http://google.com", query = list(a = 1, b = NULL))
   expect_equal(url, "http://google.com/?a=1")
-
+  url <- modify_url("http://google.com", query = list(a = NULL))
+  expect_equal(url, "http://google.com/")
+  url <- modify_url("http://google.com", query = list())
+  expect_equal(url, "http://google.com/")
 })
 
 test_that("parse_url pulls off domain correctly given query without trailing '/'", {
diff --git a/vignettes/api-packages.Rmd b/vignettes/api-packages.Rmd
index c44033c..ac0cee6 100644
--- a/vignettes/api-packages.Rmd
+++ b/vignettes/api-packages.Rmd
@@ -1,10 +1,8 @@
 ---
-title: "Best practices for writing an API package"
-author: "Hadley Wickham"
-date: "`r Sys.Date()`"
+title: "Best practices for API packages"
 output: rmarkdown::html_vignette
 vignette: >
-  %\VignetteIndexEntry{Best practices for writing an API package}
+  %\VignetteIndexEntry{Best practices for API packages}
   %\VignetteEngine{knitr::rmarkdown}
   %\VignetteEncoding{UTF-8}
 ---
@@ -100,7 +98,7 @@ resp
 
 ### Parse the response
 
-Next, you need to take the response returned by the API and turn it into a useful object. Any API will return an HTTP response that consists of headers and a body. While the response can come in a multiple a forms (see above), two of the most common structured formats are XML and JSON. 
+Next, you need to take the response returned by the API and turn it into a useful object. Any API will return an HTTP response that consists of headers and a body. While the response can come in multiple forms (see above), two of the most common structured formats are XML and JSON. 
 
 Note that while most APIs will return only one or the other, some, like the colour lovers API, allow you to choose which one with a url parameter:
 
@@ -109,7 +107,7 @@ GET("http://www.colourlovers.com/api/color/6B4106?format=xml")
 GET("http://www.colourlovers.com/api/color/6B4106?format=json")
 ```
 
-Others use [content negotiation](http://en.wikipedia.org/wiki/Content_negotiation) to determine whats sort of data to send back. If the API you're wrapping does this, then you'll need to include one of `accept_json()` and `accept_xml()` in your request. 
+Others use [content negotiation](http://en.wikipedia.org/wiki/Content_negotiation) to determine what sort of data to send back. If the API you're wrapping does this, then you'll need to include one of `accept_json()` and `accept_xml()` in your request. 
 
 If you have a choice, choose json: it's usually much easier to work with than xml.
 
@@ -192,7 +190,7 @@ The API might return invalid data, but this should be rare, so you can just rely
 
 ### Turn API errors into R errors
 
-Next, you need to make sure that your API wrapper throws an error if the request failed. Using a web API introduces additional possible points of failure into R code aside from those occuring in R itself. These include:
+Next, you need to make sure that your API wrapper throws an error if the request failed. Using a web API introduces additional possible points of failure into R code aside from those occurring in R itself. These include:
 
 - Client-side exceptions
 - Network / communication exceptions
@@ -200,7 +198,7 @@ Next, you need to make sure that your API wrapper throws an error if the request
 
 You need to make sure these are all converted into regular R errors. You can figure out if there's a problem with `http_error()`, which checks the HTTP status code. Status codes in the 400 range usually mean that you've done something wrong. Status codes in the 500 range typically mean that something has gone wrong on the server side.
 
-Often the API will information about the error in the body of the response: you should use this where available. If the API returns special errors for common problems, you might want to provide more detail in the error. For example, if you run out of requests and are [rate limited](https://developer.github.com/v3/#rate-limiting) you might want to tell the user how long to wait until they can make the next request (or even automatically wait that long!).
+Often the API will provide information about the error in the body of the response: you should use this where available. If the API returns special errors for common problems, you might want to provide more detail in the error. For example, if you run out of requests and are [rate limited](https://developer.github.com/v3/#rate-limiting) you might want to tell the user how long to wait until they can make the next request (or even automatically wait that long!).
 
 ```{r, error = TRUE}
 github_api <- function(path) {
@@ -296,7 +294,7 @@ Most APIs work by executing an HTTP method on a specified URL with some addition
 3. HTTP headers: `add_headers()` 
 4. Request body: The `body` argument to `GET()`, `POST()`, etc.
 
-[RESTful APIs](https://en.wikipedia.org/wiki/Representational_state_transfer) also use the HTTP verb to communicate arguments (e.g,., `GET` retrieves a file, `POST` adds a file, `DELETE` removes a file, etc.). We can use the helpful [httpbin service](http://httpbin.org) to show how to send arguments in each of these ways.
+[RESTful APIs](https://en.wikipedia.org/wiki/Representational_state_transfer) also use the HTTP verb to communicate arguments (e.g., `GET` retrieves a file, `POST` adds a file, `DELETE` removes a file, etc.). We can use the helpful [httpbin service](http://httpbin.org) to show how to send arguments in each of these ways.
 
 ```{r, eval = FALSE}
 # modify_url
@@ -317,7 +315,7 @@ POST("http://httpbin.org/post", body = list(foo = "bar"), encode = "json")
 
 Many APIs will use just one of these forms of argument passing, but others will use multiple of them in combination. Best practice is to insulate the user from how and where the various arguments are used by the API and instead simply expose relevant arguments via R function arguments, some of which might be used in the URL, in the headers, in the body, etc.
 
-If a parameter has a small fixed set of possible values that are allowed by the API, you can use list them in the default arguments and then use  `match.arg()` to ensure that the caller only supplies one of those values. (This also allows the user to supply the short unique prefixes.)
+If a parameter has a small fixed set of possible values that are allowed by the API, you can use list them in the default arguments and then use `match.arg()` to ensure that the caller only supplies one of those values. (This also allows the user to supply the short unique prefixes.)
 
 ```{r}
 f <- function(x = c("apple", "banana", "orange")) {
@@ -332,7 +330,7 @@ It is good practice to explicitly set default values for arguments that are not
 
 Many APIs can be called without any authentication (just as if you called them in a web browser). However, others require authentication to perform particular requests or to avoid rate limits and other limitations. The most common forms of authentication are OAuth and HTTP basic authentication:
 
-1. *"Basic" authentication*: This  requires a username and password (or 
+1. *"Basic" authentication*: This requires a username and password (or 
     sometimes just a username). This is passed as part of the HTTP request. 
     In httr, you can do: 
     `GET("http://httpbin.org", authenticate("username", "password"))`
@@ -387,7 +385,7 @@ When a response is paginated, the API response will typically respond with a hea
 These values can then be used to make further requests. This will either involve specifying a specific page of responses or specifying a "next page token" that returns the next page of results. How to deal with pagination is a difficult question and a client could implement any of the following:
 
 1. Return one page only by default with an option to return additional specific pages
-2. Return a specified page (deafulting to 1) and require the end user to handle pagination
+2. Return a specified page (defaulting to 1) and require the end user to handle pagination
 3. Return all pages by writing an internal process of checking for further pages and combining the results
 
 The choice of which to use depends on your needs and goals and the rate limits of the API.
@@ -405,7 +403,7 @@ rate_limit <- function() {
 rate_limit()
 ```
 
-After getting the first version getting working, you'll often want to polish the output to be more user friendly. For this example, we can parse the unix timestamps into more useful date types.
+After getting the first version working, you'll often want to polish the output to be more user friendly. For this example, we can parse the unix timestamps into more useful date types.
 
 ```{r}
 rate_limit <- function() {
@@ -419,69 +417,3 @@ rate_limit <- function() {
 
 rate_limit()
 ```
-
-## Packaging youe code
-
-### Documentation
-
-Like any R package, an API client needs clear and complete documentation of all functions. Examples are particularly useful but may need to be wrapped in `\donttest{}` to avoid challenges of authentication, rate limiting, lack of network access, or occasional API server down time.
-
-One other challenge unique to web API packages is the need to have documentation that clarifies any distinctions between the R implementation and the API generally (e.g., in the "details" section). It can be useful to add reference links to the API documentation to clarify the mapping between R functions and API endpoints.
-
-### Vignettes
-
-Vignettes pose additional challenges when an API requires authentication, because you don't want to bundle your own credentials with the package! However, you can take advantage of the fact that the vignette is built locally, and only checked by CRAN with this idea from Jenny Bryan:
-
-In an setup chunk, do:
-
-```{r}
-NOT_CRAN <- identical(tolower(Sys.getenv("NOT_CRAN")), "true")
-knitr::opts_chunk$set(purl = NOT_CRAN)
-```
-
-And then use `eval = NOT_CRAN` as needed as a chunk option.
-
-### Testing
-
-The same issues and solutions can also be applied to test suites for API clients that require authenticated credentials. For these packages, you may want to have a test suite that is only run locally but not on CRAN. Another solution is to use a continuous integration service such as Travis-CI or Appveyor, which allows the inclusion of encrypted environment variables or encrypted token files. This enables continuous testing in a relatively secure way.
-
-## Appendix: Storing API Authentication Keys/Tokens
-
-If your package supports an authentication workflow based on an API key or token, encourage users to store it in an environment variable. We illustrate this using the [`github` R package](https://github.com/cscheid/rgithub), which wraps the Github v3 API. Tailor this template to your API + package and include in `README.md` or a vignette.
-
-1.  Create a personal access token in the  [Personal access tokens area](https://github.com/settings/tokens) of your GitHub personal settings. Copy token to the clipboard.
-2.  Identify your home directory. Not sure? Enter `normalizePath("~/")` in  the R console. 
-3.  Create a new text file. If in RStudio, do *File > New File > Text file.*
-4.  Create a line like this:
-
-```bash
-GITHUB_PAT=blahblahblahblahblahblah
-```
-
-where the name `GITHUB_PAT` reminds you which API this is for and  `blahblahblahblahblahblah` is your personal access token, pasted from the clipboard. 
-
-1.  Make sure the last line in the file is empty (if it isn't R will 
-    __silently__ fail to load the file. If you're using an editor that shows
-    line numbers, there should be two lines, where the second one is empty. 
-
-1.  Save in your home directory with the filename `.Renviron`. If questioned,  
-    YES you do want to use a filename that begins with a dot `.`.
-
-    Note that by default [dotfiles](http://linux.about.com/cs/linux101/g/dot_file.htm)
-    are usually hidden. But within RStudio, the file browser will make
-    `.Renviron` visible and therefore easy to edit in the future. 
-
-1.  Restart R. `.Renviron` is processed only [at the start of an R session](http://stat.ethz.ch/R-manual/R-patched/library/base/html/Startup.html). 
-
-1.  Use `Sys.getenv()` to access your token. For example, here's how to use
-    your `GITHUB_PAT` with the `github` package: 
-
-    ```R
-    library(github)
-    ctx <- create.github.context(access_token = Sys.getenv("GITHUB_PAT"))
-    # ... proceed to use other package functions to open issues, etc.
-    ```
-
-FAQ: Why define this environment variable via `.Renviron` instead of in `.bash_profile` or `.bashrc`?
-
-Because there are many combinations of OS and ways of running R where the `.Renviron` approach "just works" and the bash stuff does not. When R is a child process of, say, Emacs or RStudio, you can't always count on environment variables being passed to R. Put them in an R-specific start-up file and save yourself some grief.
diff --git a/vignettes/quickstart.Rmd b/vignettes/quickstart.Rmd
index 66f42fe..76ddc3b 100644
--- a/vignettes/quickstart.Rmd
+++ b/vignettes/quickstart.Rmd
@@ -1,7 +1,11 @@
-<!--
-%\VignetteEngine{knitr::knitr}
-%\VignetteIndexEntry{httr quickstart guide}
--->
+---
+title: "Getting started with httr"
+output: rmarkdown::html_vignette
+vignette: >
+  %\VignetteIndexEntry{Getting started with httr}
+  %\VignetteEngine{knitr::rmarkdown}
+  %\VignetteEncoding{UTF-8}
+---
 
 ```{r, echo = FALSE}
 library(httr)
@@ -14,7 +18,7 @@ The goal of this document is to get you up and running with httr as quickly as p
 
 This vignette (and parts of the httr API) derived from the excellent "[Requests quickstart guide](http://docs.python-requests.org/en/latest/user/quickstart/)" by Kenneth Reitz. Requests is a python library similar in spirit to httr.  
 
-There are two important parts to http: the __request__, the data sent to the server, and the __response__, the data sent back from the server. In the first section, you'll learn about the basics of constructing a requests and accessing the response. In the second and third sections, you'll dive into more details of each.
+There are two important parts to http: the __request__, the data sent to the server, and the __response__, the data sent back from the server. In the first section, you'll learn about the basics of constructing a request and accessing the response. In the second and third sections, you'll dive into more details of each.
 
 ## httr basics
 
@@ -39,7 +43,7 @@ headers(r)
 str(content(r))
 ```
 
-I'll use `httpbin.org` throughout this introduction. It accepts many types of http request and returns json that describes the data that it recieved. This makes it easy to see what httr is doing.
+I'll use `httpbin.org` throughout this introduction. It accepts many types of http request and returns json that describes the data that it received. This makes it easy to see what httr is doing.
 
 As well as `GET()`, you can also use the `HEAD()`, `POST()`, `PATCH()`, `PUT()` and `DELETE()` verbs. You're probably most familiar with `GET()` and `POST()`: `GET()` is used by your browser when requesting a page, and `POST()` is (usually) used when submitting a form to a server. `PUT()`, `PATCH()` and `DELETE()` are used most often by web APIs.
 
@@ -49,7 +53,7 @@ The data sent back from the server consists of three parts: the status line, the
 
 ### The status code
 
-The status code is a three digit number that summarises whether or not the request was succesful (as defined by the server that you're talking to). You can access the status code along with a descriptive message using `http_status()`:
+The status code is a three digit number that summarises whether or not the request was successful (as defined by the server that you're talking to). You can access the status code along with a descriptive message using `http_status()`:
 
 ```{r}
 r <- GET("http://httpbin.org/get")
@@ -60,7 +64,7 @@ http_status(r)
 r$status_code
 ```
 
-A succesful request always returns a status of 200. Common errors are 404 (file not found) and 403 (permission denied). If you're talking to web APIs you might also see 500, which is a generic failure code (and thus not very helpful). If you'd like to learn more, the most memorable guides are the [http status cats](https://www.flickr.com/photos/girliemac/sets/72157628409467125).
+A successful request always returns a status of 200. Common errors are 404 (file not found) and 403 (permission denied). If you're talking to web APIs you might also see 500, which is a generic failure code (and thus not very helpful). If you'd like to learn more, the most memorable guides are the [http status cats](https://www.flickr.com/photos/girliemac/sets/72157628409467125).
 
 You can automatically throw a warning or raise an error if a request did not succeed:
 
@@ -227,7 +231,7 @@ POST(url, body = body, encode = "form", verbose())
 POST(url, body = body, encode = "json", verbose())
 ```
 
-`PUT()` and `PATCH()` can also have request bodies, and they identically to `POST()`.
+`PUT()` and `PATCH()` can also have request bodies, and they take arguments identically to `POST()`.
 
 You can also send files off disk:
 
diff --git a/vignettes/secrets.Rmd b/vignettes/secrets.Rmd
new file mode 100644
index 0000000..e4b7362
--- /dev/null
+++ b/vignettes/secrets.Rmd
@@ -0,0 +1,279 @@
+---
+title: "Managing secrets"
+author: "Hadley Wickham"
+output: rmarkdown::html_vignette
+vignette: >
+  %\VignetteIndexEntry{Managing secrets}
+  %\VignetteEngine{knitr::rmarkdown}
+  %\VignetteEncoding{UTF-8}
+---
+
+```{r, echo = FALSE}
+library(httr)
+knitr::opts_chunk$set(comment = "#>", collapse = TRUE)
+```
+
+## Introduction
+
+This document gives you the basics on securely managing secrets. Most of this document is not directly related to httr, but it's common to have some secrets to manage whenever you are using an API.
+
+What is a secret? Some secrets are short alphanumeric sequences:
+
+* Passwords are clearly secrets, e.g. the second argument to `authenticate()`.
+  Passwords are particularly important because people (ill-advisedly) often 
+  use the same password in multiple places.
+
+* Personal access tokens (e.g. [github][github-token]) should be kept secret:
+  they are basically equivalent to a user name password combination, but 
+  are slightly safer because you can have multiple tokens for different 
+  purposes and it's easy to invalidate one token without affecting the others.
+
+Surprisingly, the "client secret" in an `oauth_app()` is __not__ a secret. It's not equivalent to a password, and if you are writing an API wrapper package, it should be included in the package. (If you don't believe me, here are [google's comments on the topic][google-secret].)
+
+Other secrets are files:
+
+* The JSON web token (jwt) used for server-to-server OAuth 
+  (e.g. [google][google-server]) is a secret because it's equivalent to a
+  personal access token.
+
+* The `.httr-oauth` file is a secret because it stores OAuth access tokens.
+
+The goal of this vignette is to give you the tools to manage these secrets in a secure way. We'll start with best practices for managing secrets locally, then talk about sharing secrets with selected others (including travis), and finish with the challenges that CRAN presents.
+
+Here, I assume that the main threat is accidentally sharing your secrets when you don't want to. Protecting against a committed attacker is much harder. And if someone has already hacked your computer to the point where they can run code, there's almost nothing you can do. If you're concerned about those scenarios, you'll need to take a more comprehensive approach that's outside the scope of this document.
+
+## Locally
+
+Working with secret files locally is straightforward because it's ok to store them in your project directory as long as you take three precautions:
+
+* Ensure the file is only readable by you, not by any other user on the 
+  system. You can use the R function `Sys.chmod()` to do so:
+
+    ```{r, eval = FALSE}
+    Sys.chmod("secret.file", mode = "0400")
+    ```
+    
+    It's good practice to verify this setting by examining the file metadata 
+    with your local filesystem GUI tools or commands.
+
+* If you use git: make sure the files are listed in `.gitignore` so they don't
+  accidentally get included in a public repository.
+  
+* If you're making a package: make sure they are listed in `.Rbuildignore` 
+  so they don't accidentally get included in a public R package.
+  
+httr proactively takes all of these steps for you whenever it creates a `.httr-oauth` file.
+
+The main remaining risk is that you might zip up the entire directory and share it. If you're worried about this scenario, store your secret files outside of the project directory. If you do this, make sure to provide a helper function to locate the file and provide an informative message if it's missing.
+
+```{r}
+my_secrets <- function() {
+  path <- "~/secrets/secret.json"
+  if (!file.exists(path)) {
+    stop("Can't find secret file: '", path, "'")
+  }
+  
+  jsonlite::read_json(path)
+}
+```
+
+Storing short secrets is harder because it's tempting to record them as a variable in your R script. This is a bad idea, because you end up with a file that contains a mix of secret and public code. Instead, you have three options:
+
+* Ask for the secret each time.
+* Store in an environment variable.
+* Use the keyring package. 
+
+Regardless of how you store them, to use your secrets you will still need to read them into R variables. Be careful not to expose them by printing them or saving them to a file.
+
+### Ask each time
+
+For scripts that you only use every now and then, a simple solution is to simply ask for the password each time the script is run. If you use RStudio an easy and secure way to request a password is with the rstudioapi package:
+
+```{r, eval = FALSE}
+password <- rstudioapi::askForPassword()
+```
+
+If you don't use RStudio, use a more general solution like the [getPass](https://github.com/wrathematics/getPass) package.
+
+You should __never__ type your password into the R console: this will typically be stored in the `.Rhistory` file, and it's easy to accidentally share without realising it.
+
+### Environment variables
+
+Asking each time is a hassle, so you might want to store the secret across sessions. One easy way to do that is with environment variables. Environment variables, or __envvars__ for short, are a cross platform way of passing information to processes.
+
+For passing envvars to R, you can list name-value pairs in a file called `.Renviron` in your home directory. The easiest way to edit it is to run:
+
+```{r, eval = FALSE}
+file.edit("~/.Renviron")
+```
+
+The file looks something like
+
+```
+VAR1 = value1
+VAR2 = value2
+```
+
+```{r, include = FALSE}
+Sys.setenv("VAR1" = "value1")
+```
+
+And you can access the values in R using `Sys.getenv()`:
+
+```{r}
+Sys.getenv("VAR1")
+```
+
+Note that `.Renviron` is only processed on startup, so you'll need to restart R to see changes.
+
+These environment variables will be available in every running R process, and can easily be read by any other program on your computer to access that file directly. For more security, use the keyring package. 
+
+### Keyring
+
+The [keyring](https://github.com/r-lib/keyring) package provides a way to store (and retrieve) data in your OS's secure secret store. Keyring has a simple API:
+
+```{r, eval = FALSE}
+keyring::key_set("MY_SECRET")
+keyring::key_get("MY_SECRET")
+```
+
+By default, keyring will use the system keyring. This is unlocked by default when you log in, which means while the password is stored securely pretty much any process can access it.
+
+If you want to be even more secure, you can create custom keyring and keep it locked. That will require you to enter a password every time you want to access your secret. 
+
+```{r, eval = FALSE}
+keyring::keyring_create("httr")
+keyring::key_set("MY_SECRET", keyring = "httr")
+```
+
+Note that accessing the key always unlocks the keyring, so if you're being really careful, make sure to lock it again afterwards.
+
+```{r, eval = FALSE}
+keyring::keyring_lock("httr")
+```
+
+You might wonder if we've actually achieved anything here because we still need to enter a password! However, that one password lets you access every secret, and you can control how often you need to re-enter it by manually locking and unlocking the keyring.
+
+## Sharing with others
+
+By and large, managing secrets on your own computer is straightforward. The challenge comes when you need to share them with selected others:
+
+* You may need to share a secret with me so that I can run your reprex
+  and figure out what is wrong with httr.
+  
+* You might want to share a secret amongst a group of developers all working
+  on the same GitHub project.
+  
+* You might want to automatically run authenticated tests on travis.
+
+To make this work, all the techniques in this section rely on __public key cryptography__. This is a type of asymmetric encryption where you use a public key to produce content that can only be decrypted by the holder of the matching private key.
+
+### Reprexes
+
+The most common place you might need to share a secret is to generate a reprex. First, do everything you can do eliminate the need to share a secret:
+
+* If it is an http problem, make sure to run all requests with `verbose()`.
+* If you get an R error, make sure to include `traceback()`.
+
+If you're lucky, that will be sufficient information to fix the problem.
+
+Otherwise, you'll need to encrypt the secret so you can share it with me. The easiest way to do so is with the following snippet:
+
+```{r, eval = FALSE}
+library(openssl)
+library(jsonlite)
+library(curl)
+
+encrypt <- function(secret, username) {
+  url <- paste("https://api.github.com/users", username, "keys", sep = "/")
+
+  resp <- httr::GET(url)
+  httr::stop_for_status(resp)
+  pubkey <- httr::content(resp)[[1]]$key
+
+  opubkey <- openssl::read_pubkey(pubkey)
+  cipher <- openssl::rsa_encrypt(charToRaw(secret), opubkey)
+  jsonlite::base64_enc(cipher)
+}
+  
+cipher <- encrypt("<username>\n<password>", "hadley")
+cat(cipher)
+```
+
+Then I can run the following code on my computer to access it:
+
+```{r, eval = FALSE}
+decrypt <- function(cipher, key = openssl::my_key()) {
+  cipherraw <- jsonlite::base64_dec(cipher)
+  rawToChar(openssl::rsa_decrypt(cipherraw, key = key))
+}
+
+decrypt(cipher)
+#> username
+#> password
+```
+
+Change your password before and after you share it with me or anyone else. 
+
+### GitHub
+
+If you want to share secrets with a group of other people on GitHub, use the [secret](https://github.com/gaborcsardi/secret) or [cyphr](https://github.com/richfitz/cyphr) packages.
+
+### Travis
+
+The easiest way to handle short secrets is to use environment variables. You'll set in your `.Renviron` locally and in the settings pane on travis. That way you can use `Sys.getenv()` to access in both places. It's also possible to set encrypted env vars in your `.travis.yml`: see [the documentation][travis-envvar] for details.
+
+Regardless of how you set it, make sure you have a helper to retrieve the value. A good error message will save you a lot of time when debugging problems!
+
+```{r}
+my_secret <- function() {
+  val <- Sys.getenv("SECRET")
+  if (identical(val, "")) {
+    stop("`SECRET` env var has not been set")
+  }
+  val
+}
+```
+
+Note that encrypted data is not available in pull requests in forks. Typically you'll need to check PRs locally once you've confirmed that the code isn't actively malicious.
+
+To share secret files on travis, see <https://docs.travis-ci.com/user/encrypting-files/>. Basically you will encrypt the file locally and check it in to git. Then you'll add a decryption step to your `.travis.yml` which makes it decrypts it for each run. See [bigquery][travis-bigrquery] for an example.
+
+Be careful to not accidentally expose the secret on travis. An easy way to accidentally expose the secret is to print it out so that it's captured in the log. Don't do that!
+
+## CRAN 
+
+There is no way to securely share information with arbitrary R users, including CRAN. That means that if you're developing a package, you need to make sure that `R CMD check` passes cleanly even when authentication is not available. This tends to primarily affect the documentation, vignettes, and tests.
+
+### Documentation
+
+Like any R package, an API client needs clear and complete documentation of all functions. Examples are particularly useful but may need to be wrapped in `\donttest{}` to avoid challenges of authentication, rate limiting, lack of network access, or occasional API server down time.
+
+### Vignettes
+
+Vignettes pose additional challenges when an API requires authentication, because you don't want to bundle your own credentials with the package! However, you can take advantage of the fact that the vignette is built locally, and only checked by CRAN. In a setup chunk, do:
+
+```{r}
+NOT_CRAN <- identical(tolower(Sys.getenv("NOT_CRAN")), "true")
+knitr::opts_chunk$set(purl = NOT_CRAN)
+```
+
+And then use `eval = NOT_CRAN` in any chunk that requires access to a secret.
+
+### Testing
+
+Use `testthat::skip()` to automatically skip tests that require authentication. I typically will wrap this into a little helper function that I call at the start of every test requiring auth.
+
+```{r}
+skip_if_no_auth <- function() {
+  if (identical(Sys.getenv("MY_SECRET"), "")) {
+    skip("No authentication available")
+  }
+}
+```
+
+[google-secret]: https://developers.google.com/identity/protocols/OAuth2#installed
+[google-server]: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
+[github-token]: https://github.com/blog/1509-personal-api-tokens
+[travis-envvar]: https://docs.travis-ci.com/user/environment-variables/
+[travis-bigrquery]: https://github.com/rstats-db/bigrquery/blob/master/.travis.yml

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/r-cran-httr.git



More information about the debian-med-commit mailing list