[med-svn] [r-cran-httr] 07/09: New upstream version 1.2.1

Andreas Tille tille at debian.org
Fri Sep 29 19:39:13 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 d8a1fb6c238c0596c27517bb052432e3f37d626a
Author: Andreas Tille <tille at debian.org>
Date:   Fri Sep 29 21:37:25 2017 +0200

    New upstream version 1.2.1
---
 DESCRIPTION                             |  25 ++
 LICENSE                                 |   2 +
 MD5                                     | 168 +++++++++
 NAMESPACE                               | 113 ++++++
 NEWS.md                                 | 531 +++++++++++++++++++++++++++++
 R/authenticate.r                        |  31 ++
 R/body.R                                |  77 +++++
 R/cache.R                               | 144 ++++++++
 R/config.r                              | 202 +++++++++++
 R/content-parse.r                       | 125 +++++++
 R/content.r                             | 109 ++++++
 R/cookies.r                             |  41 +++
 R/date.R                                |  57 ++++
 R/doctor.R                              |  30 ++
 R/envvar.R                              |  98 ++++++
 R/handle-pool.r                         |  37 ++
 R/handle-url.r                          |  17 +
 R/handle.r                              |  58 ++++
 R/headers.r                             | 149 ++++++++
 R/http-browse.r                         |  19 ++
 R/http-delete.r                         |  37 ++
 R/http-get.r                            |  68 ++++
 R/http-head.r                           |  29 ++
 R/http-patch.r                          |  15 +
 R/http-post.r                           |  51 +++
 R/http-put.r                            |  21 ++
 R/http-verb.R                           |  24 ++
 R/httr.r                                |  28 ++
 R/insensitive.r                         |  34 ++
 R/media-guess.r                         |  11 +
 R/media-parse.r                         |  56 +++
 R/oauth-app.r                           |  56 +++
 R/oauth-cache.R                         |  98 ++++++
 R/oauth-endpoint.r                      | 123 +++++++
 R/oauth-error.r                         |  25 ++
 R/oauth-exchanger.r                     |  23 ++
 R/oauth-init.R                          | 123 +++++++
 R/oauth-listener.r                      |  90 +++++
 R/oauth-refresh.R                       |  38 +++
 R/oauth-server-side.R                   |  95 ++++++
 R/oauth-signature.r                     | 107 ++++++
 R/oauth-token-utils.R                   |  70 ++++
 R/oauth-token.r                         | 314 +++++++++++++++++
 R/progress.R                            |  78 +++++
 R/proxy.r                               |  30 ++
 R/request.R                             | 165 +++++++++
 R/response-status.r                     | 323 ++++++++++++++++++
 R/response-type.R                       |  23 ++
 R/response.r                            |  85 +++++
 R/retry.R                               |  57 ++++
 R/safe-callback.R                       |  10 +
 R/sha1.r                                |  40 +++
 R/str.R                                 |  58 ++++
 R/timeout.r                             |  18 +
 R/upload-file.r                         |  21 ++
 R/url-query.r                           |  29 ++
 R/url.r                                 | 165 +++++++++
 R/user-agent.r                          |  15 +
 R/utils.r                               |  86 +++++
 R/verbose.r                             |  75 ++++
 R/write-function.R                      |  90 +++++
 R/zzz.R                                 |  17 +
 README.md                               |  51 +++
 build/vignette.rds                      | Bin 0 -> 261 bytes
 debian/README.test                      |   9 -
 debian/changelog                        |  13 -
 debian/compat                           |   1 -
 debian/control                          |  28 --
 debian/copyright                        |  31 --
 debian/docs                             |   3 -
 debian/rules                            |   4 -
 debian/source/format                    |   1 -
 debian/tests/control                    |   3 -
 debian/tests/run-unit-test              |  12 -
 debian/watch                            |   3 -
 demo/00Index                            |  12 +
 demo/connection-sharing.r               |  17 +
 demo/oauth1-twitter.r                   |  23 ++
 demo/oauth1-vimeo.r                     |  21 ++
 demo/oauth1-withings.r                  |  30 ++
 demo/oauth1-yahoo.r                     |  15 +
 demo/oauth2-azure.r                     |  52 +++
 demo/oauth2-facebook.r                  |  22 ++
 demo/oauth2-github.r                    |  28 ++
 demo/oauth2-google.r                    |  21 ++
 demo/oauth2-linkedin.r                  |  44 +++
 demo/oauth2-reddit.R                    |  17 +
 demo/service-account.R                  |  22 ++
 inst/doc/api-packages.R                 | 199 +++++++++++
 inst/doc/api-packages.Rmd               | 487 ++++++++++++++++++++++++++
 inst/doc/api-packages.html              | 547 ++++++++++++++++++++++++++++++
 inst/doc/quickstart.R                   | 106 ++++++
 inst/doc/quickstart.Rmd                 | 251 ++++++++++++++
 inst/doc/quickstart.html                | 584 ++++++++++++++++++++++++++++++++
 man/BROWSE.Rd                           |  43 +++
 man/DELETE.Rd                           |  92 +++++
 man/GET.Rd                              |  82 +++++
 man/HEAD.Rd                             |  59 ++++
 man/PATCH.Rd                            |  67 ++++
 man/POST.Rd                             |  79 +++++
 man/PUT.Rd                              |  77 +++++
 man/RETRY.Rd                            |  89 +++++
 man/Token-class.Rd                      |  59 ++++
 man/VERB.Rd                             |  78 +++++
 man/add_headers.Rd                      |  41 +++
 man/authenticate.Rd                     |  33 ++
 man/cache_info.Rd                       |  51 +++
 man/config.Rd                           |  64 ++++
 man/content.Rd                          |  87 +++++
 man/content_type.Rd                     |  47 +++
 man/cookies.Rd                          |  22 ++
 man/guess_media.Rd                      |  16 +
 man/handle.Rd                           |  48 +++
 man/handle_pool.Rd                      |  24 ++
 man/has_content.Rd                      |  17 +
 man/headers.Rd                          |  23 ++
 man/hmac_sha1.Rd                        |  18 +
 man/http_condition.Rd                   |  54 +++
 man/http_error.Rd                       |  43 +++
 man/http_status.Rd                      |  50 +++
 man/http_type.Rd                        |  28 ++
 man/httr.Rd                             |  33 ++
 man/httr_dr.Rd                          |  12 +
 man/httr_options.Rd                     |  45 +++
 man/init_oauth1.0.Rd                    |  27 ++
 man/init_oauth2.0.Rd                    |  41 +++
 man/insensitive.Rd                      |  23 ++
 man/jwt_signature.Rd                    |  28 ++
 man/modify_url.Rd                       |  20 ++
 man/oauth1.0_token.Rd                   |  45 +++
 man/oauth2.0_token.Rd                   |  63 ++++
 man/oauth_app.Rd                        |  47 +++
 man/oauth_callback.Rd                   |  14 +
 man/oauth_endpoint.Rd                   |  42 +++
 man/oauth_endpoints.Rd                  |  19 ++
 man/oauth_exchanger.Rd                  |  24 ++
 man/oauth_listener.Rd                   |  29 ++
 man/oauth_service_token.Rd              |  36 ++
 man/oauth_signature.Rd                  |  31 ++
 man/parse_http_date.Rd                  |  40 +++
 man/parse_media.Rd                      |  34 ++
 man/parse_url.Rd                        |  40 +++
 man/progress.Rd                         |  27 ++
 man/response.Rd                         |  29 ++
 man/revoke_all.Rd                       |  18 +
 man/safe_callback.Rd                    |  16 +
 man/set_config.Rd                       |  36 ++
 man/set_cookies.Rd                      |  32 ++
 man/sha1_hash.Rd                        |  20 ++
 man/sign_oauth.Rd                       |  19 ++
 man/status_code.Rd                      |  15 +
 man/stop_for_status.Rd                  |  63 ++++
 man/timeout.Rd                          |  28 ++
 man/upload_file.Rd                      |  23 ++
 man/use_proxy.Rd                        |  32 ++
 man/user_agent.Rd                       |  25 ++
 man/verbose.Rd                          |  72 ++++
 man/with_config.Rd                      |  39 +++
 man/write_disk.Rd                       |  37 ++
 man/write_function.Rd                   |  18 +
 man/write_stream.Rd                     |  27 ++
 tests/testthat.R                        |   2 +
 tests/testthat/data.txt                 |   3 +
 tests/testthat/test-body.r              |  95 ++++++
 tests/testthat/test-config.r            |  37 ++
 tests/testthat/test-content.R           |  24 ++
 tests/testthat/test-encoding.R          |  15 +
 tests/testthat/test-header.r            |  57 ++++
 tests/testthat/test-http-condition.R    |  15 +
 tests/testthat/test-http-error.R        |  19 ++
 tests/testthat/test-oauth-cache.R       |  41 +++
 tests/testthat/test-oauth-server-side.R |  23 ++
 tests/testthat/test-oauth.R             |  46 +++
 tests/testthat/test-parse_media.R       |  17 +
 tests/testthat/test-request.r           |  11 +
 tests/testthat/test-response.r          |  57 ++++
 tests/testthat/test-ssl.R               |   6 +
 tests/testthat/test-url.r               |  93 +++++
 vignettes/api-packages.Rmd              | 487 ++++++++++++++++++++++++++
 vignettes/quickstart.Rmd                | 251 ++++++++++++++
 180 files changed, 11462 insertions(+), 108 deletions(-)

diff --git a/DESCRIPTION b/DESCRIPTION
new file mode 100644
index 0000000..b3f965c
--- /dev/null
+++ b/DESCRIPTION
@@ -0,0 +1,25 @@
+Package: httr
+Version: 1.2.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
+    additional request components (authenticate(), add_headers() and so on).
+Authors at R: c(
+    person("Hadley", "Wickham", , "hadley at rstudio.com", role = c("aut", "cre")),
+    person("RStudio", role = "cph")
+    )
+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
+VignetteBuilder: knitr
+License: MIT + file LICENSE
+URL: https://github.com/hadley/httr
+RoxygenNote: 5.0.1
+NeedsCompilation: no
+Packaged: 2016-07-03 17:01:25 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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..eea4c84
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,2 @@
+YEAR: 2014
+COPYRIGHT HOLDER: Hadley Wickham; RStudio
diff --git a/MD5 b/MD5
new file mode 100644
index 0000000..f6fff03
--- /dev/null
+++ b/MD5
@@ -0,0 +1,168 @@
+0469ba848f54ac99f0702dc78cb66547 *DESCRIPTION
+d8d2f9bd4836cd6c839888295d3b1da5 *LICENSE
+0e42ce7b977f0463ce599dedd8108932 *NAMESPACE
+abb7cd67ea6cd855fd7badac905021be *NEWS.md
+bd92750bfa50b28c02c7b90e049e186a *R/authenticate.r
+b48786effe2ad5fb819a41cb34ddcc2f *R/body.R
+aaba53b8b8faf228de81282eb510a22c *R/cache.R
+186e762d7e02335e191a9aa4275d18c3 *R/config.r
+036ef2bc6958ab6b8a49e59e897f018f *R/content-parse.r
+ae47dabf8973aa9714f60ca86dde57f3 *R/content.r
+648908115553c6b0e9da63c1d8693d5b *R/cookies.r
+2ca8b65f19d27ae984ed4e1ad295112e *R/date.R
+9e376216ca3c78e2af4d590f91b5354f *R/doctor.R
+72c38e916893b267291d17caf6a175de *R/envvar.R
+e3e79952d1831087d2354bd6c3648846 *R/handle-pool.r
+f27b306d1606ee49f65505d21ce754ef *R/handle-url.r
+c41c717ffcde8a8b1b3502f853a93c27 *R/handle.r
+394746817a32c07dae3d8a025bd70d7c *R/headers.r
+9996ec31d1f08f593cf7d7d3559090d7 *R/http-browse.r
+25186600762a002bc2169441103a7f45 *R/http-delete.r
+41340a619a0dbcfbe89284dfc16d966f *R/http-get.r
+98bd9022843c4bb5738562963a95f90b *R/http-head.r
+552c5826400f6cf85689a46928dd0383 *R/http-patch.r
+8aaa841abc0d1a1850ef9e63d5ea8118 *R/http-post.r
+b0408bd78c3d7a391142a639199b280e *R/http-put.r
+9f4ceb2002108a4bfe90f83c72cf6cd6 *R/http-verb.R
+b879ebac436858946e832273628c272a *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
+9b91edccda60dd6f6e19ba9fe707e4af *R/oauth-error.r
+ed21a90d8f42cea7077bfbbdfb20b09e *R/oauth-exchanger.r
+cffe6a96c254fd8d14b8d06b0c6ecc8c *R/oauth-init.R
+f8135a8eb3405f696e03b38553055847 *R/oauth-listener.r
+64f4f6cc35841da3dcd6a690bb252d99 *R/oauth-refresh.R
+7c4d8f48650a3a04e90da1f676cf9fac *R/oauth-server-side.R
+386b280bbdb8ce0b010f52bd3af2a843 *R/oauth-signature.r
+92de85f0fff3463c39d5d3d27d46cb80 *R/oauth-token-utils.R
+13c4bb1d32d822c4447527541b82dd49 *R/oauth-token.r
+4c9ff31809d82b3911940235ed9ff687 *R/progress.R
+d8556502db0dea0410fe50da9a96c32c *R/proxy.r
+6fa67c500f7360fc608efd66bbad2826 *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
+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
+824dafb1434170d307c8966f03f580c0 *R/user-agent.r
+577336a211079c5837e45dd9f5d24d56 *R/utils.r
+f9ca13c89c4014c460fe3d4272f5d54f *R/verbose.r
+f82164fab7adbbe500b3a1d5b4ddcd30 *R/write-function.R
+ada599319a3dd7c4c2b13d0921688ca0 *R/zzz.R
+c12ca1179b8fa14b689be708fe712b77 *README.md
+1ff114b30673d19ebf5bd230cdcd06ac *build/vignette.rds
+4d3d0fc8511f908ca94f30b6c2d0d5dc *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
+8911d6fd881354e1208eaf810e48a01d *demo/oauth2-google.r
+328191c10a2aeaa4521937b1cac34312 *demo/oauth2-linkedin.r
+c2544c2fe736898cb88e98d5617cf814 *demo/oauth2-reddit.R
+56b0c883a1b838991459f7eecc14bbc5 *demo/service-account.R
+87d51740a72639b47b511b1e65483341 *inst/doc/api-packages.R
+f6864e8c7578d7f38b36afd4cc526ba6 *inst/doc/api-packages.Rmd
+5e030c82fad4dcd6db4af986bb2a1026 *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
+24f3564620367449ffc825c5bafb0964 *tests/testthat.R
+7fe4b98fb68caf83fa31905435f08da0 *tests/testthat/data.txt
+a0b8fa3b6d09d21a6834f93f678e9d67 *tests/testthat/test-body.r
+0b30ee69b1e565aba653220cce46bd13 *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
+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
diff --git a/NAMESPACE b/NAMESPACE
new file mode 100644
index 0000000..eb9cab5
--- /dev/null
+++ b/NAMESPACE
@@ -0,0 +1,113 @@
+# Generated by roxygen2: do not edit by hand
+
+S3method("$",insensitive)
+S3method("[",insensitive)
+S3method("[[",insensitive)
+S3method(as.character,form_file)
+S3method(as.character,response)
+S3method(c,request)
+S3method(cookies,handle)
+S3method(cookies,response)
+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)
+S3method(print,oauth_endpoint)
+S3method(print,opts_list)
+S3method(print,request)
+S3method(print,response)
+S3method(status_code,numeric)
+S3method(status_code,response)
+export(BROWSE)
+export(DELETE)
+export(GET)
+export(HEAD)
+export(PATCH)
+export(POST)
+export(PUT)
+export(RETRY)
+export(Token)
+export(Token1.0)
+export(Token2.0)
+export(TokenServiceAccount)
+export(VERB)
+export(accept)
+export(accept_json)
+export(accept_xml)
+export(add_headers)
+export(authenticate)
+export(build_url)
+export(cache_info)
+export(config)
+export(content)
+export(content_type)
+export(content_type_json)
+export(content_type_xml)
+export(cookies)
+export(curl_docs)
+export(guess_media)
+export(handle)
+export(handle_find)
+export(handle_reset)
+export(has_content)
+export(headers)
+export(hmac_sha1)
+export(http_condition)
+export(http_date)
+export(http_error)
+export(http_status)
+export(http_type)
+export(httr_dr)
+export(httr_options)
+export(init_oauth1.0)
+export(init_oauth2.0)
+export(insensitive)
+export(message_for_status)
+export(modify_url)
+export(oauth1.0_token)
+export(oauth2.0_token)
+export(oauth_app)
+export(oauth_callback)
+export(oauth_endpoint)
+export(oauth_endpoints)
+export(oauth_exchanger)
+export(oauth_header)
+export(oauth_listener)
+export(oauth_service_token)
+export(oauth_signature)
+export(parse_http_date)
+export(parse_media)
+export(parse_url)
+export(parsed_content)
+export(progress)
+export(rerequest)
+export(reset_config)
+export(revoke_all)
+export(safe_callback)
+export(set_config)
+export(set_cookies)
+export(sha1_hash)
+export(sign_oauth1.0)
+export(sign_oauth2.0)
+export(status_code)
+export(stop_for_status)
+export(text_content)
+export(timeout)
+export(upload_file)
+export(url_ok)
+export(url_success)
+export(use_proxy)
+export(user_agent)
+export(verbose)
+export(warn_for_status)
+export(with_config)
+export(with_verbose)
+export(write_disk)
+export(write_function)
+export(write_memory)
+export(write_stream)
+importFrom(R6,R6Class)
diff --git a/NEWS.md b/NEWS.md
new file mode 100644
index 0000000..4356c06
--- /dev/null
+++ b/NEWS.md
@@ -0,0 +1,531 @@
+# httr 1.2.1
+
+* Fix bug with new cache creation code: need to check that 
+  cache isn't an empty file.
+
+# httr 1.2.0
+
+## New features
+
+* `oauth_signature()` no longer prepends 'oauth\_'  to additional parameters.
+  (@jimhester, #373)
+
+* All `print()` methods now invisibly return `x` (#355).
+
+* `DELETE()` gains a body parameter (#326).
+
+* New `encode = "raw"` allows you to do your own encoding for requests with
+  bodies.
+
+* New `http_type()` returns the content/mime type of a request, sans parameters.
+
+## Bug fixes and minor improvements
+
+* No longer uses use custom requests for standard `POST` requests (#356, 
+  #357). This has the side-effect of properly following redirects after 
+  `POST`, fixing some login issues (eg hadley/rvest#133).
+  
+* Long deprecated `multipart` argument to `POST()`, `PUT()` and `PATCH()`
+  has been removed.
+
+* The cross-session OAuth cache is now created with permission 0600, and should
+  give a better error if it can't be created (#365).
+
+* New `RETRY()` function allows you to retry a request multiple times until
+  it succeeds (#353).
+
+* The default user agent string is now computed once and cached. This 
+  is a small performance improvement, but important for local connections
+  (#322, @richfitz).
+
+* `oauth_callback()` gains trailing slash for facebook compatibility (#324).
+
+* `progress()` gains `con` argument to control where progress bar is rendered
+  (#359).
+
+* When `use_basic_auth` option is used to obtain a token, token refreshes 
+  will now use basic authentication too.
+
+* Suppress unhelpful "No encoding supplied: defaulting to UTF-8." when 
+  printing a response (#327).
+
+* All auto parser functions now have consistent arguments. This fixes problem
+  where `...` is pass on to another function (#330).
+
+* `parse_media()` can once again parse multiple parameters (#362, #366).
+
+* Correctly cast `config` in `POST()`.
+
+* Fix in readfunction to close connection when done.
+
+# httr 1.1.0
+
+## New features
+
+* `stop_for_status()`, `warn_for_status()` and (new) `message_for_status()`
+  replace `message` argument with new `task` argument that optionally describes
+  the current task. This allows API wrappers to provide more informative
+  error messages on failure (#277, #302). `stop_for_status()` and
+  `warn_for_status()` return the response if there were no errors. This 
+  makes them easier to use in pipelines (#278).
+
+* `url_ok()` and `url_successful()` have been deprecated in favour of the more
+  flexible `http_error()`, which works with urls, responses and integer status
+  codes (#299).
+
+## OAuth
+
+* `oauth1.0_token()` gains RSA-SHA1 signature support with the `private_key`
+  argument (@nathangoulding, #316).
+
+* `oauth2.0_token()` throws an error if it fails to get an access token (#250)
+  and gains two new arguments:
+
+    * `user_params` allows you to pass arbitrary additional parameters to the 
+      token access endpoint when acquiring or refreshing a token 
+      (@cornf4ke, #312)
+    
+    * `use_basic_auth` allows you to pick use http authentication when
+      getting a token (#310, @grahamrp).
+
+* `oauth_service_token()` checks that its arguments are the correct types 
+  (#282) and anways returns a `request` object (#313, @nathangoulding).
+
+* `refresh_oauth2.0()` checks for known OAuth2.0 errors and clears the
+  locally cached token in the presense of any (@nathangoulding, #315).
+
+## Bug fixes and minor improvements
+
+* httr no longer bundles `cacert.pem`, and instead it relies on the bundle in 
+  openssl. This bundle is only used a last-resort on windows with R <3.2.0.
+
+* Switch to 'openssl' package for hashing, hmac, signatures, and base64.
+
+* httr no longer depends on stringr (#285, @jimhester).
+
+* `build_url()` collapses vector `path` with `/` (#280, @artemklevtsov).
+
+* `content(x)` uses xml2 for XML documents and readr for csv and tsv.
+
+* `content(, type = "text")` defaults to UTF-8 encoding if not otherwise
+  specified.
+
+* `has_content()` correctly tests for the presence/absence of body content (#91).
+
+* `parse_url()` correctly parses urls like `file:///a/b/c` work (#309).
+
+* `progress()` returns `TRUE` to fix for 'progress callback must return boolean' 
+  warning (@jeroenooms, #252).
+
+* `upload_file()` supports very large files (> 2.5 Gb) (@jeroenooms, #257).
+
+# httr 1.0.0
+
+* httr no longer uses the RCurl package. Instead it uses the curl package, 
+  a modern binding to libcurl written by Jeroen Ooms (#172). This should make 
+  httr more reliable and prevent the "easy handle already used in multi handle" 
+  error. This change shouldn't affect any code that uses httr - all the changes 
+  have happened behind the scenes.
+
+* The `oauth_listener` can now listen on a custom IP address and port (the 
+  previously hardwired ip:port of `127.0.0.1:1410` is now just the default).
+  This permits authentication to work under other settings, such as inside 
+  docker containers (which require localhost uses `0.0.0.0` instead). To 
+  configure, set the system environmental variables `HTTR_LOCALHOST` and 
+  `HTTR_PORT` respectively (@cboettig, #211).
+
+* `POST(encode = 'json')` now automatically turns length-1 vectors into json
+  scalars. To prevent this automatic "unboxing", wrap the vector in `I()` 
+  (#187).
+
+* `POST()`, `PUT()` and `PATCH()` now drop `NULL` body elements. This is 
+  convenient and consistent with the behaviour for url query params.
+
+## Minor improvements and bug fixes
+
+* `cookies` argument to `handle()` is deprecated - cookies are always
+  turned on by default.
+
+* `brew_dr()` has been renamed to `httr_dr()` - that's what it should've 
+  been in the first place!
+
+* `content(type = "text")` compares encodings in a case-insensitive manner
+  (#209).
+
+* `context(type = "auto")` uses a better strategy for text based formats (#209).
+  This should allow the `encoding` argument to work more reliably.
+
+* `config()` now cleans up duplicated options (#213).
+
+* Uses `CURL_CA_BUNDLE` environment variable to look for cert bundle on 
+  Windows (#223).
+  
+* `safe_callback()` is deprecated - it's no longer needed with curl.
+
+* `POST()` and `PUT()` now clean up after themselves when uploading a single 
+  file (@mtmorgan).
+
+* `proxy()` gains an `auth` argument which allows you to pick the type of
+  http authentication used by the proxy (#216).
+
+* `VERB()` gains `body` and `encode` arguments so you can generate 
+  arbitrary requests with a body.
+
+* tumblr added as an `oauth_endpoint`.
+
+# httr 0.6.1
+
+* Correctly parse headers with multiple `:`, thanks to @mmorgan (#180).
+
+* In `content()`, if no type is provided to function or specified in headers,
+  and we can't guess the type from the extension, we now assume that it's 
+  `application/octet-stream` (#181).
+
+* Throw error if `timeout()` is less than 1 ms (#175).
+
+* Improved LinkedIn OAuth demo (#173).
+
+# httr 0.6.0
+
+## New features
+
+* New `write_stream()` allows you to process the response from a server as 
+  a stream of raw vectors (#143).
+
+* Suport for Google OAuth2 
+  [service accounts](https://developers.google.com/accounts/docs/OAuth2ServiceAccount).
+  (#119, thanks to help from @siddharthab).
+
+* `VERB()` allows to you use custom http verbs (#169).
+
+* New `handle_reset()` to allow you to reset the handle if you get the error
+  "easy handle already used in multi handle" (#112).
+
+* Uses R6 instead of RC. This makes it possible to extend the OAuth
+  classes from outside of httr (#113).
+
+* Now only set `capath` on Windows - system defaults on linux and mac ox 
+  seem to be adequate (and in some cases better). I've added a couple of tests
+  to ensure that this continues to work in the future.
+
+## Minor improvements and bug fixes
+
+* `vignette("api-packages")` gains more detailed instructions on
+  setting environment variables, thanks to @jennybc.
+
+* Add `revoke_all()` to revoke all stored tokens (if possible) (#77).
+
+* Fix for OAuth 2 process when using `options(httr_oob_default = TRUE)`
+  (#126, @WillemPaling).
+
+* New `brew_dr()` checks for common problems. Currently checks if your libCurl 
+  uses NSS. This is unlikely to work so it gives you some advice on how to 
+  fix the problem (thanks to @eddelbuettel for debugging this problem).
+
+* `Content-Type` set to title case to avoid errors in servers which do not
+  correctly implement case insensitivity in header names. (#142, #146) thanks
+  to Håkon Malmedal (@hmalmedal) and Jim Hester (@jimhester).
+
+* Correctly parse http status when it only contains two components (#162).
+
+* Correctly parse http headers when field name is followed by any amount
+  (including none) of white space.
+
+* Default "Accepts" header set to 
+  `application/json, text/xml, application/xml, */*`: this should slightly
+  increase the likelihood of getting xml back. `application/xml` is correctly
+  converted to text before being parsed to `XML::xmlParse()` (#160).
+
+* Make it again possible to override the content type set up by `POST()`
+  when sending data (#140).
+
+* New `safe_callback()` function operator that makes R functions safe for
+  use as RCurl callbacks (#144).
+  
+* Added support for passing oauth1 tokens in URL instead of the headers 
+  (#145, @bogstag).
+
+* Default to out-of-band credential exchange when `httpuv` isn't installed.
+  (#168)
+
+## Deprecated and deleted functions
+
+* `new_token()` has been removed - this was always an internal function
+  so you should never have been using it. If you were, switch to creating
+  the tokens directly. 
+
+* Deprecate `guess_media()`, and instead use `mime::guess_type()` (#148).
+
+# httr 0.5
+
+* You can now save response bodies directly to disk by using the `write_disk()`
+  config. This is useful if you want to capture large files that don't fit in
+  memory (#44).
+
+* Default accept header is now "application/json, text/xml, */*" - this should
+  encourage servers to send json or xml if they know how.
+
+* `httr_options()` allows you to easily filter the options, e.g. 
+  `httr_options("post")`
+  
+* `POST()` now specifies Curl options more precisely so that Curl know's 
+  that you're doing a POST and can respond appropriately to redirects.
+  
+## Caching
+
+* Preliminary and experimental support for caching with `cache_info()` and
+  `rerequest()` (#129). Be aware that this API is likely to change in 
+  the future.
+
+* `parse_http_date()` parses http dates according RFC2616 spec.
+
+* Requests now print the time they were made.
+
+* Mime type `application/xml` is automatically parsed with ``XML::xmlParse()`.
+  (#128)
+
+## Minor improvements and bug fixes
+
+* Now possible to specify both handle and url when making a request.
+
+* `content(type = "text")` uses `readBin()` instead of `rawToChar()` so
+  that strings with embedded NULLs (e.g. WINDOWS-1252) can be re-encoded
+  to UTF-8.
+
+* `DELETE()` now returns body of request (#138).
+
+* `headers()` is now a generic with a method for response objects.
+
+* `parse_media()` failed to take into account that media types are 
+  case-insenstive - this lead to bad re-encoding for content-types like
+  "text/html; Charset=UTF-8"
+
+* Typo which broke `set_cookies()` fixed by @hrbrmstr.
+
+* `url_ok()` works correctly now, instead of always returning `FALSE`,
+  a bug since version 0.4 (#133).
+  
+* Remove redundant arguments `simplifyDataFrame` and `simplifyMatrix` for json parser.
+
+# httr 0.4
+
+## New features
+
+* New `headers()` and `cookies()` functions to extract headers and cookies 
+  from responses. Previoulsy internal `status_code()` function now exported
+  to extract `status_code()` from responses.
+
+* `POST()`, `PUT()`, and `PATCH()` now use `encode` argument to determine how
+  list inputs are encoded. Valid values are "multiple", "form" or "json".
+  The `multipart` argument is now deprecated (#103). You can stream a single 
+  file from disk with  `upload_file("path/")`. The mime type will be guessed 
+  from the extension, or can be supplied explicitly as the second argument to 
+  `upload_file()`.
+
+* `progress()` will display a progress bar, useful if you're doing large 
+  uploads or downloads (#17).
+
+* `verbose()` now uses a custom debug function so that you can see exactly
+  what data is sent to the server. Arguments control exactly what is included,
+  and the defaults have been selected to be more helpful for the most common
+  cases (#102).
+
+* `with_verbose()` makes it easier to see verbose information when http 
+  requests are made within other functions (#87).
+
+## Documentation improvements
+
+* New `quickstart` vignette to help you get up and running with httr.
+
+* New `api-packages` vignette describes how best practices to follow when
+  writing R packages that wrap web APIs.
+
+* `httr_options()` lists all known config options, translating between
+  their short R names and the full libcurl names. The `curl_doc()` helper
+  function allows you to jump directly to the online documentation for an
+  option.
+
+## Minor improvements
+
+* `authenticate()` now defaults to `type = "basic"` which is pretty much the
+  only type of authentication anyone uses.
+
+* Updated `cacert.pem` to version at 2014-04-22 (#114).
+
+* `content_type()`, `content_type_xml()` and `content_type_json()` make it
+  easier to set the content type for `POST` requests (and other requests with
+  a body).
+
+* `has_content()` tells you if request has any content associated with it (#91).
+
+* Add `is_interactive()` parameter to `oauth_listener()`, `init_oauth1.0()` and
+  `init_oauth2.0()` (#90).
+
+* `oauth_signature()` and `oauth_header()` now exported to make it easier to 
+  construct custom authentication for APIs that use only some components of
+  the full OAuth process (e.g. 2 legged OAuth).
+
+* NULL `query` parameters are now dropped automatically.
+
+* When `print()`ing a response, httr will only attempt to print the first few 
+  lines if it's a text format (i.e. either the main type is text or is
+  application/json). It will also truncate each line so that it fits on
+  screen - this should hopefully make it easier to see a little bit of the
+  content, without filling the screen with gibberish.
+
+* `new_bin()` has been removed: it's easier to see what's going on in 
+  examples with `httpbin.org`.
+
+## Bug fixes
+
+* `user_agent()` once again overrides default (closes #97)
+
+* `parse(type = "auto")` returns NULL if no content associated with request 
+  (#91).
+  
+* Better strategy for resetting Curl handles prevents carry-over of error
+  status and other problems (#112).
+
+* `set_config()` and `with_config()` now work with `token`s (#111).
+
+# httr 0.3
+
+## OAuth improvements
+
+OAuth 2.0 has recieved a major overhaul in this version. The authentication
+dance now works in more environments (including RStudio), and is generally
+a little faster. When working on a remote server, or if R's internet connection
+is constrained in other ways, you can now use out-of-band authentication,
+copying and pasting from any browser to your R session. OAuth tokens from
+endpoints that regularly expire access tokens can now be refreshed, and will
+be refresh automatically on authentication failure.
+
+httr now uses project (working directory) based caching: every time you
+create or refresh a token, a copy of the credentials will be saved in
+`.httr-oauth`. You can override this default for individual tokens with the
+`cache` parameter, or globally with the `httr_oauth_cache` option. Supply
+either a logical vector (`TRUE` = always cache, `FALSE` = never cache,
+`NA` = ask), or a string (the path to the cache file).
+
+You should NOT include this cache file in source code control - if you do,
+delete it, and reset your access token through the corresponding web interface.
+To help, httr will automatically add appropriate entries to `.gitignore` and
+`.Rbuildignore`.
+
+These changes mean that you should only ever have to authenticate
+once per project, and you can authenticate from any environment in which
+you can run R. A big thanks go to Craig Citro (@craigcitro) from google,
+who contributed much code and many ideas to make this possible.
+
+* The OAuth token objects are now reference classes, which mean they can be
+  updated in place, such as when an access token expires and needs to be
+  refreshed. You can manually refresh by calling `$refresh()` on the object.
+  You can force reinitialisation (to do the complete dance from
+  scratch) by calling `$reinit(force = TRUE)`.
+
+* If a signed OAuth2 request fails with a 401 and the credentials have a
+  `refresh_token`, then the OAuth token will be automatically refreshed (#74).
+
+* OAuth tokens are cached locally in a file called `.httr-oauth` (unless
+  you opt out). This file should not be included in source code control,
+  and httr will automatically add to `.gitignore` and `.Rbuildignore`.
+  The caching policy is described in more detail in the help for the
+  `Token` class.
+
+* The OAuth2 dance can now be performed without running a local webserver
+  (#33, thanks to @craigcitro). To make that the default, set
+  `options(httr_oob_default = TRUE)`. This is useful when running R remotely.
+
+* Add support for passing oauth2 tokens in headers instead of the URL, and
+  make this the default (#34, thanks to @craigcitro).
+
+* OAuth endpoints can store arbitrary extra urls.
+
+* Use the httpuv webserver for the OAuth dance instead of the built-in
+  httpd server (#32, thanks to @jdeboer). This makes the dance work in
+  Rstudio, and also seems a little faster. Rook is no longer required.
+
+* `oauth_endpoints()` includes some popular OAuth endpoints.
+
+## Other improvements
+
+* HTTP verbs (`GET()`, `POST()` etc) now pass unnamed arguments to `config()`
+  and named arguments to `modify_url()` (#81).
+
+* The placement of `...` in `POST()`, `PATCH()` and `PUT()` has been tweaked
+  so that you must always specify `body` and `multipart` arguments with their
+  full name. This has always been recommended practice; now it is enforced.
+
+* `httr` includes its own copy of `cacert.pem`, which is more recent than
+  the version included in RCurl (#67).
+
+* Added default user agent which includes versions of Curl, RCurl and httr.
+
+* Switched to jsonlite from rjson.
+
+* Content parsers no longer load packages on to search path.
+
+* `stop_for_status()` now raises errors with useful classes so that you can
+  use `tryCatch()` to take different actions depending on the type of error.
+  See `http_condition()` for more details.
+
+* httr now imports the methods package so that it works when called with
+  Rscript.
+
+* New automatic parsers for mime types `text/tab-separated-values` and
+  `text/csv` (#49)
+
+* Add support for `fragment` in url building/parsing (#70, thanks to
+  @craigcitro).
+
+* You can suppress the body entirely in `POST()`, `PATCH()` and `PUT()`
+  with `body = FALSE`.
+
+## Bug fixes
+
+* If you supply multiple headers of the same name, the value of the most
+  recently set header will always be used.
+
+* Urls with missing query param values (e.g. `http://x.com/?q=`) are now
+  parsed correctly (#27). The names of query params are now also escaped
+  and unescaped correctly when parsing and building urls.
+
+* Default html parser is now `XML::htmlParse()` which is easier to use
+  with xpath (#66).
+
+# httr 0.2
+
+* OAuth now uses custom escaping function which is guaranteed to work on all
+  platforms (Fixes #21)
+
+* When concatenating configs, concatenate all the headers. (Fixes #19)
+
+* export `hmac_sha1` since so many authentication protocols need this
+
+* `content` will automatically guess what type of output (parsed, text or raw)
+  based on the content-type header. It also automatically converts text
+  content to UTF-8 (using the charset in the media type) and can guess at mime
+  type from extension if server doesn't supply one. Media type and encoding
+  can be overridden with the `type` and `encoding` arguments respectively.
+
+* response objects automatically print content type to aid debugging.
+
+* `text_content` has become `context(, "text")` and `parsed_content`
+  `content(, "parsed")`. The previous calls are deprecated and will be removed
+  in a future version.
+
+* In `oauth_listener`, use existing httpd port if help server has already been
+  started. This allows the ouath authentication dance to work if you're in
+  RStudio. (Fixes #15).
+
+* add several functions related to checking the status of an http request.
+  Those are : `status`, `url_ok` and `url_success` as well as
+  `stop_for_status` and `warn_for_status`.
+
+* `build_url`: correctly add params back into full url.
+
+# httr 0.1.1
+
+* Add new default config: use the standard SSL certificate
+
+* Add recommendation to use custom handles with `authenticate`
diff --git a/R/authenticate.r b/R/authenticate.r
new file mode 100644
index 0000000..9f6fe73
--- /dev/null
+++ b/R/authenticate.r
@@ -0,0 +1,31 @@
+#' Use http authentication.
+#'
+#' It's not obvious how to turn authentication off after using it, so
+#' I recommend using custom handles with authentication.
+#'
+#' @param user user name
+#' @param password password
+#' @param type type of HTTP authentication.  Should be one of the following
+#'   types supported by Curl: basic, digest, digest_ie, gssnegotiate,
+#'   ntlm, any.  It defaults to "basic", the most common type.
+#' @export
+#' @family config
+#' @examples
+#' GET("http://httpbin.org/basic-auth/user/passwd")
+#' GET("http://httpbin.org/basic-auth/user/passwd",
+#'   authenticate("user", "passwd"))
+authenticate <- function(user, password, type = "basic") {
+  stopifnot(is.character(user), length(user) == 1)
+  stopifnot(is.character(password), length(password) == 1)
+  stopifnot(is.character(type), length(type) == 1)
+
+  config(httpauth = auth_flags(type), userpwd = paste0(user, ":", password))
+}
+
+auth_flags <- function(x = "basic") {
+  constants <- c(basic = 1, digest = 2, gssnegotiate = 4, ntlm = 8,
+    digest_ie = 16, any = -17)
+  x <- match.arg(x, names(constants))
+
+  constants[[x]]
+}
diff --git a/R/body.R b/R/body.R
new file mode 100644
index 0000000..8fbb1f5
--- /dev/null
+++ b/R/body.R
@@ -0,0 +1,77 @@
+body_config <- function(body = NULL,
+                        encode = c("form", "json", "multipart", "raw"),
+                        type = NULL)  {
+  # No body
+  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")
+    size <- file.info(body$path)$size
+
+    return(c(
+      config(
+        post = TRUE,
+        readfunction = function(nbytes, ...) {
+          if(is.null(con))
+            return(raw())
+          bin <- readBin(con, "raw", nbytes)
+          if (length(bin) < nbytes){
+            close(con)
+            con <<- NULL
+          }
+          bin
+        },
+        postfieldsize_large = size
+      ),
+      content_type(body$type)
+    ))
+  }
+
+  # Post with empty body
+  if (is.null(body))
+    return(body_raw(raw()))
+
+  if (!is.list(body)) {
+    stop("Unknown type of `body`: must be NULL, FALSE, character, raw or list",
+      call. = FALSE)
+  }
+
+  body <- compact(body)
+
+  # Deal with three ways to encode: form, multipart & json
+  encode <- match.arg(encode)
+  if (encode == "raw") {
+    body_raw(body)
+  } else if (encode == "form") {
+    body_raw(compose_query(body), "application/x-www-form-urlencoded")
+  } else if (encode == "json") {
+    body_raw(jsonlite::toJSON(body, auto_unbox = TRUE), "application/json")
+  } 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))
+  }
+}
+
+body_raw <- function(body, type = NULL) {
+  if (is.character(body)) {
+    body <- charToRaw(paste(body, collapse = "\n"))
+  }
+  stopifnot(is.raw(body))
+
+  c(
+    config(
+      post = TRUE,
+      postfieldsize = length(body),
+      postfields = body
+    ),
+    # For raw bodies, override default POST content-type
+    content_type(type %||% "")
+  )
+}
diff --git a/R/cache.R b/R/cache.R
new file mode 100644
index 0000000..9b77206
--- /dev/null
+++ b/R/cache.R
@@ -0,0 +1,144 @@
+#' Compute caching information for a response.
+#'
+#' \code{cache_info()} gives details of cacheability of a response,
+#' \code{rerequest()} re-performs the original request doing as little work
+#' as possible (if not expired, returns response as is, or performs
+#' revalidation if Etag or Last-Modified headers are present).
+#'
+#' @param r A response
+#' @export
+#' @examples
+#' # Never cached, always causes redownload
+#' r1 <- GET("https://www.google.com")
+#' cache_info(r1)
+#' r1$date
+#' rerequest(r1)$date
+#'
+#' # Expires in a year
+#' r2 <- GET("https://www.google.com/images/srpr/logo11w.png")
+#' cache_info(r2)
+#' r2$date
+#' rerequest(r2)$date
+#'
+#' # Has last-modified and etag, so does revalidation
+#' r3 <- GET("http://httpbin.org/cache")
+#' cache_info(r3)
+#' r3$date
+#' rerequest(r3)$date
+#'
+#' # Expires after 5 seconds
+#' \dontrun{
+#' r4 <- GET("http://httpbin.org/cache/5")
+#' cache_info(r4)
+#' r4$date
+#' rerequest(r4)$date
+#' Sys.sleep(5)
+#' cache_info(r4)
+#' rerequest(r4)$date
+#' }
+cache_info <- function(r) {
+  stopifnot(is.response(r))
+
+  # If parsing fails using -Inf; If missing use NA
+  expires <- parse_http_date(r$headers$expires, Inf) %||% NULL
+
+  # Parse cache control
+  control <- parse_cache_control(r$headers$`cache-control`)
+  max_age <- as.integer(control$`max-age`) %||% NULL
+
+  # Compute expiry
+  if (!is.null(max_age)) {
+    expires <- r$date + max_age
+  } else if (!is.null(r$headers$expires)) {
+    expires <- parse_http_date(r$headers$expires, -Inf)
+  } else {
+    expires <- NULL
+  }
+
+  # Is this cacheable?
+  cacheable <- r$request$method %in% c("GET", "HEAD") &&
+    status_code(r) %in% c(200L, 203L, 300L, 301L, 410L) &&
+    (!is.null(expires) || !is.null(r$headers$etag) ||
+        !is.null(r$headers$`last-modified`)) &&
+    !any(c("no-store", "no-cache") %in% control$flags)
+  # What impact should any(c("public", "private") %in% control$flags) have?
+
+  structure(
+    list(
+      method = r$method,
+      url = r$url,
+
+      cacheable = cacheable,
+      expires = expires,
+      etag = r$headers$etag %||% NULL,
+      modified = parse_http_date(r$headers$`last-modified`, NULL)
+    ),
+    class = "cache_info"
+  )
+}
+
+is.cache_info <- function(x) inherits(x, "cache_info")
+
+#' @export
+print.cache_info <- function(x, ...) {
+  cat("<cache_info> ", x$method, " ", x$url, "\n", sep = "")
+  cat("  Cacheable:     ", x$cacheable, "\n", sep = "")
+  if (!is.null(x$expires)) {
+    cat("  Expires:       ", http_date(x$expires), sep = "")
+    if (x$expires < Sys.time()) cat(" <expired>")
+    cat("\n")
+  }
+  cat("  Last-Modified: ", http_date(x$modified), "\n", sep = "")
+  cat("  Etag:          ", x$etag, "\n", sep = "")
+  invisible(x)
+}
+
+parse_cache_control <- function(x) {
+  if (is.null(x)) return(list())
+
+  pieces <- strsplit(x, ",")[[1]]
+  pieces <- gsub("^\\s+|\\s+$", "", pieces)
+  pieces <- tolower(pieces)
+
+  is_value <- grepl("=", pieces)
+  flags <- pieces[!is_value]
+
+  keyvalues <- strsplit(pieces[is_value], "\\s*=\\s*")
+  keys <- c("flags", lapply(keyvalues, "[[", 1))
+  values <- c(flags, lapply(keyvalues, "[[", 2))
+
+  stats::setNames(values, keys)
+}
+
+#' @rdname cache_info
+#' @export
+rerequest <- function(r) {
+  x <- cache_info(r)
+  if (!x$cacheable) {
+    return(reperform(r))
+  }
+
+  # Cacheable, and hasn't expired
+  if (!is.null(x$expires) && x$expires >= Sys.time()) {
+    return(r)
+  }
+
+  # Requires validation
+  req <- c(r$request,
+    request(headers = c(
+      `If-Modified-Since` = http_date(x$modified),
+      `If-None-Match` = x$etag
+    ))
+  )
+  validated <- request_perform(req, r$handle)
+
+  if (status_code(validated) == 304L) {
+    r
+  } else {
+    validated
+  }
+}
+
+reperform <- function(resp) {
+  request_perform(resp$request, resp$handle)
+}
diff --git a/R/config.r b/R/config.r
new file mode 100644
index 0000000..a6665db
--- /dev/null
+++ b/R/config.r
@@ -0,0 +1,202 @@
+#' Set curl options.
+#'
+#' Generally you should only need to use this function to set CURL options
+#' directly if there isn't already a helpful wrapper function, like
+#' \code{\link{set_cookies}}, \code{\link{add_headers}} or
+#' \code{\link{authenticate}}. To use this function effectively requires
+#' some knowledge of CURL, and CURL options. Use \code{\link{httr_options}} to
+#' see a complete list of available options. To see the libcurl documentation
+#' for a given option, use \code{\link{curl_docs}}.
+#'
+#' Unlike Curl (and RCurl), all configuration options are per request, not
+#' per handle.
+#'
+#' @seealso \code{\link{set_config}} to set global config defaults, and
+#'  \code{\link{with_config}} to temporarily run code with set options.
+#' @param token An OAuth token (1.0 or 2.0)
+#' @family config
+#' @family ways to set configuration
+#' @seealso All known available options are listed in \code{\link{httr_options}}
+#' @param ... named Curl options.
+#' @export
+#' @examples
+#' # There are a number of ways to modify the configuration of a request
+#' # * you can add directly to a request
+#' HEAD("https://www.google.com", verbose())
+#'
+#' # * you can wrap with with_config()
+#' with_config(verbose(), HEAD("https://www.google.com"))
+#'
+#' # * you can set global with set_config()
+#' old <- set_config(verbose())
+#' HEAD("https://www.google.com")
+#' # and re-establish the previous settings with
+#' set_config(old, override = TRUE)
+#' HEAD("https://www.google.com")
+#' # or
+#' reset_config()
+#' HEAD("https://www.google.com")
+#'
+#' # If available, you should use a friendly httr wrapper over RCurl
+#' # options. But you can pass Curl options (as listed in httr_options())
+#' # in config
+#' HEAD("https://www.google.com/", config(verbose = TRUE))
+config <- function(..., token = NULL) {
+  request(options = list(...), auth_token = token)
+}
+
+#' List available options.
+#'
+#' This function lists all available options for \code{\link{config}()}.
+#' It provides both the short R name which you use with httr, and the longer
+#' Curl name, which is useful when searching the documentation. \code{curl_doc}
+#' opens a link to the libcurl documentation for an option in your browser.
+#'
+#' RCurl and httr use slightly different names to libcurl: the initial
+#' \code{CURLOPT_} is removed, all underscores are converted to periods and
+#' the option is given in lower case.  Thus "CURLOPT_SSLENGINE_DEFAULT"
+#' becomes "sslengine.default".
+#'
+#' @param x An option name (either short or full).
+#' @param matches If not missing, this restricts the output so that either
+#'   the httr or curl option matches this regular expression.
+#' @return A data frame with three columns:
+#' \item{httr}{The short name used in httr}
+#' \item{libcurl}{The full name used by libcurl}
+#' \item{type}{The type of R object that the option accepts}
+#' @export
+#' @examples
+#' httr_options()
+#' httr_options("post")
+#'
+#' # Use curl_docs to read the curl documentation for each option.
+#' # You can use either the httr or curl option name.
+#' curl_docs("userpwd")
+#' curl_docs("CURLOPT_USERPWD")
+httr_options <- function(matches) {
+
+  constants <- curl::curl_options()
+  constants <- constants[order(names(constants))]
+
+  rcurl <- tolower(names(constants))
+
+  opts <- data.frame(
+    httr = rcurl,
+    libcurl = translate_curl(rcurl),
+    type = curl_option_types(constants),
+    stringsAsFactors = FALSE
+  )
+
+  if (!missing(matches)) {
+    sel <- grepl(matches, opts$httr, ignore.case = TRUE) |
+      grepl(matches, opts$libcurl, ignore.case = TRUE)
+    opts <- opts[sel, , drop = FALSE]
+  }
+
+  opts
+}
+
+curl_option_types <- function(opts = curl::curl_options()) {
+  type_name <- c("integer", "string", "function", "number")
+  type <- floor(opts / 10000)
+
+  type_name[type + 1]
+}
+
+#' @export
+print.opts_list <- function(x, ...) {
+  cat(paste0(format(names(x)), ": ", x, collapse = "\n"), "\n", sep = "")
+  invisible(x)
+}
+
+translate_curl <- function(x) {
+  paste0("CURLOPT_", gsub(".", "_", toupper(x), fixed = TRUE))
+}
+
+#' @export
+#' @rdname httr_options
+curl_docs <- function(x) {
+  stopifnot(is.character(x), length(x) == 1)
+
+  opts <- httr_options()
+  if (x %in% opts$httr) {
+    x <- opts$libcurl[match(x, opts$httr)]
+  }
+  if (!(x %in% opts$libcurl)) {
+    stop(x, " is not a known curl option", call. = FALSE)
+  }
+
+  url <- paste0("http://curl.haxx.se/libcurl/c/", x, ".html")
+  BROWSE(url)
+}
+
+cache <- new.env(parent = emptyenv())
+cache$default_ua <- NULL
+
+default_ua <- function() {
+  if (is.null(cache$default_ua)) {
+    versions <- c(
+      libcurl = curl::curl_version()$version,
+      `r-curl` = as.character(utils::packageVersion("curl")),
+      httr = as.character(utils::packageVersion("httr")))
+    cache$default_ua <- paste0(names(versions), "/", versions, collapse = " ")
+  }
+  cache$default_ua
+}
+
+#' Set (and reset) global httr configuration.
+#'
+#' @param config Settings as generated by \code{\link{add_headers}},
+#'   \code{\link{set_cookies}} or \code{\link{authenticate}}.
+#' @param override if \code{TRUE}, ignore existing settings, if \code{FALSE},
+#'   combine new config with old.
+#' @return invisibility, the old global config.
+#' @family ways to set configuration
+#' @export
+#' @examples
+#' GET("http://google.com")
+#' set_config(verbose())
+#' GET("http://google.com")
+#' reset_config()
+#' GET("http://google.com")
+set_config <- function(config, override = FALSE) {
+  stopifnot(is.request(config))
+
+  old <- getOption("httr_config") %||% config()
+  if (!override) config <- c(old, config)
+  options(httr_config = config)
+  invisible(old)
+}
+
+#' @export
+#' @rdname set_config
+reset_config <- function() set_config(config(), TRUE)
+
+#' Execute code with configuration set.
+#'
+#' @family ways to set configuration
+#' @inheritParams set_config
+#' @param expr code to execute under specified configuration
+#' @export
+#' @examples
+#' with_config(verbose(), {
+#'   GET("http://had.co.nz")
+#'   GET("http://google.com")
+#' })
+#'
+#' # Or even easier:
+#' with_verbose(GET("http://google.com"))
+with_config <- function(config = config(), expr, override = FALSE) {
+  stopifnot(is.request(config))
+
+  old <- set_config(config, override)
+  on.exit(set_config(old, override = TRUE))
+  force(expr)
+}
+
+#' @export
+#' @param ... Other arguments passed on to \code{\link{verbose}}
+#' @rdname with_config
+with_verbose <- function(expr, ...) {
+  with_config(verbose(...), expr)
+}
diff --git a/R/content-parse.r b/R/content-parse.r
new file mode 100644
index 0000000..45d78f9
--- /dev/null
+++ b/R/content-parse.r
@@ -0,0 +1,125 @@
+check_encoding <- function(x) {
+  if ((tolower(x) %in% tolower(iconvlist())))
+    return(x)
+
+  message("Invalid encoding ", x, ": defaulting to UTF-8.")
+  "UTF-8"
+}
+
+guess_encoding <- function(encoding = NULL, type = NULL) {
+  if (!is.null(encoding))
+    return(check_encoding(encoding))
+
+  charset <- if (!is.null(type)) parse_media(type)$params$charset
+
+  if (is.null(charset)) {
+    message("No encoding supplied: defaulting to UTF-8.")
+    return("UTF-8")
+  }
+
+  check_encoding(charset)
+}
+
+parse_text <- function(content, type = NULL, encoding = NULL) {
+  encoding <- guess_encoding(encoding, type)
+  iconv(readBin(content, character()), from = encoding, to = "UTF-8")
+}
+
+parse_auto <- function(content, type = NULL, encoding = NULL, ...) {
+  if (length(content) == 0) {
+    return(NULL)
+  }
+
+  if (is.null(type)) {
+    stop("Unknown mime type: can't parse automatically. Use the `type` ",
+      "argument to specify manually.", call. = FALSE)
+  }
+
+  mt <- parse_media(type)
+  parser <- parsers[[mt$complete]]
+  if (is.null(parser)) {
+    stop("No automatic parser available for ", mt$complete, ".",
+      call. = FALSE)
+  }
+
+  parser(x = content, type = type, encoding = encoding, ...)
+}
+
+parseability <- function(type) {
+  if (is.null(type) || type == "") return("raw")
+  mt <- parse_media(type)
+
+  if (exists(mt$complete, parsers)) {
+    "parsed"
+  } else if (mt$type == "text") {
+    "text"
+  } else {
+    "raw"
+  }
+}
+
+parsers <- new.env(parent = emptyenv())
+
+# Binary formats ---------------------------------------------------------------
+
+# http://www.ietf.org/rfc/rfc4627.txt - section 3. (encoding)
+parsers$`application/json` <- function(x, type = NULL, encoding = NULL,
+                                       simplifyVector = FALSE, ...) {
+  jsonlite::fromJSON(parse_text(x, encoding = "UTF-8"),
+    simplifyVector = simplifyVector, ...)
+}
+parsers$`application/x-www-form-urlencoded` <- function(x, encoding = NULL,
+                                                        type = NULL, ...) {
+  parse_query(parse_text(x, encoding = "UTF-8"))
+}
+
+# Text formats -----------------------------------------------------------------
+parsers$`image/jpeg` <- function(x, type = NULL, encoding = NULL, ...) {
+  need_package("jpeg")
+  jpeg::readJPEG(x)
+}
+
+parsers$`image/png` <- function(x, type = NULL, encoding = NULL, ...) {
+  need_package("png")
+  png::readPNG(x)
+}
+
+parsers$`text/plain` <- function(x, type = NULL, encoding = NULL, ...) {
+  encoding <- guess_encoding(encoding, type)
+  parse_text(x, type = type, encoding = encoding)
+}
+
+parsers$`text/html` <- function(x, type = NULL, encoding = NULL, ...) {
+  need_package("xml2")
+
+  encoding <- guess_encoding(encoding, type)
+  xml2::read_html(x, encoding = encoding, ...)
+}
+
+parsers$`application/xml` <- function(x, type = NULL, encoding = NULL, ...) {
+  need_package("xml2")
+
+  encoding <- guess_encoding(encoding, type)
+  xml2::read_xml(x, encoding = encoding, ...)
+}
+
+parsers$`text/xml` <- function(x, type = NULL, encoding = NULL, ...) {
+  need_package("xml2")
+
+  encoding <- guess_encoding(encoding, type)
+  xml2::read_xml(x, encoding = encoding, ...)
+}
+
+parsers$`text/csv` <- function(x, type = NULL, encoding = NULL, ...) {
+  need_package("readr")
+
+  encoding <- guess_encoding(encoding, type)
+  readr::read_csv(x, locale = readr::locale(encoding = encoding), ...)
+}
+
+parsers$`text/tab-separated-values` <- function(x, type = NULL, encoding = NULL, ...) {
+  need_package("readr")
+
+  encoding <- guess_encoding(encoding, type)
+  readr::read_tsv(x, locale = readr::locale(encoding = encoding), ...)
+}
diff --git a/R/content.r b/R/content.r
new file mode 100644
index 0000000..53d51b1
--- /dev/null
+++ b/R/content.r
@@ -0,0 +1,109 @@
+#' Extract content from a request.
+#'
+#' There are currently three ways to retrieve the contents of a request:
+#' as a raw object (\code{as = "raw"}), as a character vector,
+#' (\code{as = "text"}), and as parsed into an R object where possible,
+#' (\code{as = "parsed"}). If \code{as} is not specified, \code{content}
+#' does its best to guess which output is most appropriate.
+#'
+#' \code{content} currently knows about the following mime types:
+#'
+#' \itemize{
+#'  \item \code{text/html}: \code{\link[xml2]{read_html}}
+#'  \item \code{text/xml}: \code{\link[xml2]{read_xml}}
+#'  \item \code{text/csv}: \code{\link[readr]{read_csv}}
+#'  \item \code{text/tab-separated-values}: \code{\link[readr]{read_tsv}}
+#'  \item \code{application/json}: \code{\link[jsonlite]{fromJSON}}
+#'  \item \code{application/x-www-form-urlencoded}: \code{parse_query}
+#'  \item \code{image/jpeg}: \code{\link[jpeg]{readJPEG}}
+#'  \item \code{image/png}: \code{\link[png]{readPNG}}
+#' }
+#'
+#' \code{as = "parsed"} is provided as a convenience only: if the type you
+#' are trying to parse is not available, use \code{as = "text"} and parse
+#' yourself.
+#'
+#' @section WARNING:
+#'
+#' When using \code{content()} in a package, DO NOT use on \code{as = "parsed"}.
+#' 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.
+#'
+#' @param x request object
+#' @param as desired type of output: \code{raw}, \code{text} or
+#'   \code{parsed}. \code{content} attempts to automatically figure out
+#'   which one is most appropriate, based on the content-type.
+#' @param type MIME type (aka internet media type) used to override
+#'   the content type returned by the server. See
+#'   \url{http://en.wikipedia.org/wiki/Internet_media_type} for a list of
+#'   common types.
+#' @param encoding For text, overrides the charset or the Latin1 (ISO-8859-1)
+#'   default, if you know that the server is returning the incorrect encoding
+#'   as the charset in the content-type. Use for text and parsed outputs.
+#' @param ... Other parameters parsed on to the parsing functions, if
+#'  \code{as = "parsed"}
+#' @family response methods
+#' @return
+#' For "raw", a raw vector.
+#'
+#' For "text", a character vector of length 1. The character vector is always
+#' re-encoded to UTF-8. If this encoding fails (usually because the page
+#' declares an incorrect encoding), \code{content()} will return \code{NA}.
+#'
+#' For "auto", a parsed R object.
+#' @export
+#' @examples
+#' r <- POST("http://httpbin.org/post", body = list(a = 1, b = 2))
+#' content(r) # automatically parses JSON
+#' cat(content(r, "text"), "\n") # text content
+#' content(r, "raw") # raw bytes from server
+#'
+#' rlogo <- content(GET("http://cran.r-project.org/Rlogo.jpg"))
+#' plot(0:1, 0:1, type = "n")
+#' rasterImage(rlogo, 0, 0, 1, 1)
+#' @aliases text_content parsed_content
+content <- function(x, as = NULL, type = NULL, encoding = NULL, ...) {
+  stopifnot(is.response(x))
+
+  type <- type %||% x$headers[["Content-Type"]] %||%
+    mime::guess_type(x$url, empty = "application/octet-stream")
+
+  as <- as %||% parseability(type)
+  as <- match.arg(as, c("raw", "text", "parsed"))
+
+  if (is.path(x$content)) {
+    raw <- readBin(x$content, "raw", file.info(x$content)$size)
+  } else {
+    raw <- x$content
+  }
+
+  switch(as,
+    raw = raw,
+    text = parse_text(raw, type, encoding),
+    parsed = parse_auto(raw, type, encoding, ...)
+  )
+}
+
+#' @export
+text_content <- function(x) {
+  message("text_content() deprecated. Use content(x, as = 'text')")
+  content(x, as = "text")
+}
+
+#' @export
+parsed_content <- function(x, ...) {
+  message("text_content() deprecated. Use parsed_content(x, as = 'parsed')")
+  content(x, as = "parsed", ...)
+}
+
+#' Does the request have content associated with it?
+#'
+#' @keywords internal
+#' @export
+#' @examples
+#' has_content(POST("http://httpbin.org/post", body = FALSE))
+#' has_content(HEAD("http://httpbin.org/headers"))
+has_content <- function(x) {
+  length(x$content) > 0
+}
diff --git a/R/cookies.r b/R/cookies.r
new file mode 100644
index 0000000..ac6f2ab
--- /dev/null
+++ b/R/cookies.r
@@ -0,0 +1,41 @@
+#' Set cookies.
+#'
+#' @param ... a named cookie values
+#' @param .cookies a named character vector
+#' @export
+#' @family config
+#' @seealso \code{\link{cookies}()} to see cookies in response.
+#' @examples
+#' set_cookies(a = 1, b = 2)
+#' set_cookies(.cookies = c(a = "1", b = "2"))
+#'
+#' GET("http://httpbin.org/cookies")
+#' GET("http://httpbin.org/cookies", set_cookies(a = 1, b = 2))
+set_cookies <- function(..., .cookies = character(0)) {
+  cookies <- c(..., .cookies)
+  stopifnot(is.character(cookies))
+
+  cookies_str <- vapply(cookies, curl::curl_escape, FUN.VALUE = character(1))
+
+  cookie <- paste(names(cookies), cookies_str, sep = "=", collapse = ";")
+
+  config(cookie = cookie)
+}
+
+#' Access cookies in a response.
+#'
+#' @seealso \code{\link{set_cookies}()} to send cookies in request.
+#' @param x A response.
+#' @examples
+#' r <- GET("http://httpbin.org/cookies/set", query = list(a = 1, b = 2))
+#' cookies(r)
+#' @export
+cookies <- function(x) UseMethod("cookies")
+
+#' @export
+cookies.response <- function(x) x$cookies
+
+#' @export
+cookies.handle <- function(x) {
+  curl::handle_cookies(x$handle)
+}
diff --git a/R/date.R b/R/date.R
new file mode 100644
index 0000000..b142c5f
--- /dev/null
+++ b/R/date.R
@@ -0,0 +1,57 @@
+#' Parse and print http dates.
+#'
+#' As defined in RFC2616,
+#' \url{http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3}, there are
+#' three valid formats:
+#' \itemize{
+#'  \item Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
+#'  \item Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
+#'  \item Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
+#' }
+#' @param x For \code{parse_http_date}, a character vector of strings to parse.
+#'   All elements must be of the same type.
+#'
+#'   For \code{http_date}, a \code{POSIXt} vector.
+#' @param failure What to return on failure?
+#' @export
+#' @return A POSIXct object if succesful, otherwise \code{failure}
+#' @examples
+#' parse_http_date("Sun, 06 Nov 1994 08:49:37 GMT")
+#' parse_http_date("Sunday, 06-Nov-94 08:49:37 GMT")
+#' parse_http_date("Sun Nov  6 08:49:37 1994")
+#'
+#' http_date(Sys.time())
+parse_http_date <- function(x, failure = NA) {
+  if (length(x) == 0) return(NULL)
+
+  fmts <- c(
+    "%a, %d %b %Y %H:%M:%S",
+    "%A, %d-%b-%y %H:%M:%S",
+    "%a %b %d %H:%M:%S %Y"
+  )
+
+  # Need to set C locale to ensure that months/days are parsed in English
+
+  for (fmt in fmts) {
+    parsed <- c_time(as.POSIXct(strptime(x, fmt, tz = "GMT")))
+    if (all(!is.na(parsed))) return(parsed)
+  }
+
+  rep(failure, length(x))
+}
+
+#' @export
+#' @rdname parse_http_date
+http_date <- function(x) {
+  if (is.null(x)) return(NULL)
+  stopifnot(inherits(x, "POSIXt"))
+  c_time(strftime(x, "%a, %d %b %Y %H:%M:%S", tz = "GMT", usetz = TRUE))
+}
+
+c_time <- function(code) {
+  old <- Sys.getlocale(category = "LC_TIME")
+  Sys.setlocale(category = "LC_TIME", locale = "C")
+  on.exit(Sys.setlocale(category = "LC_TIME", locale = old))
+
+  force(code)
+}
diff --git a/R/doctor.R b/R/doctor.R
new file mode 100644
index 0000000..4f18655
--- /dev/null
+++ b/R/doctor.R
@@ -0,0 +1,30 @@
+#' Diagnose common configuration problems
+#'
+#' Currently one check: that curl uses nss.
+#'
+#' @export
+httr_dr <- function() {
+  check_for_nss()
+}
+
+check_for_nss <- function() {
+  if (!grepl("^NSS", curl::curl_version()$ssl_version)) return()
+
+  warning('
+  ------------------------------------------------------------------------
+  Your installed RCurl is linked to the NSS library (`libcurl4-nss-dev`)
+  which is likely to cause issues.
+
+  To resolve the problem:
+
+  1. Quit R.
+
+  2. Install OpenSSL (`apt-get install libcurl4-openssl-dev`) or
+     GnuTLS (`apt-get install libcurl4-gnutls-dev`) variants of libCurl.
+
+  3. Restart R.
+
+  4. Reinstall RCurl: `install.packages("RCurl")`.
+  ------------------------------------------------------------------------
+  ', call. = FALSE)
+}
diff --git a/R/envvar.R b/R/envvar.R
new file mode 100644
index 0000000..e05026b
--- /dev/null
+++ b/R/envvar.R
@@ -0,0 +1,98 @@
+#' Get and set environment variables.
+#'
+#' This is a bad idea because it will leave it on the history
+#'
+#' @param Name of environment variable.
+#' @param Value of environment variable. Use \code{NA} to unset.
+#' @param Scope of change. If "session", the change is ephemeral and will
+#'   disappear when you restart R. If "user", modifies your \code{~/.Renviron}
+#'   so that it affects every project. If "site", modifies the site .Renviron
+#'   so that it affects every user.
+#' @return Invisibly, the previous value of the environment variable.
+#' @noRd
+#' @examples
+#' \dontrun{
+#' # Set locally
+#' set_envvar("HTTR", "true")
+#'
+#' # Set for every new session (and this session)
+#' set_envvar("HTTR", "false", "user")
+#' # Update existing value
+#' set_envvar("HTTR", "true", "user")
+#' }
+set_envvar <- function(name, value, scope = c("session", "user", "site")) {
+  stopifnot(length(name) == 1, length(value) == 1)
+  scope <- match.arg(scope)
+
+  old <- get_envvar(value)
+
+  path <- switch(scope,
+    session = NULL,
+    user = Sys.getenv("R_ENVIRON_USER", "~/.Renviron"),
+    site = Sys.getenv("R_ENVIRON", file.path(R.home("etc"), "Renviron.site"))
+  )
+  set_envvar_local(name, value)
+  if (scope != "session")
+    set_envvar_renviron(name, value, path)
+
+  invisible(old)
+}
+
+
+#' @rdname set_envvar
+#' @noRd
+get_envvar <- function(name) {
+  stopifnot(is.character(name))
+  Sys.getenv(name, unset = NA_character_)
+}
+
+set_envvar_local <- function(name, value) {
+  if (is.na(value)) {
+    Sys.unsetenv(name)
+  } else {
+    l <- stats::setNames(list(quote(value)), name)
+    do.call("Sys.setenv", l)
+  }
+}
+
+# @return \code{TRUE} if an existing env var was modified, \code{FALSE}
+#   otherwise (invisibly).
+set_envvar_renviron <- function(name, value, path) {
+  ev <- paste0(name, '="', value, '"')
+  if (is.na(value)) value <- ""
+
+  if (!file.exists(path)) {
+    # Create if it doesn't already exist
+    cat(ev, "\n", sep = "", file = path)
+    return(invisible(FALSE))
+  }
+
+  lines <- tryCatch(readLines(path), error = function(e) {
+    stop("Failed to read ", path, " with error:\n", e$message, call. = FALSE)
+  })
+  re <- paste0("^", name, "=")
+  matches <- which(grepl(re, lines))
+
+  # No matches, so append to end of file
+  if (length(matches) == 0) {
+    message("Setting ", name, " in ", path)
+    cat(ev, "\n", sep = "", file = path, append = TRUE)
+    return(invisible(FALSE))
+  }
+
+  message("Updating ", name, " in ", path)
+  if (length(matches) == 1) {
+    lines[matches] <- ev
+  } else {
+    lines[matches[1]] <- ev
+    lines <- lines[-matches[-1]]
+  }
+
+  lines <- tryCatch(writeLines(lines, path), error = function(e) {
+    stop("Failed to write ", path, " with error:\n", e$message, call. = FALSE)
+  })
+
+  invisible(TRUE)
+}
+
+can_write <- function(x) file.access(x, 6)
diff --git a/R/handle-pool.r b/R/handle-pool.r
new file mode 100644
index 0000000..a7ba6a1
--- /dev/null
+++ b/R/handle-pool.r
@@ -0,0 +1,37 @@
+#' Maintain a pool of handles.
+#'
+#' The handle pool is used to automatically reuse Curl handles for the same
+#' scheme/host/port combination. This ensures that the http session is
+#' automatically reused, and cookies are maintained across requests to a site
+#' without user intervention.
+#'
+#' @format An environment.
+#' @keywords internal
+handle_pool <- new.env(hash = TRUE, parent = emptyenv())
+
+handle_name <- function(url) {
+  build_url(parse_url(url)[c("scheme", "hostname", "port")])
+}
+
+#' @export
+#' @rdname handle_pool
+handle_find <- function(url) {
+  name <- handle_name(url)
+  if (exists(name, handle_pool)) {
+    handle <- handle_pool[[name]]
+  } else {
+    handle <- handle(name)
+    handle_pool[[name]] <- handle
+  }
+
+  handle
+}
+
+#' @export
+#' @rdname handle_pool
+handle_reset <- function(url) {
+  name <- handle_name(url)
+  if (exists(name, envir = handle_pool)) {
+    rm(list = name, envir = handle_pool)
+  }
+}
diff --git a/R/handle-url.r b/R/handle-url.r
new file mode 100644
index 0000000..e84e23e
--- /dev/null
+++ b/R/handle-url.r
@@ -0,0 +1,17 @@
+handle_url <- function(handle = NULL, url = NULL, ...) {
+  if (is.null(url) && is.null(handle)) {
+    stop("Must specify at least one of url or handle")
+  }
+
+  if (is.null(handle))  handle <- handle_find(url)
+  if (is.null(url))     url <- handle$url
+
+  # If named components in ..., modify the url
+  new <- named(list(...))
+  if (length(new) > 0 || is.url(url)) {
+    old <- parse_url(url)
+    url <- build_url(utils::modifyList(old, new))
+  }
+
+  list(handle = handle, url = url)
+}
diff --git a/R/handle.r b/R/handle.r
new file mode 100644
index 0000000..f4e9a1f
--- /dev/null
+++ b/R/handle.r
@@ -0,0 +1,58 @@
+#' Create a handle tied to a particular host.
+#'
+#' This handle preserves settings and cookies across multiple requests. It is
+#' the foundation of all requests performed through the httr package, although
+#' it will mostly be hidden from the user.
+#'
+#' @param url full url to site
+#' @param cookies DEPRECATED
+#' @export
+#' @note
+#' Because of the way argument dispatch works in R, using handle() in the
+#' http methods (See \code{\link{GET}}) will cause problems when trying to
+#' pass configuration arguments (See examples below). Directly specifying the
+#' handle when using http methods is not recommended in general, since the
+#' selection of the correct handle is taken care of when the user passes an url
+#' (See \code{\link{handle_pool}}).
+#'
+#' @examples
+#' handle("http://google.com")
+#' handle("https://google.com")
+#'
+#' h <- handle("http://google.com")
+#' GET(handle = h)
+#' # Should see cookies sent back to server
+#' GET(handle = h, config = verbose())
+#'
+#' h <- handle("http://google.com", cookies = FALSE)
+#' GET(handle = h)$cookies
+#'
+#' \dontrun{
+#' # Using the preferred way of configuring the http methods
+#' # will not work when using handle():
+#' GET(handle = h, timeout(10))
+#' # Passing named arguments will work properly:
+#' GET(handle = h, config = list(timeout(10), add_headers(Accept = "")))
+#' }
+#'
+handle <- function(url, cookies = TRUE) {
+  stopifnot(is.character(url), length(url) == 1)
+
+  if (!missing(cookies))
+    warning("Cookies argument is depcrated", call. = FALSE)
+
+  h <- curl::new_handle()
+  structure(list(handle = h, url = url), class = "handle")
+}
+
+#' @export
+print.handle <- function(x, ...) {
+  cat("Host: ", x$url , " <", ref(x$handle), ">\n", sep = "")
+  invisible(x)
+}
+
+ref <- function(x) {
+  str_extract(utils::capture.output(print(x))[1], "0x[0-9a-f]*")
+}
+
+is.handle <- function(x) inherits(x, "handle")
diff --git a/R/headers.r b/R/headers.r
new file mode 100644
index 0000000..1142534
--- /dev/null
+++ b/R/headers.r
@@ -0,0 +1,149 @@
+#' Extract the headers from a response
+#'
+#' @param x A request object
+#' @seealso \code{\link{add_headers}()} to send additional headers in a
+#'   request
+#' @export
+#' @examples
+#' r <- GET("http://httpbin.org/get")
+#' headers(r)
+headers <- function(x) UseMethod("headers")
+
+#' @export
+headers.response <- function(x) {
+  x$headers
+}
+
+#' Add additional headers to a request.
+#'
+#' Wikipedia provides a useful list of common http headers:
+#' \url{http://en.wikipedia.org/wiki/List_of_HTTP_header_fields}.
+#'
+#' @param ... named header values.  To stop an existing header from being
+#'   set, pass an empty string: \code{""}.
+#' @param .headers a named character vector
+#' @export
+#' @seealso \code{\link{accept}} and \code{\link{content_type}} for
+#'   convenience functions for setting accept and content-type headers.
+#' @family config
+#' @examples
+#' add_headers(a = 1, b = 2)
+#' add_headers(.headers = c(a = "1", b = "2"))
+#'
+#' GET("http://httpbin.org/headers")
+#'
+#' # Add arbitrary headers
+#' GET("http://httpbin.org/headers",
+#'  add_headers(version = version$version.string))
+#'
+#' # Override default headers with empty strings
+#' GET("http://httpbin.org/headers", add_headers(Accept = ""))
+add_headers <- function(..., .headers = character()) {
+  request(headers = c(..., .headers))
+}
+
+
+#' Set content-type and accept headers.
+#'
+#' These are convenient wrappers aroud \code{\link{add_headers}}.
+#'
+#' \code{accept_json}/\code{accept_xml} and
+#' \code{content_type_json}/\code{content_type_xml} are useful shortcuts to
+#' ask for json or xml responses or tell the server you are sending json/xml.
+#'
+#' @param type A mime type or a file extension. If a file extension (i.e. starts
+#'   with \code{.}) will guess the mime type using \code{\link[mime]{guess_type}}.
+#' @export
+#' @examples
+#' GET("http://httpbin.org/headers")
+#'
+#' GET("http://httpbin.org/headers", accept_json())
+#' GET("http://httpbin.org/headers", accept("text/csv"))
+#' GET("http://httpbin.org/headers", accept(".doc"))
+#'
+#' 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"))
+content_type <- function(type) {
+  if (is.null(type)) return()
+
+  if (substr(type, 1, 1) == ".") {
+    type <- mime::guess_type(type, empty = NULL)
+  }
+
+  add_headers("Content-Type" = type)
+}
+
+#' @export
+#' @rdname content_type
+content_type_json <- function() content_type("application/json")
+
+#' @export
+#' @rdname content_type
+content_type_xml <- function() content_type("application/xml")
+
+
+
+#' @export
+#' @rdname content_type
+accept <- function(type) {
+  if (substr(type, 1, 1) == ".") {
+    type <- mime::guess_type(type, empty = NULL)
+  }
+  add_headers("Accept" = type)
+
+}
+
+#' @export
+#' @rdname content_type
+accept_json <- function() accept("application/json")
+
+#' @export
+#' @rdname content_type
+accept_xml <- function() accept("application/xml")
+
+
+
+
+# Parses a header lines as recieved from libcurl. Multiple responses
+# will be intermingled, each separated by an http status line.
+parse_headers <- function(raw) {
+  lines <- strsplit(rawToChar(raw), "\r?\n")[[1]]
+
+  new_response <- grepl("^HTTP", lines)
+  grps <- cumsum(new_response)
+
+  lapply(unname(split(lines, grps)), parse_single_header)
+}
+
+parse_single_header <- function(lines) {
+  status <- parse_http_status(lines[[1]])
+
+  # Parse headers into name-value pairs
+  header_lines <- lines[lines != ""][-1]
+  pos <- regexec("^([^:]*):\\s*(.*)$", header_lines)
+  pieces <- regmatches(header_lines, pos)
+
+  n <- vapply(pieces, length, integer(1))
+  if (any(n != 3)) {
+    bad <- header_lines[n != 3]
+    pieces <- pieces[n == 3]
+
+    warning("Failed to parse headers:\n", paste0(bad, "\n"), call. = FALSE)
+  }
+
+  names <- vapply(pieces, "[[", 2, FUN.VALUE = character(1))
+  values <- lapply(pieces, "[[", 3)
+  headers <- insensitive(stats::setNames(values, names))
+
+  list(status = status$status, version = status$version, headers = headers)
+}
+
+parse_http_status <- function(x) {
+  status <- as.list(strsplit(x, "\\s+")[[1]])
+  names(status) <- c("version", "status", "message")[seq_along(status)]
+  status$status <- as.integer(status$status)
+
+
+  status
+}
diff --git a/R/http-browse.r b/R/http-browse.r
new file mode 100644
index 0000000..117def6
--- /dev/null
+++ b/R/http-browse.r
@@ -0,0 +1,19 @@
+#' Open specified url in browser.
+#'
+#' (This isn't really a http verb, but it seems to follow the same format).
+#'
+#' Only works in interactive sessions.
+#'
+#' @param config All configuration options are ignored because the request
+#'   is handled by the browser, not \pkg{RCurl}.
+#' @inheritParams GET
+#' @family http methods
+#' @export
+#' @examples
+#' 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)
+}
diff --git a/R/http-delete.r b/R/http-delete.r
new file mode 100644
index 0000000..39494a4
--- /dev/null
+++ b/R/http-delete.r
@@ -0,0 +1,37 @@
+#' Send a DELETE request.
+#'
+#' @section RFC2616:
+#'
+#' The DELETE method requests that the origin server delete the resource
+#' identified by the Request-URI. This method MAY be overridden by human
+#' intervention (or other means) on the origin server. The client cannot be
+#' guaranteed that the operation has been carried out, even if the status code
+#' returned from the origin server indicates that the action has been
+#' completed successfully. However, the server SHOULD NOT indicate success
+#' unless, at the time the response is given, it intends to delete the
+#' resource or move it to an inaccessible location.
+#'
+#' A successful response SHOULD be 200 (OK) if the response includes an entity
+#' describing the status, 202 (Accepted) if the action has not yet been
+#' enacted, or 204 (No Content) if the action has been enacted but the
+#' response does not include an entity.
+#'
+#' 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.
+#'
+#' @inheritParams GET
+#' @inheritParams POST
+#' @family http methods
+#' @export
+#' @examples
+#' DELETE("http://httpbin.org/delete")
+#' POST("http://httpbin.org/delete")
+DELETE <- function(url = NULL, config = list(), ...,
+                   body = NULL, encode = c("multipart", "form", "json", "raw"),
+                   handle = NULL) {
+  hu <- handle_url(handle, url, ...)
+  req <- request_build("DELETE", hu$url, body_config(body, match.arg(encode)),
+    as.request(config), ...)
+  request_perform(req, hu$handle$handle)
+}
diff --git a/R/http-get.r b/R/http-get.r
new file mode 100644
index 0000000..9763f4d
--- /dev/null
+++ b/R/http-get.r
@@ -0,0 +1,68 @@
+#' GET a url.
+#'
+#' @section RFC2616:
+#' The GET method means retrieve whatever information (in the form of an
+#' entity) is identified by the Request-URI. If the Request-URI refers to a
+#' data-producing process, it is the produced data which shall be returned as
+#' the entity in the response and not the source text of the process, unless
+#' that text happens to be the output of the process.
+#'
+#' The semantics of the GET method change to a "conditional GET" if the
+#' request message includes an If-Modified-Since, If-Unmodified-Since,
+#' If-Match, If-None-Match, or If-Range header field. A conditional GET method
+#' requests that the entity be transferred only under the circumstances
+#' described by the conditional header field(s). The conditional GET method is
+#' intended to reduce unnecessary network usage by allowing cached entities to
+#' be refreshed without requiring multiple requests or transferring data
+#' already held by the client.
+#'
+#' The semantics of the GET method change to a "partial GET" if the request
+#' message includes a Range header field. A partial GET requests that only
+#' part of the entity be transferred, as described in \url{http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35}
+#' 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.
+#'
+#' @param url the url of the page to retrieve
+#' @param ... Further named parameters, such as \code{query}, \code{path}, etc,
+#'   passed on to \code{\link{modify_url}}. Unnamed parameters will be combined
+#'   with \code{\link{config}}.
+#' @param config Additional configuration settings such as http
+#'   authentication (\code{\link{authenticate}}), additional headers
+#'   (\code{\link{add_headers}}), cookies (\code{\link{set_cookies}}) etc.
+#'   See \code{\link{config}} for full details and list of helpers.
+#' @param 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}
+#    automatically reuses the same http connection (aka handle) for mulitple
+#'   requests to the same scheme/host/port combo. This substantially reduces
+#'   connection time, and ensures that cookies are maintained over multiple
+#'   requests to the same host. See \code{\link{handle_pool}} for more
+#'   details.
+#'
+#' @family http methods
+#' @export
+#' @examples
+#' GET("http://google.com/")
+#' GET("http://google.com/", path = "search")
+#' GET("http://google.com/", path = "search", query = list(q = "ham"))
+#'
+#' # See what GET is doing with httpbin.org
+#' url <- "http://httpbin.org/get"
+#' GET(url)
+#' GET(url, add_headers(a = 1, b = 2))
+#' GET(url, set_cookies(a = 1, b = 2))
+#' GET(url, add_headers(a = 1, b = 2), set_cookies(a = 1, b = 2))
+#' GET(url, authenticate("username", "password"))
+#' GET(url, verbose())
+#'
+#' # You might want to manually specify the handle so you can have multiple
+#' # independent logins to the same website.
+#' google <- handle("http://google.com")
+#' GET(handle = google, path = "/")
+#' GET(handle = google, path = "search")
+GET <- function(url = NULL, config = list(), ..., handle = NULL) {
+  hu <- handle_url(handle, url, ...)
+  req <- request_build("GET", hu$url, as.request(config), ...)
+  request_perform(req, hu$handle$handle)
+}
diff --git a/R/http-head.r b/R/http-head.r
new file mode 100644
index 0000000..7f94f9d
--- /dev/null
+++ b/R/http-head.r
@@ -0,0 +1,29 @@
+#' Get url HEADers.
+#'
+#' @section RFC2616:
+#' The HEAD method is identical to GET except that the server MUST NOT return
+#' a message-body in the response. The metainformation contained in the HTTP
+#' headers in response to a HEAD request SHOULD be identical to the
+#' information sent in response to a GET request. This method can be used for
+#' obtaining metainformation about the entity implied by the request without
+#' transferring the entity-body itself. This method is often used for testing
+#' hypertext links for validity, accessibility, and recent modification.
+#'
+#' The response to a HEAD request MAY be cacheable in the sense that the
+#' information contained in the response MAY be used to update a previously
+#' cached entity from that resource. If the new field values indicate that the
+#' 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.
+#'
+#' @inheritParams GET
+#' @family http methods
+#' @export
+#' @examples
+#' HEAD("http://google.com")
+#' headers(HEAD("http://google.com"))
+HEAD <- function(url = NULL, config = list(), ..., handle = NULL) {
+  hu <- handle_url(handle, url, ...)
+  req <- request_build("HEAD", hu$url, config, ..., config(nobody = TRUE))
+  request_perform(req, hu$handle$handle)
+}
diff --git a/R/http-patch.r b/R/http-patch.r
new file mode 100644
index 0000000..e9a4bf5
--- /dev/null
+++ b/R/http-patch.r
@@ -0,0 +1,15 @@
+#' Send PATCH request to a server.
+#'
+#' @inheritParams GET
+#' @inheritParams POST
+#' @family http methods
+#' @export
+PATCH <- function(url = NULL, config = list(), ...,
+                  body = NULL, encode = c("multipart", "form", "json", "raw"),
+                  handle = NULL) {
+  encode <- match.arg(encode)
+
+  hu <- handle_url(handle, url, ...)
+  req <- request_build("PATCH", hu$url, body_config(body, match.arg(encode)), config, ...)
+  request_perform(req, hu$handle$handle)
+}
diff --git a/R/http-post.r b/R/http-post.r
new file mode 100644
index 0000000..e6c9196
--- /dev/null
+++ b/R/http-post.r
@@ -0,0 +1,51 @@
+#' POST file to a server.
+#'
+#' @inheritParams GET
+#' @param body One of the following:
+#'   \itemize{
+#'     \item \code{FALSE}: No body. This is typically not used with \code{POST},
+#'       \code{PUT}, or \code{PATCH}, but can be useful if you need to send a
+#'       bodyless request (like \code{GET}) with \code{VERB()}.
+#'     \item \code{NULL}: An empty body
+#'     \item \code{""}: A length 0 body
+#'     \item \code{upload_file("path/")}: The contents of a file.  The mime
+#'       type will be guessed from the extension, or can be supplied explicitly
+#'       as the second argument to \code{upload_file()}
+#'     \item A character or raw vector: sent as is in body. Use
+#'       \code{\link{content_type}} to tell the server what sort of data
+#'       you are sending.
+#'     \item A named list: See details for encode.
+#'   }
+#' @param encode If the body is a named list, how should it be encoded? Can be
+#'   one of form (application/x-www-form-urlencoded), multipart,
+#'   (multipart/form-data), or json (application/json).
+#'
+#'   For "multipart", list elements can be strings or objects created by
+#'   \code{\link{upload_file}}. For "form", elements are coerced to strings
+#'   and escaped, use \code{I()} to prevent double-escaping. For "json",
+#'   parameters are automatically "unboxed" (i.e. length 1 vectors are
+#'   converted to scalars). To preserve a length 1 vector as a vector,
+#'   wrap in \code{I()}. For "raw", either a character or raw vector. You'll
+#'   need to make sure to set the \code{\link{content_type}()} yourself.
+#' @export
+#' @family http methods
+#' @examples
+#' b2 <- "http://httpbin.org/post"
+#' POST(b2, body = "A simple text string")
+#' POST(b2, body = list(x = "A simple text string"))
+#' POST(b2, body = list(y = upload_file(system.file("CITATION"))))
+#' POST(b2, body = list(x = "A simple text string"), encode = "json")
+#'
+#' # Various types of empty body:
+#' POST(b2, body = NULL, verbose())
+#' POST(b2, body = FALSE, verbose())
+#' POST(b2, body = "", verbose())
+POST <- function(url = NULL, config = list(), ..., body = NULL,
+                 encode = c("multipart", "form", "json", "raw"),
+                 handle = NULL) {
+  encode <- match.arg(encode)
+
+  hu <- handle_url(handle, url, ...)
+  req <- request_build("POST", hu$url, body_config(body, match.arg(encode)), as.request(config), ...)
+  request_perform(req, hu$handle$handle)
+}
diff --git a/R/http-put.r b/R/http-put.r
new file mode 100644
index 0000000..eb85364
--- /dev/null
+++ b/R/http-put.r
@@ -0,0 +1,21 @@
+#' Send PUT request to server.
+#'
+#' @inheritParams POST
+#' @family http methods
+#' @export
+#' @examples
+#' POST("http://httpbin.org/put")
+#' PUT("http://httpbin.org/put")
+#'
+#' b2 <- "http://httpbin.org/put"
+#' PUT(b2, body = "A simple text string")
+#' PUT(b2, body = list(x = "A simple text string"))
+#' PUT(b2, body = list(y = upload_file(system.file("CITATION"))))
+#' PUT(b2, body = list(x = "A simple text string"), encode = "json")
+PUT <- function(url = NULL, config = list(), ...,
+                body = NULL, encode = c("multipart", "form", "json", "raw"),
+                handle = NULL) {
+  hu <- handle_url(handle, url, ...)
+  req <- request_build("PUT", hu$url, body_config(body, match.arg(encode)), config, ...)
+  request_perform(req, hu$handle$handle)
+}
diff --git a/R/http-verb.R b/R/http-verb.R
new file mode 100644
index 0000000..890d636
--- /dev/null
+++ b/R/http-verb.R
@@ -0,0 +1,24 @@
+#' VERB a url.
+#'
+#' Use an arbitrary verb.
+#'
+#' @inheritParams GET
+#' @inheritParams POST
+#' @param verb Name of verb to use.
+#' @family http methods
+#' @export
+#' @examples
+#' r <- VERB("PROPFIND", "http://svn.r-project.org/R/tags/",
+#'   add_headers(depth = 1), verbose())
+#' stop_for_status(r)
+#' content(r)
+#'
+#' VERB("POST", url = "http://httpbin.org/post")
+#' VERB("POST", url = "http://httpbin.org/post", body = "foobar")
+VERB <- function(verb, url = NULL, config = list(), ...,
+                 body = NULL, encode = c("multipart", "form", "json", "raw"),
+                 handle = NULL) {
+  hu <- handle_url(handle, url, ...)
+  req <- request_build(verb, hu$url, body_config(body, match.arg(encode)), config, ...)
+  request_perform(req, hu$handle$handle)
+}
diff --git a/R/httr.r b/R/httr.r
new file mode 100644
index 0000000..d1aad16
--- /dev/null
+++ b/R/httr.r
@@ -0,0 +1,28 @@
+#' \pkg{httr} makes http easy.
+#'
+#' \code{httr} is organised around the five most common http verbs:
+#' \code{\link{GET}}, \code{\link{PATCH}},
+#' \code{\link{POST}}, \code{\link{HEAD}},
+#' \code{\link{PUT}}, and \code{\link{DELETE}}.
+#'
+#' Each request returns a \code{\link{response}} object which provides
+#' easy access to status code, cookies, headers, timings, and other useful
+#' info.  The content of the request is available as a raw vector
+#' (\code{\link{content}}), character vector (\code{\link{text_content}}),
+#' or parsed into an R object (\code{\link{parsed_content}}), currently for
+#' html, xml, json, png and jpeg).
+#'
+#' Requests can be modified by various config options like
+#' \code{\link{set_cookies}}, \code{\link{add_headers}},
+#' \code{\link{authenticate}}, \code{\link{use_proxy}},
+#' \code{\link{verbose}}, and \code{\link{timeout}}
+#'
+#' httr supports OAuth 1.0 and 2.0. Use \code{\link{oauth1.0_token}} and
+#' \code{\link{oauth2.0_token}} to get user tokens, and
+#' \code{\link{sign_oauth1.0}} and \code{\link{sign_oauth2.0}} to sign
+#' 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
diff --git a/R/insensitive.r b/R/insensitive.r
new file mode 100644
index 0000000..61f0bac
--- /dev/null
+++ b/R/insensitive.r
@@ -0,0 +1,34 @@
+#' Create a vector with case insensitive name matching.
+#'
+#' @param x vector to modify
+#' @export
+#' @keywords internal
+#' @examples
+#' x <- c("abc" = 1, "def" = 2)
+#' x["ABC"]
+#' y <- insensitive(x)
+#' y["ABC"]
+#' y[["ABC"]]
+insensitive <- function(x) {
+  names(x) <- tolower(names(x))
+  structure(x, class = c("insensitive", class(x)))
+}
+
+#' @export
+`[.insensitive` <- function(x, i, ...) {
+  if (is.character(i)) {
+    i <- tolower(i)
+  }
+
+  NextMethod()
+}
+
+#' @export
+`[[.insensitive` <- `[.insensitive`
+
+
+#' @export
+"$.insensitive" <- function(x, name) {
+  name <- tolower(name)
+  x[[name]]
+}
diff --git a/R/media-guess.r b/R/media-guess.r
new file mode 100644
index 0000000..6ae11d5
--- /dev/null
+++ b/R/media-guess.r
@@ -0,0 +1,11 @@
+#' Guess the media type of a path from its extension.
+#'
+#' DEPRECATED: please use \code{mime::guess_type} instead.
+#'
+#' @param x path to file
+#' @keywords internal
+#' @export
+guess_media <- function(x) {
+  .Deprecated("mime::guess_type")
+  mime::guess_type(x, empty = NULL)
+}
diff --git a/R/media-parse.r b/R/media-parse.r
new file mode 100644
index 0000000..76ed28e
--- /dev/null
+++ b/R/media-parse.r
@@ -0,0 +1,56 @@
+#' Parse a media type.
+#'
+#' Parsed according to RFC 2616, as at
+#' \url{http://pretty-rfc.herokuapp.com/RFC2616#media.types}.
+#'
+#' A simplified minimal EBNF is:
+#'
+#' \itemize{
+#'  \item media-type     = type "/" subtype *( ";" parameter )
+#'  \item type           = token
+#'  \item subtype        = token
+#'  \item parameter      = attribute "=" value
+#'  \item attribute      = token
+#'  \item value          = token | quoted-string
+#'  \item token          = 1*<any CHAR except CTLs or ()<>@@,;:\"/[]?={}
+#'  \item quoted-string  = " *(any text except ", unless escaped with \) "
+#' }
+#'
+#' @export
+#' @keywords internal
+#' @examples
+#' parse_media("text/plain")
+#' parse_media("text/plain; charset=utf-8")
+#' parse_media("text/plain; charset=\"utf-8\"")
+#' parse_media("text/plain; randomparam=\";=;=\"")
+parse_media <- function(x) {
+  stopifnot(!is.null(x))
+  # Use scan to deal with quoted strings. It loses the quotes, but it's
+  # ok because the attribute can't be a quoted string so there's no ambiguity
+  # about who the = belongs to.
+  parse <- function(x, sep) {
+    scan(text = x, what = character(), sep = sep, quiet = TRUE, quote = '"')
+  }
+  pieces <- str_trim(parse(tolower(x), ";"))
+
+  types <- str_split_fixed(pieces[1], "/", 2)[1, ]
+  type <- tolower(types[1])
+  subtype <- tolower(types[2])
+
+  if (length(pieces) > 1) {
+    param_pieces <- lapply(pieces[-1], str_split_fixed, "=", 2)
+    names <- vapply(param_pieces, "[", 1, FUN.VALUE = character(1))
+    values <- vapply(param_pieces, "[", 2, FUN.VALUE = character(1))
+
+    params <- stats::setNames(as.list(values), names)
+  } else {
+    params <- list()
+  }
+
+  list(
+    complete = paste(type, "/", subtype, sep = ""),
+    type = type,
+    subtype = subtype,
+    params = params
+  )
+}
diff --git a/R/oauth-app.r b/R/oauth-app.r
new file mode 100644
index 0000000..2932d6a
--- /dev/null
+++ b/R/oauth-app.r
@@ -0,0 +1,56 @@
+#' Create an OAuth application.
+#'
+#' The OAuth framework doesn't match perfectly to use from R. Each user of the
+#' package for a particular OAuth enabled site must create their own
+#' application. See the demos for instructions on how to do this for
+#' linkedin, twitter, vimeo, facebook, github and google.
+#'
+#' @param appname name of the application.  This is not used for OAuth, but
+#'   is used to make it easier to identify different applications and
+#'   provide a consistent way of storing secrets in environment variables.
+#' @param key consumer key (equivalent to a user name)
+#' @param secret consumer secret. This is not equivalent to a password, and
+#'   is not really a secret. If you are writing an API wrapper package, it
+#'   is fine to include this secret in your package code.
+#'
+#'   Use \code{NULL} to not store a secret: this is useful if you're relying on
+#'   cached OAuth tokens.
+#' @export
+#' @family OAuth
+#' @examples
+#' \dontrun{
+#' # These work on my computer because I have the right envvars set up
+#' linkedin_app <- oauth_app("linkedin", key = "outmkw3859gy")
+#' github_app <- oauth_app("github", "56b637a5baffac62cad9")
+#' }
+#'
+#' # If you're relying on caching, supply an explicit NULL to
+#' # suppress the warning message
+#' oauth_app("my_app", "mykey")
+#' oauth_app("my_app", "mykey", NULL)
+oauth_app <- function(appname, key, secret = NULL) {
+  if (missing(secret)) {
+    env_name <- paste0(toupper(appname), "_CONSUMER_SECRET")
+    secret <- Sys.getenv(env_name)
+    if (secret == "") {
+      warning("Couldn't find secret in environment variable ", env_name,
+        call. = FALSE)
+      secret <- NULL
+    } else {
+      message("Using secret stored in environment variable ", env_name)
+    }
+  }
+  structure(list(appname = appname, secret = secret, key = key),
+    class = "oauth_app")
+}
+
+is.oauth_app <- function(x) inherits(x, "oauth_app")
+
+#' @export
+print.oauth_app <- function(x, ...) {
+  cat("<oauth_app> ", x$appname, "\n", sep = "")
+  cat("  key:    ", x$key, "\n", sep = "")
+  secret <- if (is.null(x$secret)) "<not supplied>" else "<hidden>"
+  cat("  secret: ", secret, "\n", sep = "")
+  invisible(x)
+}
diff --git a/R/oauth-cache.R b/R/oauth-cache.R
new file mode 100644
index 0000000..9b76377
--- /dev/null
+++ b/R/oauth-cache.R
@@ -0,0 +1,98 @@
+use_cache <- function(cache = getOption("httr_oauth_cache")) {
+  if (length(cache) != 1) {
+    stop("cache should be length 1 vector", call. = FALSE)
+  }
+  if (!is.logical(cache) && !is.character(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)
+  }
+
+  if (cache) ".httr-oauth" else NULL
+}
+
+can_use_cache <- function(path = ".httr-oauth") {
+  file.exists(path) || (should_cache(path) && create_cache(path))
+}
+
+should_cache <- function(path = ".httr-oauth") {
+  if (!interactive()) return(FALSE)
+
+  cat("Use a local file ('", path, "'), to cache OAuth access credentials ",
+    "between R sessions?\n", sep = "")
+  utils::menu(c("Yes", "No")) == 1
+}
+
+create_cache <- function(path = ".httr-oauth") {
+  file.create(path, showWarnings = FALSE)
+  if (!file.exists(path)) {
+    stop("Failed to create local cache ('", path, "')", call. = FALSE)
+  }
+
+  # Protect cache as much as possible
+  Sys.chmod(path, "0600")
+
+  if (file.exists("DESCRIPTION")) {
+    add_line(".Rbuildignore", paste0("^", gsub("\\.", "\\\\.", path), "$"))
+  }
+  add_line(".gitignore", path)
+
+  TRUE
+}
+
+
+add_line <- function(path, line, quiet = FALSE) {
+  if (file.exists(path)) {
+    lines <- readLines(path, warn = FALSE)
+    lines <- lines[lines != ""]
+  } else {
+    lines <- character()
+  }
+
+  if (line %in% lines) return(TRUE)
+  if (!quiet) message("Adding ", line, " to ", path)
+
+  lines <- c(lines, line)
+  writeLines(lines, path)
+  TRUE
+}
+
+cache_token <- function(token, cache_path) {
+  if (is.null(cache_path)) return()
+
+  tokens <- load_cache(cache_path)
+  tokens[[token$hash()]] <- token
+  saveRDS(tokens, cache_path)
+}
+
+fetch_cached_token <- function(hash, cache_path) {
+  if (is.null(cache_path)) return()
+
+  load_cache(cache_path)[[hash]]
+}
+
+remove_cached_token <- function(token) {
+  if (is.null(token$cache_path)) return()
+
+  tokens <- load_cache(token$cache_path)
+  tokens[[token$hash()]] <- NULL
+  saveRDS(tokens, token$cache_path)
+}
+
+load_cache <- function(cache_path) {
+  if (!file.exists(cache_path) || file_size(cache_path) == 0) {
+    list()
+  } else {
+    readRDS(cache_path)
+  }
+}
+
+file_size <- function(x) file.info(x, extra_cols = FALSE)$size
diff --git a/R/oauth-endpoint.r b/R/oauth-endpoint.r
new file mode 100644
index 0000000..b3edf91
--- /dev/null
+++ b/R/oauth-endpoint.r
@@ -0,0 +1,123 @@
+#' Describe an OAuth endpoint.
+#'
+#' See \code{\link{oauth_endpoints}} for a list of popular OAuth endpoints
+#' baked into httr.
+#'
+#' @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 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},
+#'   \code{authorize} and \code{access} urls.
+#' @family OAuth
+#' @export
+#' @examples
+#' linkedin <- oauth_endpoint("requestToken", "authorize", "accessToken",
+#'   base_url = "https://api.linkedin.com/uas/oauth")
+#' github <- oauth_endpoint(NULL, "authorize", "access_token",
+#'   base_url = "https://github.com/login/oauth")
+#' facebook <- oauth_endpoint(
+#'   authorize = "https://www.facebook.com/dialog/oauth",
+#'   access = "https://graph.facebook.com/oauth/access_token")
+#'
+#' oauth_endpoints
+oauth_endpoint <- function(request = NULL, authorize, access, ...,
+                           base_url = NULL) {
+  urls <- list(request = request, authorize = authorize, access = access, ...)
+
+  if (is.null(base_url)) {
+    return(do.call(endpoint, urls))
+  }
+
+  # If base_url provided, add it as a prefix
+  path <- parse_url(base_url)$path
+  add_base_url <- function(x) {
+    if (is.null(x)) return(x)
+    if (substr(x, 1, 4) == "http") return(x)
+    modify_url(base_url, path = file.path(path, x))
+  }
+  urls <- lapply(urls, add_base_url)
+  do.call(endpoint, urls)
+}
+endpoint <- function(request, authorize, access, ...) {
+  structure(list(request = request, authorize = authorize, access = access, ...),
+    class = "oauth_endpoint")
+}
+
+is.oauth_endpoint <- function(x) inherits(x, "oauth_endpoint")
+
+#' @export
+print.oauth_endpoint <- function(x, ...) {
+  x <- compact(x)
+  cat("<oauth_endpoint>\n")
+  cat(paste0(" ", format(paste0(names(x), ": ")), unlist(x), collapse = "\n"))
+  cat("\n")
+  invisible(x)
+}
+
+#' Popular oauth endpoints.
+#'
+#' Provides some common OAuth endpoints.
+#'
+#' @param name One of the following endpoints: linkedin, twitter,
+#'   vimeo, google, facebook, github, azure.
+#' @export
+#' @examples
+#' oauth_endpoints("twitter")
+oauth_endpoints <- function(name) {
+  switch(name,
+    linkedin = oauth_endpoint(
+      base_url = "https://www.linkedin.com/uas/oauth2",
+      authorize = "authorization",
+      access = "accessToken"
+    ),
+    twitter = oauth_endpoint(
+      base_url = "https://api.twitter.com/oauth",
+      request = "request_token",
+      authorize = "authenticate",
+      access = "access_token"
+    ),
+    vimeo = oauth_endpoint(
+      base_url = "https://vimeo.com/oauth",
+      request = "request_token",
+      authorize = "authorize",
+      access = "access_token"
+    ),
+    yahoo = oauth_endpoint(
+      base_url = "https://api.login.yahoo.com/oauth/v2",
+      request = "get_request_token",
+      authorize = "request_auth",
+      access = "get_token"
+    ),
+    google = oauth_endpoint(
+      base_url = "https://accounts.google.com/o/oauth2",
+      authorize = "auth",
+      access = "token",
+      validate = "https://www.googleapis.com/oauth2/v1/tokeninfo",
+      revoke = "revoke"
+    ),
+    tumblr = oauth_endpoint(
+      base_url = "http://www.tumblr.com/oauth/",
+      request = "request_token",
+      authorize = "authorize",
+      access = "access_token"
+    ),
+    facebook = oauth_endpoint(
+      authorize = "https://www.facebook.com/dialog/oauth",
+      access = "https://graph.facebook.com/oauth/access_token"
+    ),
+    github = oauth_endpoint(
+      base_url = "https://github.com/login/oauth",
+      request = NULL,
+      authorize = "authorize",
+      access = "access_token"
+    ),
+    azure = oauth_endpoint(
+      base_url = "https://login.windows.net/common/oauth2",
+      authorize = "authorize",
+      access = "token"
+    ),
+    stop("Unknown endpoint", call. = FALSE)
+  )
+}
diff --git a/R/oauth-error.r b/R/oauth-error.r
new file mode 100644
index 0000000..186c581
--- /dev/null
+++ b/R/oauth-error.r
@@ -0,0 +1,25 @@
+oauth2.0_error_codes <- c(
+  400,
+  401
+)
+
+oauth2.0_errors <- c(
+  "invalid_request",
+  "invalid_client",
+  "invalid_grant",
+  "unauthorized_client",
+  "unsupported_grant_type",
+  "invalid_scope"
+)
+
+# This implements error checking according to the OAuth2.0
+# specification: https://tools.ietf.org/html/rfc6749#section-5.2
+known_oauth2.0_error <- function(response) {
+  if (status_code(response) %in% oauth2.0_error_codes) {
+    content <- content(response)
+    if (content$error %in% oauth2.0_errors) {
+      return(TRUE)
+    }
+  }
+  FALSE
+}
diff --git a/R/oauth-exchanger.r b/R/oauth-exchanger.r
new file mode 100644
index 0000000..9ba175d
--- /dev/null
+++ b/R/oauth-exchanger.r
@@ -0,0 +1,23 @@
+#' 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).
+#'
+#' This function should generally not be called directly by the user.
+#'
+#' @param request_url the url to provide to the user
+#' @export
+#' @keywords internal
+oauth_exchanger <- function(request_url) {
+  message("Please point your browser to the following url: ")
+  message("")
+  message("  ", request_url)
+  message("")
+  authorization_code <- str_trim(readline("Enter authorization code: "))
+  info <- list(code = authorization_code)
+  info
+}
diff --git a/R/oauth-init.R b/R/oauth-init.R
new file mode 100644
index 0000000..2376ed3
--- /dev/null
+++ b/R/oauth-init.R
@@ -0,0 +1,123 @@
+#' Retrieve OAuth 1.0 access token.
+#'
+#' See demos for use.
+#'
+#' @param endpoint An OAuth endpoint, created by \code{\link{oauth_endpoint}}
+#' @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 private_key Optional, a key provided by \code{\link[openssl]{read_key}}.
+#'   Used for signed OAuth 1.0.
+#' @export
+#' @keywords internal
+init_oauth1.0 <- function(endpoint, app, permission = NULL,
+                          is_interactive = interactive(),
+                          private_key = NULL) {
+
+  oauth_sig <- function(url, method, token = NULL, token_secret = NULL, private_key = NULL, ...) {
+    oauth_header(oauth_signature(url, method, app, token, token_secret, private_key,
+        other_params = c(list(...), oauth_callback = oauth_callback())))
+  }
+
+  # 1. Get an unauthorized request token
+  response <- POST(endpoint$request, oauth_sig(endpoint$request, "POST", private_key = private_key))
+  stop_for_status(response)
+  params <- content(response, type = "application/x-www-form-urlencoded")
+  token <- params$oauth_token
+  secret <- params$oauth_token_secret
+
+  # 2. Authorize the token
+  authorize_url <- modify_url(endpoint$authorize, query = list(
+    oauth_token = token,
+    permission = "read"))
+  verifier <- oauth_listener(authorize_url, is_interactive)
+  verifier <- verifier$oauth_verifier %||% verifier[[1]]
+
+  # 3. Request access token
+  response <- POST(endpoint$access,
+    oauth_sig(endpoint$access, "POST", token, secret, oauth_verifier = verifier, private_key = private_key),
+    body = ""
+  )
+  stop_for_status(response)
+  content(response, type = "application/x-www-form-urlencoded")
+}
+
+#' Retrieve OAuth 2.0 access token.
+#'
+#' 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.
+#' @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?
+#' @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.
+#' @export
+#' @keywords internal
+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
+  }
+
+  if (isTRUE(use_oob)) {
+    stopifnot(interactive())
+    redirect_uri <- "urn:ietf:wg:oauth:2.0:oob"
+    state <- NULL
+  } else {
+    redirect_uri <- oauth_callback()
+    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
+  } else {
+    code <- oauth_listener(authorize_url, is_interactive)$code
+  }
+
+  # Use authorisation code to get (temporary) access token
+
+  # 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(
+    client_id = app$key,
+    redirect_uri = redirect_uri,
+    grant_type = "authorization_code",
+    code = code)
+
+  if (!is.null(user_params)) {
+    req_params <- utils::modifyList(user_params, req_params)
+  }
+
+  if (isTRUE(use_basic_auth)) {
+    req <- POST(endpoint$access, encode = "form", body = req_params,
+      authenticate(app$key, app$secret, type = "basic"))
+  } else {
+    req_params$client_secret <- app$secret
+    req <- POST(endpoint$access, encode = "form", body = req_params)
+  }
+
+  stop_for_status(req, task = "get an access token")
+  content(req, type = type)
+}
diff --git a/R/oauth-listener.r b/R/oauth-listener.r
new file mode 100644
index 0000000..39e87eb
--- /dev/null
+++ b/R/oauth-listener.r
@@ -0,0 +1,90 @@
+#' Create a webserver to listen for OAuth callback.
+#'
+#' This opens a web browser pointing to \code{request_url}, and opens a
+#' webserver on port 1410 to listen to the reponse.  The redirect url for
+#' should be either set previously (during the OAuth authentication) dance
+#' or supplied as a parameter to the url.  See \code{\link{oauth1.0_token}}
+#' and \code{\link{oauth2.0_token}} for examples of both techniques.
+#'
+#' 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 host ip address for the listener
+#' @param port for the listener
+#' @export
+#' @keywords internal
+oauth_listener <- function(request_url, is_interactive = interactive()) {
+  if (!is_installed("httpuv")) {
+    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, "/")) {
+      return(list(
+        status = 404L,
+        headers = list("Content-Type" = "text/plain"),
+        body = "Not found")
+      )
+    }
+
+    query <- env$QUERY_STRING
+    if (!is.character(query) || identical(query, "")) {
+      info <<- NA
+    } else {
+      info <<- parse_query(gsub("^\\?", "", query))
+    }
+
+    list(
+      status = 200L,
+      headers = list("Content-Type" = "text/plain"),
+      body = "Authentication complete. Please close this page and return to R."
+    )
+  }
+  use <- listener_endpoint()
+  server <- httpuv::startServer(use$host, use$port, list(call = listen))
+  on.exit(httpuv::stopServer(server))
+
+  message("Waiting for authentication in browser...")
+  message("Press Esc/Ctrl + C to abort")
+  BROWSE(request_url)
+  while(is.null(info)) {
+    httpuv::service()
+    Sys.sleep(0.001)
+  }
+  httpuv::service() # to send text back to browser
+
+  if (identical(info, NA)) {
+    stop("Authentication failed.", call. = FALSE)
+  }
+
+  message("Authentication complete.")
+  info
+}
+
+#' The oauth callback url.
+#'
+#' The url that \code{\link{oauth_listener}} expects that the client be
+#' referred to.
+#'
+#' @keywords internal
+#' @export
+oauth_callback <- function() {
+  paste0(
+    "http://",
+    Sys.getenv("HTTR_SERVER", "localhost"),
+    ":",
+    Sys.getenv("HTTR_SERVER_PORT", "1410"),
+    "/"
+  )
+}
+
+listener_endpoint <- function() {
+  list(host = Sys.getenv("HTTR_LOCALHOST", "127.0.0.1"),
+       port = as.integer(Sys.getenv("HTTR_PORT", "1410")))
+}
diff --git a/R/oauth-refresh.R b/R/oauth-refresh.R
new file mode 100644
index 0000000..5b957f5
--- /dev/null
+++ b/R/oauth-refresh.R
@@ -0,0 +1,38 @@
+# Refresh an OAuth 2.0 credential.
+#
+# Refreshes the given token, and returns a new credential with a
+# valid access_token. Based on:
+# https://developers.google.com/accounts/docs/OAuth2InstalledApp#refresh
+refresh_oauth2.0 <- function(endpoint, app, credentials, user_params = NULL,
+                             use_basic_auth = FALSE) {
+  if (is.null(credentials$refresh_token)) {
+    stop("Refresh token not available", call. = FALSE)
+  }
+
+  refresh_url <- endpoint$access
+  req_params <- list(
+      refresh_token = credentials$refresh_token,
+      client_id = app$key,
+      grant_type = "refresh_token")
+
+  if (! is.null(user_params)) {
+    req_params <- utils::modifyList(user_params, req_params)
+  }
+
+  if (isTRUE(use_basic_auth)) {
+    response <- POST(refresh_url, body = req_params, encode = "form",
+      authenticate(app$key, app$secret, type = "basic"))
+  } else {
+    req_params$client_secret <- app$secret
+    response <- POST(refresh_url, body = req_params, encode = "form")
+  }
+
+  if (known_oauth2.0_error(response)) {
+    warning("Unable to refresh token", call. = FALSE)
+    return(NULL)
+  }
+  stop_for_status(response)
+
+  refresh_data <- content(response)
+  utils::modifyList(credentials, refresh_data)
+}
diff --git a/R/oauth-server-side.R b/R/oauth-server-side.R
new file mode 100644
index 0000000..e0714ad
--- /dev/null
+++ b/R/oauth-server-side.R
@@ -0,0 +1,95 @@
+
+init_oauth_service_account <- function(secrets, scope = NULL) {
+  signature <- jwt_signature(secrets, aud = secrets$token_uri, scope = scope)
+
+  res <- POST(
+    secrets$token_uri,
+    body = list(
+      grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer",
+      assertion = signature
+    ),
+    encode = "form"
+  )
+  stop_for_status(res)
+
+  content(res, type = "application/json")
+}
+
+#' Generate a JWT signature given credentials.
+#'
+#' As described in
+#' \url{https://developers.google.com/accounts/docs/OAuth2ServiceAccount}
+#'
+#' @param credentials Parsed contents of the credentials file.
+#' @param scope A space-delimited list of the permissions that the application
+#'    requests.
+#' @param duration Duration of token, in seconds.
+#' @keywords internal
+#' @examples
+#' \dontrun{
+#' 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_sign(cs, credentials$private_key)
+}
+
+jwt_sign <- function(claimset, private_key, header = jwt_header()) {
+  key <- openssl::read_key(private_key)
+  to_sign_base64 <- paste0(jwt_base64(header), ".", jwt_base64(claimset))
+  to_sign <- charToRaw(to_sign_base64)
+  sig <- openssl::signature_create(to_sign, openssl::sha256, key)
+  sig_base64 <- base64url(sig)
+
+  paste0(to_sign_base64, ".", sig_base64)
+}
+
+jwt_header <- function() {
+  list(
+    alg = "RS256",
+    typ = "JWT"
+  )
+}
+
+#' @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)
+base64url <- function(x) {
+  if (is.character(x)) {
+    x <- charToRaw(x)
+  }
+  out <- chartr('+/', '-_', openssl::base64_encode(x))
+  gsub("=+$", "", out)
+}
diff --git a/R/oauth-signature.r b/R/oauth-signature.r
new file mode 100644
index 0000000..031ccce
--- /dev/null
+++ b/R/oauth-signature.r
@@ -0,0 +1,107 @@
+#' Sign an OAuth request
+#'
+#' Deprecated. Instead create a config object directly using
+#' \code{config(token = my_token)}.
+#'
+#' @keywords internal
+#' @name sign_oauth
+NULL
+
+#' @export
+#' @rdname sign_oauth
+sign_oauth1.0 <- function(app, token = NULL, token_secret = NULL,
+                          as_header = TRUE, ...) {
+  params <- list(as_header = as_header)
+
+  credentials <- list(oauth_token = token, oauth_token_secret = token_secret)
+  token <- Token1.0$new(endpoint = NULL, params = params, app = app,
+    credentials = credentials)
+  request(auth_token = token)
+}
+
+#' @export
+#' @rdname sign_oauth
+sign_oauth2.0 <- function(access_token, as_header = TRUE) {
+  stop("Deprecated: supply token object to config directly", call. = FALSE)
+}
+
+#' Generate oauth signature.
+#'
+#' 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)
+#'
+#' @param url,method Url and http method of request.
+#' @param app \code{\link{oauth_app}} object representing application.
+#' @param token,token_secret OAuth token and secret.
+#' @param other_params Named argument providing additional parameters
+#'   (e.g. \code{oauth_callback} or \code{oauth_body_hash}).
+#' @export
+#' @keywords internal
+#' @return A list of oauth parameters.
+oauth_signature <- function(url, method = "GET", app, token = NULL,
+                            token_secret = NULL,
+                            private_key = NULL, other_params = NULL) {
+  if (!is.null(private_key)) {
+    signature_method <- "RSA-SHA1"
+  } 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_consumer_key = app$key,
+    oauth_nonce = nonce(),
+    oauth_signature_method = signature_method,
+    oauth_timestamp = as.integer(Sys.time()),
+    oauth_version = "1.0",
+    oauth_token = token
+  ))
+
+  if (length(other_params) > 0) {
+    oauth <- c(oauth, 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))
+
+  # 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)
+
+  sort_names(oauth)
+}
+
+#' @rdname oauth_signature
+#' @export
+oauth_header <- function(info) {
+  oauth <- paste0("OAuth ", paste0(
+    oauth_encode(names(info)), "=\"", oauth_encode(info), "\"", collapse = ", "))
+  add_headers(Authorization = oauth)
+}
+
+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))))
+
+  x <- as.character(x)
+  chars <- strsplit(x, "")[[1]]
+  ok <- !str_detect(chars, "[^A-Za-z0-9_.~-]")
+
+  if (all(ok)) return(x)
+
+  chars[!ok] <- unlist(lapply(chars[!ok], encode))
+  paste0(chars, collapse = "")
+}
diff --git a/R/oauth-token-utils.R b/R/oauth-token-utils.R
new file mode 100644
index 0000000..5cc3fd5
--- /dev/null
+++ b/R/oauth-token-utils.R
@@ -0,0 +1,70 @@
+#' Revoke all OAuth tokens in the cache.
+#'
+#' 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.
+#'
+#' @param cache_path Path to cache file. Defaults to `.httr-oauth` in
+#'   current directory.
+#' @export
+revoke_all <- function(cache_path = NA) {
+  cache_path <- use_cache(cache_path)
+
+  if (is.null(cache_path)) {
+    stop("Can't find cache")
+  }
+
+  tokens <- load_cache(cache_path)
+
+  cant_revoke <- vapply(tokens, function(x) is.null(x$endpoint$revoke),
+    logical(1))
+  if (any(cant_revoke)) {
+    manual <- tokens[cant_revoke]
+    apps <- vapply(manual, function(x) {
+      paste0(x$app$appname, " (", x$app$key, ")")
+    }, character(1), USE.NAMES = FALSE)
+    warning("Can't revoke the following tokens automatically: ",
+      paste0(apps, collapse = ", "), call. = FALSE)
+  }
+
+  lapply(tokens, function(x) try(revoke_oauth2.0(x)))
+  invisible(TRUE)
+}
+
+revoke_oauth2.0 <- function(endpoint, credentials) {
+  if (is.null(endpoint$revoke)) {
+    stop("No revoke endpoint", call. = FALSE)
+  }
+
+  url <- modify_url(endpoint$revoke,
+    query = list(token = credentials$access_token))
+  response <- GET(url, accept_json())
+  stop_for_status(response)
+
+  invisible(TRUE)
+}
+
+validate_oauth2.0 <- function(endpoint, credentials) {
+  validate_url <- endpoint_validation_url(endpoint, credentials)
+  response <- GET(validate_url, accept_json())
+  status_code(response) == 200
+}
+
+get_token_scopes <- function(endpoint, credentials) {
+  validate_url <- endpoint_validation_url(endpoint, credentials)
+  response <- GET(validate_url, accept_json())
+  if (response$status_code == 200) {
+    strsplit(content(response)$scope, " ")[[1]]
+  }
+}
+
+endpoint_validation_url <- function(endpoint, credentials) {
+  if (is.null(endpoint$validate)) {
+    stop("No validation endpoint", call. = FALSE)
+  }
+  base_url <- endpoint$validate
+  url <- parse_url(base_url)
+  url$query$access_token <- credentials$access_token
+  build_url(url)
+}
+
diff --git a/R/oauth-token.r b/R/oauth-token.r
new file mode 100644
index 0000000..470eab3
--- /dev/null
+++ b/R/oauth-token.r
@@ -0,0 +1,314 @@
+#' OAuth token objects.
+#'
+#' These objects represent the complete set of data needed for OAuth access:
+#' an app, an endpoint, cached credentials and parameters. They should be
+#' created through their constructor functions \code{\link{oauth1.0_token}}
+#' and \code{\link{oauth2.0_token}}.
+#'
+#' @section Methods:
+#' \itemize{
+#'  \item \code{cache()}: caches token to disk
+#'  \item \code{sign(method, url)}: returns list of url and config
+#'  \item \code{refresh()}: refresh access token (if possible)
+#'  \item \code{validate()}: TRUE if the token is still valid, FALSE otherwise
+#' }
+#'
+#' @section Caching:
+#' OAuth tokens are cached on disk in a file called \code{.httr-oauth}
+#' saved in the current working directory.  Caching is enabled if:
+#'
+#' \itemize{
+#' \item The session is interactive, and the user agrees to it, OR
+#' \item The \code{.httr-oauth} file is already present, OR
+#' \item \code{getOption("httr_oauth_cache")} is \code{TRUE}
+#' }
+#'
+#' You can suppress caching by setting the \code{httr_oauth_cache} option to
+#' \code{FALSE}.
+#'
+#' Tokens are cached based on their endpoint and parameters.
+#'
+#' 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.
+#'
+#' @docType class
+#' @keywords internal
+#' @format An R6 class object.
+#' @importFrom R6 R6Class
+#' @export
+#' @name Token-class
+Token <- R6::R6Class("Token", list(
+  endpoint = NULL,
+  app = NULL,
+  credentials = NULL,
+  params = NULL,
+  cache_path = FALSE,
+  private_key = NULL,
+
+  initialize = function(app, endpoint, params = list(), credentials = NULL,
+                        private_key = NULL,
+                        cache_path = getOption("httr_oauth_cache")) {
+    stopifnot(
+      is.oauth_endpoint(endpoint) || !is.null(credentials),
+      is.oauth_app(app),
+      is.list(params)
+    )
+
+    self$app <- app
+    self$endpoint <- endpoint
+    self$params <- params
+    self$cache_path <- use_cache(cache_path)
+    self$private_key <- private_key
+
+    if (!is.null(credentials)) {
+      # Use credentials created elsewhere - usually for tests
+      self$credentials <- credentials
+      return(self)
+    }
+
+    # Are credentials cache already?
+    if (self$load_from_cache()) {
+      self
+    } else {
+      self$init_credentials()
+      self$cache()
+    }
+  },
+
+  print = function(...) {
+    cat("<Token>\n", sep = "")
+    print(self$endpoint)
+    print(self$app)
+    cat("<credentials> ", paste0(names(self$credentials), collapse = ", "), "\n",
+      sep = "")
+    cat("---\n")
+  },
+  cache = function(path = self$cache_path) {
+    cache_token(self, path)
+    self
+  },
+  load_from_cache = function() {
+    if (is.null(self$cache_path)) return(FALSE)
+
+    cached <- fetch_cached_token(self$hash(), self$cache_path)
+    if (is.null(cached)) return(FALSE)
+
+    self$endpoint <- cached$endpoint
+    self$app <- cached$app
+    self$credentials <- cached$credentials
+    self$params <- cached$params
+    TRUE
+  },
+  hash = function() {
+    # endpoint = which site
+    # app = client identification
+    # params = scope
+    msg <- serialize(list(self$endpoint, self$app, self$params$scope), NULL)
+
+    # for compatibility with digest::digest()
+    paste(openssl::md5(msg[-(1:14)]), collapse = "")
+  },
+  sign = function() {
+    stop("Must be implemented by subclass", call. = FALSE)
+  },
+  refresh = function() {
+    stop("Must be implemented by subclass", call. = FALSE)
+  },
+  init_credentials = function() {
+    stop("Must be implemented by subclass", call. = FALSE)
+  }
+))
+
+#' Generate an oauth1.0 token.
+#'
+#' This is the final object in the OAuth dance - it encapsulates the app,
+#' the endpoint, other parameters and the received credentials.
+#'
+#' See \code{\link{Token}} for full details about the token object, and the
+#' caching policies used to store credentials across sessions.
+#'
+#' @inheritParams init_oauth1.0
+#' @param as_header If \code{TRUE}, the default, sends oauth in header.
+#'   If \code{FALSE}, adds as parameter to url.
+#' @param cache A logical value or a string. \code{TRUE} means to cache
+#'   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.
+#' @return A \code{Token1.0} reference class (RC) object.
+#' @family OAuth
+#' @export
+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)
+
+  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)
+  },
+  can_refresh = function() {
+    FALSE
+  },
+  refresh = function() {
+    stop("Not implemented")
+  },
+  sign = function(method, url) {
+    oauth <- oauth_signature(url, method, self$app, self$credentials$oauth_token,
+      self$credentials$oauth_token_secret, self$private_key)
+    if (isTRUE(self$params$as_header)) {
+      c(request(url = url), oauth_header(oauth))
+    } else {
+      url <- parse_url(url)
+      url$query <- c(url$query, oauth)
+      request(url = build_url(url))
+    }
+  }
+))
+
+#' Generate an oauth2.0 token.
+#'
+#' This is the final object in the OAuth dance - it encapsulates the app,
+#' the endpoint, other parameters and the received credentials. It is a
+#' reference class so that it can be seamlessly updated (e.g. using
+#' \code{$refresh()}) when access expires.
+#'
+#' See \code{\link{Token}} for full details about the token object, and the
+#' caching policies used to store credentials across sessions.
+#'
+#' @inheritParams init_oauth2.0
+#' @param as_header If \code{TRUE}, the default, configures the token to add
+#'   itself to the bearer header of subsequent requests. If \code{FALSE},
+#'   configures the token to add itself as a url parameter of subsequent
+#'   requests.
+#' @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"),
+                           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)
+
+  Token2.0$new(app = app, endpoint = endpoint, params = params,
+    cache_path = cache)
+}
+
+#' @export
+#' @rdname Token-class
+Token2.0 <- R6::R6Class("Token2.0", inherit = Token, list(
+  init_credentials = function() {
+    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)
+  },
+  can_refresh = function() {
+    !is.null(self$credentials$refresh_token)
+  },
+  refresh = function() {
+    cred <- refresh_oauth2.0(self$endpoint, self$app,
+        self$credentials, self$params$user_params, self$params$use_basic_auth)
+    if (is.null(cred)) {
+      remove_cached_token(self)
+    } else {
+      self$credentials <- cred
+      self$cache()
+    }
+    self
+  },
+  sign = function(method, url) {
+    if (self$params$as_header) {
+      request(url = url, headers = c(
+        Authorization = paste('Bearer', self$credentials$access_token))
+      )
+    } else {
+      url <- parse_url(url)
+      url$query$access_token <- self$credentials$access_token
+      request(url = build_url(url))
+    }
+  },
+  validate = function() {
+    validate_oauth2.0(self$endpoint, self$credentials)
+  },
+  revoke = function() {
+    revoke_oauth2.0(self$endpoint, self$credentials)
+  }
+))
+
+
+#' Generate OAuth token for service accounts.
+#'
+#' Service accounts provide a way of using OAuth2 without user intervention.
+#' They instead assume that the server has access to a private key used
+#' to sign requests. The OAuth app is not needed for service accounts:
+#' that information is embedded in the account itself.
+#'
+#' @inheritParams oauth2.0_token
+#' @param secrets Secrets loaded from JSON file, downloaded from console.
+#' @family OAuth
+#' @export
+#' @examples
+#' \dontrun{
+#' endpoint <- oauth_endpoints("google")
+#' secrets <- jsonlite::fromJSON("~/Desktop/httrtest-45693cbfac92.json")
+#' scope <- "https://www.googleapis.com/auth/bigquery.readonly"
+#'
+#' token <- oauth_service_token(endpoint, secrets, scope)
+#' }
+oauth_service_token <- function(endpoint, secrets, scope = 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)
+
+  TokenServiceAccount$new(
+    endpoint = endpoint,
+    secrets = secrets,
+    params = list(scope = scope)
+  )
+}
+
+#' @export
+#' @rdname Token-class
+TokenServiceAccount <- R6::R6Class("TokenServiceAccount", inherit = Token2.0, list(
+  secrets = NULL,
+  initialize = function(endpoint, secrets, params) {
+    self$endpoint <- endpoint
+    self$secrets <- secrets
+    self$params <- params
+
+    self$refresh()
+  },
+  can_refresh = function() {
+    TRUE
+  },
+  refresh = function() {
+    self$credentials <- init_oauth_service_account(self$secrets, self$params$scope)
+    self
+  },
+  sign = function(method, url) {
+    config <- add_headers(
+      Authorization = paste('Bearer', self$credentials$access_token)
+    )
+    request_build(method = method, url = url, config)
+  },
+
+  # Never cache
+  cache = function(path) self,
+  load_from_cache = function() self
+))
diff --git a/R/progress.R b/R/progress.R
new file mode 100644
index 0000000..8e44eb1
--- /dev/null
+++ b/R/progress.R
@@ -0,0 +1,78 @@
+#' Add a progress bar.
+#'
+#' @param type Type of progress to display: either number of bytes uploaded
+#'   or downloaded.
+#' @param con Connection to send output too. Usually \code{stdout()} or
+#'    \code{stderr}.
+#' @export
+#' @examples
+#' \donttest{
+#' # If file size is known, you get a progress bar:
+#' x <- GET("http://courses.had.co.nz/12-oscon/slides.zip", progress())
+#' # Otherwise you get the number of bytes downloaded:
+#' x <- GET("http://httpbin.org/drip?numbytes=4000&duration=3", progress())
+#' }
+progress <- function(type = c("down", "up"), con = stdout()) {
+  type <- match.arg(type)
+
+  request(options = list(
+    noprogress = FALSE,
+    progressfunction = progress_bar(type, con)
+  ))
+}
+
+progress_bar <- function(type, con) {
+  bar <- NULL
+  first <- TRUE
+
+  show_progress <- function(down, up) {
+    if (type == "down") {
+      total <- down[[1]]
+      now <- down[[2]]
+    } else {
+      total <- up[[1]]
+      now <- up[[2]]
+    }
+
+    # First progress request on new file
+    if (total == 0 && now == 0) {
+      bar <<- NULL
+      first <<- TRUE
+      return(TRUE)
+    }
+
+    if (total == 0) {
+      if (first) {
+        first <<- FALSE
+      }
+      cat("\rDownloading: ", bytes(now, digits = 2), "     ", sep = "", file = con)
+      if (now == total) cat("\n", file = con)
+      utils::flush.console()
+    } else {
+      if (is.null(bar)) {
+        bar <<- utils::txtProgressBar(max = total, style = 3, file = con)
+      }
+      utils::setTxtProgressBar(bar, now)
+    }
+
+    TRUE
+  }
+
+  show_progress
+}
+
+
+bytes <- function(x, digits = 3, ...) {
+  power <- min(floor(log(abs(x), 1000)), 4)
+  if (power < 1) {
+    unit <- "B"
+  } else {
+    unit <- c("kB", "MB", "GB", "TB")[[power]]
+    x <- x / (1000 ^ power)
+  }
+
+  formatted <- format(signif(x, digits = digits), big.mark = ",",
+    scientific = FALSE)
+
+  paste0(formatted, " ", unit)
+}
diff --git a/R/proxy.r b/R/proxy.r
new file mode 100644
index 0000000..a4a8d00
--- /dev/null
+++ b/R/proxy.r
@@ -0,0 +1,30 @@
+#' Use a proxy to connect to the internet.
+#'
+#' @param url,port location of proxy
+#' @param username,password login details for proxy, if needed
+#' @param auth type of HTTP authentication to use. Should be one of the
+#'   following: basic, digest, digest_ie, gssnegotiate, ntlm, any.
+#' @family config
+#' @export
+#' @examples
+#' # See http://www.hidemyass.com/proxy-list for a list of public proxies
+#' # to test with
+#' # GET("http://had.co.nz", use_proxy("64.251.21.73", 8080), verbose())
+use_proxy <- function(url, port = NULL, username = NULL, password = NULL,
+                      auth = "basic") {
+  if (!is.null(username) || !is.null(password)) {
+    proxyuserpwd <- paste0(username, ":", password)
+  } else {
+    proxyuserpwd <- NULL
+  }
+
+  if (!is.null(port)) stopifnot(is.numeric(port))
+
+  config(
+    proxy = url,
+    proxyuserpwd = proxyuserpwd,
+    proxyport = port,
+    proxyauth = auth_flags(auth)
+  )
+}
+
diff --git a/R/request.R b/R/request.R
new file mode 100644
index 0000000..ff98084
--- /dev/null
+++ b/R/request.R
@@ -0,0 +1,165 @@
+request <- function(method = NULL, url = NULL, headers = NULL,
+                    fields = NULL, options = NULL, auth_token = NULL,
+                    output = NULL) {
+  if (!is.null(method))
+    stopifnot(is.character(method), length(method) == 1)
+  if (!is.null(url))
+    stopifnot(is.character(url), length(url) == 1)
+  if (!is.null(headers))
+    stopifnot(is.character(headers))
+  if (!is.null(fields))
+    stopifnot(is.list(fields))
+  if (!is.null(output))
+    stopifnot(inherits(output, "write_function"))
+
+  structure(
+    list(
+      method = method,
+      url = url,
+      headers = keep_last(headers),
+      fields = fields,
+      options = compact(keep_last(options)),
+      auth_token = auth_token,
+      output = output
+    ),
+    class = "request"
+  )
+}
+is.request <- function(x) inherits(x, "request")
+
+request_default <- function() {
+  c(
+    request(
+      options = list(
+        useragent = default_ua(),
+        cainfo = find_cert_bundle()
+      ),
+      headers = c(Accept = "application/json, text/xml, application/xml, */*"),
+      output = write_function("write_memory")
+    ),
+    getOption("httr_config")
+  )
+}
+
+#' @export
+c.request <- function(...) {
+  Reduce(request_combine, list(...))
+}
+
+as.request <- function(x) UseMethod("as.request")
+as.request.list <- function(x) structure(x, class = "request")
+as.request.request <- function(x) x
+as.request.NULL <- function(x) request()
+as.request.Token <- function(x) request(auth_token = x)
+
+request_build <- function(method, url, ...) {
+  extra <- list(...)
+  extra[has_name(extra)] <- NULL
+
+  req <- Reduce(request_combine, extra, init = request())
+
+  req$method <- method
+  req$url <- url
+
+  req
+}
+
+request_combine <- function(x, y) {
+  if (length(x) == 0 && length(y) == 0) return(request())
+  if (length(x) == 0) return(y)
+  if (length(y) == 0) return(x)
+  stopifnot(is.request(x), is.request(y))
+
+  request(
+    method =     y$method %||% x$method,
+    url =        y$url %||% x$url,
+    headers =    keep_last(x$headers, y$headers),
+    fields =     c(x$fields, y$fields),
+    options =    keep_last(x$options, y$options),
+    auth_token = y$auth_token %||% x$auth_token,
+    output =     y$output %||% x$output
+  )
+}
+
+#' @export
+print.request <- function(x, ...) {
+  cat("<request>\n")
+  if (!is.null(x$method) && !is.null(x$url))
+    cat(toupper(x$method), " ", x$url, "\n", sep = "")
+  if (!is.null(x$output))
+    cat("Output: ", class(x$output)[[1]], "\n", sep = "")
+  named_vector("Options", x$options)
+  named_vector("Headers", x$headers)
+  named_vector("Fields", x$fields)
+  if (!is.null(x$auth_token)) {
+    cat("Auth token: ", class(x$auth_token)[[1]], "\n", sep = "")
+  }
+
+  invisible(x)
+}
+
+request_prepare <- function(req) {
+  req <- request_combine(request_default(), req)
+
+  # Use specific options for GET and POST; otherwise, perform a custom request.
+  # The PUT/UPLOAD options don't appear to work, instead hanging forever.
+  switch(req$method,
+    GET =  req$options$httpget <- TRUE,
+    POST = req$options$post <- TRUE,
+    req$options$customrequest <- req$method
+  )
+
+  # Sign request, if needed
+  token <- req$auth_token
+  if (!is.null(token)) {
+    signed_req <- token$sign(req$method, req$url)
+    stopifnot(is.request(signed_req))
+    req <- c(req, signed_req)
+  }
+
+  req
+}
+
+request_perform <- function(req, handle, refresh = TRUE) {
+  stopifnot(is.request(req), inherits(handle, "curl_handle"))
+  req <- request_prepare(req)
+
+  curl::handle_setopt(handle, .list = req$options)
+  if (!is.null(req$fields))
+    curl::handle_setform(handle, .list = req$fields)
+  curl::handle_setheaders(handle, .list = req$headers)
+  on.exit(curl::handle_reset(handle), add = TRUE)
+
+  resp <- request_fetch(req$output, req$url, handle)
+
+  # If return 401 and have auth token, refresh it and then try again
+  needs_refresh <- refresh && resp$status_code == 401L &&
+    !is.null(req$auth_token) && req$auth_token$can_refresh()
+  if (needs_refresh) {
+    message("Auto-refreshing stale OAuth token.")
+    req$auth_token$refresh()
+    return(request_perform(req, handle, refresh = FALSE))
+  }
+
+  all_headers <- parse_headers(resp$headers)
+  headers <- last(all_headers)$headers
+  if (!is.null(headers$date)) {
+    date <- parse_http_date(headers$Date)
+  } else {
+    date <- Sys.time()
+  }
+
+  response(
+    url = resp$url,
+    status_code = resp$status_code,
+    headers = headers,
+    all_headers = all_headers,
+    cookies = curl::handle_cookies(handle),
+    content = resp$content,
+    date = date,
+    times = resp$times,
+    request = req,
+    handle = handle
+  )
+}
+
diff --git a/R/response-status.r b/R/response-status.r
new file mode 100644
index 0000000..72ca61c
--- /dev/null
+++ b/R/response-status.r
@@ -0,0 +1,323 @@
+#' Extract status code from response.
+#'
+#' @param x A response
+#' @export
+status_code <- function(x) UseMethod("status_code")
+#' @export
+status_code.response <- function(x) x$status_code
+#' @export
+status_code.numeric <- function(x) x
+
+#' Give information on the status of a request.
+#'
+#' Extract the http status code and convert it into a human readable message.
+#'
+#' http servers send a status code with the response to each request. This code
+#' gives information regarding the outcome of the execution of the request
+#' on the server. Roughly speaking, codes in the 100s and 200s mean the request
+#' was successfully executed; codes in the 300s mean the page was redirected;
+#' codes in the 400s mean there was a mistake in the way the client sent the
+#' request; codes in the 500s mean the server failed to fulfill
+#' an apparently valid request. More details on the codes can be found at
+#' \code{http://en.wikipedia.org/wiki/Http_error_codes}.
+#'
+#' @param x a request object or a number.
+#' @return If the status code does not match a known status, an error.
+#'  Otherwise, a list with components
+#'  \item{category}{the broad category of the status}
+#'  \item{message}{the meaning of the status code}
+#' @family response methods
+#' @examples
+#' http_status(100)
+#' http_status(404)
+#'
+#' x <- GET("http://httpbin.org/status/200")
+#' http_status(x)
+#'
+#' http_status(GET("http://httpbin.org/status/300"))
+#' http_status(GET("http://httpbin.org/status/301"))
+#' http_status(GET("http://httpbin.org/status/404"))
+#'
+#' # errors out on unknown status
+#' \dontrun{http_status(GET("http://httpbin.org/status/320"))}
+#' @export
+http_status <- function(x) {
+  status <- status_code(x)
+
+  status_desc <- http_statuses[[as.character(status)]]
+  if (is.na(status_desc)) {
+    stop("Unknown http status code: ", status, call. = FALSE)
+  }
+
+  status_types <- c("Information", "Success", "Redirection", "Client error",
+    "Server error")
+  status_type <- status_types[[status %/% 100]]
+
+  # create the final information message
+  message <- paste(status_type, ": (", status, ") ", status_desc, sep = "")
+
+  list(
+    category = status_type,
+    reason = status_desc,
+    message = message
+  )
+}
+
+http_statuses <- c(
+  "100" = "Continue",
+  "101" = "Switching Protocols",
+  "102" = "Processing (WebDAV; RFC 2518)",
+  "200" = "OK",
+  "201" = "Created",
+  "202" = "Accepted",
+  "203" = "Non-Authoritative Information",
+  "204" = "No Content",
+  "205" = "Reset Content",
+  "206" = "Partial Content",
+  "207" = "Multi-Status (WebDAV; RFC 4918)",
+  "208" = "Already Reported (WebDAV; RFC 5842)",
+  "226" = "IM Used (RFC 3229)",
+  "300" = "Multiple Choices",
+  "301" = "Moved Permanently",
+  "302" = "Found",
+  "303" = "See Other",
+  "304" = "Not Modified",
+  "305" = "Use Proxy",
+  "306" = "Switch Proxy",
+  "307" = "Temporary Redirect",
+  "308" = "Permanent Redirect (experimental Internet-Draft)",
+  "400" = "Bad Request",
+  "401" = "Unauthorized",
+  "402" = "Payment Required",
+  "403" = "Forbidden",
+  "404" = "Not Found",
+  "405" = "Method Not Allowed",
+  "406" = "Not Acceptable",
+  "407" = "Proxy Authentication Required",
+  "408" = "Request Timeout",
+  "409" = "Conflict",
+  "410" = "Gone",
+  "411" = "Length Required",
+  "412" = "Precondition Failed",
+  "413" = "Request Entity Too Large",
+  "414" = "Request-URI Too Long",
+  "415" = "Unsupported Media Type",
+  "416" = "Requested Range Not Satisfiable",
+  "417" = "Expectation Failed",
+  "418" = "I'm a teapot (RFC 2324)",
+  "420" = "Enhance Your Calm (Twitter)",
+  "422" = "Unprocessable Entity (WebDAV; RFC 4918)",
+  "423" = "Locked (WebDAV; RFC 4918)",
+  "424" = "Failed Dependency (WebDAV; RFC 4918)",
+  "424" = "Method Failure (WebDAV)",
+  "425" = "Unordered Collection (Internet draft)",
+  "426" = "Upgrade Required (RFC 2817)",
+  "428" = "Precondition Required (RFC 6585)",
+  "429" = "Too Many Requests (RFC 6585)",
+  "431" = "Request Header Fields Too Large (RFC 6585)",
+  "444" = "No Response (Nginx)",
+  "449" = "Retry With (Microsoft)",
+  "450" = "Blocked by Windows Parental Controls (Microsoft)",
+  "451" = "Unavailable For Legal Reasons (Internet draft)",
+  "499" = "Client Closed Request (Nginx)",
+  "500" = "Internal Server Error",
+  "501" = "Not Implemented",
+  "502" = "Bad Gateway",
+  "503" = "Service Unavailable",
+  "504" = "Gateway Timeout",
+  "505" = "HTTP Version Not Supported",
+  "506" = "Variant Also Negotiates (RFC 2295)",
+  "507" = "Insufficient Storage (WebDAV; RFC 4918)",
+  "508" = "Loop Detected (WebDAV; RFC 5842)",
+  "509" = "Bandwidth Limit Exceeded (Apache bw/limited extension)",
+  "510" = "Not Extended (RFC 2774)",
+  "511" = "Network Authentication Required (RFC 6585)",
+  "598" = "Network read timeout error (Unknown)",
+  "599" = "Network connect timeout error (Unknown)"
+)
+
+#' Check for an http error.
+#'
+#' @param x Object to check. Default methods are provided for strings
+#'   (which perform an \code{\link{HEAD}} request), responses, and
+#'   integer status codes.
+#' @param ... Other arguments passed on to methods.
+#' @return  \code{TRUE} if the request fails (status code 400 or above),
+#'   otherwise \code{FALSE}.
+#' @export
+#' @family response methods
+#' @examples
+#' # You can pass a url:
+#' http_error("http://www.google.com")
+#' http_error("http://httpbin.org/status/404")
+#'
+#' # Or a request
+#' r <- GET("http://httpbin.org/status/201")
+#' http_error(r)
+#'
+#' # Or an (integer) status code
+#' http_error(200L)
+#' http_error(404L)
+http_error <- function(x, ...) {
+  UseMethod("http_error")
+}
+#' @export
+http_error.character <- function(x, ...) {
+  http_error(HEAD(x, ...))
+}
+#' @export
+http_error.response <- function(x, ...) {
+  http_error(status_code(x))
+}
+
+#' @export
+http_error.integer <- function(x, ...) {
+  x >= 400L
+}
+
+#' @export
+#' @rdname http_error
+#' @usage NULL
+url_success <- function(x, ...) {
+  warning(
+    "`url_success(x)` is deprecated; please use `!http_error(x)` instead.",
+    call. = FALSE
+  )
+  !http_error(x, ...)
+}
+
+#' @export
+#' @rdname http_error
+#' @usage NULL
+url_ok <- function(x, ...) {
+  warning(
+    "`url_ok(x)` is deprecated; ",
+    "please use `identical(status_code(x), 200L)` instead.",
+    call. = FALSE
+  )
+  identical(status_code(HEAD(x, ...)), 200L)
+}
+
+#' Take action on http error.
+#'
+#' Converts http errors to R errors or warnings - these should always
+#' be used whenever you're creating requests inside a function, so
+#' that the user knows why a request has failed.
+#'
+#' @return If request was successful, the response (invisibly). Otherwise,
+#'   raised a classed http error or warning, as generated by
+#'   \code{\link{http_condition}}
+#' @inheritParams http_condition
+#' @seealso \code{\link{http_status}} and
+#'   \code{http://en.wikipedia.org/wiki/Http_status_codes} for more information
+#'   on http status codes.
+#' @family response methods
+#' @examples
+#' x <- GET("http://httpbin.org/status/200")
+#' stop_for_status(x) # nothing happens
+#' warn_for_status(x)
+#' message_for_status(x)
+#'
+#' x <- GET("http://httpbin.org/status/300")
+#' \dontrun{stop_for_status(x)}
+#' warn_for_status(x)
+#' message_for_status(x)
+#'
+#' x <- GET("http://httpbin.org/status/404")
+#' \dontrun{stop_for_status(x)}
+#' warn_for_status(x)
+#' message_for_status(x)
+#'
+#' # You can provide more information with the task argumgnet
+#' warn_for_status(x, "download spreadsheet")
+#' message_for_status(x, "download spreadsheet")
+#'
+#' @export
+stop_for_status <- function(x, task = NULL) {
+  if (status_code(x) < 300)
+    return(invisible(x))
+
+  call <- sys.call(-1)
+  stop(http_condition(x, "error", task = task, call = call))
+}
+
+#' @rdname stop_for_status
+#' @export
+warn_for_status <- function(x, task = NULL) {
+  if (status_code(x) < 300)
+    return(invisible(x))
+
+  call <- sys.call(-1)
+  warning(http_condition(x, "warning", task = task, call = call))
+}
+
+#' @rdname stop_for_status
+#' @export
+message_for_status <- function(x, task = NULL) {
+  call <- sys.call(-1)
+  message(http_condition(x, "message", task = task, call = call))
+}
+
+
+#' Generate a classed http condition.
+#'
+#' This function generate S3 condition objects which are passed to
+#' \code{\link{stop}} or \code{\link{warning}} to generate classes warnings
+#' and error. These can be used in conjunction with \code{\link{tryCatch}}
+#' to respond differently to different type of failure.
+#'
+#' @keywords internal
+#' @return An S3 object that inherits from (e.g.) condition, \code{type},
+#'   http_error, http_400 and http_404.
+#' @param x a response, or numeric http code (or other object with
+#'   \code{status_code} method)
+#' @param type type of condition to generate. Must be one of error,
+#'   warning or message.
+#' @param task The text of the message: either \code{NULL} or a
+#'   character vector. If non-\code{NULL}, the error message will finish with
+#'   "Failed to \code{task}".
+#' @param call The call stored in the condition object.
+#' @seealso
+#'   \url{http://adv-r.had.co.nz/Exceptions-Debugging.html#condition-handling}
+#'   for more details about R's condition handling model
+#' @export
+#' @examples
+#' # You can use tryCatch to take different actions based on the type
+#' # of error. Note that tryCatch will call the first handler that
+#' # matches any classes of the condition, not the best matching, so
+#' # always list handlers from most specific to least specific
+#' f <- function(url) {
+#'   tryCatch(stop_for_status(GET(url)),
+#'     http_404 = function(c) "That url doesn't exist",
+#'     http_403 = function(c) "You need to authenticate!",
+#'     http_400 = function(c) "You made a mistake!",
+#'     http_500 = function(c) "The server screwed up"
+#'   )
+#' }
+#' f("http://httpbin.org/status/404")
+#' f("http://httpbin.org/status/403")
+#' f("http://httpbin.org/status/505")
+http_condition <- function(x, type, task = NULL, call = sys.call(-1)) {
+  type <- match.arg(type, c("error", "warning", "message"))
+
+  if (is.null(task)) {
+    task <- ""
+  } else if (is.character(task)) {
+    task <- paste0(" Failed to ", task, ".")
+  } else {
+    stop("`task` must be NULL or a character vector", call. = FALSE)
+  }
+
+  status <- status_code(x)
+  reason <- http_status(status)$reason
+
+  message <- sprintf("%s (HTTP %d).%s", reason, status, task)
+
+  status_type <- (status %/% 100) * 100
+  http_class <- paste0("http_", unique(c(status, status_type, "error")))
+
+  structure(
+    list(message = message, call = call),
+    class = c(http_class, type, "condition")
+  )
+}
diff --git a/R/response-type.R b/R/response-type.R
new file mode 100644
index 0000000..7a9c42e
--- /dev/null
+++ b/R/response-type.R
@@ -0,0 +1,23 @@
+#' Extract the content type of a response
+#'
+#' @param x A response
+#' @return A string giving the complete mime type, with all parameters
+#'   stripped off.
+#' @export
+#' @examples
+#' r1 <- GET("http://httpbin.org/image/png")
+#' http_type(r1)
+#' headers(r1)[["Content-Type"]]
+#'
+#' r2 <- GET("http://httpbin.org/ip")
+#' http_type(r2)
+#' headers(r2)[["Content-Type"]]
+http_type <- function(x) {
+  stopifnot(is.response(x))
+
+  type <- x$headers[["Content-Type"]] %||%
+    mime::guess_type(x$url, empty = "application/octet-stream")
+
+  parse_media(type)$complete
+}
+
diff --git a/R/response.r b/R/response.r
new file mode 100644
index 0000000..30e4963
--- /dev/null
+++ b/R/response.r
@@ -0,0 +1,85 @@
+#' The response object.
+#'
+#' The response object captures all information from a request.  It includes
+#' fields:
+#'
+#' \itemize{
+#'   \item \code{url} the url the request was actually sent to
+#'     (after redirects)
+#'   \item \code{handle} the handle associated with the url
+#'   \item \code{status_code} the http status code
+#'   \item \code{header} a named list of headers returned by the server
+#'   \item \code{cookies} a named list of cookies returned by the server
+#'   \item \code{content} the body of the response, as raw vector. See
+#'      \code{\link{content}} for various ways to access the content.
+#'   \item \code{time} request timing information
+#'   \item \code{config} configuration for the request
+#' }
+#' @name response
+#' @family response methods
+NULL
+
+response <- function(...) {
+  structure(list(...), class = "response")
+}
+
+is.response <- function(x) {
+  inherits(x, "response")
+}
+
+#' @export
+print.response <- function(x, ..., max.lines = 10, width = getOption("width")) {
+  content_type <- x$headers$`content-type`
+
+  cat("Response [", x$url, "]\n", sep = "")
+  cat("  Date: ", format(x$date, "%Y-%m-%d %H:%M"), "\n", sep = "")
+  cat("  Status: ", x$status_code, "\n", sep = "")
+  cat("  Content-Type: ", content_type %||% "<unknown>", "\n", sep = "")
+
+  size <- length(x$content)
+  if (size == 0) {
+    cat("<EMPTY BODY>\n")
+    return()
+  }
+
+  cat("  Size: ", bytes(size), "\n", sep = "")
+  if (is.path(x$content)) {
+    cat("<ON DISK> ", x$content)
+    return()
+  }
+  if (!is_text(content_type)) {
+    cat("<BINARY BODY>\n")
+    return()
+  }
+
+  # Content is text, so print up to `max.lines` lines, truncating each line to
+  # at most `width` characters wide
+  suppressMessages(text <- content(x, "text"))
+
+  breaks <- gregexpr("\n", text, fixed = TRUE)[[1]]
+  last_line <- breaks[min(length(breaks), max.lines)]
+  lines <- strsplit(substr(text, 1, last_line), "\n")[[1]]
+
+  too_wide <- nchar(lines) > width
+  lines[too_wide] <- paste0(substr(lines[too_wide], 1, width - 3), "...")
+
+  cat(lines, sep = "\n")
+  if (max.lines < length(breaks)) cat("...\n")
+
+  invisible(x)
+}
+
+is_text <- function(type) {
+  if (is.null(type)) return(FALSE)
+
+  media <- parse_media(type)
+  if (media$type == "text") return(TRUE)
+  if (media$type == "application" && media$subtype == "json") return(TRUE)
+
+  FALSE
+}
+
+#' @export
+as.character.response <- function(x, ...) {
+  content(x, "text")
+}
diff --git a/R/retry.R b/R/retry.R
new file mode 100644
index 0000000..c26663c
--- /dev/null
+++ b/R/retry.R
@@ -0,0 +1,57 @@
+#' 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
+#' 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}.)
+#'
+#' @inheritParams VERB
+#' @inheritParams GET
+#' @inheritParams POST
+#' @param times Maximum number of requests to attempt.
+#' @param pause_base,pause_cap This method uses exponential back-off with
+#'   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 quiet If \code{FALSE}, will print a message displaying how long
+#'   until the next request.
+#' @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}()}.
+#' @export
+#' @examples
+#' # Succeeds straight away
+#' RETRY("GET", "http://httpbin.org/status/200")
+#' # Never succeeds
+#' RETRY("GET", "http://httpbin.org/status/500")
+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) {
+  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)
+
+  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)
+
+  i <- 1
+  while (i < times && http_error(resp)) {
+    backoff_full_jitter(i, status_code(resp), pause_base, pause_cap, quiet = quiet)
+
+    i <- i + 1
+    resp <- request_perform(req, hu$handle$handle)
+  }
+
+  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))))
+  if (!quiet) {
+    message("Request failed [", status, "]. Retrying in ", length, " seconds...")
+  }
+  Sys.sleep(length)
+}
diff --git a/R/safe-callback.R b/R/safe-callback.R
new file mode 100644
index 0000000..8969b99
--- /dev/null
+++ b/R/safe-callback.R
@@ -0,0 +1,10 @@
+#' 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/sha1.r b/R/sha1.r
new file mode 100644
index 0000000..6912397
--- /dev/null
+++ b/R/sha1.r
@@ -0,0 +1,40 @@
+#' SHA1 hash
+#'
+#' Creates a SHA1 hash of data using either HMAC or RSA.
+#'
+#' @param key The key to create the hash with
+#' @param string data to securely hash
+#' @param method The method to use, either HMAC-SHA1 or RSA-SHA1
+#' @keywords internal
+#' @export
+sha1_hash <- function(key, string, method = "HMAC-SHA1") {
+
+  if (is.character(string))
+    string <- charToRaw(paste(string, collapse = "\n"))
+  if (is.character(key))
+    key <- charToRaw(paste(key, collapse = "\n"))
+
+  if (!method %in% c("HMAC-SHA1", "RSA-SHA1")) {
+    stop(paste0("Unsupported hashing method: ", method), call. = FALSE)
+  }
+
+  if (method == "HMAC-SHA1") {
+    hash <- openssl::sha1(string, key = key)
+  } else {
+    hash <- openssl::signature_create(string, openssl::sha1, key = key)
+  }
+
+  openssl::base64_encode(hash)
+}
+
+#' HMAC SHA1
+#'
+#' As described in \url{http://datatracker.ietf.org/doc/rfc2104/}.
+#'
+#' @param key secret key
+#' @param string data to securely hash
+#' @keywords internal
+#' @export
+hmac_sha1 <- function(key, string) {
+  sha1_hash(key, string, "HMAC-SHA1")
+}
diff --git a/R/str.R b/R/str.R
new file mode 100644
index 0000000..655705a
--- /dev/null
+++ b/R/str.R
@@ -0,0 +1,58 @@
+# These are drop in replacements for the stringr:: functions used in httr. They
+# do not retain all functionality from stringr, only that which is used in
+# httr. Notably they are generally not vectorized.
+str_trim <- function(x) {
+  gsub("(^\\s+)|(\\s+$)", "", x)
+}
+
+str_split_fixed <- function(string, pattern, n) {
+  if (length(string) == 0) return(matrix(character(), nrow = 1, ncol = n))
+  m <- gregexpr(pattern, string)[[1]]
+  if (length(m) == 1 && m == -1) {
+    res <- string
+  } else {
+    m_starts <- m
+    m_ends <- m + attr(m, "match.length") - 1L
+    starts <- c(1, m_ends + 1L)[seq_len(n)]
+    ends <- c((m_starts - 1L)[seq_len(n - 1)], nchar(string))
+    res <- lapply(string, function(x)
+                  unlist(Map(substr, x, starts, ends, USE.NAMES = FALSE)))
+  }
+
+  mat <- matrix("", nrow = length(res), ncol = n, byrow = TRUE)
+  mat[seq_along(unlist(res))] <- unlist(res)
+  mat[, seq_len(n), drop = FALSE]
+}
+
+str_split <- function(string, pattern, n = Inf) {
+  res <- strsplit(string, pattern)
+  if (is.finite(n)) {
+    res[seq_len(n)]
+  } else {
+    res
+  }
+}
+
+str_detect <- function(string, pattern) {
+  grepl(pattern, string)
+}
+
+str_match <- function(string, pattern) {
+  m <- regexpr(pattern, string, perl = TRUE)
+  cbind(substr(string, m, attr(m, "match.length") + m - 1L),
+        substr(string, attr(m, "capture.start"),
+               attr(m, "capture.length") + attr(m, "capture.start") - 1L))
+}
+
+str_replace <- function(string, pattern, replace) {
+  m <- regexpr(pattern, string)
+  regmatches(string, m) <- replace
+  string
+}
+
+str_extract <- function(string, pattern) {
+  m <- regexpr(pattern, string)
+  res <- substr(string, m, attr(m, "match.length") + m - 1L)
+  res[m == -1] <- NA_character_
+  res
+}
diff --git a/R/timeout.r b/R/timeout.r
new file mode 100644
index 0000000..dacd4e0
--- /dev/null
+++ b/R/timeout.r
@@ -0,0 +1,18 @@
+#' Set maximum request time.
+#'
+#' @param seconds number of seconds to wait for a response until giving up.
+#'   Can not be less than 1 ms.
+#' @family config
+#' @export
+#' @examples
+#' \dontrun{
+#' GET("http://httpbin.org/delay/3", timeout(1))
+#' GET("http://httpbin.org/delay/1", timeout(2))
+#' }
+timeout <- function(seconds) {
+  if (seconds < 0.001) {
+    stop("Timeout cannot be less than 1 ms", call. = FALSE)
+  }
+
+  config(timeout_ms = seconds * 1000)
+}
diff --git a/R/upload-file.r b/R/upload-file.r
new file mode 100644
index 0000000..3f7d1d9
--- /dev/null
+++ b/R/upload-file.r
@@ -0,0 +1,21 @@
+#' Upload a file with \code{\link{POST}} or \code{\link{PUT}}.
+#'
+#' @param path path to file
+#' @param type mime type of path. If not supplied, will be guess by
+#'   \code{\link[mime]{guess_type}} when needed.
+#' @export
+#' @examples
+#' citation <- upload_file(system.file("CITATION"))
+#' POST("http://httpbin.org/post", body = citation)
+#' POST("http://httpbin.org/post", body = list(y = citation))
+upload_file <- function(path, type = NULL) {
+  stopifnot(is.character(path), length(path) == 1, file.exists(path))
+
+  if (is.null(type))
+    type <- mime::guess_type(path)
+
+  curl::form_file(path, type)
+}
+
+#' @export
+as.character.form_file <- function(x, ...) x
diff --git a/R/url-query.r b/R/url-query.r
new file mode 100644
index 0000000..12607ee
--- /dev/null
+++ b/R/url-query.r
@@ -0,0 +1,29 @@
+parse_query <- function(query) {
+  params <- vapply(strsplit(query, "&")[[1]], str_split_fixed, "=", 2,
+    FUN.VALUE = character(2))
+
+  values <- as.list(curl::curl_unescape(params[2, ]))
+  names(values) <- curl::curl_unescape(params[1, ])
+  values
+}
+
+compose_query <- function(elements) {
+  if (length(elements) == 0)
+    return("")
+
+  if (!all(has_name(elements)))
+    stop("All components of query must be named", call. = FALSE)
+
+  stopifnot(is.list(elements))
+  elements <- compact(elements)
+
+  names <- curl::curl_escape(names(elements))
+
+  encode <- function(x) {
+    if (inherits(x, "AsIs")) return(x)
+    curl::curl_escape(x)
+  }
+  values <- vapply(elements, encode, character(1))
+
+  paste0(names, "=", values, collapse = "&")
+}
diff --git a/R/url.r b/R/url.r
new file mode 100644
index 0000000..c89d87f
--- /dev/null
+++ b/R/url.r
@@ -0,0 +1,165 @@
+# Good example for testing
+# http://stevenlevithan.com/demo/parseuri/js/
+
+#' Parse and build urls according to RFC1808.
+#'
+#' 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.
+#' @return a list containing: \itemize{
+#'  \item scheme
+#'  \item hostname
+#'  \item port
+#'  \item path
+#'  \item params
+#'  \item fragment
+#'  \item query, a list
+#'  \item username
+#'  \item password
+#' }
+#' @export
+#' @examples
+#' 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/"))
+parse_url <- function(url) {
+  if (is.url(url)) return(url)
+
+  url <- as.character(url)
+  stopifnot(length(url) == 1)
+
+  pull_off <- function(pattern) {
+    if (!str_detect(url, pattern)) return(NULL)
+
+    piece <- str_match(url, pattern)[, 2]
+    url <<- str_replace(url, pattern, "")
+
+    piece
+  }
+
+  fragment <- pull_off("#(.*)$")
+  scheme <- pull_off("^([[:alpha:]+.-]+):")
+  netloc <- pull_off("^//([^/?]*)/?")
+
+  if (identical(netloc, "")) { # corresponds to ///
+    url <- paste0("/", url)
+    port <- username <- password <- hostname <- NULL
+  } else if (!is.null(netloc)) {
+
+    pieces <- strsplit(netloc, "@")[[1]]
+    if (length(pieces) == 1) {
+      username <- NULL
+      password <- NULL
+
+      host <- pieces
+    } else {
+      user_pass <- strsplit(pieces[[1]], ":")[[1]]
+      username <- user_pass[1]
+      if (length(user_pass) == 1) {
+        password <- NULL
+      } else {
+        password <- user_pass[2]
+      }
+
+      host <- pieces[2]
+    }
+
+    host_pieces <- str_split(host, ":")[[1]]
+    hostname <- host_pieces[1]
+    port <- if (length(host_pieces) > 1) host_pieces[2]
+  } else {
+    port <- username <- password <- hostname <- NULL
+  }
+
+  query <- pull_off("\\?(.*)$")
+  if (!is.null(query)) {
+    query <- parse_query(query)
+  }
+  params <- pull_off(";(.*)$")
+
+  structure(list(
+      scheme = scheme, hostname = hostname, port = port, path = url,
+      query = query, params = params, fragment = fragment,
+      username = username, password = password),
+    class = "url")
+}
+
+is.url <- function(x) inherits(x, "url")
+print.url <- function(x, ...) {
+  cat("Url: ", build_url(x), "\n", sep = "")
+  invisible(x)
+}
+"[.url" <- function(x, ...) {
+  structure(NextMethod(), class = "url")
+}
+
+#' @export
+#' @rdname parse_url
+build_url <- function(url) {
+  stopifnot(is.url(url))
+
+  scheme <- url$scheme
+  hostname <- url$hostname
+
+  if (!is.null(url$port)) {
+    port <- paste0(":", url$port)
+  } else {
+    port <- NULL
+  }
+
+  path <- paste(gsub("^/", "", url$path), collapse = "/")
+
+  if (!is.null(url$params)) {
+    params <- paste0(";", url$params)
+  } else {
+    params <- NULL
+  }
+
+  if (is.list(url$query)) {
+    query <- compose_query(url$query)
+  } else {
+    query <- url$query
+  }
+  if (!is.null(query)) {
+    stopifnot(is.character(query), length(query) == 1)
+    query <- paste0("?", query)
+  }
+
+  if (is.null(url$username) && !is.null(url$password)) {
+    stop("Cannot set password without username")
+  }
+
+  paste0(scheme, "://",
+        url$username, if (!is.null(url$password)) ":", url$password,
+        if (!is.null(url$username)) "@",
+        hostname, port, "/", path, params, query,
+        if (!is.null(url$fragment)) "#", url$fragment)
+}
+
+#' Modify a url.
+#'
+#' Modify a url by first parsing it and then replacing components with
+#' the non-NULL arguments of this function.
+#'
+#' @export
+#' @param url the url to modify
+#' @param scheme,hostname,port,path,query,params,fragment,username,password
+#'   components of the url to change
+modify_url <- function(url, scheme = NULL, hostname = NULL, port = NULL,
+                       path = NULL, query = NULL, params = NULL, fragment = NULL,
+                       username = NULL, password = NULL) {
+
+  old <- parse_url(url)
+  new <- compact(list(
+    scheme = scheme, hostname = hostname, port = port, path = path,
+    query = query, params = params, fragment = fragment,
+    username = username, password = password))
+
+  build_url(utils::modifyList(old, new))
+}
+
+compact <- function(x) Filter(Negate(is.null), x)
diff --git a/R/user-agent.r b/R/user-agent.r
new file mode 100644
index 0000000..61c90c2
--- /dev/null
+++ b/R/user-agent.r
@@ -0,0 +1,15 @@
+#' Set user agent.
+#'
+#' Override the default RCurl user agent of \code{NULL}
+#'
+#' @param agent string giving user agent
+#' @export
+#' @family config
+#' @examples
+#' GET("http://httpbin.org/user-agent")
+#' GET("http://httpbin.org/user-agent", user_agent("httr"))
+user_agent <- function(agent) {
+  stopifnot(is.character(agent), length(agent) == 1)
+
+  config(useragent = agent)
+}
diff --git a/R/utils.r b/R/utils.r
new file mode 100644
index 0000000..6c81a7e
--- /dev/null
+++ b/R/utils.r
@@ -0,0 +1,86 @@
+"%||%" <- function(a, b) {
+  if (length(a) > 0) a else b
+}
+
+timestamp <- function(x = Sys.time()) {
+  format(x, "%Y-%m-%dT%H:%M:%SZ", tz = "UTC")
+}
+
+sort_names <- function(x) x[order(names(x))]
+
+nonce <- function(length = 10) {
+  paste(sample(c(letters, LETTERS, 0:9), length, replace = TRUE),
+    collapse = "")
+}
+
+has_env_var <- function(x) !identical(Sys.getenv(x), "")
+
+named <- function(x) x[has_name(x)]
+unnamed <- function(x) x[!has_name(x)]
+
+has_name <- function(x) {
+  nms <- names(x)
+  if (is.null(nms))
+    return(rep(FALSE, length(x)))
+
+  !is.na(nms) & nms != ""
+}
+
+travis_encrypt <- function(vars) {
+  values <- Sys.getenv(vars)
+  cat(paste0("travis encrypt ", paste0(vars, "=", values, collapse = " ")))
+}
+
+is_installed <- function(pkg) {
+  system.file(package = pkg) != ""
+}
+
+need_package <- function(pkg) {
+  if (is_installed(pkg)) return(invisible())
+
+  stop("Please install ", pkg, " package", call. = FALSE)
+}
+
+last <- function(x) {
+  if (length(x) < 1) return(x)
+  x[[length(x)]]
+}
+
+compact <- function(x) {
+  null <- vapply(x, is.null, logical(1))
+  x[!null]
+}
+
+keep_last <- function(...) {
+  x <- c(...)
+  x[!duplicated(names(x), fromLast = TRUE)]
+}
+
+named_vector <- function(title, x) {
+  if (length(x) == 0) return()
+
+  cat(title, ":\n", sep = "")
+  bullets <- paste0("* ", names(x), ": ", as.character(x))
+  cat(bullets, sep = "\n")
+}
+
+keep_last <- function(...) {
+  x <- c(...)
+  x[!duplicated(names(x), fromLast = TRUE)]
+}
+
+find_cert_bundle <- function() {
+  if (.Platform$OS.type != "windows")
+    return()
+
+  env <- Sys.getenv("CURL_CA_BUNDLE")
+  if (!identical(env, ""))
+    return(env)
+
+  bundled <- file.path(R.home("etc"), "curl-ca-bundle.crt")
+  if (file.exists(bundled))
+    bundled
+
+  # Fall back to certificate bundle in openssl
+  system.file("cacert.pem", package = "openssl")
+}
diff --git a/R/verbose.r b/R/verbose.r
new file mode 100644
index 0000000..27fc7f7
--- /dev/null
+++ b/R/verbose.r
@@ -0,0 +1,75 @@
+#' Give verbose output.
+#'
+#' A verbose connection provides much more information about the flow of
+#' information between the client and server.
+#'
+#' @section Prefixes:
+#'
+#' \code{verbose()} uses the following prefixes to distinguish between
+#' different components of the http messages:
+#'
+#' \itemize{
+#'   \item \code{*} informative curl messages
+#'
+#'   \item \code{->} headers sent (out)
+#'   \item \code{>>} data sent (out)
+#'   \item \code{*>} ssl data sent (out)
+#'
+#'   \item \code{<-} headers received (in)
+#'   \item \code{<<} data received (in)
+#'   \item \code{<*} ssl data received (in)
+#' }
+#'
+#' @family config
+#' @param data_out Show data sent to the server.
+#' @param info Show informational text from curl. This is mainly useful
+#'   for debugging https and auth problems, so is disabled by default.
+#' @param data_in Show data recieved from the server.
+#' @param ssl Show even data sent/recieved over SSL connections?
+#' @seealso \code{\link{with_verbose}()} makes it easier to use verbose mode
+#'  even when the requests are buried inside another function call.
+#' @export
+#' @examples
+#' GET("http://httpbin.org", verbose())
+#' GET("http://httpbin.org", verbose(info = TRUE))
+#'
+#' f <- function() {
+#'   GET("http://httpbin.org")
+#' }
+#' with_verbose(f())
+#' with_verbose(f(), info = TRUE)
+#'
+#' # verbose() makes it easy to see exactly what POST requests send
+#' POST_verbose <- function(body, ...) {
+#'   POST("https://httpbin.org/post", body = body, verbose(), ...)
+#'   invisible()
+#' }
+#' POST_verbose(list(x = "a", y = "b"))
+#' POST_verbose(list(x = "a", y = "b"), encode = "form")
+#' POST_verbose(FALSE)
+#' POST_verbose(NULL)
+#' POST_verbose("")
+#' POST_verbose("xyz")
+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)
+    )
+  }
+  config(debugfunction = debug, verbose = TRUE)
+}
+
+prefix_message <- function(prefix, x, blank_line = FALSE) {
+  x <- readBin(x, character())
+
+  lines <- unlist(strsplit(x, "\n", fixed = TRUE, useBytes = TRUE))
+  out <- paste0(prefix, lines, collapse = "\n")
+  message(out)
+  if (blank_line) cat("\n")
+}
diff --git a/R/write-function.R b/R/write-function.R
new file mode 100644
index 0000000..ac9144e
--- /dev/null
+++ b/R/write-function.R
@@ -0,0 +1,90 @@
+#' S3 object to define response writer.
+#'
+#' This S3 object allows you to control how the response body is saved.
+#'
+#' @param subclass,... Class name and fields. Used in class constructors.
+#' @param x A \code{write_function} object to process.
+#' @keywords internal
+#' @export
+write_function <- function(subclass, ...) {
+  structure(list(...), class = c(subclass, "write_function"))
+}
+
+#' Control where the response body is written.
+#'
+#' The default behaviour is to use \code{write_memory()}, which caches
+#' the response locally in memory. This is useful when talking to APIs as
+#' it avoids a round-trip to disk. If you want to save a file that's bigger
+#' than memory, use \code{write_disk()} to save it to a known path.
+#'
+#' @param path Path to content to.
+#' @param overwrite Will only overwrite existing \code{path} if TRUE.
+#' @export
+#' @examples
+#' tmp <- tempfile()
+#' r1 <- GET("https://www.google.com", write_disk(tmp))
+#' readLines(tmp)
+#'
+#' # The default
+#' r2 <- GET("https://www.google.com", write_memory())
+#'
+#' # Save a very large file
+#' \dontrun{
+#' GET("http://www2.census.gov/acs2011_5yr/pums/csv_pus.zip",
+#'   write_disk("csv_pus.zip"), progress())
+#' }
+write_disk <- function(path, overwrite = FALSE) {
+  if (!overwrite && file.exists(path)) {
+    stop("Path exists and overwrite is FALSE", call. = FALSE)
+  }
+  request(output = write_function("write_disk", path = path, file = NULL))
+}
+
+#' @rdname write_disk
+#' @export
+write_memory <- function() {
+  request(output = write_function("write_memory"))
+}
+
+# Streaming -----------------------------------------------------------------------
+
+#' Process output in a streaming manner.
+#'
+#' This is the most general way of processing the response from the server -
+#' you receive the raw bytes as they come in, and you can do whatever you want
+#' with them.
+#'
+#' @param f Callback function. It should have a single argument, a raw
+#'   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",
+#'   write_stream(function(x) {
+#'    print(length(x))
+#'    length(x)
+#'  })
+#' )
+#' @export
+write_stream <- function(f) {
+  stopifnot(is.function(f), length(formals(f)) == 1)
+  request(output = write_function("write_stream", f = f))
+}
+
+
+request_fetch <- function(x, url, handle) UseMethod("request_fetch")
+request_fetch.write_memory <- function(x, url, handle) {
+  curl::curl_fetch_memory(url, handle = handle)
+}
+request_fetch.write_disk <- function(x, url, handle) {
+  resp <- curl::curl_fetch_disk(url, x$path, handle = handle)
+  resp$content <- path(resp$content)
+  resp
+}
+request_fetch.write_stream <- function(x, url, handle) {
+  curl::curl_fetch_stream(url, x$f, handle = 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/R/zzz.R b/R/zzz.R
new file mode 100644
index 0000000..1c7c5aa
--- /dev/null
+++ b/R/zzz.R
@@ -0,0 +1,17 @@
+.onLoad <- function(libname, pkgname) {
+  op <- options()
+  op.dplyr <- list(
+    httr_oob_default = FALSE,
+    httr_oauth_cache = NA
+  )
+  toset <- !(names(op.dplyr) %in% names(op))
+  if(any(toset)) options(op.dplyr[toset])
+
+  invisible()
+}
+
+release_questions <- function() {
+  c(
+    "Have you run all the OAuth demos?"
+  )
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bff7fec
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+# 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)
+
+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.
+
+Key features:
+
+* Functions for the most important http verbs: `GET()`, `HEAD()`, `PATCH()`, 
+  `PUT()`, `DELETE()` and `POST()`.
+
+* Automatic connection sharing across requests to the same website (by
+  default, curl handles are managed automatically), cookies are maintained
+  across requests, and a up-to-date root-level SSL certificate store is used.
+
+* Requests return a standard reponse object that captures the http status line,
+  headers and body, along with other useful information.
+
+  * Response content is available with `content()` as a raw vector (`as =
+    "raw"`), a character vector (`as = "text"`), or parsed into an R object
+    (`as = "parsed"`), currently for html, xml, json, png and jpeg.
+
+  * You can convert http errors into R errors with `stop_for_status()`.
+
+* Config functions make it easier to modify the request in common ways:
+  `set_cookies()`, `add_headers()`, `authenticate()`, `use_proxy()`, 
+  `verbose()`, `timeout()`, `content_type()`, `accept()`, `progress()`.
+
+* Support for OAuth 1.0 and 2.0 with `oauth1.0_token()` and `oauth2.0_token()`.
+  The demo directory has eight OAuth demos: four for 1.0 (twitter, vimeo,
+  withings and yahoo) and four for 2.0 (facebook, github, google, linkedin). 
+  OAuth credentials are automatically cached within a project. 
+
+`httr` wouldn't be possible without the hard work of the authors of [curl](https://cran.r-project.org/package=curl) and [libcurl](http://curl.haxx.se/). Thanks! `httr` is inspired by http libraries in other languages, such as [Resty](http://beders.github.com/Resty/Resty/Examples.html), [Requests](http://docs.python-requests.org/en/latest/index.html) and [httparty](http://github.com/jnunemaker/httparty/tree/master).
+
+## Installation
+
+To get the current released version from CRAN:
+
+```R
+install.packages("httr")
+```
+
+To get the current development version from github:
+
+```R
+# install.packages("devtools")
+devtools::install_github("hadley/httr")
+```
diff --git a/build/vignette.rds b/build/vignette.rds
new file mode 100644
index 0000000..74676b6
Binary files /dev/null and b/build/vignette.rds differ
diff --git a/debian/README.test b/debian/README.test
deleted file mode 100644
index 8d70ca3..0000000
--- a/debian/README.test
+++ /dev/null
@@ -1,9 +0,0 @@
-Notes on how this package can be tested.
-────────────────────────────────────────
-
-This package can be tested by running the provided test:
-
-cd tests
-LC_ALL=C R --no-save < testthat.R
-
-in order to confirm its integrity.
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index 544f3f2..0000000
--- a/debian/changelog
+++ /dev/null
@@ -1,13 +0,0 @@
-r-cran-httr (1.2.1-1) unstable; urgency=medium
-
-  * New upstream version
-  * Convert to dh-r
-  * Canonical homepage for CRAN
-
- -- Andreas Tille <tille at debian.org>  Sat, 29 Oct 2016 09:21:28 +0200
-
-r-cran-httr (1.1.0-1) unstable; urgency=low
-
-  * Initial release (Closes: #819086)
-
- -- Andreas Tille <tille at debian.org>  Wed, 23 Mar 2016 16:20:23 +0100
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index ec63514..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-9
diff --git a/debian/control b/debian/control
deleted file mode 100644
index 622219a..0000000
--- a/debian/control
+++ /dev/null
@@ -1,28 +0,0 @@
-Source: r-cran-httr
-Maintainer: Debian Med Packaging Team <debian-med-packaging at lists.alioth.debian.org>
-Uploaders: Andreas Tille <tille at debian.org>
-Section: gnu-r
-Priority: optional
-Build-Depends: debhelper (>= 9),
-               dh-r,
-               r-base-dev,
-               r-cran-jsonlite,
-               r-cran-mime,
-               r-cran-curl,
-               r-cran-openssl,
-               r-cran-r6
-Standards-Version: 3.9.8
-Vcs-Browser: https://anonscm.debian.org/viewvc/debian-med/trunk/packages/R/r-cran-httr/trunk/
-Vcs-Svn: svn://anonscm.debian.org/debian-med/trunk/packages/R/r-cran-httr/trunk/
-Homepage: https://cran.r-project.org/package=httr
-
-Package: r-cran-httr
-Architecture: all
-Depends: ${misc:Depends},
-         ${R:Depends}
-Recommends: ${R:Recommends}
-Suggests: ${R:Suggests}
-Description: GNU R tools for working with URLs and HTTP
- Useful tools for working with HTTP organised by HTTP verbs (GET(),
- POST(), etc). Configuration functions make it easy to control additional
- request components (authenticate(), add_headers() and so on).
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index de059f2..0000000
--- a/debian/copyright
+++ /dev/null
@@ -1,31 +0,0 @@
-Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Contact: Hadley Wickham <hadley at rstudio.com>
-Source: https://cran.r-project.org/package=httr
-
-Files: *
-Copyright: 2013-2016 Hadley Wickham <hadley at rstudio.com>,
-License: MIT
-
-Files: debian/*
-Copyright: 2016 Andreas Tille <tille at debian.org>
-License: MIT
-
-License: MIT
- Permission is hereby granted, free of charge, to any person obtaining a
- copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
- .
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
- .
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/debian/docs b/debian/docs
deleted file mode 100644
index 960011c..0000000
--- a/debian/docs
+++ /dev/null
@@ -1,3 +0,0 @@
-tests
-debian/README.test
-debian/tests/run-unit-test
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index 68d9a36..0000000
--- a/debian/rules
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/make -f
-
-%:
-	dh $@ --buildsystem R
diff --git a/debian/source/format b/debian/source/format
deleted file mode 100644
index 163aaf8..0000000
--- a/debian/source/format
+++ /dev/null
@@ -1 +0,0 @@
-3.0 (quilt)
diff --git a/debian/tests/control b/debian/tests/control
deleted file mode 100644
index b044b0c..0000000
--- a/debian/tests/control
+++ /dev/null
@@ -1,3 +0,0 @@
-Tests: run-unit-test
-Depends: @, r-cran-testthat
-Restrictions: allow-stderr
diff --git a/debian/tests/run-unit-test b/debian/tests/run-unit-test
deleted file mode 100644
index db28313..0000000
--- a/debian/tests/run-unit-test
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/sh -e
-
-oname=httr
-pkg=r-cran-`echo $oname | tr [A-Z] [a-z]`
-
-if [ "$ADTTMP" = "" ] ; then
-  ADTTMP=`mktemp -d /tmp/${pkg}-test.XXXXXX`
-fi
-cd $ADTTMP
-cp -a /usr/share/doc/${pkg}/tests/* $ADTTMP
-LC_ALL=C R --no-save < testthat.R
-rm -fr $ADTTMP/*
diff --git a/debian/watch b/debian/watch
deleted file mode 100644
index b09a2bc..0000000
--- a/debian/watch
+++ /dev/null
@@ -1,3 +0,0 @@
-version=3
-http://cran.r-project.org/src/contrib/httr_([-0-9\.]*).tar.gz
-
diff --git a/demo/00Index b/demo/00Index
new file mode 100644
index 0000000..0f1e26a
--- /dev/null
+++ b/demo/00Index
@@ -0,0 +1,12 @@
+connection-sharing  Demonstration of how connection sharing saves time
+oauth1-twitter      Using twitter api with OAuth 1.0
+oauth1-vimeo        Using vimeo api with OAuth 1.0
+oauth1-yahoo        Using yahoo api with OAuth 1.0
+oauth1-withings     Using withings api with OAuth 1.0
+oauth2-azure        Using Azure apis with OAuth 2.0
+oauth2-facebook     Using the facebook api with OAuth 2.0
+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
+service-account     Using Google service account
diff --git a/demo/connection-sharing.r b/demo/connection-sharing.r
new file mode 100644
index 0000000..1ab896c
--- /dev/null
+++ b/demo/connection-sharing.r
@@ -0,0 +1,17 @@
+test_server <- "http://had.co.nz"
+
+# Create a new handle for every request - no connection sharing
+rowMeans(replicate(20,
+  GET(handle = handle(test_server), path = "index.html")$times)
+)
+
+test_handle <- handle(test_server)
+# Re use the same handle for multiple requests
+rowMeans(replicate(20,
+  GET(handle = test_handle, path = "index.html")$times)
+)
+
+# With httr, handles are automatically pooled
+rowMeans(replicate(20,
+  GET(test_server, path = "index.html")$times)
+)
diff --git a/demo/oauth1-twitter.r b/demo/oauth1-twitter.r
new file mode 100644
index 0000000..88c1a5b
--- /dev/null
+++ b/demo/oauth1-twitter.r
@@ -0,0 +1,23 @@
+library(httr)
+
+# 1. Find OAuth settings for twitter:
+#    https://dev.twitter.com/docs/auth/oauth
+oauth_endpoints("twitter")
+
+# 2. Register an application at https://apps.twitter.com/
+#    Make sure to set callback url to "http://127.0.0.1:1410/"
+#
+#    Replace key and secret below
+myapp <- oauth_app("twitter",
+  key = "TYrWFPkFAkn4G5BbkWINYw",
+  secret = "qjOkmKYU9kWfUFWmekJuu5tztE9aEfLbt26WlhZL8"
+)
+
+# 3. Get OAuth credentials
+twitter_token <- oauth1.0_token(oauth_endpoints("twitter"), myapp)
+
+# 4. Use API
+req <- GET("https://api.twitter.com/1.1/statuses/home_timeline.json",
+  config(token = twitter_token))
+stop_for_status(req)
+content(req)
diff --git a/demo/oauth1-vimeo.r b/demo/oauth1-vimeo.r
new file mode 100644
index 0000000..5df2b90
--- /dev/null
+++ b/demo/oauth1-vimeo.r
@@ -0,0 +1,21 @@
+library(httr)
+
+# 1. Find OAuth settings for vimeo:
+#    http://vimeo.com/api/docs/authentication
+oauth_endpoints("vimeo")
+
+# 2. Register an application at https://developer.vimeo.com/apps
+#    Replace key and secret below.
+myapp <- oauth_app("vimeo",
+  key = "bd535bc38ed5caccd79330ff33075eb9",
+  secret = "51ab8cb2cbb8b7eb")
+
+# 3. Get OAuth credentials
+vimeo_token <- oauth1.0_token(oauth_endpoints("vimeo"), myapp)
+
+# 4. Use API
+req <- GET("https://vimeo.com/api/rest/v2",
+  query = list(method = "vimeo.videos.getAll", format = "json"),
+  config(token = vimeo_token))
+stop_for_status(req)
+str(content(req))
diff --git a/demo/oauth1-withings.r b/demo/oauth1-withings.r
new file mode 100644
index 0000000..83b2eb5
--- /dev/null
+++ b/demo/oauth1-withings.r
@@ -0,0 +1,30 @@
+library(httr)
+
+# 1. Create endpoint
+withings <- oauth_endpoint(base_url = "https://oauth.withings.com/account",
+  "request_token",
+  "authorize",
+  "access_token"
+)
+
+# 2. Register an application at https://oauth.withings.com/partner/add
+#    Insert your key and secret below
+withingsapp <- oauth_app("withings",
+  key = "e71b82e1d7fc2d2e3e2c2b398eeb617c16cee050c924c84df640e2e15eccb",
+  secret = "3707510707116e836299c04f26f5ebbb51026d9d9cd758e36e4c7833dd7d"
+)
+
+# 3. Get OAuth credentials
+withings_token <- oauth1.0_token(withings, withingsapp, as_header = FALSE)
+
+# 4. Use API
+req <- GET("http://wbsapi.withings.net/measure",
+  query = list(
+    action = "getmeas",
+    userid = withings_token$credentials$userid
+  ),
+  config(token = withings_token)
+)
+
+stop_for_status(req)
+content(req, type = "application/json")
diff --git a/demo/oauth1-yahoo.r b/demo/oauth1-yahoo.r
new file mode 100644
index 0000000..037c6a6
--- /dev/null
+++ b/demo/oauth1-yahoo.r
@@ -0,0 +1,15 @@
+library(httr)
+
+# 1. Find OAuth settings for yahoo:
+#    https://developer.yahoo.com/oauth/guide/oauth-auth-flow.html
+oauth_endpoints("yahoo")
+
+# 2. Register an application at https://developer.apps.yahoo.com/projects
+#    Replace key and secret below.
+myapp <- oauth_app("yahoo",
+  key = "dj0yJmk9ZEp0d2J2MFRuakNQJmQ9WVdrOU0zaHRUMlJpTTJNbWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD00Nw--",
+  secret = "82f339a41f71a3b4d9b840be427dde132e36d115"
+)
+
+# 3. Get OAuth credentials
+yahoo_token <- oauth1.0_token(oauth_endpoints("yahoo"), myapp)
diff --git a/demo/oauth2-azure.r b/demo/oauth2-azure.r
new file mode 100644
index 0000000..6b20f94
--- /dev/null
+++ b/demo/oauth2-azure.r
@@ -0,0 +1,52 @@
+# !!! The special redirect URI "urn:ietf:wg:oauth:2.0:oob used
+# !!! by httr in case httuv is not installed is currently not
+# !!! supported by Azure Active Directory (AAD).
+# !!! Therefore it is required to install httpuv to make this work.
+
+# 1. Register an app app in AAD, e.g. as a "Native app", with
+#    redirect URI <http://localhost:1410/>.
+# 2. Insert the App name:
+app_name <- 'myapp' # not important for authorization grant flow
+# 3. Insert the created apps client ID which was issued after app creation:
+client_id <- 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
+# In case your app was registered as a web app instead of a native app,
+# you might have to add your secret key string here:
+client_secret <- NULL
+# API resource ID to request access for, e.g. Power BI:
+resource_uri <- 'https://analysis.windows.net/powerbi/api'
+
+# Obtain OAuth2 endpoint settings for azure:
+#    This uses the "common" endpoint.
+#    To use a tenant url, create an
+#    oauth_endpoint(authorize = "https://login.windows.net/<tenant_id>/oauth2/authorize",
+#                   access = "https://login.windows.net/<tenant_id>/oauth2/token")
+#    with <tenant_id> replaced by your endpoint ID.
+azure_endpoint <- oauth_endpoints('azure')
+
+# Create the app instance.
+myapp <- oauth_app(appname = app_name,
+                   key = client_id,
+                   secret = client_secret)
+
+# Step through the authorization chain:
+#    1. You will be redirected to you authorization endpoint via web browser.
+#    2. Once you responded to the request, the endpoint will redirect you to
+#       the local address specified by httr.
+#    3. httr will acquire the authorization code (or error) from the data
+#       posted to the redirect URI.
+#    4. If a code was acquired, httr will contact your authorized token access
+#       endpoint to obtain the token.
+mytoken <- oauth2.0_token(azure_endpoint, myapp,
+                          user_params = list(resource = resource_uri),
+                          use_oob = FALSE)
+if (('error' %in% names(mytoken$credentials)) && (nchar(mytoken$credentials$error) > 0)) {
+  errorMsg <- paste('Error while acquiring token.',
+                    paste('Error message:', mytoken$credentials$error),
+                    paste('Error description:', mytoken$credentials$error_description),
+                    paste('Error code:', mytoken$credentials$error_codes),
+                    sep = '\n')
+  stop(errorMsg)
+}
+
+# Resource API can be accessed through "mytoken" at this point.
+
diff --git a/demo/oauth2-facebook.r b/demo/oauth2-facebook.r
new file mode 100644
index 0000000..f679e62
--- /dev/null
+++ b/demo/oauth2-facebook.r
@@ -0,0 +1,22 @@
+library(httr)
+
+# 1. Find OAuth settings for facebook:
+#    http://developers.facebook.com/docs/authentication/server-side/
+oauth_endpoints("facebook")
+
+# 2. Register an application at https://developers.facebook.com/apps/
+#    Insert your values below - if secret is omitted, it will look it up in
+#    the FACEBOOK_CONSUMER_SECRET environmental variable.
+myapp <- oauth_app("facebook", "353609681364760", "1777c63343eba28359537764fab99b9a")
+
+# 3. Get OAuth credentials
+facebook_token <- oauth2.0_token(
+  oauth_endpoints("facebook"),
+  myapp,
+  type = "application/x-www-form-urlencoded"
+)
+
+# 4. Use API
+req <- GET("https://graph.facebook.com/me", config(token = facebook_token))
+stop_for_status(req)
+str(content(req))
diff --git a/demo/oauth2-github.r b/demo/oauth2-github.r
new file mode 100644
index 0000000..42d5823
--- /dev/null
+++ b/demo/oauth2-github.r
@@ -0,0 +1,28 @@
+library(httr)
+
+# 1. Find OAuth settings for github:
+#    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
+#    (http://github.com is fine) and  http://localhost:1410 as the callback url
+#
+#    Replace your key and secret below.
+myapp <- oauth_app("github",
+  key = "56b637a5baffac62cad9",
+  secret = "8e107541ae1791259e9987d544ca568633da2ebf")
+
+# 3. Get OAuth credentials
+github_token <- oauth2.0_token(oauth_endpoints("github"), myapp)
+
+# 4. Use API
+gtoken <- config(token = github_token)
+req <- GET("https://api.github.com/rate_limit", gtoken)
+stop_for_status(req)
+content(req)
+
+# OR:
+req <- with_config(gtoken, GET("https://api.github.com/rate_limit"))
+stop_for_status(req)
+content(req)
diff --git a/demo/oauth2-google.r b/demo/oauth2-google.r
new file mode 100644
index 0000000..29618b0
--- /dev/null
+++ b/demo/oauth2-google.r
@@ -0,0 +1,21 @@
+library(httr)
+
+# 1. Find OAuth settings for google:
+#    https://developers.google.com/accounts/docs/OAuth2InstalledApp
+oauth_endpoints("google")
+
+# 2. Register an application at https://cloud.google.com/console#/project
+#    Replace key and secret below.
+myapp <- oauth_app("google",
+  key = "16795585089.apps.googleusercontent.com",
+  secret = "hlJNgK73GjUXILBQvyvOyurl")
+
+# 3. Get OAuth credentials
+google_token <- oauth2.0_token(oauth_endpoints("google"), myapp,
+  scope = "https://www.googleapis.com/auth/userinfo.profile")
+
+# 4. Use API
+req <- GET("https://www.googleapis.com/oauth2/v1/userinfo",
+  config(token = google_token))
+stop_for_status(req)
+content(req)
diff --git a/demo/oauth2-linkedin.r b/demo/oauth2-linkedin.r
new file mode 100644
index 0000000..5b9ba35
--- /dev/null
+++ b/demo/oauth2-linkedin.r
@@ -0,0 +1,44 @@
+library(httr)
+
+# 1. Find OAuth settings for linkedin:
+#    https://developer.linkedin.com/documents/linkedins-oauth-details
+oauth_endpoints("linkedin")
+
+# 2. Register an application at https://www.linkedin.com/secure/developer
+#    Make sure to register http://localhost:1410/ as an "OAuth 2.0 Redirect URL".
+#    (the trailing slash is important!)
+#
+#    Replace key and secret below.
+myapp <- oauth_app("linkedin",
+  key = "outmkw3859gy",
+  secret = "n7vBr3lokGOCDKCd")
+
+# 3. Get OAuth credentials
+# LinkedIn doesn't implement OAuth 2.0 standard
+# (http://tools.ietf.org/html/rfc6750#section-2) so we extend the Token2.0
+# ref class to implement a custom sign method.
+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())
+  },
+  can_refresh = function() {
+    TRUE
+  },
+  refresh = function() {
+    self$credentials <- init_oauth2.0(self$endpoint, self$app,
+      scope = self$params$scope, type = self$params$type,
+      use_oob = self$params$use_oob)
+  }
+))
+token <- TokenLinkedIn$new(
+  endpoint = oauth_endpoints("linkedin"),
+  app = myapp,
+  params = list(use_oob = FALSE, scope = NULL, type = NULL)
+)
+
+# 4. Use API
+req <- GET("https://api.linkedin.com/v1/people/~", config(token = token))
+stop_for_status(req)
+content(req)
diff --git a/demo/oauth2-reddit.R b/demo/oauth2-reddit.R
new file mode 100644
index 0000000..1c6cc8e
--- /dev/null
+++ b/demo/oauth2-reddit.R
@@ -0,0 +1,17 @@
+library(httr)
+
+# 1. Find OAuth settings for reddit:
+#    https://github.com/reddit/reddit/wiki/OAuth2
+reddit <- oauth_endpoint(
+  authorize = "https://www.reddit.com/api/v1/authorize",
+  access =    "https://www.reddit.com/api/v1/access_token"
+)
+
+# 2. Register an application at https://www.reddit.com/prefs/apps
+app <- oauth_app("reddit", "bvmjj2EOBvOknQ", "n8ueSvTNdlE0BDDJpLljvmgUGUw")
+
+# 3. Get OAuth credentials
+token <- oauth2.0_token(reddit, app,
+  scope = c("read", "modposts"),
+  use_basic_auth = TRUE
+)
diff --git a/demo/service-account.R b/demo/service-account.R
new file mode 100644
index 0000000..aa43e2a
--- /dev/null
+++ b/demo/service-account.R
@@ -0,0 +1,22 @@
+
+# 1. Find OAuth settings for google:
+#    https://developers.google.com/accounts/docs/OAuth2InstalledApp
+oauth_endpoints("google")
+
+# 2. Register an project at https://cloud.google.com/console#/project
+
+# 3. Navigate to API Manager, then credentials. Create a new
+#    "service account key". This will generate a JSON file that you need to
+#    save in a secure location. This file is equivalent to a username +
+#    password pair.
+
+token <- oauth_service_token(
+  oauth_endpoints("google"),
+  jsonlite::fromJSON("demo/service-account.json"),
+  "https://www.googleapis.com/auth/userinfo.profile"
+)
+
+# 4. Use API
+req <- GET("https://www.googleapis.com/oauth2/v1/userinfo", config(token = token))
+stop_for_status(req)
+content(req)
diff --git a/inst/doc/api-packages.R b/inst/doc/api-packages.R
new file mode 100644
index 0000000..db79b0e
--- /dev/null
+++ b/inst/doc/api-packages.R
@@ -0,0 +1,199 @@
+## ----setup, include = FALSE----------------------------------------------
+library(httr)
+knitr::opts_chunk$set(comment = "#>", collapse = TRUE)
+
+## ------------------------------------------------------------------------
+library(httr)
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  GET(url)
+}
+
+resp <- github_api("/repos/hadley/httr")
+resp
+
+## ------------------------------------------------------------------------
+GET("http://www.colourlovers.com/api/color/6B4106?format=xml")
+GET("http://www.colourlovers.com/api/color/6B4106?format=json")
+
+## ------------------------------------------------------------------------
+http_type(resp)
+
+## ------------------------------------------------------------------------
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  
+  resp <- GET(url)
+  if (http_type(resp) != "application/json") {
+    stop("API did not return json", call. = FALSE)
+  }
+  
+  resp
+}
+
+## ------------------------------------------------------------------------
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  
+  resp <- GET(url)
+  if (http_type(resp) != "application/json") {
+    stop("API did not return json", call. = FALSE)
+  }
+  
+  jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE)
+}
+
+## ------------------------------------------------------------------------
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  
+  resp <- GET(url)
+  if (http_type(resp) != "application/json") {
+    stop("API did not return json", call. = FALSE)
+  }
+  
+  parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE)
+  
+  structure(
+    list(
+      content = parsed,
+      path = path,
+      response = resp
+    ),
+    class = "github_api"
+  )
+}
+
+print.github_api <- function(x, ...) {
+  cat("<GitHub ", x$path, ">\n", sep = "")
+  str(x$content)
+  invisible(x)
+}
+
+github_api("/users/hadley")
+
+## ---- error = TRUE-------------------------------------------------------
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  
+  resp <- GET(url)
+  if (http_type(resp) != "application/json") {
+    stop("API did not return json", call. = FALSE)
+  }
+  
+  parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE)
+  
+  if (http_error(resp)) {
+    stop(
+      sprintf(
+        "GitHub API request failed [%s]\n%s\n<%s>", 
+        status_code(resp),
+        parsed$message,
+        parsed$documentation_url
+      ),
+      call. = FALSE
+    )
+  }
+  
+  structure(
+    list(
+      content = parsed,
+      path = path,
+      response = resp
+    ),
+    class = "github_api"
+  )
+}
+github_api("/user/hadley")
+
+## ------------------------------------------------------------------------
+ua <- user_agent("http://github.com/hadley/httr")
+ua
+
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  
+  resp <- GET(url, ua)
+  if (http_type(resp) != "application/json") {
+    stop("API did not return json", call. = FALSE)
+  }
+  
+  parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE)
+  
+  if (status_code(resp) != 200) {
+    stop(
+      sprintf(
+        "GitHub API request failed [%s]\n%s\n<%s>", 
+        status_code(resp),
+        parsed$message,
+        parsed$documentation_url
+      ),
+      call. = FALSE
+    )
+  }
+  
+  structure(
+    list(
+      content = parsed,
+      path = path,
+      response = resp
+    ),
+    class = "github_api"
+  )
+}
+
+## ---- eval = FALSE-------------------------------------------------------
+#  # modify_url
+#  POST(modify_url("https://httpbin.org", path = "/post"))
+#  
+#  # query arguments
+#  POST("http://httpbin.org/post", query = list(foo = "bar"))
+#  
+#  # headers
+#  POST("http://httpbin.org/post", add_headers(foo = "bar"))
+#  
+#  # body
+#  ## as form
+#  POST("http://httpbin.org/post", body = list(foo = "bar"), encode = "form")
+#  ## as json
+#  POST("http://httpbin.org/post", body = list(foo = "bar"), encode = "json")
+
+## ------------------------------------------------------------------------
+f <- function(x = c("apple", "banana", "orange")) {
+  match.arg(x)
+}
+f("a")
+
+## ------------------------------------------------------------------------
+github_pat <- function() {
+  pat <- Sys.getenv('GITHUB_PAT')
+  if (identical(pat, "")) {
+    stop("Please set env var GITHUB_PAT to your github personal access token",
+      call. = FALSE)
+  }
+
+  pat
+}
+
+## ------------------------------------------------------------------------
+rate_limit <- function() {
+  github_api("/rate_limit")
+}
+rate_limit()
+
+## ------------------------------------------------------------------------
+rate_limit <- function() {
+  req <- github_api("/rate_limit")
+  core <- req$content$resources$core
+
+  reset <- as.POSIXct(core$reset, origin = "1970-01-01")
+  cat(core$remaining, " / ", core$limit,
+    " (Resets at ", strftime(reset, "%H:%M:%S"), ")\n", sep = "")
+}
+
+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
new file mode 100644
index 0000000..c44033c
--- /dev/null
+++ b/inst/doc/api-packages.Rmd
@@ -0,0 +1,487 @@
+---
+title: "Best practices for writing an API package"
+author: "Hadley Wickham"
+date: "`r Sys.Date()`"
+output: rmarkdown::html_vignette
+vignette: >
+  %\VignetteIndexEntry{Best practices for writing an API package}
+  %\VignetteEngine{knitr::rmarkdown}
+  %\VignetteEncoding{UTF-8}
+---
+
+```{r setup, include = FALSE}
+library(httr)
+knitr::opts_chunk$set(comment = "#>", collapse = TRUE)
+```
+
+So you want to write an R client for a web API? This document walks through the key issues involved in writing API wrappers in R. If you're new to working with web APIs, you may want to start by reading "[An introduction to APIs](https://zapier.com/learn/apis)" by zapier.
+
+## Overall design
+
+APIs vary widely. Before starting to code, it is important to understand how the API you are working with handles important issues so that you can implement a complete and coherent R client for the API. 
+
+The key features of any API are the structure of the requests and the structure of the responses. An HTTP request consists of the following parts:
+
+1. HTTP verb (`GET`, `POST`, `DELETE`, etc.)
+1. The base URL for the API
+1. The URL path or endpoint
+1. URL query arguments (e.g., `?foo=bar`)
+1. Optional headers
+1. An optional request body
+
+An API package needs to be able to generate these components in order to perform the desired API call, which will typically involve some sort of authentication. 
+
+For example, to request that the GitHub API provides a list of all issues for the httr repo, we send an HTTP request that looks like:
+
+```
+-> GET /repos/hadley/httr HTTP/1.1
+-> Host: api.github.com
+-> Accept: application/vnd.github.v3+json
+```
+
+Here we're using a `GET` request to the host `api.github.com`. The url is `/repos/hadley/httr`, and we send an accept header that tells GitHub what sort of data we want.
+
+In response to this request, the API will return an HTTP response that includes:
+
+1. An HTTP status code.
+1. Headers, key-value pairs.
+1. A body typically consisting of XML, JSON, plain text, HTML, 
+   or some kind of binary representation. 
+   
+An API client needs to parse these responses, turning API errors into R errors, and return a useful object to the end user. For the previous HTTP request, GitHub returns:
+
+```
+<- HTTP/1.1 200 OK
+<- Server: GitHub.com
+<- Content-Type: application/json; charset=utf-8
+<- X-RateLimit-Limit: 5000
+<- X-RateLimit-Remaining: 4998
+<- X-RateLimit-Reset: 1459554901
+<- 
+<- {
+<-   "id": 2756403,
+<-   "name": "httr",
+<-   "full_name": "hadley/httr",
+<-   "owner": {
+<-     "login": "hadley",
+<-     "id": 4196,
+<-     "avatar_url": "https://avatars.githubusercontent.com/u/4196?v=3",
+<-     ...
+<-   },
+<-   "private": false,
+<-   "html_url": "https://github.com/hadley/httr",
+<-   "description": "httr: a friendly http package for R",
+<-   "fork": false,
+<-   "url": "https://api.github.com/repos/hadley/httr",
+<-   ...
+<-   "network_count": 1368,
+<-   "subscribers_count": 64
+<- }
+```
+
+Designing a good API client requires identifying how each of these API features is used to compose a request and what type of response is expected for each. It's best practice to insulate the end user from *how* the API works so they only need to understand how to use an R function, not the details of how APIs work. It's your job to suffer so that others don't have to!
+
+## First steps
+
+### Send a simple request
+
+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 `repos/hadley/httr`:
+
+```{R}
+library(httr)
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  GET(url)
+}
+
+resp <- github_api("/repos/hadley/httr")
+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. 
+
+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:
+
+```{r}
+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. 
+
+If you have a choice, choose json: it's usually much easier to work with than xml.
+
+Most APIs will return most or all useful information in the response body, which can be accessed using `content()`. To determine what type of information is returned, you can use `http_type()`
+
+```{r}
+http_type(resp)
+```
+
+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:
+
+```{r}
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  
+  resp <- GET(url)
+  if (http_type(resp) != "application/json") {
+    stop("API did not return json", call. = FALSE)
+  }
+  
+  resp
+}
+```
+
+NB: some poorly written APIs will say the content is type A, but it will actually be type B. In this case you should complain to the API authors, and until they fix the problem, simply drop the check for content type.
+
+Next we need to parse the output into an R object. httr provides some default parsers with `content(..., as = "auto")` but I don't recommend using them inside a package. Instead it's better to explicitly parse it yourself:
+
+1. To parse json, use `jsonlite` package.
+1. To parse xml, use the `xml2` package. 
+
+```{r}
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  
+  resp <- GET(url)
+  if (http_type(resp) != "application/json") {
+    stop("API did not return json", call. = FALSE)
+  }
+  
+  jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE)
+}
+```
+
+### Return a helpful object
+
+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.
+
+```{r}
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  
+  resp <- GET(url)
+  if (http_type(resp) != "application/json") {
+    stop("API did not return json", call. = FALSE)
+  }
+  
+  parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE)
+  
+  structure(
+    list(
+      content = parsed,
+      path = path,
+      response = resp
+    ),
+    class = "github_api"
+  )
+}
+
+print.github_api <- function(x, ...) {
+  cat("<GitHub ", x$path, ">\n", sep = "")
+  str(x$content)
+  invisible(x)
+}
+
+github_api("/users/hadley")
+```
+
+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.
+
+### 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:
+
+- Client-side exceptions
+- Network / communication exceptions
+- Server-side exceptions
+
+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!).
+
+```{r, error = TRUE}
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  
+  resp <- GET(url)
+  if (http_type(resp) != "application/json") {
+    stop("API did not return json", call. = FALSE)
+  }
+  
+  parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE)
+  
+  if (http_error(resp)) {
+    stop(
+      sprintf(
+        "GitHub API request failed [%s]\n%s\n<%s>", 
+        status_code(resp),
+        parsed$message,
+        parsed$documentation_url
+      ),
+      call. = FALSE
+    )
+  }
+  
+  structure(
+    list(
+      content = parsed,
+      path = path,
+      response = resp
+    ),
+    class = "github_api"
+  )
+}
+github_api("/user/hadley")
+```
+
+> Some poorly written APIs will return different types of response based on 
+> whether or not the request succeeded or failed. If your API does this you'll 
+> need to make your request function check the `status_code()` before parsing 
+> the response.
+
+For many APIs, the common approach is to retry API calls that return something in the 500 range. However, when doing this, it's **extremely** important to make sure to do this with some form of exponential backoff: if something's wrong on the server-side, hammering the server with retries may make things worse, and may lead to you exhausting quota (or hitting other sorts of rate limits). A common policy is to retry up to 5 times, starting at 1s, and each time doubling and adding a small  [...]
+
+### Set a user agent
+
+While we're in this function, there's one important header that you should set for every API wrapper: the user agent. The user agent is a string used to identify the client. This is most useful for the API owner as it allows them to see who is using the API. It's also useful for you if you have a contact on the inside as it often makes it easier for them to pull your requests from their logs and see what's going wrong. If you're hitting a commercial API, this also makes it easier for int [...]
+
+A good default for an R API package wrapper is to make it the URL to your GitHub repo:
+
+```{r}
+ua <- user_agent("http://github.com/hadley/httr")
+ua
+
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  
+  resp <- GET(url, ua)
+  if (http_type(resp) != "application/json") {
+    stop("API did not return json", call. = FALSE)
+  }
+  
+  parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE)
+  
+  if (status_code(resp) != 200) {
+    stop(
+      sprintf(
+        "GitHub API request failed [%s]\n%s\n<%s>", 
+        status_code(resp),
+        parsed$message,
+        parsed$documentation_url
+      ),
+      call. = FALSE
+    )
+  }
+  
+  structure(
+    list(
+      content = parsed,
+      path = path,
+      response = resp
+    ),
+    class = "github_api"
+  )
+}
+```
+
+### Passing parameters
+
+Most APIs work by executing an HTTP method on a specified URL with some additional parameters. These parameters can be specified in a number of ways, including in the URL path, in URL query arguments, in HTTP headers, and in the request body itself.  These parameters can be controlled using httr functions:
+
+1. URL path: `modify_url()`
+2. Query arguments: The `query` argument to `GET()`, `POST()`, etc.
+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.
+
+```{r, eval = FALSE}
+# modify_url
+POST(modify_url("https://httpbin.org", path = "/post"))
+
+# query arguments
+POST("http://httpbin.org/post", query = list(foo = "bar"))
+
+# headers
+POST("http://httpbin.org/post", add_headers(foo = "bar"))
+
+# body
+## as form
+POST("http://httpbin.org/post", body = list(foo = "bar"), encode = "form")
+## as json
+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.)
+
+```{r}
+f <- function(x = c("apple", "banana", "orange")) {
+  match.arg(x)
+}
+f("a")
+```
+
+It is good practice to explicitly set default values for arguments that are not required to `NULL`. If there is a default value, it should be the first one listed in the vector of allowed arguments. 
+
+## Authentication
+
+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 
+    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"))`
+
+2.  *Basic authentication with an API key*: An alternative provided by many APIs 
+    is an API "key" or "token" which is passed as part of the request. It is 
+    better than a username/password combination because it can be 
+    regenerated independent of the username and password. 
+    
+    This API key can be specified in a number of different ways: in a URL query
+    argument, in an HTTP header such as the `Authorization` header, or in an
+    argument inside the request body.
+    
+3. *OAuth*: OAuth is a protocol for generating a user- or session-specific
+    authentication token to use in subsequent requests. (An early standard, 
+    OAuth 1.0, is not terribly common any more. See `oauth1.0_token()` for 
+    details.) The current OAuth 2.0 standard is very common in modern web apps. 
+    It involves a round trip between the client and server to establish if the 
+    API client has the authority to access the data. See `oauth2.0_token()`. 
+    It's ok to publish the app ID and app "secret" - these are not actually
+    important for security of user data. 
+
+> 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.
+
+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:
+
+```{r}
+github_pat <- function() {
+  pat <- Sys.getenv('GITHUB_PAT')
+  if (identical(pat, "")) {
+    stop("Please set env var GITHUB_PAT to your github personal access token",
+      call. = FALSE)
+  }
+
+  pat
+}
+```
+
+## Pagination (handling multi-page responses)
+
+One particularly frustrating aspect of many APIs is dealing with paginated responses. This is common in APIs that offer search functionality and have the potential to return a very large number of responses. Responses might be paginated because there is a large number of response elements or because elements are updated frequently. Often they will be sorted by an explicit or implicit argument specified in the request. 
+
+When a response is paginated, the API response will typically respond with a header or value specified in the body that contains one of the following:
+
+1. The total number of pages of responses
+2. The total number of response elements (with multiple elements per page)
+3. An indicator for whether any further elements or pages are available.
+4. A URL containing the next page
+
+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
+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.
+
+### Rate limiting
+
+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 `Sys.sleep()` that waits long enough.
+
+For example, we could implement a `rate_limit()` function that tells you how many calls against the github API are available to you.
+
+```{r}
+rate_limit <- function() {
+  github_api("/rate_limit")
+}
+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.
+
+```{r}
+rate_limit <- function() {
+  req <- github_api("/rate_limit")
+  core <- req$content$resources$core
+
+  reset <- as.POSIXct(core$reset, origin = "1970-01-01")
+  cat(core$remaining, " / ", core$limit,
+    " (Resets at ", strftime(reset, "%H:%M:%S"), ")\n", sep = "")
+}
+
+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
new file mode 100644
index 0000000..7d540a1
--- /dev/null
+++ b/inst/doc/api-packages.html
@@ -0,0 +1,547 @@
+<!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" />
+
+<meta name="date" content="2016-07-03" />
+
+<title>Best practices for writing an API package</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">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>
+
+
+
+<p>So you want to write an R client for a web API? This document walks through the key issues involved in writing API wrappers in R. If you’re new to working with web APIs, you may want to start by reading “<a href="https://zapier.com/learn/apis">An introduction to APIs</a>” by zapier.</p>
+<div id="overall-design" class="section level2">
+<h2>Overall design</h2>
+<p>APIs vary widely. Before starting to code, it is important to understand how the API you are working with handles important issues so that you can implement a complete and coherent R client for the API.</p>
+<p>The key features of any API are the structure of the requests and the structure of the responses. An HTTP request consists of the following parts:</p>
+<ol style="list-style-type: decimal">
+<li>HTTP verb (<code>GET</code>, <code>POST</code>, <code>DELETE</code>, etc.)</li>
+<li>The base URL for the API</li>
+<li>The URL path or endpoint</li>
+<li>URL query arguments (e.g., <code>?foo=bar</code>)</li>
+<li>Optional headers</li>
+<li>An optional request body</li>
+</ol>
+<p>An API package needs to be able to generate these components in order to perform the desired API call, which will typically involve some sort of authentication.</p>
+<p>For example, to request that the GitHub API provides a list of all issues for the httr repo, we send an HTTP request that looks like:</p>
+<pre><code>-> GET /repos/hadley/httr HTTP/1.1
+-> Host: api.github.com
+-> Accept: application/vnd.github.v3+json</code></pre>
+<p>Here we’re using a <code>GET</code> request to the host <code>api.github.com</code>. The url is <code>/repos/hadley/httr</code>, and we send an accept header that tells GitHub what sort of data we want.</p>
+<p>In response to this request, the API will return an HTTP response that includes:</p>
+<ol style="list-style-type: decimal">
+<li>An HTTP status code.</li>
+<li>Headers, key-value pairs.</li>
+<li>A body typically consisting of XML, JSON, plain text, HTML, or some kind of binary representation.</li>
+</ol>
+<p>An API client needs to parse these responses, turning API errors into R errors, and return a useful object to the end user. For the previous HTTP request, GitHub returns:</p>
+<pre><code><- HTTP/1.1 200 OK
+<- Server: GitHub.com
+<- Content-Type: application/json; charset=utf-8
+<- X-RateLimit-Limit: 5000
+<- X-RateLimit-Remaining: 4998
+<- X-RateLimit-Reset: 1459554901
+<- 
+<- {
+<-   "id": 2756403,
+<-   "name": "httr",
+<-   "full_name": "hadley/httr",
+<-   "owner": {
+<-     "login": "hadley",
+<-     "id": 4196,
+<-     "avatar_url": "https://avatars.githubusercontent.com/u/4196?v=3",
+<-     ...
+<-   },
+<-   "private": false,
+<-   "html_url": "https://github.com/hadley/httr",
+<-   "description": "httr: a friendly http package for R",
+<-   "fork": false,
+<-   "url": "https://api.github.com/repos/hadley/httr",
+<-   ...
+<-   "network_count": 1368,
+<-   "subscribers_count": 64
+<- }</code></pre>
+<p>Designing a good API client requires identifying how each of these API features is used to compose a request and what type of response is expected for each. It’s best practice to insulate the end user from <em>how</em> the API works so they only need to understand how to use an R function, not the details of how APIs work. It’s your job to suffer so that others don’t have to!</p>
+</div>
+<div id="first-steps" class="section level2">
+<h2>First steps</h2>
+<div id="send-a-simple-request" class="section level3">
+<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) {
+  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">#>   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">#> {</span>
+<span class="co">#>   "id": 2756403,</span>
+<span class="co">#>   "name": "httr",</span>
+<span class="co">#>   "full_name": "hadley/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">#>     "gravatar_id": "",</span>
+<span class="co">#>     "url": "https://api.github.com/users/hadley",</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>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">#>   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">#>  <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">#>      <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">#>   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>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) {
+  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="kw">stop</span>(<span class="st">"API did not return json"</span>, <span class="dt">call. =</span> <span class="ot">FALSE</span>)
+  }
+  
+  resp
+}</code></pre></div>
+<p>NB: some poorly written APIs will say the content is type A, but it will actually be type B. In this case you should complain to the API authors, and until they fix the problem, simply drop the check for content type.</p>
+<p>Next we need to parse the output into an R object. httr provides some default parsers with <code>content(..., as = "auto")</code> but I don’t recommend using them inside a package. Instead it’s better to explicitly parse it yourself:</p>
+<ol style="list-style-type: decimal">
+<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) {
+  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="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>)
+}</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) {
+  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="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>)
+  
+  <span class="kw">structure</span>(
+    <span class="kw">list</span>(
+      <span class="dt">content =</span> parsed,
+      <span class="dt">path =</span> path,
+      <span class="dt">response =</span> resp
+    ),
+    <span class="dt">class =</span> <span class="st">"github_api"</span>
+  )
+}
+
+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)
+  <span class="kw">invisible</span>(x)
+}
+
+<span class="kw">github_api</span>(<span class="st">"/users/hadley"</span>)
+<span class="co">#> <GitHub /users/hadley></span>
+<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">#>  $ 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>
+<span class="co">#>  $ followers_url      : chr "https://api.github.com/users/hadley/followers"</span>
+<span class="co">#>  $ following_url      : chr "https://api.github.com/users/hadley/following{/other_user}"</span>
+<span class="co">#>  $ gists_url          : chr "https://api.github.com/users/hadley/gists{/gist_id}"</span>
+<span class="co">#>  $ starred_url        : chr "https://api.github.com/users/hadley/starred{/owner}{/repo}"</span>
+<span class="co">#>  $ subscriptions_url  : chr "https://api.github.com/users/hadley/subscriptions"</span>
+<span class="co">#>  $ organizations_url  : chr "https://api.github.com/users/hadley/orgs"</span>
+<span class="co">#>  $ repos_url          : chr "https://api.github.com/users/hadley/repos"</span>
+<span class="co">#>  $ events_url         : chr "https://api.github.com/users/hadley/events{/privacy}"</span>
+<span class="co">#>  $ received_events_url: chr "https://api.github.com/users/hadley/received_events"</span>
+<span class="co">#>  $ type               : chr "User"</span>
+<span class="co">#>  $ site_admin         : logi FALSE</span>
+<span class="co">#>  $ name               : chr "Hadley Wickham"</span>
+<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">#>  $ 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">#>  $ 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>
+<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>
+<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) {
+  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="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>)
+  
+  if (<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
+      ),
+      <span class="dt">call. =</span> <span class="ot">FALSE</span>
+    )
+  }
+  
+  <span class="kw">structure</span>(
+    <span class="kw">list</span>(
+      <span class="dt">content =</span> parsed,
+      <span class="dt">path =</span> path,
+      <span class="dt">response =</span> resp
+    ),
+    <span class="dt">class =</span> <span class="st">"github_api"</span>
+  )
+}
+<span class="kw">github_api</span>(<span class="st">"/user/hadley"</span>)
+<span class="co">#> Error: GitHub API request failed [404]</span>
+<span class="co">#> Not Found</span>
+<span class="co">#> <https://developer.github.com/v3></span></code></pre></div>
+<blockquote>
+<p>Some poorly written APIs will return different types of response based on whether or not the request succeeded or failed. If your API does this you’ll need to make your request function check the <code>status_code()</code> before parsing the response.</p>
+</blockquote>
+<p>For many APIs, the common approach is to retry API calls that return something in the 500 range. However, when doing this, it’s <strong>extremely</strong> important to make sure to do this with some form of exponential backoff: if something’s wrong on the server-side, hammering the server with retries may make things worse, and may lead to you exhausting quota (or hitting other sorts of rate limits). A common policy is to retry up to 5 times, starting at 1s, and each time doubling and [...]
+</div>
+<div id="set-a-user-agent" class="section level3">
+<h3>Set a user agent</h3>
+<p>While we’re in this function, there’s one important header that you should set for every API wrapper: the user agent. The user agent is a string used to identify the client. This is most useful for the API owner as it allows them to see who is using the API. It’s also useful for you if you have a contact on the inside as it often makes it easier for them to pull your requests from their logs and see what’s going wrong. If you’re hitting a commercial API, this also makes it easier for  [...]
+<p>A good default for an R API package wrapper is to make it the URL to your GitHub repo:</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">ua <-<span class="st"> </span><span class="kw">user_agent</span>(<span class="st">"http://github.com/hadley/httr"</span>)
+ua
+<span class="co">#> <request></span>
+<span class="co">#> Options:</span>
+<span class="co">#> * useragent: http://github.com/hadley/httr</span>
+
+github_api <-<span class="st"> </span>function(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="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>)
+  
+  if (<span class="kw">status_code</span>(resp) !=<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
+      ),
+      <span class="dt">call. =</span> <span class="ot">FALSE</span>
+    )
+  }
+  
+  <span class="kw">structure</span>(
+    <span class="kw">list</span>(
+      <span class="dt">content =</span> parsed,
+      <span class="dt">path =</span> path,
+      <span class="dt">response =</span> resp
+    ),
+    <span class="dt">class =</span> <span class="st">"github_api"</span>
+  )
+}</code></pre></div>
+</div>
+<div id="passing-parameters" class="section level3">
+<h3>Passing parameters</h3>
+<p>Most APIs work by executing an HTTP method on a specified URL with some additional parameters. These parameters can be specified in a number of ways, including in the URL path, in URL query arguments, in HTTP headers, and in the request body itself. These parameters can be controlled using httr functions:</p>
+<ol style="list-style-type: decimal">
+<li>URL path: <code>modify_url()</code></li>
+<li>Query arguments: The <code>query</code> argument to <code>GET()</code>, <code>POST()</code>, etc.</li>
+<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>
+<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>))
+
+<span class="co"># query arguments</span>
+<span class="kw">POST</span>(<span class="st">"http://httpbin.org/post"</span>, <span class="dt">query =</span> <span class="kw">list</span>(<span class="dt">foo =</span> <span class="st">"bar"</span>))
+
+<span class="co"># headers</span>
+<span class="kw">POST</span>(<span class="st">"http://httpbin.org/post"</span>, <span class="kw">add_headers</span>(<span class="dt">foo =</span> <span class="st">"bar"</span>))
+
+<span class="co"># body</span>
+## as form
+<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">"form"</span>)
+## as json
+<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>)) {
+  <span class="kw">match.arg</span>(x)
+}
+<span class="kw">f</span>(<span class="st">"a"</span>)
+<span class="co">#> [1] "apple"</span></code></pre></div>
+<p>It is good practice to explicitly set default values for arguments that are not required to <code>NULL</code>. If there is a default value, it should be the first one listed in the vector of allowed arguments.</p>
+</div>
+</div>
+<div id="authentication" class="section level2">
+<h2>Authentication</h2>
+<p>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:</p>
+<ol style="list-style-type: decimal">
+<li><p><em>“Basic” authentication</em>: 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: <code>GET("http://httpbin.org", authenticate("username", "password"))</code></p></li>
+<li><p><em>Basic authentication with an API key</em>: An alternative provided by many APIs is an API “key” or “token” which is passed as part of the request. It is better than a username/password combination because it can be regenerated independent of the username and password.</p>
+<p>This API key can be specified in a number of different ways: in a URL query argument, in an HTTP header such as the <code>Authorization</code> header, or in an argument inside the request body.</p></li>
+<li><p><em>OAuth</em>: OAuth is a protocol for generating a user- or session-specific authentication token to use in subsequent requests. (An early standard, OAuth 1.0, is not terribly common any more. See <code>oauth1.0_token()</code> for details.) The current OAuth 2.0 standard is very common in modern web apps. It involves a round trip between the client and server to establish if the API client has the authority to access the data. See <code>oauth2.0_token()</code>. It’s ok to publis [...]
+</ol>
+<blockquote>
+<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() {
+  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="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>)
+  }
+
+  pat
+}</code></pre></div>
+</div>
+<div id="pagination-handling-multi-page-responses" class="section level2">
+<h2>Pagination (handling multi-page responses)</h2>
+<p>One particularly frustrating aspect of many APIs is dealing with paginated responses. This is common in APIs that offer search functionality and have the potential to return a very large number of responses. Responses might be paginated because there is a large number of response elements or because elements are updated frequently. Often they will be sorted by an explicit or implicit argument specified in the request.</p>
+<p>When a response is paginated, the API response will typically respond with a header or value specified in the body that contains one of the following:</p>
+<ol style="list-style-type: decimal">
+<li>The total number of pages of responses</li>
+<li>The total number of response elements (with multiple elements per page)</li>
+<li>An indicator for whether any further elements or pages are available.</li>
+<li>A URL containing the next page</li>
+</ol>
+<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 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>
+<div id="rate-limiting" class="section level3">
+<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() {
+  <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">#>   .. ..$ 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">#>   .. ..$ limit    : int 10</span>
+<span class="co">#>   .. ..$ remaining: int 10</span>
+<span class="co">#>   .. ..$ reset    : int 1467565342</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() {
+  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
+
+  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,
+    <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>
+</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>
+
+
+
+<!-- dynamically load mathjax for compatibility with self-contained -->
+<script>
+  (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";
+    document.getElementsByTagName("head")[0].appendChild(script);
+  })();
+</script>
+
+</body>
+</html>
diff --git a/inst/doc/quickstart.R b/inst/doc/quickstart.R
new file mode 100644
index 0000000..6a9c238
--- /dev/null
+++ b/inst/doc/quickstart.R
@@ -0,0 +1,106 @@
+## ---- echo = FALSE-------------------------------------------------------
+library(httr)
+knitr::opts_chunk$set(comment = "#>", collapse = TRUE)
+
+## ------------------------------------------------------------------------
+library(httr)
+r <- GET("http://httpbin.org/get")
+
+## ------------------------------------------------------------------------
+r
+
+## ------------------------------------------------------------------------
+status_code(r)
+headers(r)
+str(content(r))
+
+## ------------------------------------------------------------------------
+r <- GET("http://httpbin.org/get")
+# Get an informative description:
+http_status(r)
+
+# Or just access the raw code:
+r$status_code
+
+## ------------------------------------------------------------------------
+warn_for_status(r)
+stop_for_status(r)
+
+## ------------------------------------------------------------------------
+r <- GET("http://httpbin.org/get")
+content(r, "text")
+
+## ---- eval = FALSE-------------------------------------------------------
+#  content(r, "text", encoding = "ISO-8859-1")
+
+## ------------------------------------------------------------------------
+content(r, "raw")
+
+## ---- eval = FALSE-------------------------------------------------------
+#  bin <- content(r, "raw")
+#  writeBin(bin, "myfile.txt")
+
+## ------------------------------------------------------------------------
+# JSON automatically parsed into named list
+str(content(r, "parsed"))
+
+## ------------------------------------------------------------------------
+headers(r)
+
+## ------------------------------------------------------------------------
+headers(r)$date
+headers(r)$DATE
+
+## ------------------------------------------------------------------------
+r <- GET("http://httpbin.org/cookies/set", query = list(a = 1))
+cookies(r)
+
+## ------------------------------------------------------------------------
+r <- GET("http://httpbin.org/cookies/set", query = list(b = 1))
+cookies(r)
+
+## ------------------------------------------------------------------------
+r <- GET("http://httpbin.org/get", 
+  query = list(key1 = "value1", key2 = "value2")
+)
+content(r)$args
+
+## ------------------------------------------------------------------------
+r <- GET("http://httpbin.org/get", 
+  query = list(key1 = "value 1", "key 2" = "value2", key2 = NULL))
+content(r)$args
+
+## ------------------------------------------------------------------------
+r <- GET("http://httpbin.org/get", add_headers(Name = "Hadley"))
+str(content(r)$headers)
+
+## ------------------------------------------------------------------------
+r <- GET("http://httpbin.org/cookies", set_cookies("MeWant" = "cookies"))
+content(r)$cookies
+
+## ------------------------------------------------------------------------
+r <- POST("http://httpbin.org/post", body = list(a = 1, b = 2, c = 3))
+
+## ------------------------------------------------------------------------
+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")
+
+## ---- eval = FALSE-------------------------------------------------------
+#  POST(url, body = body, encode = "multipart", verbose()) # the default
+#  POST(url, body = body, encode = "form", verbose())
+#  POST(url, body = body, encode = "json", verbose())
+
+## ---- eval = FALSE-------------------------------------------------------
+#  POST(url, body = upload_file("mypath.txt"))
+#  POST(url, body = list(x = upload_file("mypath.txt")))
+
+## ------------------------------------------------------------------------
+sessionInfo()
+
diff --git a/inst/doc/quickstart.Rmd b/inst/doc/quickstart.Rmd
new file mode 100644
index 0000000..66f42fe
--- /dev/null
+++ b/inst/doc/quickstart.Rmd
@@ -0,0 +1,251 @@
+<!--
+%\VignetteEngine{knitr::knitr}
+%\VignetteIndexEntry{httr quickstart guide}
+-->
+
+```{r, echo = FALSE}
+library(httr)
+knitr::opts_chunk$set(comment = "#>", collapse = TRUE)
+```
+
+# httr quickstart guide
+
+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 "[HTTP: The Protocol Every Web Developer Must Know][http-tutorial]" or "[HTTP made really easy](http://www.jmarshall.com/easy/http/)".
+
+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.
+
+## httr basics
+
+To make a request, first load httr, then call `GET()` with a url:
+
+```{r}
+library(httr)
+r <- GET("http://httpbin.org/get")
+```
+
+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.
+
+```{r}
+r
+```
+
+You can pull out important parts of the response with various helper methods, or dig directly into the object:
+
+```{r}
+status_code(r)
+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.
+
+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.
+
+## The response 
+
+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.
+
+### 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()`:
+
+```{r}
+r <- GET("http://httpbin.org/get")
+# Get an informative description:
+http_status(r)
+
+# Or just access the raw code:
+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).
+
+You can automatically throw a warning or raise an error if a request did not succeed:
+
+```{r}
+warn_for_status(r)
+stop_for_status(r)
+```
+
+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.
+
+### The body
+
+There are three ways to access the body of the request, all using `content()`:
+
+*   `content(r, "text")` accesses the body as a character vector:
+
+    ```{r}
+    r <- GET("http://httpbin.org/get")
+    content(r, "text")
+    ```
+
+    httr will automatically decode content from the server using the encoding 
+    supplied in the `content-type` HTTP header. Unfortunately you can't always 
+    trust what the server tells you, so you can override encoding if needed:
+
+    ```{r, eval = FALSE}
+    content(r, "text", encoding = "ISO-8859-1")
+    ```
+
+    If you're having problems figuring out what the correct encoding 
+    should be, try `stringi::stri_enc_detect(content(r, "raw"))`.
+
+*   For non-text requests, you can access the body of the request as a 
+    raw vector:
+
+    ```{r}
+    content(r, "raw")
+    ```
+    
+    This is exactly the sequence of bytes that the web server sent, so this is
+    the highest fidelity way of saving files to disk:
+    
+    ```{r, eval = FALSE}
+    bin <- content(r, "raw")
+    writeBin(bin, "myfile.txt")
+    ```
+
+*   httr provides a number of default parsers for common file types:
+
+    ```{r}
+    # JSON automatically parsed into named list
+    str(content(r, "parsed"))
+    ```
+    
+    See `?content` for a complete list.
+    
+    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.
+
+### The headers
+
+Access response headers with `headers()`:
+
+```{r}
+headers(r)
+```
+
+This is basically a named list, but because http headers are case insensitive, indexing this object ignores case:
+
+```{r}
+headers(r)$date
+headers(r)$DATE
+```
+
+### Cookies
+
+You can access cookies in a similar way:
+
+```{r}
+r <- GET("http://httpbin.org/cookies/set", query = list(a = 1))
+cookies(r)
+```
+
+Cookies are automatically persisted between requests to the same domain:
+
+```{r}
+r <- GET("http://httpbin.org/cookies/set", query = list(b = 1))
+cookies(r)
+```
+
+## The request
+
+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 `POST()`, `PUT()` and `PATCH()` requests.
+
+### The url query string
+
+A common way of sending simple key-value pairs to the server is the query string: e.g. `http://httpbin.org/get?key=val`. httr allows you to provide these arguments as a named list with the `query` argument. For example, if you wanted to pass `key1=value1` and `key2=value2` to `http://httpbin.org/get` you could do:
+
+```{r}
+r <- GET("http://httpbin.org/get", 
+  query = list(key1 = "value1", key2 = "value2")
+)
+content(r)$args
+```
+
+Any `NULL` elements are automatically dropped from the list, and both keys and values are escaped automatically.
+
+```{r}
+r <- GET("http://httpbin.org/get", 
+  query = list(key1 = "value 1", "key 2" = "value2", key2 = NULL))
+content(r)$args
+```
+
+### Custom headers
+
+You can add custom headers to a request with `add_headers()`:
+
+```{r}
+r <- GET("http://httpbin.org/get", add_headers(Name = "Hadley"))
+str(content(r)$headers)
+```
+
+(Note that `content(r)$header` retrieves the headers that httpbin received. `headers(r)` gives the headers that it sent back in its response.)
+
+## Cookies
+
+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 `set_cookies()`:
+
+```{r}
+r <- GET("http://httpbin.org/cookies", set_cookies("MeWant" = "cookies"))
+content(r)$cookies
+```
+
+Note that this response includes the `a` and `b` cookies that were added by the server earlier.
+
+### Request body
+
+When `POST()`ing, you can include data in the `body` of the request. httr allows you to supply this in a number of different ways. The most common way is a named list:
+
+```{r}
+r <- POST("http://httpbin.org/post", body = list(a = 1, b = 2, c = 3))
+```
+
+You can use the `encode` argument to determine how this data is sent to the server:
+
+```{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")
+```
+
+To see exactly what's being sent to the server, use `verbose()`. Unfortunately due to the way that `verbose()` works, knitr can't capture the messages, so you'll need to run these from an interactive console to see what's going on.
+
+```{r, eval = FALSE}
+POST(url, body = body, encode = "multipart", verbose()) # the default
+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()`.
+
+You can also send files off disk:
+
+```{r, eval = FALSE}
+POST(url, body = upload_file("mypath.txt"))
+POST(url, body = list(x = upload_file("mypath.txt")))
+```
+
+(`upload_file()` will guess the mime-type from the extension - using the `type` argument to override/supply yourself.)
+
+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.
+
+See `POST()` for more details on the other types of thing that you can send: no body, empty body, and character and raw vectors.
+
+##### Built with
+
+```{r}
+sessionInfo()
+```
+
+[http-tutorial]:http://code.tutsplus.com/tutorials/http-the-protocol-every-web-developer-must-know-part-1--net-31177
diff --git a/inst/doc/quickstart.html b/inst/doc/quickstart.html
new file mode 100644
index 0000000..f80398a
--- /dev/null
+++ b/inst/doc/quickstart.html
@@ -0,0 +1,584 @@
+<!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>
+
+<!-- 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;
+   }
+
+   pre .comment {
+     color: #998;
+     font-style: italic
+   }
+
+   pre .keyword {
+     color: #900;
+     font-weight: bold
+   }
+
+   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>
+
+
+
+<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;
+   }
+
+   pre, blockquote {
+      padding-right: 1em;
+      page-break-inside: avoid;
+   }
+
+   tr, img {
+      page-break-inside: avoid;
+   }
+
+   img {
+      max-width: 100% !important;
+   }
+
+   @page :left {
+      margin: 15mm 20mm 15mm 10mm;
+   }
+
+   @page :right {
+      margin: 15mm 10mm 15mm 20mm;
+   }
+
+   p, h2, h3 {
+      orphans: 3; widows: 3;
+   }
+
+   h2, h3 {
+      page-break-after: avoid;
+   }
+}
+</style>
+
+
+
+</head>
+
+<body>
+<!--
+%\VignetteEngine{knitr::knitr}
+%\VignetteIndexEntry{httr quickstart guide}
+-->
+
+<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>
+
+<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>
+
+<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 [...]
+
+<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>
+
+<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>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>
+
+<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>
+<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>
+
+<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>
+</ul>
+
+<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>
+
+<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>
+
+<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>
+
+<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>
+
+<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>
+
+<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")
+)
+content(r)$args
+#> $key1
+#> [1] "value1"
+#> 
+#> $key2
+#> [1] "value2"
+</code></pre>
+
+<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>
+
+<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>
+
+<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>
+
+<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>Note that this response includes the <code>a</code> and <code>b</code> cookies that were added by the server earlier.</p>
+
+<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>
+
+<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>
+
+<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>
+
+<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>
+
+<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>
+
+</body>
+
+</html>
diff --git a/man/BROWSE.Rd b/man/BROWSE.Rd
new file mode 100644
index 0000000..642fdea
--- /dev/null
+++ b/man/BROWSE.Rd
@@ -0,0 +1,43 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/http-browse.r
+\name{BROWSE}
+\alias{BROWSE}
+\title{Open specified url in browser.}
+\usage{
+BROWSE(url = NULL, config = list(), ..., handle = NULL)
+}
+\arguments{
+\item{url}{the url of the page to retrieve}
+
+\item{config}{All configuration options are ignored because the request
+is handled by the browser, not \pkg{RCurl}.}
+
+\item{...}{Further named parameters, such as \code{query}, \code{path}, etc,
+passed on to \code{\link{modify_url}}. Unnamed parameters will be combined
+with \code{\link{config}}.}
+
+\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}
+requests to the same scheme/host/port combo. This substantially reduces
+connection time, and ensures that cookies are maintained over multiple
+requests to the same host. See \code{\link{handle_pool}} for more
+details.}
+}
+\description{
+(This isn't really a http verb, but it seems to follow the same format).
+}
+\details{
+Only works in interactive sessions.
+}
+\examples{
+BROWSE("http://google.com")
+BROWSE("http://had.co.nz")
+}
+\seealso{
+Other http methods: \code{\link{DELETE}},
+  \code{\link{GET}}, \code{\link{HEAD}},
+  \code{\link{PATCH}}, \code{\link{POST}},
+  \code{\link{PUT}}, \code{\link{VERB}}
+}
+
diff --git a/man/DELETE.Rd b/man/DELETE.Rd
new file mode 100644
index 0000000..2801ac2
--- /dev/null
+++ b/man/DELETE.Rd
@@ -0,0 +1,92 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/http-delete.r
+\name{DELETE}
+\alias{DELETE}
+\title{Send a DELETE request.}
+\usage{
+DELETE(url = NULL, config = list(), ..., body = NULL,
+  encode = c("multipart", "form", "json", "raw"), handle = NULL)
+}
+\arguments{
+\item{url}{the url of the page to retrieve}
+
+\item{config}{Additional configuration settings such as http
+authentication (\code{\link{authenticate}}), additional headers
+(\code{\link{add_headers}}), cookies (\code{\link{set_cookies}}) etc.
+See \code{\link{config}} for full details and list of helpers.}
+
+\item{...}{Further named parameters, such as \code{query}, \code{path}, etc,
+passed on to \code{\link{modify_url}}. Unnamed parameters will be combined
+with \code{\link{config}}.}
+
+\item{body}{One of the following:
+\itemize{
+  \item \code{FALSE}: No body. This is typically not used with \code{POST},
+    \code{PUT}, or \code{PATCH}, but can be useful if you need to send a
+    bodyless request (like \code{GET}) with \code{VERB()}.
+  \item \code{NULL}: An empty body
+  \item \code{""}: A length 0 body
+  \item \code{upload_file("path/")}: The contents of a file.  The mime
+    type will be guessed from the extension, or can be supplied explicitly
+    as the second argument to \code{upload_file()}
+  \item A character or raw vector: sent as is in body. Use
+    \code{\link{content_type}} to tell the server what sort of data
+    you are sending.
+  \item A named list: See details for encode.
+}}
+
+\item{encode}{If the body is a named list, how should it be encoded? Can be
+  one of form (application/x-www-form-urlencoded), multipart,
+  (multipart/form-data), or json (application/json).
+
+  For "multipart", list elements can be strings or objects created by
+  \code{\link{upload_file}}. For "form", elements are coerced to strings
+  and escaped, use \code{I()} to prevent double-escaping. For "json",
+  parameters are automatically "unboxed" (i.e. length 1 vectors are
+  converted to scalars). To preserve a length 1 vector as a vector,
+  wrap in \code{I()}. For "raw", either a character or raw vector. You'll
+  need to make sure to set the \code{\link{content_type}()} yourself.}
+
+\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}
+requests to the same scheme/host/port combo. This substantially reduces
+connection time, and ensures that cookies are maintained over multiple
+requests to the same host. See \code{\link{handle_pool}} for more
+details.}
+}
+\description{
+Send a DELETE request.
+}
+\section{RFC2616}{
+
+
+The DELETE method requests that the origin server delete the resource
+identified by the Request-URI. This method MAY be overridden by human
+intervention (or other means) on the origin server. The client cannot be
+guaranteed that the operation has been carried out, even if the status code
+returned from the origin server indicates that the action has been
+completed successfully. However, the server SHOULD NOT indicate success
+unless, at the time the response is given, it intends to delete the
+resource or move it to an inaccessible location.
+
+A successful response SHOULD be 200 (OK) if the response includes an entity
+describing the status, 202 (Accepted) if the action has not yet been
+enacted, or 204 (No Content) if the action has been enacted but the
+response does not include an entity.
+
+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")
+}
+\seealso{
+Other http methods: \code{\link{BROWSE}},
+  \code{\link{GET}}, \code{\link{HEAD}},
+  \code{\link{PATCH}}, \code{\link{POST}},
+  \code{\link{PUT}}, \code{\link{VERB}}
+}
+
diff --git a/man/GET.Rd b/man/GET.Rd
new file mode 100644
index 0000000..42ce67d
--- /dev/null
+++ b/man/GET.Rd
@@ -0,0 +1,82 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/http-get.r
+\name{GET}
+\alias{GET}
+\title{GET a url.}
+\usage{
+GET(url = NULL, config = list(), ..., handle = NULL)
+}
+\arguments{
+\item{url}{the url of the page to retrieve}
+
+\item{config}{Additional configuration settings such as http
+authentication (\code{\link{authenticate}}), additional headers
+(\code{\link{add_headers}}), cookies (\code{\link{set_cookies}}) etc.
+See \code{\link{config}} for full details and list of helpers.}
+
+\item{...}{Further named parameters, such as \code{query}, \code{path}, etc,
+passed on to \code{\link{modify_url}}. Unnamed parameters will be combined
+with \code{\link{config}}.}
+
+\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}
+requests to the same scheme/host/port combo. This substantially reduces
+connection time, and ensures that cookies are maintained over multiple
+requests to the same host. See \code{\link{handle_pool}} for more
+details.}
+}
+\description{
+GET a url.
+}
+\section{RFC2616}{
+
+The GET method means retrieve whatever information (in the form of an
+entity) is identified by the Request-URI. If the Request-URI refers to a
+data-producing process, it is the produced data which shall be returned as
+the entity in the response and not the source text of the process, unless
+that text happens to be the output of the process.
+
+The semantics of the GET method change to a "conditional GET" if the
+request message includes an If-Modified-Since, If-Unmodified-Since,
+If-Match, If-None-Match, or If-Range header field. A conditional GET method
+requests that the entity be transferred only under the circumstances
+described by the conditional header field(s). The conditional GET method is
+intended to reduce unnecessary network usage by allowing cached entities to
+be refreshed without requiring multiple requests or transferring data
+already held by the client.
+
+The semantics of the GET method change to a "partial GET" if the request
+message includes a Range header field. A partial GET requests that only
+part of the entity be transferred, as described in \url{http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35}
+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")
+GET("http://google.com/", path = "search", query = list(q = "ham"))
+
+# See what GET is doing with httpbin.org
+url <- "http://httpbin.org/get"
+GET(url)
+GET(url, add_headers(a = 1, b = 2))
+GET(url, set_cookies(a = 1, b = 2))
+GET(url, add_headers(a = 1, b = 2), set_cookies(a = 1, b = 2))
+GET(url, authenticate("username", "password"))
+GET(url, verbose())
+
+# You might want to manually specify the handle so you can have multiple
+# independent logins to the same website.
+google <- handle("http://google.com")
+GET(handle = google, path = "/")
+GET(handle = google, path = "search")
+}
+\seealso{
+Other http methods: \code{\link{BROWSE}},
+  \code{\link{DELETE}}, \code{\link{HEAD}},
+  \code{\link{PATCH}}, \code{\link{POST}},
+  \code{\link{PUT}}, \code{\link{VERB}}
+}
+
diff --git a/man/HEAD.Rd b/man/HEAD.Rd
new file mode 100644
index 0000000..6f42eb9
--- /dev/null
+++ b/man/HEAD.Rd
@@ -0,0 +1,59 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/http-head.r
+\name{HEAD}
+\alias{HEAD}
+\title{Get url HEADers.}
+\usage{
+HEAD(url = NULL, config = list(), ..., handle = NULL)
+}
+\arguments{
+\item{url}{the url of the page to retrieve}
+
+\item{config}{Additional configuration settings such as http
+authentication (\code{\link{authenticate}}), additional headers
+(\code{\link{add_headers}}), cookies (\code{\link{set_cookies}}) etc.
+See \code{\link{config}} for full details and list of helpers.}
+
+\item{...}{Further named parameters, such as \code{query}, \code{path}, etc,
+passed on to \code{\link{modify_url}}. Unnamed parameters will be combined
+with \code{\link{config}}.}
+
+\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}
+requests to the same scheme/host/port combo. This substantially reduces
+connection time, and ensures that cookies are maintained over multiple
+requests to the same host. See \code{\link{handle_pool}} for more
+details.}
+}
+\description{
+Get url HEADers.
+}
+\section{RFC2616}{
+
+The HEAD method is identical to GET except that the server MUST NOT return
+a message-body in the response. The metainformation contained in the HTTP
+headers in response to a HEAD request SHOULD be identical to the
+information sent in response to a GET request. This method can be used for
+obtaining metainformation about the entity implied by the request without
+transferring the entity-body itself. This method is often used for testing
+hypertext links for validity, accessibility, and recent modification.
+
+The response to a HEAD request MAY be cacheable in the sense that the
+information contained in the response MAY be used to update a previously
+cached entity from that resource. If the new field values indicate that the
+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"))
+}
+\seealso{
+Other http methods: \code{\link{BROWSE}},
+  \code{\link{DELETE}}, \code{\link{GET}},
+  \code{\link{PATCH}}, \code{\link{POST}},
+  \code{\link{PUT}}, \code{\link{VERB}}
+}
+
diff --git a/man/PATCH.Rd b/man/PATCH.Rd
new file mode 100644
index 0000000..a214b7a
--- /dev/null
+++ b/man/PATCH.Rd
@@ -0,0 +1,67 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/http-patch.r
+\name{PATCH}
+\alias{PATCH}
+\title{Send PATCH request to a server.}
+\usage{
+PATCH(url = NULL, config = list(), ..., body = NULL,
+  encode = c("multipart", "form", "json", "raw"), handle = NULL)
+}
+\arguments{
+\item{url}{the url of the page to retrieve}
+
+\item{config}{Additional configuration settings such as http
+authentication (\code{\link{authenticate}}), additional headers
+(\code{\link{add_headers}}), cookies (\code{\link{set_cookies}}) etc.
+See \code{\link{config}} for full details and list of helpers.}
+
+\item{...}{Further named parameters, such as \code{query}, \code{path}, etc,
+passed on to \code{\link{modify_url}}. Unnamed parameters will be combined
+with \code{\link{config}}.}
+
+\item{body}{One of the following:
+\itemize{
+  \item \code{FALSE}: No body. This is typically not used with \code{POST},
+    \code{PUT}, or \code{PATCH}, but can be useful if you need to send a
+    bodyless request (like \code{GET}) with \code{VERB()}.
+  \item \code{NULL}: An empty body
+  \item \code{""}: A length 0 body
+  \item \code{upload_file("path/")}: The contents of a file.  The mime
+    type will be guessed from the extension, or can be supplied explicitly
+    as the second argument to \code{upload_file()}
+  \item A character or raw vector: sent as is in body. Use
+    \code{\link{content_type}} to tell the server what sort of data
+    you are sending.
+  \item A named list: See details for encode.
+}}
+
+\item{encode}{If the body is a named list, how should it be encoded? Can be
+  one of form (application/x-www-form-urlencoded), multipart,
+  (multipart/form-data), or json (application/json).
+
+  For "multipart", list elements can be strings or objects created by
+  \code{\link{upload_file}}. For "form", elements are coerced to strings
+  and escaped, use \code{I()} to prevent double-escaping. For "json",
+  parameters are automatically "unboxed" (i.e. length 1 vectors are
+  converted to scalars). To preserve a length 1 vector as a vector,
+  wrap in \code{I()}. For "raw", either a character or raw vector. You'll
+  need to make sure to set the \code{\link{content_type}()} yourself.}
+
+\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}
+requests to the same scheme/host/port combo. This substantially reduces
+connection time, and ensures that cookies are maintained over multiple
+requests to the same host. See \code{\link{handle_pool}} for more
+details.}
+}
+\description{
+Send PATCH request to a server.
+}
+\seealso{
+Other http methods: \code{\link{BROWSE}},
+  \code{\link{DELETE}}, \code{\link{GET}},
+  \code{\link{HEAD}}, \code{\link{POST}},
+  \code{\link{PUT}}, \code{\link{VERB}}
+}
+
diff --git a/man/POST.Rd b/man/POST.Rd
new file mode 100644
index 0000000..32144ff
--- /dev/null
+++ b/man/POST.Rd
@@ -0,0 +1,79 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/http-post.r
+\name{POST}
+\alias{POST}
+\title{POST file to a server.}
+\usage{
+POST(url = NULL, config = list(), ..., body = NULL,
+  encode = c("multipart", "form", "json", "raw"), handle = NULL)
+}
+\arguments{
+\item{url}{the url of the page to retrieve}
+
+\item{config}{Additional configuration settings such as http
+authentication (\code{\link{authenticate}}), additional headers
+(\code{\link{add_headers}}), cookies (\code{\link{set_cookies}}) etc.
+See \code{\link{config}} for full details and list of helpers.}
+
+\item{...}{Further named parameters, such as \code{query}, \code{path}, etc,
+passed on to \code{\link{modify_url}}. Unnamed parameters will be combined
+with \code{\link{config}}.}
+
+\item{body}{One of the following:
+\itemize{
+  \item \code{FALSE}: No body. This is typically not used with \code{POST},
+    \code{PUT}, or \code{PATCH}, but can be useful if you need to send a
+    bodyless request (like \code{GET}) with \code{VERB()}.
+  \item \code{NULL}: An empty body
+  \item \code{""}: A length 0 body
+  \item \code{upload_file("path/")}: The contents of a file.  The mime
+    type will be guessed from the extension, or can be supplied explicitly
+    as the second argument to \code{upload_file()}
+  \item A character or raw vector: sent as is in body. Use
+    \code{\link{content_type}} to tell the server what sort of data
+    you are sending.
+  \item A named list: See details for encode.
+}}
+
+\item{encode}{If the body is a named list, how should it be encoded? Can be
+  one of form (application/x-www-form-urlencoded), multipart,
+  (multipart/form-data), or json (application/json).
+
+  For "multipart", list elements can be strings or objects created by
+  \code{\link{upload_file}}. For "form", elements are coerced to strings
+  and escaped, use \code{I()} to prevent double-escaping. For "json",
+  parameters are automatically "unboxed" (i.e. length 1 vectors are
+  converted to scalars). To preserve a length 1 vector as a vector,
+  wrap in \code{I()}. For "raw", either a character or raw vector. You'll
+  need to make sure to set the \code{\link{content_type}()} yourself.}
+
+\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}
+requests to the same scheme/host/port combo. This substantially reduces
+connection time, and ensures that cookies are maintained over multiple
+requests to the same host. See \code{\link{handle_pool}} for more
+details.}
+}
+\description{
+POST file to a server.
+}
+\examples{
+b2 <- "http://httpbin.org/post"
+POST(b2, body = "A simple text string")
+POST(b2, body = list(x = "A simple text string"))
+POST(b2, body = list(y = upload_file(system.file("CITATION"))))
+POST(b2, body = list(x = "A simple text string"), encode = "json")
+
+# Various types of empty body:
+POST(b2, body = NULL, verbose())
+POST(b2, body = FALSE, verbose())
+POST(b2, body = "", verbose())
+}
+\seealso{
+Other http methods: \code{\link{BROWSE}},
+  \code{\link{DELETE}}, \code{\link{GET}},
+  \code{\link{HEAD}}, \code{\link{PATCH}},
+  \code{\link{PUT}}, \code{\link{VERB}}
+}
+
diff --git a/man/PUT.Rd b/man/PUT.Rd
new file mode 100644
index 0000000..f6f2c38
--- /dev/null
+++ b/man/PUT.Rd
@@ -0,0 +1,77 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/http-put.r
+\name{PUT}
+\alias{PUT}
+\title{Send PUT request to server.}
+\usage{
+PUT(url = NULL, config = list(), ..., body = NULL,
+  encode = c("multipart", "form", "json", "raw"), handle = NULL)
+}
+\arguments{
+\item{url}{the url of the page to retrieve}
+
+\item{config}{Additional configuration settings such as http
+authentication (\code{\link{authenticate}}), additional headers
+(\code{\link{add_headers}}), cookies (\code{\link{set_cookies}}) etc.
+See \code{\link{config}} for full details and list of helpers.}
+
+\item{...}{Further named parameters, such as \code{query}, \code{path}, etc,
+passed on to \code{\link{modify_url}}. Unnamed parameters will be combined
+with \code{\link{config}}.}
+
+\item{body}{One of the following:
+\itemize{
+  \item \code{FALSE}: No body. This is typically not used with \code{POST},
+    \code{PUT}, or \code{PATCH}, but can be useful if you need to send a
+    bodyless request (like \code{GET}) with \code{VERB()}.
+  \item \code{NULL}: An empty body
+  \item \code{""}: A length 0 body
+  \item \code{upload_file("path/")}: The contents of a file.  The mime
+    type will be guessed from the extension, or can be supplied explicitly
+    as the second argument to \code{upload_file()}
+  \item A character or raw vector: sent as is in body. Use
+    \code{\link{content_type}} to tell the server what sort of data
+    you are sending.
+  \item A named list: See details for encode.
+}}
+
+\item{encode}{If the body is a named list, how should it be encoded? Can be
+  one of form (application/x-www-form-urlencoded), multipart,
+  (multipart/form-data), or json (application/json).
+
+  For "multipart", list elements can be strings or objects created by
+  \code{\link{upload_file}}. For "form", elements are coerced to strings
+  and escaped, use \code{I()} to prevent double-escaping. For "json",
+  parameters are automatically "unboxed" (i.e. length 1 vectors are
+  converted to scalars). To preserve a length 1 vector as a vector,
+  wrap in \code{I()}. For "raw", either a character or raw vector. You'll
+  need to make sure to set the \code{\link{content_type}()} yourself.}
+
+\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}
+requests to the same scheme/host/port combo. This substantially reduces
+connection time, and ensures that cookies are maintained over multiple
+requests to the same host. See \code{\link{handle_pool}} for more
+details.}
+}
+\description{
+Send PUT request to server.
+}
+\examples{
+POST("http://httpbin.org/put")
+PUT("http://httpbin.org/put")
+
+b2 <- "http://httpbin.org/put"
+PUT(b2, body = "A simple text string")
+PUT(b2, body = list(x = "A simple text string"))
+PUT(b2, body = list(y = upload_file(system.file("CITATION"))))
+PUT(b2, body = list(x = "A simple text string"), encode = "json")
+}
+\seealso{
+Other http methods: \code{\link{BROWSE}},
+  \code{\link{DELETE}}, \code{\link{GET}},
+  \code{\link{HEAD}}, \code{\link{PATCH}},
+  \code{\link{POST}}, \code{\link{VERB}}
+}
+
diff --git a/man/RETRY.Rd b/man/RETRY.Rd
new file mode 100644
index 0000000..301b761
--- /dev/null
+++ b/man/RETRY.Rd
@@ -0,0 +1,89 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/retry.R
+\name{RETRY}
+\alias{RETRY}
+\title{Retry a request until it succeeds.}
+\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)
+}
+\arguments{
+\item{verb}{Name of verb to use.}
+
+\item{url}{the url of the page to retrieve}
+
+\item{config}{Additional configuration settings such as http
+authentication (\code{\link{authenticate}}), additional headers
+(\code{\link{add_headers}}), cookies (\code{\link{set_cookies}}) etc.
+See \code{\link{config}} for full details and list of helpers.}
+
+\item{...}{Further named parameters, such as \code{query}, \code{path}, etc,
+passed on to \code{\link{modify_url}}. Unnamed parameters will be combined
+with \code{\link{config}}.}
+
+\item{body}{One of the following:
+\itemize{
+  \item \code{FALSE}: No body. This is typically not used with \code{POST},
+    \code{PUT}, or \code{PATCH}, but can be useful if you need to send a
+    bodyless request (like \code{GET}) with \code{VERB()}.
+  \item \code{NULL}: An empty body
+  \item \code{""}: A length 0 body
+  \item \code{upload_file("path/")}: The contents of a file.  The mime
+    type will be guessed from the extension, or can be supplied explicitly
+    as the second argument to \code{upload_file()}
+  \item A character or raw vector: sent as is in body. Use
+    \code{\link{content_type}} to tell the server what sort of data
+    you are sending.
+  \item A named list: See details for encode.
+}}
+
+\item{encode}{If the body is a named list, how should it be encoded? Can be
+  one of form (application/x-www-form-urlencoded), multipart,
+  (multipart/form-data), or json (application/json).
+
+  For "multipart", list elements can be strings or objects created by
+  \code{\link{upload_file}}. For "form", elements are coerced to strings
+  and escaped, use \code{I()} to prevent double-escaping. For "json",
+  parameters are automatically "unboxed" (i.e. length 1 vectors are
+  converted to scalars). To preserve a length 1 vector as a vector,
+  wrap in \code{I()}. For "raw", either a character or raw vector. You'll
+  need to make sure to set the \code{\link{content_type}()} yourself.}
+
+\item{times}{Maximum number of requests to attempt.}
+
+\item{pause_base, pause_cap}{This method uses exponential back-off with
+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{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}
+requests to the same scheme/host/port combo. This substantially reduces
+connection time, and ensures that cookies are maintained over multiple
+requests to the same host. See \code{\link{handle_pool}} for more
+details.}
+
+\item{quiet}{If \code{FALSE}, will print a message displaying how long
+until the next request.}
+}
+\value{
+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}()}.
+}
+\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
+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}.)
+}
+\examples{
+# Succeeds straight away
+RETRY("GET", "http://httpbin.org/status/200")
+# Never succeeds
+RETRY("GET", "http://httpbin.org/status/500")
+}
+
diff --git a/man/Token-class.Rd b/man/Token-class.Rd
new file mode 100644
index 0000000..f463c7a
--- /dev/null
+++ b/man/Token-class.Rd
@@ -0,0 +1,59 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/oauth-token.r
+\docType{class}
+\name{Token-class}
+\alias{Token}
+\alias{Token-class}
+\alias{Token1.0}
+\alias{Token2.0}
+\alias{TokenServiceAccount}
+\title{OAuth token objects.}
+\format{An R6 class object.}
+\usage{
+Token
+
+Token1.0
+
+Token2.0
+
+TokenServiceAccount
+}
+\description{
+These objects represent the complete set of data needed for OAuth access:
+an app, an endpoint, cached credentials and parameters. They should be
+created through their constructor functions \code{\link{oauth1.0_token}}
+and \code{\link{oauth2.0_token}}.
+}
+\section{Methods}{
+
+\itemize{
+ \item \code{cache()}: caches token to disk
+ \item \code{sign(method, url)}: returns list of url and config
+ \item \code{refresh()}: refresh access token (if possible)
+ \item \code{validate()}: TRUE if the token is still valid, FALSE otherwise
+}
+}
+
+\section{Caching}{
+
+OAuth tokens are cached on disk in a file called \code{.httr-oauth}
+saved in the current working directory.  Caching is enabled if:
+
+\itemize{
+\item The session is interactive, and the user agrees to it, OR
+\item The \code{.httr-oauth} file is already present, OR
+\item \code{getOption("httr_oauth_cache")} is \code{TRUE}
+}
+
+You can suppress caching by setting the \code{httr_oauth_cache} option to
+\code{FALSE}.
+
+Tokens are cached based on their endpoint and parameters.
+
+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
new file mode 100644
index 0000000..f0ddd81
--- /dev/null
+++ b/man/VERB.Rd
@@ -0,0 +1,78 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/http-verb.R
+\name{VERB}
+\alias{VERB}
+\title{VERB a url.}
+\usage{
+VERB(verb, url = NULL, config = list(), ..., body = NULL,
+  encode = c("multipart", "form", "json", "raw"), handle = NULL)
+}
+\arguments{
+\item{verb}{Name of verb to use.}
+
+\item{url}{the url of the page to retrieve}
+
+\item{config}{Additional configuration settings such as http
+authentication (\code{\link{authenticate}}), additional headers
+(\code{\link{add_headers}}), cookies (\code{\link{set_cookies}}) etc.
+See \code{\link{config}} for full details and list of helpers.}
+
+\item{...}{Further named parameters, such as \code{query}, \code{path}, etc,
+passed on to \code{\link{modify_url}}. Unnamed parameters will be combined
+with \code{\link{config}}.}
+
+\item{body}{One of the following:
+\itemize{
+  \item \code{FALSE}: No body. This is typically not used with \code{POST},
+    \code{PUT}, or \code{PATCH}, but can be useful if you need to send a
+    bodyless request (like \code{GET}) with \code{VERB()}.
+  \item \code{NULL}: An empty body
+  \item \code{""}: A length 0 body
+  \item \code{upload_file("path/")}: The contents of a file.  The mime
+    type will be guessed from the extension, or can be supplied explicitly
+    as the second argument to \code{upload_file()}
+  \item A character or raw vector: sent as is in body. Use
+    \code{\link{content_type}} to tell the server what sort of data
+    you are sending.
+  \item A named list: See details for encode.
+}}
+
+\item{encode}{If the body is a named list, how should it be encoded? Can be
+  one of form (application/x-www-form-urlencoded), multipart,
+  (multipart/form-data), or json (application/json).
+
+  For "multipart", list elements can be strings or objects created by
+  \code{\link{upload_file}}. For "form", elements are coerced to strings
+  and escaped, use \code{I()} to prevent double-escaping. For "json",
+  parameters are automatically "unboxed" (i.e. length 1 vectors are
+  converted to scalars). To preserve a length 1 vector as a vector,
+  wrap in \code{I()}. For "raw", either a character or raw vector. You'll
+  need to make sure to set the \code{\link{content_type}()} yourself.}
+
+\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}
+requests to the same scheme/host/port combo. This substantially reduces
+connection time, and ensures that cookies are maintained over multiple
+requests to the same host. See \code{\link{handle_pool}} for more
+details.}
+}
+\description{
+Use an arbitrary verb.
+}
+\examples{
+r <- VERB("PROPFIND", "http://svn.r-project.org/R/tags/",
+  add_headers(depth = 1), verbose())
+stop_for_status(r)
+content(r)
+
+VERB("POST", url = "http://httpbin.org/post")
+VERB("POST", url = "http://httpbin.org/post", body = "foobar")
+}
+\seealso{
+Other http methods: \code{\link{BROWSE}},
+  \code{\link{DELETE}}, \code{\link{GET}},
+  \code{\link{HEAD}}, \code{\link{PATCH}},
+  \code{\link{POST}}, \code{\link{PUT}}
+}
+
diff --git a/man/add_headers.Rd b/man/add_headers.Rd
new file mode 100644
index 0000000..dcae56c
--- /dev/null
+++ b/man/add_headers.Rd
@@ -0,0 +1,41 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/headers.r
+\name{add_headers}
+\alias{add_headers}
+\title{Add additional headers to a request.}
+\usage{
+add_headers(..., .headers = character())
+}
+\arguments{
+\item{...}{named header values.  To stop an existing header from being
+set, pass an empty string: \code{""}.}
+
+\item{.headers}{a named character vector}
+}
+\description{
+Wikipedia provides a useful list of common http headers:
+\url{http://en.wikipedia.org/wiki/List_of_HTTP_header_fields}.
+}
+\examples{
+add_headers(a = 1, b = 2)
+add_headers(.headers = c(a = "1", b = "2"))
+
+GET("http://httpbin.org/headers")
+
+# Add arbitrary headers
+GET("http://httpbin.org/headers",
+ add_headers(version = version$version.string))
+
+# Override default headers with empty strings
+GET("http://httpbin.org/headers", add_headers(Accept = ""))
+}
+\seealso{
+\code{\link{accept}} and \code{\link{content_type}} for
+  convenience functions for setting accept and content-type headers.
+
+Other config: \code{\link{authenticate}},
+  \code{\link{config}}, \code{\link{set_cookies}},
+  \code{\link{timeout}}, \code{\link{use_proxy}},
+  \code{\link{user_agent}}, \code{\link{verbose}}
+}
+
diff --git a/man/authenticate.Rd b/man/authenticate.Rd
new file mode 100644
index 0000000..2985923
--- /dev/null
+++ b/man/authenticate.Rd
@@ -0,0 +1,33 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/authenticate.r
+\name{authenticate}
+\alias{authenticate}
+\title{Use http authentication.}
+\usage{
+authenticate(user, password, type = "basic")
+}
+\arguments{
+\item{user}{user name}
+
+\item{password}{password}
+
+\item{type}{type of HTTP authentication.  Should be one of the following
+types supported by Curl: basic, digest, digest_ie, gssnegotiate,
+ntlm, any.  It defaults to "basic", the most common type.}
+}
+\description{
+It's not obvious how to turn authentication off after using it, so
+I recommend using custom handles with authentication.
+}
+\examples{
+GET("http://httpbin.org/basic-auth/user/passwd")
+GET("http://httpbin.org/basic-auth/user/passwd",
+  authenticate("user", "passwd"))
+}
+\seealso{
+Other config: \code{\link{add_headers}},
+  \code{\link{config}}, \code{\link{set_cookies}},
+  \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
new file mode 100644
index 0000000..160f1cb
--- /dev/null
+++ b/man/cache_info.Rd
@@ -0,0 +1,51 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/cache.R
+\name{cache_info}
+\alias{cache_info}
+\alias{rerequest}
+\title{Compute caching information for a response.}
+\usage{
+cache_info(r)
+
+rerequest(r)
+}
+\arguments{
+\item{r}{A response}
+}
+\description{
+\code{cache_info()} gives details of cacheability of a response,
+\code{rerequest()} re-performs the original request doing as little work
+as possible (if not expired, returns response as is, or performs
+revalidation if Etag or Last-Modified headers are present).
+}
+\examples{
+# Never cached, always causes redownload
+r1 <- GET("https://www.google.com")
+cache_info(r1)
+r1$date
+rerequest(r1)$date
+
+# Expires in a year
+r2 <- GET("https://www.google.com/images/srpr/logo11w.png")
+cache_info(r2)
+r2$date
+rerequest(r2)$date
+
+# Has last-modified and etag, so does revalidation
+r3 <- GET("http://httpbin.org/cache")
+cache_info(r3)
+r3$date
+rerequest(r3)$date
+
+# Expires after 5 seconds
+\dontrun{
+r4 <- GET("http://httpbin.org/cache/5")
+cache_info(r4)
+r4$date
+rerequest(r4)$date
+Sys.sleep(5)
+cache_info(r4)
+rerequest(r4)$date
+}
+}
+
diff --git a/man/config.Rd b/man/config.Rd
new file mode 100644
index 0000000..578a79e
--- /dev/null
+++ b/man/config.Rd
@@ -0,0 +1,64 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/config.r
+\name{config}
+\alias{config}
+\title{Set curl options.}
+\usage{
+config(..., token = NULL)
+}
+\arguments{
+\item{...}{named Curl options.}
+
+\item{token}{An OAuth token (1.0 or 2.0)}
+}
+\description{
+Generally you should only need to use this function to set CURL options
+directly if there isn't already a helpful wrapper function, like
+\code{\link{set_cookies}}, \code{\link{add_headers}} or
+\code{\link{authenticate}}. To use this function effectively requires
+some knowledge of CURL, and CURL options. Use \code{\link{httr_options}} to
+see a complete list of available options. To see the libcurl documentation
+for a given option, use \code{\link{curl_docs}}.
+}
+\details{
+Unlike Curl (and RCurl), all configuration options are per request, not
+per handle.
+}
+\examples{
+# There are a number of ways to modify the configuration of a request
+# * you can add directly to a request
+HEAD("https://www.google.com", verbose())
+
+# * you can wrap with with_config()
+with_config(verbose(), HEAD("https://www.google.com"))
+
+# * you can set global with set_config()
+old <- set_config(verbose())
+HEAD("https://www.google.com")
+# and re-establish the previous settings with
+set_config(old, override = TRUE)
+HEAD("https://www.google.com")
+# or
+reset_config()
+HEAD("https://www.google.com")
+
+# If available, you should use a friendly httr wrapper over RCurl
+# options. But you can pass Curl options (as listed in httr_options())
+# in config
+HEAD("https://www.google.com/", config(verbose = TRUE))
+}
+\seealso{
+\code{\link{set_config}} to set global config defaults, and
+ \code{\link{with_config}} to temporarily run code with set options.
+
+All known available options are listed in \code{\link{httr_options}}
+
+Other config: \code{\link{add_headers}},
+  \code{\link{authenticate}}, \code{\link{set_cookies}},
+  \code{\link{timeout}}, \code{\link{use_proxy}},
+  \code{\link{user_agent}}, \code{\link{verbose}}
+
+Other ways to set configuration: \code{\link{set_config}},
+  \code{\link{with_config}}
+}
+
diff --git a/man/content.Rd b/man/content.Rd
new file mode 100644
index 0000000..36c9695
--- /dev/null
+++ b/man/content.Rd
@@ -0,0 +1,87 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/content.r
+\name{content}
+\alias{content}
+\alias{parsed_content}
+\alias{text_content}
+\title{Extract content from a request.}
+\usage{
+content(x, as = NULL, type = NULL, encoding = NULL, ...)
+}
+\arguments{
+\item{x}{request object}
+
+\item{as}{desired type of output: \code{raw}, \code{text} or
+\code{parsed}. \code{content} attempts to automatically figure out
+which one is most appropriate, based on the content-type.}
+
+\item{type}{MIME type (aka internet media type) used to override
+the content type returned by the server. See
+\url{http://en.wikipedia.org/wiki/Internet_media_type} for a list of
+common types.}
+
+\item{encoding}{For text, overrides the charset or the Latin1 (ISO-8859-1)
+default, if you know that the server is returning the incorrect encoding
+as the charset in the content-type. Use for text and parsed outputs.}
+
+\item{...}{Other parameters parsed on to the parsing functions, if
+\code{as = "parsed"}}
+}
+\value{
+For "raw", a raw vector.
+
+For "text", a character vector of length 1. The character vector is always
+re-encoded to UTF-8. If this encoding fails (usually because the page
+declares an incorrect encoding), \code{content()} will return \code{NA}.
+
+For "auto", a parsed R object.
+}
+\description{
+There are currently three ways to retrieve the contents of a request:
+as a raw object (\code{as = "raw"}), as a character vector,
+(\code{as = "text"}), and as parsed into an R object where possible,
+(\code{as = "parsed"}). If \code{as} is not specified, \code{content}
+does its best to guess which output is most appropriate.
+}
+\details{
+\code{content} currently knows about the following mime types:
+
+\itemize{
+ \item \code{text/html}: \code{\link[xml2]{read_html}}
+ \item \code{text/xml}: \code{\link[xml2]{read_xml}}
+ \item \code{text/csv}: \code{\link[readr]{read_csv}}
+ \item \code{text/tab-separated-values}: \code{\link[readr]{read_tsv}}
+ \item \code{application/json}: \code{\link[jsonlite]{fromJSON}}
+ \item \code{application/x-www-form-urlencoded}: \code{parse_query}
+ \item \code{image/jpeg}: \code{\link[jpeg]{readJPEG}}
+ \item \code{image/png}: \code{\link[png]{readPNG}}
+}
+
+\code{as = "parsed"} is provided as a convenience only: if the type you
+are trying to parse is not available, use \code{as = "text"} and parse
+yourself.
+}
+\section{WARNING}{
+
+
+When using \code{content()} in a package, DO NOT use on \code{as = "parsed"}.
+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
+cat(content(r, "text"), "\\n") # text content
+content(r, "raw") # raw bytes from server
+
+rlogo <- content(GET("http://cran.r-project.org/Rlogo.jpg"))
+plot(0:1, 0:1, type = "n")
+rasterImage(rlogo, 0, 0, 1, 1)
+}
+\seealso{
+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
new file mode 100644
index 0000000..6ec3384
--- /dev/null
+++ b/man/content_type.Rd
@@ -0,0 +1,47 @@
+% 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}
+\title{Set content-type and accept headers.}
+\usage{
+content_type(type)
+
+content_type_json()
+
+content_type_xml()
+
+accept(type)
+
+accept_json()
+
+accept_xml()
+}
+\arguments{
+\item{type}{A mime type or a file extension. If a file extension (i.e. starts
+with \code{.}) will guess the mime type using \code{\link[mime]{guess_type}}.}
+}
+\description{
+These are convenient wrappers aroud \code{\link{add_headers}}.
+}
+\details{
+\code{accept_json}/\code{accept_xml} and
+\code{content_type_json}/\code{content_type_xml} are useful shortcuts to
+ask for json or xml responses or tell the server you are sending json/xml.
+}
+\examples{
+GET("http://httpbin.org/headers")
+
+GET("http://httpbin.org/headers", accept_json())
+GET("http://httpbin.org/headers", accept("text/csv"))
+GET("http://httpbin.org/headers", accept(".doc"))
+
+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
new file mode 100644
index 0000000..91ce6ff
--- /dev/null
+++ b/man/cookies.Rd
@@ -0,0 +1,22 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/cookies.r
+\name{cookies}
+\alias{cookies}
+\title{Access cookies in a response.}
+\usage{
+cookies(x)
+}
+\arguments{
+\item{x}{A response.}
+}
+\description{
+Access cookies in a response.
+}
+\examples{
+r <- GET("http://httpbin.org/cookies/set", query = list(a = 1, b = 2))
+cookies(r)
+}
+\seealso{
+\code{\link{set_cookies}()} to send cookies in request.
+}
+
diff --git a/man/guess_media.Rd b/man/guess_media.Rd
new file mode 100644
index 0000000..e5938f5
--- /dev/null
+++ b/man/guess_media.Rd
@@ -0,0 +1,16 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/media-guess.r
+\name{guess_media}
+\alias{guess_media}
+\title{Guess the media type of a path from its extension.}
+\usage{
+guess_media(x)
+}
+\arguments{
+\item{x}{path to file}
+}
+\description{
+DEPRECATED: please use \code{mime::guess_type} instead.
+}
+\keyword{internal}
+
diff --git a/man/handle.Rd b/man/handle.Rd
new file mode 100644
index 0000000..4006956
--- /dev/null
+++ b/man/handle.Rd
@@ -0,0 +1,48 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/handle.r
+\name{handle}
+\alias{handle}
+\title{Create a handle tied to a particular host.}
+\usage{
+handle(url, cookies = TRUE)
+}
+\arguments{
+\item{url}{full url to site}
+
+\item{cookies}{DEPRECATED}
+}
+\description{
+This handle preserves settings and cookies across multiple requests. It is
+the foundation of all requests performed through the httr package, although
+it will mostly be hidden from the user.
+}
+\note{
+Because of the way argument dispatch works in R, using handle() in the
+http methods (See \code{\link{GET}}) will cause problems when trying to
+pass configuration arguments (See examples below). Directly specifying the
+handle when using http methods is not recommended in general, since the
+selection of the correct handle is taken care of when the user passes an url
+(See \code{\link{handle_pool}}).
+}
+\examples{
+handle("http://google.com")
+handle("https://google.com")
+
+h <- handle("http://google.com")
+GET(handle = h)
+# Should see cookies sent back to server
+GET(handle = h, config = verbose())
+
+h <- handle("http://google.com", cookies = FALSE)
+GET(handle = h)$cookies
+
+\dontrun{
+# Using the preferred way of configuring the http methods
+# will not work when using handle():
+GET(handle = h, timeout(10))
+# Passing named arguments will work properly:
+GET(handle = h, config = list(timeout(10), add_headers(Accept = "")))
+}
+
+}
+
diff --git a/man/handle_pool.Rd b/man/handle_pool.Rd
new file mode 100644
index 0000000..510597c
--- /dev/null
+++ b/man/handle_pool.Rd
@@ -0,0 +1,24 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/handle-pool.r
+\docType{data}
+\name{handle_pool}
+\alias{handle_find}
+\alias{handle_pool}
+\alias{handle_reset}
+\title{Maintain a pool of handles.}
+\format{An environment.}
+\usage{
+handle_pool
+
+handle_find(url)
+
+handle_reset(url)
+}
+\description{
+The handle pool is used to automatically reuse Curl handles for the same
+scheme/host/port combination. This ensures that the http session is
+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
new file mode 100644
index 0000000..babcadf
--- /dev/null
+++ b/man/has_content.Rd
@@ -0,0 +1,17 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/content.r
+\name{has_content}
+\alias{has_content}
+\title{Does the request have content associated with it?}
+\usage{
+has_content(x)
+}
+\description{
+Does the request have content associated with it?
+}
+\examples{
+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
new file mode 100644
index 0000000..d7be9f6
--- /dev/null
+++ b/man/headers.Rd
@@ -0,0 +1,23 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/headers.r
+\name{headers}
+\alias{headers}
+\title{Extract the headers from a response}
+\usage{
+headers(x)
+}
+\arguments{
+\item{x}{A request object}
+}
+\description{
+Extract the headers from a response
+}
+\examples{
+r <- GET("http://httpbin.org/get")
+headers(r)
+}
+\seealso{
+\code{\link{add_headers}()} to send additional headers in a
+  request
+}
+
diff --git a/man/hmac_sha1.Rd b/man/hmac_sha1.Rd
new file mode 100644
index 0000000..32358cc
--- /dev/null
+++ b/man/hmac_sha1.Rd
@@ -0,0 +1,18 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/sha1.r
+\name{hmac_sha1}
+\alias{hmac_sha1}
+\title{HMAC SHA1}
+\usage{
+hmac_sha1(key, string)
+}
+\arguments{
+\item{key}{secret key}
+
+\item{string}{data to securely hash}
+}
+\description{
+As described in \url{http://datatracker.ietf.org/doc/rfc2104/}.
+}
+\keyword{internal}
+
diff --git a/man/http_condition.Rd b/man/http_condition.Rd
new file mode 100644
index 0000000..4700b81
--- /dev/null
+++ b/man/http_condition.Rd
@@ -0,0 +1,54 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/response-status.r
+\name{http_condition}
+\alias{http_condition}
+\title{Generate a classed http condition.}
+\usage{
+http_condition(x, type, task = NULL, call = sys.call(-1))
+}
+\arguments{
+\item{x}{a response, or numeric http code (or other object with
+\code{status_code} method)}
+
+\item{type}{type of condition to generate. Must be one of error,
+warning or message.}
+
+\item{task}{The text of the message: either \code{NULL} or a
+character vector. If non-\code{NULL}, the error message will finish with
+"Failed to \code{task}".}
+
+\item{call}{The call stored in the condition object.}
+}
+\value{
+An S3 object that inherits from (e.g.) condition, \code{type},
+  http_error, http_400 and http_404.
+}
+\description{
+This function generate S3 condition objects which are passed to
+\code{\link{stop}} or \code{\link{warning}} to generate classes warnings
+and error. These can be used in conjunction with \code{\link{tryCatch}}
+to respond differently to different type of failure.
+}
+\examples{
+# You can use tryCatch to take different actions based on the type
+# of error. Note that tryCatch will call the first handler that
+# matches any classes of the condition, not the best matching, so
+# always list handlers from most specific to least specific
+f <- function(url) {
+  tryCatch(stop_for_status(GET(url)),
+    http_404 = function(c) "That url doesn't exist",
+    http_403 = function(c) "You need to authenticate!",
+    http_400 = function(c) "You made a mistake!",
+    http_500 = function(c) "The server screwed up"
+  )
+}
+f("http://httpbin.org/status/404")
+f("http://httpbin.org/status/403")
+f("http://httpbin.org/status/505")
+}
+\seealso{
+\url{http://adv-r.had.co.nz/Exceptions-Debugging.html#condition-handling}
+  for more details about R's condition handling model
+}
+\keyword{internal}
+
diff --git a/man/http_error.Rd b/man/http_error.Rd
new file mode 100644
index 0000000..649d8f6
--- /dev/null
+++ b/man/http_error.Rd
@@ -0,0 +1,43 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/response-status.r
+\name{http_error}
+\alias{http_error}
+\alias{url_ok}
+\alias{url_success}
+\title{Check for an http error.}
+\usage{
+http_error(x, ...)
+}
+\arguments{
+\item{x}{Object to check. Default methods are provided for strings
+(which perform an \code{\link{HEAD}} request), responses, and
+integer status codes.}
+
+\item{...}{Other arguments passed on to methods.}
+}
+\value{
+\code{TRUE} if the request fails (status code 400 or above),
+  otherwise \code{FALSE}.
+}
+\description{
+Check for an http error.
+}
+\examples{
+# You can pass a url:
+http_error("http://www.google.com")
+http_error("http://httpbin.org/status/404")
+
+# Or a request
+r <- GET("http://httpbin.org/status/201")
+http_error(r)
+
+# Or an (integer) status code
+http_error(200L)
+http_error(404L)
+}
+\seealso{
+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
new file mode 100644
index 0000000..66310c3
--- /dev/null
+++ b/man/http_status.Rd
@@ -0,0 +1,50 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/response-status.r
+\name{http_status}
+\alias{http_status}
+\title{Give information on the status of a request.}
+\usage{
+http_status(x)
+}
+\arguments{
+\item{x}{a request object or a number.}
+}
+\value{
+If the status code does not match a known status, an error.
+ Otherwise, a list with components
+ \item{category}{the broad category of the status}
+ \item{message}{the meaning of the status code}
+}
+\description{
+Extract the http status code and convert it into a human readable message.
+}
+\details{
+http servers send a status code with the response to each request. This code
+gives information regarding the outcome of the execution of the request
+on the server. Roughly speaking, codes in the 100s and 200s mean the request
+was successfully executed; codes in the 300s mean the page was redirected;
+codes in the 400s mean there was a mistake in the way the client sent the
+request; codes in the 500s mean the server failed to fulfill
+an apparently valid request. More details on the codes can be found at
+\code{http://en.wikipedia.org/wiki/Http_error_codes}.
+}
+\examples{
+http_status(100)
+http_status(404)
+
+x <- GET("http://httpbin.org/status/200")
+http_status(x)
+
+http_status(GET("http://httpbin.org/status/300"))
+http_status(GET("http://httpbin.org/status/301"))
+http_status(GET("http://httpbin.org/status/404"))
+
+# errors out on unknown status
+\dontrun{http_status(GET("http://httpbin.org/status/320"))}
+}
+\seealso{
+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
new file mode 100644
index 0000000..96f5e81
--- /dev/null
+++ b/man/http_type.Rd
@@ -0,0 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/response-type.R
+\name{http_type}
+\alias{http_type}
+\title{Extract the content type of a response}
+\usage{
+http_type(x)
+}
+\arguments{
+\item{x}{A response}
+}
+\value{
+A string giving the complete mime type, with all parameters
+  stripped off.
+}
+\description{
+Extract the content type of a response
+}
+\examples{
+r1 <- GET("http://httpbin.org/image/png")
+http_type(r1)
+headers(r1)[["Content-Type"]]
+
+r2 <- GET("http://httpbin.org/ip")
+http_type(r2)
+headers(r2)[["Content-Type"]]
+}
+
diff --git a/man/httr.Rd b/man/httr.Rd
new file mode 100644
index 0000000..e63b018
--- /dev/null
+++ b/man/httr.Rd
@@ -0,0 +1,33 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/httr.r
+\docType{package}
+\name{httr}
+\alias{httr}
+\alias{httr-package}
+\title{\pkg{httr} makes http easy.}
+\description{
+\code{httr} is organised around the five most common http verbs:
+\code{\link{GET}}, \code{\link{PATCH}},
+\code{\link{POST}}, \code{\link{HEAD}},
+\code{\link{PUT}}, and \code{\link{DELETE}}.
+}
+\details{
+Each request returns a \code{\link{response}} object which provides
+easy access to status code, cookies, headers, timings, and other useful
+info.  The content of the request is available as a raw vector
+(\code{\link{content}}), character vector (\code{\link{text_content}}),
+or parsed into an R object (\code{\link{parsed_content}}), currently for
+html, xml, json, png and jpeg).
+
+Requests can be modified by various config options like
+\code{\link{set_cookies}}, \code{\link{add_headers}},
+\code{\link{authenticate}}, \code{\link{use_proxy}},
+\code{\link{verbose}}, and \code{\link{timeout}}
+
+httr supports OAuth 1.0 and 2.0. Use \code{\link{oauth1.0_token}} and
+\code{\link{oauth2.0_token}} to get user tokens, and
+\code{\link{sign_oauth1.0}} and \code{\link{sign_oauth2.0}} to sign
+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).
+}
+
diff --git a/man/httr_dr.Rd b/man/httr_dr.Rd
new file mode 100644
index 0000000..965c382
--- /dev/null
+++ b/man/httr_dr.Rd
@@ -0,0 +1,12 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/doctor.R
+\name{httr_dr}
+\alias{httr_dr}
+\title{Diagnose common configuration problems}
+\usage{
+httr_dr()
+}
+\description{
+Currently one check: that curl uses nss.
+}
+
diff --git a/man/httr_options.Rd b/man/httr_options.Rd
new file mode 100644
index 0000000..a64998e
--- /dev/null
+++ b/man/httr_options.Rd
@@ -0,0 +1,45 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/config.r
+\name{httr_options}
+\alias{curl_docs}
+\alias{httr_options}
+\title{List available options.}
+\usage{
+httr_options(matches)
+
+curl_docs(x)
+}
+\arguments{
+\item{matches}{If not missing, this restricts the output so that either
+the httr or curl option matches this regular expression.}
+
+\item{x}{An option name (either short or full).}
+}
+\value{
+A data frame with three columns:
+\item{httr}{The short name used in httr}
+\item{libcurl}{The full name used by libcurl}
+\item{type}{The type of R object that the option accepts}
+}
+\description{
+This function lists all available options for \code{\link{config}()}.
+It provides both the short R name which you use with httr, and the longer
+Curl name, which is useful when searching the documentation. \code{curl_doc}
+opens a link to the libcurl documentation for an option in your browser.
+}
+\details{
+RCurl and httr use slightly different names to libcurl: the initial
+\code{CURLOPT_} is removed, all underscores are converted to periods and
+the option is given in lower case.  Thus "CURLOPT_SSLENGINE_DEFAULT"
+becomes "sslengine.default".
+}
+\examples{
+httr_options()
+httr_options("post")
+
+# Use curl_docs to read the curl documentation for each option.
+# You can use either the httr or curl option name.
+curl_docs("userpwd")
+curl_docs("CURLOPT_USERPWD")
+}
+
diff --git a/man/init_oauth1.0.Rd b/man/init_oauth1.0.Rd
new file mode 100644
index 0000000..5847eff
--- /dev/null
+++ b/man/init_oauth1.0.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/oauth-init.R
+\name{init_oauth1.0}
+\alias{init_oauth1.0}
+\title{Retrieve OAuth 1.0 access token.}
+\usage{
+init_oauth1.0(endpoint, app, permission = NULL,
+  is_interactive = interactive(), private_key = NULL)
+}
+\arguments{
+\item{endpoint}{An OAuth endpoint, created by \code{\link{oauth_endpoint}}}
+
+\item{app}{An OAuth consumer application, created by
+\code{\link{oauth_app}}}
+
+\item{permission}{optional, a string of permissions to ask for.}
+
+\item{is_interactive}{Is the current environment interactive?}
+
+\item{private_key}{Optional, a key provided by \code{\link[openssl]{read_key}}.
+Used for signed OAuth 1.0.}
+}
+\description{
+See demos for use.
+}
+\keyword{internal}
+
diff --git a/man/init_oauth2.0.Rd b/man/init_oauth2.0.Rd
new file mode 100644
index 0000000..c08cadd
--- /dev/null
+++ b/man/init_oauth2.0.Rd
@@ -0,0 +1,41 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/oauth-init.R
+\name{init_oauth2.0}
+\alias{init_oauth2.0}
+\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)
+}
+\arguments{
+\item{endpoint}{An OAuth endpoint, created by \code{\link{oauth_endpoint}}}
+
+\item{app}{An OAuth consumer application, created by
+\code{\link{oauth_app}}}
+
+\item{scope}{a character vector of scopes to request.}
+
+\item{user_params}{Named list holding endpoint specific parameters to pass to
+the server when posting the request for obtaining or refreshing the
+access token.}
+
+\item{type}{content type used to override incorrect server response}
+
+\item{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.}
+
+\item{is_interactive}{Is the current environment interactive?}
+
+\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.}
+}
+\description{
+See demos for use.
+}
+\keyword{internal}
+
diff --git a/man/insensitive.Rd b/man/insensitive.Rd
new file mode 100644
index 0000000..17490b3
--- /dev/null
+++ b/man/insensitive.Rd
@@ -0,0 +1,23 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/insensitive.r
+\name{insensitive}
+\alias{insensitive}
+\title{Create a vector with case insensitive name matching.}
+\usage{
+insensitive(x)
+}
+\arguments{
+\item{x}{vector to modify}
+}
+\description{
+Create a vector with case insensitive name matching.
+}
+\examples{
+x <- c("abc" = 1, "def" = 2)
+x["ABC"]
+y <- insensitive(x)
+y["ABC"]
+y[["ABC"]]
+}
+\keyword{internal}
+
diff --git a/man/jwt_signature.Rd b/man/jwt_signature.Rd
new file mode 100644
index 0000000..fc15c13
--- /dev/null
+++ b/man/jwt_signature.Rd
@@ -0,0 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/oauth-server-side.R
+\name{jwt_signature}
+\alias{jwt_signature}
+\title{Generate a JWT signature given credentials.}
+\usage{
+jwt_signature(credentials, scope, aud, duration = 60L * 60L)
+}
+\arguments{
+\item{credentials}{Parsed contents of the credentials file.}
+
+\item{scope}{A space-delimited list of the permissions that the application
+requests.}
+
+\item{duration}{Duration of token, in seconds.}
+}
+\description{
+As described in
+\url{https://developers.google.com/accounts/docs/OAuth2ServiceAccount}
+}
+\examples{
+\dontrun{
+cred <- jsonlite::fromJSON("~/Desktop/httrtest-45693cbfac92.json")
+jwt_signature(cred, "https://www.googleapis.com/auth/userinfo.profile")
+}
+}
+\keyword{internal}
+
diff --git a/man/modify_url.Rd b/man/modify_url.Rd
new file mode 100644
index 0000000..9102f8e
--- /dev/null
+++ b/man/modify_url.Rd
@@ -0,0 +1,20 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/url.r
+\name{modify_url}
+\alias{modify_url}
+\title{Modify a url.}
+\usage{
+modify_url(url, scheme = NULL, hostname = NULL, port = NULL,
+  path = NULL, query = NULL, params = NULL, fragment = NULL,
+  username = NULL, password = NULL)
+}
+\arguments{
+\item{url}{the url to modify}
+
+\item{scheme, hostname, port, path, query, params, fragment, username, password}{components of the url to change}
+}
+\description{
+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
new file mode 100644
index 0000000..a5d4d28
--- /dev/null
+++ b/man/oauth1.0_token.Rd
@@ -0,0 +1,45 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/oauth-token.r
+\name{oauth1.0_token}
+\alias{oauth1.0_token}
+\title{Generate an oauth1.0 token.}
+\usage{
+oauth1.0_token(endpoint, app, permission = NULL, as_header = TRUE,
+  private_key = NULL, cache = getOption("httr_oauth_cache"))
+}
+\arguments{
+\item{endpoint}{An OAuth endpoint, created by \code{\link{oauth_endpoint}}}
+
+\item{app}{An OAuth consumer application, created by
+\code{\link{oauth_app}}}
+
+\item{permission}{optional, a string of permissions to ask for.}
+
+\item{as_header}{If \code{TRUE}, the default, sends oauth in header.
+If \code{FALSE}, adds as parameter to url.}
+
+\item{private_key}{Optional, a key provided by \code{\link[openssl]{read_key}}.
+Used for signed OAuth 1.0.}
+
+\item{cache}{A logical value or a string. \code{TRUE} means to cache
+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.}
+}
+\value{
+A \code{Token1.0} reference class (RC) object.
+}
+\description{
+This is the final object in the OAuth dance - it encapsulates the app,
+the endpoint, other parameters and the received credentials.
+}
+\details{
+See \code{\link{Token}} for full details about the token object, and the
+caching policies used to store credentials across sessions.
+}
+\seealso{
+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
new file mode 100644
index 0000000..a051a38
--- /dev/null
+++ b/man/oauth2.0_token.Rd
@@ -0,0 +1,63 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/oauth-token.r
+\name{oauth2.0_token}
+\alias{oauth2.0_token}
+\title{Generate an oauth2.0 token.}
+\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"))
+}
+\arguments{
+\item{endpoint}{An OAuth endpoint, created by \code{\link{oauth_endpoint}}}
+
+\item{app}{An OAuth consumer application, created by
+\code{\link{oauth_app}}}
+
+\item{scope}{a character vector of scopes to request.}
+
+\item{user_params}{Named list holding endpoint specific parameters to pass to
+the server when posting the request for obtaining or refreshing the
+access token.}
+
+\item{type}{content type used to override incorrect server response}
+
+\item{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.}
+
+\item{as_header}{If \code{TRUE}, the default, configures the token to add
+itself to the bearer header of subsequent requests. If \code{FALSE},
+configures the token to add itself as a url parameter of subsequent
+requests.}
+
+\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{cache}{A logical value or a string. \code{TRUE} means to cache
+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.}
+}
+\value{
+A \code{Token2.0} reference class (RC) object.
+}
+\description{
+This is the final object in the OAuth dance - it encapsulates the app,
+the endpoint, other parameters and the received credentials. It is a
+reference class so that it can be seamlessly updated (e.g. using
+\code{$refresh()}) when access expires.
+}
+\details{
+See \code{\link{Token}} for full details about the token object, and the
+caching policies used to store credentials across sessions.
+}
+\seealso{
+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
new file mode 100644
index 0000000..a49479f
--- /dev/null
+++ b/man/oauth_app.Rd
@@ -0,0 +1,47 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/oauth-app.r
+\name{oauth_app}
+\alias{oauth_app}
+\title{Create an OAuth application.}
+\usage{
+oauth_app(appname, key, secret = NULL)
+}
+\arguments{
+\item{appname}{name of the application.  This is not used for OAuth, but
+is used to make it easier to identify different applications and
+provide a consistent way of storing secrets in environment variables.}
+
+\item{key}{consumer key (equivalent to a user name)}
+
+\item{secret}{consumer secret. This is not equivalent to a password, and
+  is not really a secret. If you are writing an API wrapper package, it
+  is fine to include this secret in your package code.
+
+  Use \code{NULL} to not store a secret: this is useful if you're relying on
+  cached OAuth tokens.}
+}
+\description{
+The OAuth framework doesn't match perfectly to use from R. Each user of the
+package for a particular OAuth enabled site must create their own
+application. See the demos for instructions on how to do this for
+linkedin, twitter, vimeo, facebook, github and google.
+}
+\examples{
+\dontrun{
+# These work on my computer because I have the right envvars set up
+linkedin_app <- oauth_app("linkedin", key = "outmkw3859gy")
+github_app <- oauth_app("github", "56b637a5baffac62cad9")
+}
+
+# If you're relying on caching, supply an explicit NULL to
+# suppress the warning message
+oauth_app("my_app", "mykey")
+oauth_app("my_app", "mykey", NULL)
+}
+\seealso{
+Other OAuth: \code{\link{oauth1.0_token}},
+  \code{\link{oauth2.0_token}},
+  \code{\link{oauth_endpoint}},
+  \code{\link{oauth_service_token}}
+}
+
diff --git a/man/oauth_callback.Rd b/man/oauth_callback.Rd
new file mode 100644
index 0000000..6281c61
--- /dev/null
+++ b/man/oauth_callback.Rd
@@ -0,0 +1,14 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/oauth-listener.r
+\name{oauth_callback}
+\alias{oauth_callback}
+\title{The oauth callback url.}
+\usage{
+oauth_callback()
+}
+\description{
+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
new file mode 100644
index 0000000..3366e2f
--- /dev/null
+++ b/man/oauth_endpoint.Rd
@@ -0,0 +1,42 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/oauth-endpoint.r
+\name{oauth_endpoint}
+\alias{oauth_endpoint}
+\title{Describe an OAuth endpoint.}
+\usage{
+oauth_endpoint(request = NULL, authorize, access, ..., base_url = NULL)
+}
+\arguments{
+\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{access}{url used to exchange unauthenticated for authenticated token.}
+
+\item{...}{other additional endpoints.}
+
+\item{base_url}{option url to use as base for \code{request},
+\code{authorize} and \code{access} urls.}
+}
+\description{
+See \code{\link{oauth_endpoints}} for a list of popular OAuth endpoints
+baked into httr.
+}
+\examples{
+linkedin <- oauth_endpoint("requestToken", "authorize", "accessToken",
+  base_url = "https://api.linkedin.com/uas/oauth")
+github <- oauth_endpoint(NULL, "authorize", "access_token",
+  base_url = "https://github.com/login/oauth")
+facebook <- oauth_endpoint(
+  authorize = "https://www.facebook.com/dialog/oauth",
+  access = "https://graph.facebook.com/oauth/access_token")
+
+oauth_endpoints
+}
+\seealso{
+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
new file mode 100644
index 0000000..00c4a20
--- /dev/null
+++ b/man/oauth_endpoints.Rd
@@ -0,0 +1,19 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/oauth-endpoint.r
+\name{oauth_endpoints}
+\alias{oauth_endpoints}
+\title{Popular oauth endpoints.}
+\usage{
+oauth_endpoints(name)
+}
+\arguments{
+\item{name}{One of the following endpoints: linkedin, twitter,
+vimeo, google, facebook, github, azure.}
+}
+\description{
+Provides some common OAuth endpoints.
+}
+\examples{
+oauth_endpoints("twitter")
+}
+
diff --git a/man/oauth_exchanger.Rd b/man/oauth_exchanger.Rd
new file mode 100644
index 0000000..56fbe37
--- /dev/null
+++ b/man/oauth_exchanger.Rd
@@ -0,0 +1,24 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/oauth-exchanger.r
+\name{oauth_exchanger}
+\alias{oauth_exchanger}
+\title{Walk the user through the OAuth2 dance without a local webserver.}
+\usage{
+oauth_exchanger(request_url)
+}
+\arguments{
+\item{request_url}{the url to provide to the user}
+}
+\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).
+}
+\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
new file mode 100644
index 0000000..65dd73d
--- /dev/null
+++ b/man/oauth_listener.Rd
@@ -0,0 +1,29 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/oauth-listener.r
+\name{oauth_listener}
+\alias{oauth_listener}
+\title{Create a webserver to listen for OAuth callback.}
+\usage{
+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{host}{ip address for the listener}
+
+\item{port}{for the listener}
+}
+\description{
+This opens a web browser pointing to \code{request_url}, and opens a
+webserver on port 1410 to listen to the reponse.  The redirect url for
+should be either set previously (during the OAuth authentication) dance
+or supplied as a parameter to the url.  See \code{\link{oauth1.0_token}}
+and \code{\link{oauth2.0_token}} for examples of both techniques.
+}
+\details{
+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
new file mode 100644
index 0000000..fb27110
--- /dev/null
+++ b/man/oauth_service_token.Rd
@@ -0,0 +1,36 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/oauth-token.r
+\name{oauth_service_token}
+\alias{oauth_service_token}
+\title{Generate OAuth token for service accounts.}
+\usage{
+oauth_service_token(endpoint, secrets, scope = NULL)
+}
+\arguments{
+\item{endpoint}{An OAuth endpoint, created by \code{\link{oauth_endpoint}}}
+
+\item{secrets}{Secrets loaded from JSON file, downloaded from console.}
+
+\item{scope}{a character vector of scopes to request.}
+}
+\description{
+Service accounts provide a way of using OAuth2 without user intervention.
+They instead assume that the server has access to a private key used
+to sign requests. The OAuth app is not needed for service accounts:
+that information is embedded in the account itself.
+}
+\examples{
+\dontrun{
+endpoint <- oauth_endpoints("google")
+secrets <- jsonlite::fromJSON("~/Desktop/httrtest-45693cbfac92.json")
+scope <- "https://www.googleapis.com/auth/bigquery.readonly"
+
+token <- oauth_service_token(endpoint, secrets, scope)
+}
+}
+\seealso{
+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
new file mode 100644
index 0000000..8570953
--- /dev/null
+++ b/man/oauth_signature.Rd
@@ -0,0 +1,31 @@
+% 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}
+\title{Generate oauth signature.}
+\usage{
+oauth_signature(url, method = "GET", app, token = NULL,
+  token_secret = NULL, private_key = NULL, other_params = NULL)
+
+oauth_header(info)
+}
+\arguments{
+\item{url, method}{Url and http method of request.}
+
+\item{app}{\code{\link{oauth_app}} object representing application.}
+
+\item{token, token_secret}{OAuth token and secret.}
+
+\item{other_params}{Named argument providing additional parameters
+(e.g. \code{oauth_callback} or \code{oauth_body_hash}).}
+}
+\value{
+A list of oauth parameters.
+}
+\description{
+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
new file mode 100644
index 0000000..d970fc5
--- /dev/null
+++ b/man/parse_http_date.Rd
@@ -0,0 +1,40 @@
+% 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}
+\title{Parse and print http dates.}
+\usage{
+parse_http_date(x, failure = NA)
+
+http_date(x)
+}
+\arguments{
+\item{x}{For \code{parse_http_date}, a character vector of strings to parse.
+  All elements must be of the same type.
+
+  For \code{http_date}, a \code{POSIXt} vector.}
+
+\item{failure}{What to return on failure?}
+}
+\value{
+A POSIXct object if succesful, otherwise \code{failure}
+}
+\description{
+As defined in RFC2616,
+\url{http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3}, there are
+three valid formats:
+\itemize{
+ \item Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
+ \item Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
+ \item Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
+}
+}
+\examples{
+parse_http_date("Sun, 06 Nov 1994 08:49:37 GMT")
+parse_http_date("Sunday, 06-Nov-94 08:49:37 GMT")
+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
new file mode 100644
index 0000000..dbfcb16
--- /dev/null
+++ b/man/parse_media.Rd
@@ -0,0 +1,34 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/media-parse.r
+\name{parse_media}
+\alias{parse_media}
+\title{Parse a media type.}
+\usage{
+parse_media(x)
+}
+\description{
+Parsed according to RFC 2616, as at
+\url{http://pretty-rfc.herokuapp.com/RFC2616#media.types}.
+}
+\details{
+A simplified minimal EBNF is:
+
+\itemize{
+ \item media-type     = type "/" subtype *( ";" parameter )
+ \item type           = token
+ \item subtype        = token
+ \item parameter      = attribute "=" value
+ \item attribute      = token
+ \item value          = token | quoted-string
+ \item token          = 1*<any CHAR except CTLs or ()<>@,;:\"/[]?={}
+ \item quoted-string  = " *(any text except ", unless escaped with \) "
+}
+}
+\examples{
+parse_media("text/plain")
+parse_media("text/plain; charset=utf-8")
+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
new file mode 100644
index 0000000..f815054
--- /dev/null
+++ b/man/parse_url.Rd
@@ -0,0 +1,40 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/url.r
+\name{parse_url}
+\alias{build_url}
+\alias{parse_url}
+\title{Parse and build urls according to RFC1808.}
+\usage{
+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.}
+}
+\value{
+a list containing: \itemize{
+ \item scheme
+ \item hostname
+ \item port
+ \item path
+ \item params
+ \item fragment
+ \item query, a list
+ \item username
+ \item password
+}
+}
+\description{
+See \url{http://tools.ietf.org/html/rfc1808.html} for details of parsing
+algorithm.
+}
+\examples{
+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/"))
+}
+
diff --git a/man/progress.Rd b/man/progress.Rd
new file mode 100644
index 0000000..7189d3f
--- /dev/null
+++ b/man/progress.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/progress.R
+\name{progress}
+\alias{progress}
+\title{Add a progress bar.}
+\usage{
+progress(type = c("down", "up"), con = stdout())
+}
+\arguments{
+\item{type}{Type of progress to display: either number of bytes uploaded
+or downloaded.}
+
+\item{con}{Connection to send output too. Usually \code{stdout()} or
+\code{stderr}.}
+}
+\description{
+Add a progress bar.
+}
+\examples{
+\donttest{
+# If file size is known, you get a progress bar:
+x <- GET("http://courses.had.co.nz/12-oscon/slides.zip", progress())
+# Otherwise you get the number of bytes downloaded:
+x <- GET("http://httpbin.org/drip?numbytes=4000&duration=3", progress())
+}
+}
+
diff --git a/man/response.Rd b/man/response.Rd
new file mode 100644
index 0000000..336d4b9
--- /dev/null
+++ b/man/response.Rd
@@ -0,0 +1,29 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/response.r
+\name{response}
+\alias{response}
+\title{The response object.}
+\description{
+The response object captures all information from a request.  It includes
+fields:
+}
+\details{
+\itemize{
+  \item \code{url} the url the request was actually sent to
+    (after redirects)
+  \item \code{handle} the handle associated with the url
+  \item \code{status_code} the http status code
+  \item \code{header} a named list of headers returned by the server
+  \item \code{cookies} a named list of cookies returned by the server
+  \item \code{content} the body of the response, as raw vector. See
+     \code{\link{content}} for various ways to access the content.
+  \item \code{time} request timing information
+  \item \code{config} configuration for the request
+}
+}
+\seealso{
+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
new file mode 100644
index 0000000..4b77388
--- /dev/null
+++ b/man/revoke_all.Rd
@@ -0,0 +1,18 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/oauth-token-utils.R
+\name{revoke_all}
+\alias{revoke_all}
+\title{Revoke all OAuth tokens in the cache.}
+\usage{
+revoke_all(cache_path = NA)
+}
+\arguments{
+\item{cache_path}{Path to cache file. Defaults to `.httr-oauth` in
+current directory.}
+}
+\description{
+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
new file mode 100644
index 0000000..95869d8
--- /dev/null
+++ b/man/safe_callback.Rd
@@ -0,0 +1,16 @@
+% 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
new file mode 100644
index 0000000..0f185cf
--- /dev/null
+++ b/man/set_config.Rd
@@ -0,0 +1,36 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/config.r
+\name{set_config}
+\alias{reset_config}
+\alias{set_config}
+\title{Set (and reset) global httr configuration.}
+\usage{
+set_config(config, override = FALSE)
+
+reset_config()
+}
+\arguments{
+\item{config}{Settings as generated by \code{\link{add_headers}},
+\code{\link{set_cookies}} or \code{\link{authenticate}}.}
+
+\item{override}{if \code{TRUE}, ignore existing settings, if \code{FALSE},
+combine new config with old.}
+}
+\value{
+invisibility, the old global config.
+}
+\description{
+Set (and reset) global httr configuration.
+}
+\examples{
+GET("http://google.com")
+set_config(verbose())
+GET("http://google.com")
+reset_config()
+GET("http://google.com")
+}
+\seealso{
+Other ways to set configuration: \code{\link{config}},
+  \code{\link{with_config}}
+}
+
diff --git a/man/set_cookies.Rd b/man/set_cookies.Rd
new file mode 100644
index 0000000..f17b05c
--- /dev/null
+++ b/man/set_cookies.Rd
@@ -0,0 +1,32 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/cookies.r
+\name{set_cookies}
+\alias{set_cookies}
+\title{Set cookies.}
+\usage{
+set_cookies(..., .cookies = character(0))
+}
+\arguments{
+\item{...}{a named cookie values}
+
+\item{.cookies}{a named character vector}
+}
+\description{
+Set cookies.
+}
+\examples{
+set_cookies(a = 1, b = 2)
+set_cookies(.cookies = c(a = "1", b = "2"))
+
+GET("http://httpbin.org/cookies")
+GET("http://httpbin.org/cookies", set_cookies(a = 1, b = 2))
+}
+\seealso{
+\code{\link{cookies}()} to see cookies in response.
+
+Other config: \code{\link{add_headers}},
+  \code{\link{authenticate}}, \code{\link{config}},
+  \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
new file mode 100644
index 0000000..a4ebe8d
--- /dev/null
+++ b/man/sha1_hash.Rd
@@ -0,0 +1,20 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/sha1.r
+\name{sha1_hash}
+\alias{sha1_hash}
+\title{SHA1 hash}
+\usage{
+sha1_hash(key, string, method = "HMAC-SHA1")
+}
+\arguments{
+\item{key}{The key to create the hash with}
+
+\item{string}{data to securely hash}
+
+\item{method}{The method to use, either HMAC-SHA1 or RSA-SHA1}
+}
+\description{
+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
new file mode 100644
index 0000000..3c6d675
--- /dev/null
+++ b/man/sign_oauth.Rd
@@ -0,0 +1,19 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/oauth-signature.r
+\name{sign_oauth}
+\alias{sign_oauth}
+\alias{sign_oauth1.0}
+\alias{sign_oauth2.0}
+\title{Sign an OAuth request}
+\usage{
+sign_oauth1.0(app, token = NULL, token_secret = NULL, as_header = TRUE,
+  ...)
+
+sign_oauth2.0(access_token, as_header = TRUE)
+}
+\description{
+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
new file mode 100644
index 0000000..b0a68b6
--- /dev/null
+++ b/man/status_code.Rd
@@ -0,0 +1,15 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/response-status.r
+\name{status_code}
+\alias{status_code}
+\title{Extract status code from response.}
+\usage{
+status_code(x)
+}
+\arguments{
+\item{x}{A response}
+}
+\description{
+Extract status code from response.
+}
+
diff --git a/man/stop_for_status.Rd b/man/stop_for_status.Rd
new file mode 100644
index 0000000..2e44c5f
--- /dev/null
+++ b/man/stop_for_status.Rd
@@ -0,0 +1,63 @@
+% 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}
+\title{Take action on http error.}
+\usage{
+stop_for_status(x, task = NULL)
+
+warn_for_status(x, task = NULL)
+
+message_for_status(x, task = NULL)
+}
+\arguments{
+\item{x}{a response, or numeric http code (or other object with
+\code{status_code} method)}
+
+\item{task}{The text of the message: either \code{NULL} or a
+character vector. If non-\code{NULL}, the error message will finish with
+"Failed to \code{task}".}
+}
+\value{
+If request was successful, the response (invisibly). Otherwise,
+  raised a classed http error or warning, as generated by
+  \code{\link{http_condition}}
+}
+\description{
+Converts http errors to R errors or warnings - these should always
+be used whenever you're creating requests inside a function, so
+that the user knows why a request has failed.
+}
+\examples{
+x <- GET("http://httpbin.org/status/200")
+stop_for_status(x) # nothing happens
+warn_for_status(x)
+message_for_status(x)
+
+x <- GET("http://httpbin.org/status/300")
+\dontrun{stop_for_status(x)}
+warn_for_status(x)
+message_for_status(x)
+
+x <- GET("http://httpbin.org/status/404")
+\dontrun{stop_for_status(x)}
+warn_for_status(x)
+message_for_status(x)
+
+# You can provide more information with the task argumgnet
+warn_for_status(x, "download spreadsheet")
+message_for_status(x, "download spreadsheet")
+
+}
+\seealso{
+\code{\link{http_status}} and
+  \code{http://en.wikipedia.org/wiki/Http_status_codes} for more information
+  on http status codes.
+
+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
new file mode 100644
index 0000000..aeb5edc
--- /dev/null
+++ b/man/timeout.Rd
@@ -0,0 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/timeout.r
+\name{timeout}
+\alias{timeout}
+\title{Set maximum request time.}
+\usage{
+timeout(seconds)
+}
+\arguments{
+\item{seconds}{number of seconds to wait for a response until giving up.
+Can not be less than 1 ms.}
+}
+\description{
+Set maximum request time.
+}
+\examples{
+\dontrun{
+GET("http://httpbin.org/delay/3", timeout(1))
+GET("http://httpbin.org/delay/1", timeout(2))
+}
+}
+\seealso{
+Other config: \code{\link{add_headers}},
+  \code{\link{authenticate}}, \code{\link{config}},
+  \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
new file mode 100644
index 0000000..f978eac
--- /dev/null
+++ b/man/upload_file.Rd
@@ -0,0 +1,23 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/upload-file.r
+\name{upload_file}
+\alias{upload_file}
+\title{Upload a file with \code{\link{POST}} or \code{\link{PUT}}.}
+\usage{
+upload_file(path, type = NULL)
+}
+\arguments{
+\item{path}{path to file}
+
+\item{type}{mime type of path. If not supplied, will be guess by
+\code{\link[mime]{guess_type}} when needed.}
+}
+\description{
+Upload a file with \code{\link{POST}} or \code{\link{PUT}}.
+}
+\examples{
+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
new file mode 100644
index 0000000..f99f3de
--- /dev/null
+++ b/man/use_proxy.Rd
@@ -0,0 +1,32 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/proxy.r
+\name{use_proxy}
+\alias{use_proxy}
+\title{Use a proxy to connect to the internet.}
+\usage{
+use_proxy(url, port = NULL, username = NULL, password = NULL,
+  auth = "basic")
+}
+\arguments{
+\item{url, port}{location of proxy}
+
+\item{username, password}{login details for proxy, if needed}
+
+\item{auth}{type of HTTP authentication to use. Should be one of the
+following: basic, digest, digest_ie, gssnegotiate, ntlm, any.}
+}
+\description{
+Use a proxy to connect to the internet.
+}
+\examples{
+# See http://www.hidemyass.com/proxy-list for a list of public proxies
+# to test with
+# GET("http://had.co.nz", use_proxy("64.251.21.73", 8080), verbose())
+}
+\seealso{
+Other config: \code{\link{add_headers}},
+  \code{\link{authenticate}}, \code{\link{config}},
+  \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
new file mode 100644
index 0000000..5e08fcf
--- /dev/null
+++ b/man/user_agent.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/user-agent.r
+\name{user_agent}
+\alias{user_agent}
+\title{Set user agent.}
+\usage{
+user_agent(agent)
+}
+\arguments{
+\item{agent}{string giving user agent}
+}
+\description{
+Override the default RCurl user agent of \code{NULL}
+}
+\examples{
+GET("http://httpbin.org/user-agent")
+GET("http://httpbin.org/user-agent", user_agent("httr"))
+}
+\seealso{
+Other config: \code{\link{add_headers}},
+  \code{\link{authenticate}}, \code{\link{config}},
+  \code{\link{set_cookies}}, \code{\link{timeout}},
+  \code{\link{use_proxy}}, \code{\link{verbose}}
+}
+
diff --git a/man/verbose.Rd b/man/verbose.Rd
new file mode 100644
index 0000000..c93b348
--- /dev/null
+++ b/man/verbose.Rd
@@ -0,0 +1,72 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/verbose.r
+\name{verbose}
+\alias{verbose}
+\title{Give verbose output.}
+\usage{
+verbose(data_out = TRUE, data_in = FALSE, info = FALSE, ssl = FALSE)
+}
+\arguments{
+\item{data_out}{Show data sent to the server.}
+
+\item{data_in}{Show data recieved from the server.}
+
+\item{info}{Show informational text from curl. This is mainly useful
+for debugging https and auth problems, so is disabled by default.}
+
+\item{ssl}{Show even data sent/recieved over SSL connections?}
+}
+\description{
+A verbose connection provides much more information about the flow of
+information between the client and server.
+}
+\section{Prefixes}{
+
+
+\code{verbose()} uses the following prefixes to distinguish between
+different components of the http messages:
+
+\itemize{
+  \item \code{*} informative curl messages
+
+  \item \code{->} headers sent (out)
+  \item \code{>>} data sent (out)
+  \item \code{*>} ssl data sent (out)
+
+  \item \code{<-} headers received (in)
+  \item \code{<<} data received (in)
+  \item \code{<*} ssl data received (in)
+}
+}
+\examples{
+GET("http://httpbin.org", verbose())
+GET("http://httpbin.org", verbose(info = TRUE))
+
+f <- function() {
+  GET("http://httpbin.org")
+}
+with_verbose(f())
+with_verbose(f(), info = TRUE)
+
+# verbose() makes it easy to see exactly what POST requests send
+POST_verbose <- function(body, ...) {
+  POST("https://httpbin.org/post", body = body, verbose(), ...)
+  invisible()
+}
+POST_verbose(list(x = "a", y = "b"))
+POST_verbose(list(x = "a", y = "b"), encode = "form")
+POST_verbose(FALSE)
+POST_verbose(NULL)
+POST_verbose("")
+POST_verbose("xyz")
+}
+\seealso{
+\code{\link{with_verbose}()} makes it easier to use verbose mode
+ even when the requests are buried inside another function call.
+
+Other config: \code{\link{add_headers}},
+  \code{\link{authenticate}}, \code{\link{config}},
+  \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
new file mode 100644
index 0000000..d15a2cd
--- /dev/null
+++ b/man/with_config.Rd
@@ -0,0 +1,39 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/config.r
+\name{with_config}
+\alias{with_config}
+\alias{with_verbose}
+\title{Execute code with configuration set.}
+\usage{
+with_config(config = config(), expr, override = FALSE)
+
+with_verbose(expr, ...)
+}
+\arguments{
+\item{config}{Settings as generated by \code{\link{add_headers}},
+\code{\link{set_cookies}} or \code{\link{authenticate}}.}
+
+\item{expr}{code to execute under specified configuration}
+
+\item{override}{if \code{TRUE}, ignore existing settings, if \code{FALSE},
+combine new config with old.}
+
+\item{...}{Other arguments passed on to \code{\link{verbose}}}
+}
+\description{
+Execute code with configuration set.
+}
+\examples{
+with_config(verbose(), {
+  GET("http://had.co.nz")
+  GET("http://google.com")
+})
+
+# Or even easier:
+with_verbose(GET("http://google.com"))
+}
+\seealso{
+Other ways to set configuration: \code{\link{config}},
+  \code{\link{set_config}}
+}
+
diff --git a/man/write_disk.Rd b/man/write_disk.Rd
new file mode 100644
index 0000000..a7c0f2f
--- /dev/null
+++ b/man/write_disk.Rd
@@ -0,0 +1,37 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/write-function.R
+\name{write_disk}
+\alias{write_disk}
+\alias{write_memory}
+\title{Control where the response body is written.}
+\usage{
+write_disk(path, overwrite = FALSE)
+
+write_memory()
+}
+\arguments{
+\item{path}{Path to content to.}
+
+\item{overwrite}{Will only overwrite existing \code{path} if TRUE.}
+}
+\description{
+The default behaviour is to use \code{write_memory()}, which caches
+the response locally in memory. This is useful when talking to APIs as
+it avoids a round-trip to disk. If you want to save a file that's bigger
+than memory, use \code{write_disk()} to save it to a known path.
+}
+\examples{
+tmp <- tempfile()
+r1 <- GET("https://www.google.com", write_disk(tmp))
+readLines(tmp)
+
+# The default
+r2 <- GET("https://www.google.com", write_memory())
+
+# Save a very large file
+\dontrun{
+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
new file mode 100644
index 0000000..2a22395
--- /dev/null
+++ b/man/write_function.Rd
@@ -0,0 +1,18 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/write-function.R
+\name{write_function}
+\alias{write_function}
+\title{S3 object to define response writer.}
+\usage{
+write_function(subclass, ...)
+}
+\arguments{
+\item{subclass, ...}{Class name and fields. Used in class constructors.}
+
+\item{x}{A \code{write_function} object to process.}
+}
+\description{
+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
new file mode 100644
index 0000000..2377a16
--- /dev/null
+++ b/man/write_stream.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/write-function.R
+\name{write_stream}
+\alias{write_stream}
+\title{Process output in a streaming manner.}
+\usage{
+write_stream(f)
+}
+\arguments{
+\item{f}{Callback function. It should have a single argument, a raw
+vector containing the bytes recieved from the server. This will usually
+be 16k or less. The return value of the function is ignored.}
+}
+\description{
+This is the most general way of processing the response from the server -
+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",
+  write_stream(function(x) {
+   print(length(x))
+   length(x)
+ })
+)
+}
+
diff --git a/tests/testthat.R b/tests/testthat.R
new file mode 100644
index 0000000..a22b3ad
--- /dev/null
+++ b/tests/testthat.R
@@ -0,0 +1,2 @@
+library(testthat)
+test_check("httr")
diff --git a/tests/testthat/data.txt b/tests/testthat/data.txt
new file mode 100644
index 0000000..8edb37e
--- /dev/null
+++ b/tests/testthat/data.txt
@@ -0,0 +1,3 @@
+abc
+def
+ghi
diff --git a/tests/testthat/test-body.r b/tests/testthat/test-body.r
new file mode 100644
index 0000000..a8019bc
--- /dev/null
+++ b/tests/testthat/test-body.r
@@ -0,0 +1,95 @@
+context("Body")
+
+round_trip <- function(...) {
+  content(POST("http://httpbin.org/post", ...))
+}
+
+data_path <- upload_file("data.txt")
+data <- readLines("data.txt")
+
+test_that("NULL body gives empty data element", {
+  out <- round_trip(body = NULL)
+  expect_equal(out$data, "")
+})
+
+test_that("FALSE body doesn't send body", {
+  out <- round_trip(body = FALSE)
+  expect_equal(out$data, NULL)
+})
+
+test_that("string/raw in body gives same string in data element", {
+  out <- round_trip(body = "test")
+  expect_equal(out$data, "test")
+
+  out <- round_trip(body = charToRaw("test"))
+  expect_equal(out$data, "test")
+})
+
+test_that("string/raw in body doesn't lose content type", {
+  body <- charToRaw("test")
+  content_type <- "application/awesome"
+  response <- content(POST("http://httpbin.org/post", body = body,
+                           add_headers("Content-Type" = content_type)))
+  expect_equal(response$headers$`Content-Type`, content_type)
+})
+
+test_that("empty list gives empty body", {
+  out <- round_trip(body = list(), encode = "form")
+  expect_equal(out$data, "")
+
+  out <- round_trip(body = list(), encode = "multipart")
+  expect_equal(out$data, "")
+})
+
+test_that("named list matches form results (encode = 'form')", {
+  out <- round_trip(body = list(a = 1, b = 2), encode = "form")
+  expect_equal(out$form$a, "1")
+  expect_equal(out$form$b, "2")
+})
+
+test_that("named list matches form results (encode = 'multipart')", {
+  out <- round_trip(body = list(a = 1, b = 2), encode = "multipart")
+  expect_equal(out$form$a, "1")
+  expect_equal(out$form$b, "2")
+})
+
+test_that("named list matches form results (encode = 'json')", {
+  out <- round_trip(body = list(a = 1, b = 2), encode = "json")
+  expect_equal(out$json$a[[1]], 1)
+  expect_equal(out$json$b[[1]], 2)
+})
+
+test_that("can do own encoding", {
+  out <- round_trip(body = '{"a":1,"b":2}', content_type_json(), encode = "raw")
+  expect_equal(out$json, list(a = 1, b = 2))
+})
+
+test_that("NULL elements are automatically dropped", {
+  out <- round_trip(body = list(x = 1, y = NULL), encode = "form")
+  expect_equal(out$form, list(x = "1"))
+
+  out <- round_trip(body = list(x = 1, y = NULL), encode = "multipart")
+  expect_equal(out$form, list(x = "1"))
+})
+
+test_that("file and form vals mixed give form and data elements", {
+  out <- round_trip(body = list(y = data_path, a = 1))
+  expect_equal(out$form$a, "1")
+  expect_equal(strsplit(out$files$y, "\r?\n")[[1]], data)
+})
+
+test_that("single file matches contents on disk", {
+  out <- round_trip(body = data_path)
+  expect_equal(strsplit(out$data, "\r?\n")[[1]], data)
+  expect_equal(out$headers$`Content-Type`, "text/plain")
+})
+
+test_that("explicit content type overrides defaults", {
+  out <- round_trip(
+    body = jsonlite::toJSON(list(a = 1, b = 2)),
+    content_type_json()
+  )
+
+  expect_equal(out$headers$`Content-Type`, "application/json")
+})
+
diff --git a/tests/testthat/test-config.r b/tests/testthat/test-config.r
new file mode 100644
index 0000000..1e3b540
--- /dev/null
+++ b/tests/testthat/test-config.r
@@ -0,0 +1,37 @@
+context("Config")
+
+
+test_that("basic authentication works", {
+  h <- handle("http://httpbin.org")
+  path <- "basic-auth/user/passwd"
+
+  r <- GET(path = path, handle = h)
+  expect_equal(r$status_code, 401)
+
+  r <- GET(path = path, handle = h,
+    config = authenticate("user", "passwd", "basic"))
+  expect_equal(r$status_code, 200)
+
+  # Authentication shouldn't persist
+  r <- GET(path = path, handle = h)
+  expect_equal(r$status_code, 401)
+})
+
+test_that("digest authentication works", {
+  h <- handle("http://httpbin.org")
+  path <- "digest-auth/qop/user/passwd"
+
+  r <- GET(path = path, handle = h)
+  expect_equal(r$status_code, 401)
+
+  r <- GET(path = path, handle = h,
+    config = authenticate("user", "passwd", "digest"))
+  expect_equal(r$status_code, 200)
+})
+
+test_that("timeout enforced", {
+  skip_on_cran()
+  expect_error(GET("http://httpbin.org/delay/1", timeout(0.5)),
+    "Timeout was reached")
+})
+
diff --git a/tests/testthat/test-content.R b/tests/testthat/test-content.R
new file mode 100644
index 0000000..0c3b3f8
--- /dev/null
+++ b/tests/testthat/test-content.R
@@ -0,0 +1,24 @@
+context("Content")
+
+test_that("parse content is null if no body", {
+  out <- content(HEAD("http://httpbin.org/headers"))
+  expect_equal(out, NULL)
+})
+
+
+# has_content -------------------------------------------------------------
+
+test_that("POST result with empty body doesn't have content", {
+  r <- POST("http://httpbin.org/post", body = FALSE)
+  expect_false(has_content(r))
+})
+
+test_that("HEAD requests don't have body", {
+  r <- HEAD("http://httpbin.org/headers")
+  expect_false(has_content(r))
+})
+
+test_that("regular POST does have content", {
+  r <- POST("http://httpbin.org/post")
+  expect_true(has_content(r))
+})
diff --git a/tests/testthat/test-encoding.R b/tests/testthat/test-encoding.R
new file mode 100644
index 0000000..717ba2a
--- /dev/null
+++ b/tests/testthat/test-encoding.R
@@ -0,0 +1,15 @@
+context("Encoding")
+
+test_that("default encoding is UTF-8", {
+  expect_message(x <- guess_encoding(), "defaulting to UTF-8")
+  expect_equal(x, "UTF-8")
+})
+
+test_that("invalid encoding gets UTF-8", {
+  expect_message(x <- guess_encoding("abcd"), "defaulting to UTF-8")
+  expect_equal(x, "UTF-8")
+})
+
+test_that("encoding guessed from type", {
+  expect_equal(guess_encoding(type = "text/plain; charset=latin1"), "latin1")
+})
diff --git a/tests/testthat/test-header.r b/tests/testthat/test-header.r
new file mode 100644
index 0000000..9ce6adb
--- /dev/null
+++ b/tests/testthat/test-header.r
@@ -0,0 +1,57 @@
+context("Headers")
+
+# Setting ---------------------------------------------------------------------
+
+test_that("Only last duplicated header kept in add_headers", {
+  expect_equal(add_headers(x = 1, x = 2)$headers, c(x = "2"))
+})
+
+test_that("Only last duplicated header kept when combined", {
+  out <- c(add_headers(x = 1), add_headers(x = 2))
+  expect_equal(out$headers, c(x = "2"))
+})
+
+# Getting ---------------------------------------------------------------------
+
+test_that("All headers captures headers from redirects", {
+  r1 <- GET("http://httpbin.org/redirect/1")
+  expect_equal(length(r1$all_headers), 1 + 1)
+
+  r3 <- GET("http://httpbin.org/redirect/3")
+  expect_equal(length(r3$all_headers), 3 + 1)
+})
+
+# Parsing ---------------------------------------------------------------------
+
+test_that("Trailing line breaks removed", {
+  header <- charToRaw("HTTP/1.1 200 OK\r\nA: B\r\n")
+  expect_equal(parse_headers(header)[[1]]$headers$A, "B")
+})
+
+test_that("Invalid header raises error", {
+  lines <- c(
+    "HTTP/1.1 200 OK",
+    "A: B",
+    "Invalid"
+  )
+  header <- charToRaw(paste(lines, collapse = "\n"))
+  expect_warning(parse_headers(header), "Failed to parse headers")
+})
+
+test_that("http status line only needs two components", {
+  headers <- parse_headers(charToRaw("HTTP/1.1 200"))
+  expect_equal(headers[[1]]$status, 200L)
+
+})
+
+test_that("Key/value parsing tolerates multiple ':'", {
+  lines <- c(
+    "HTTP/1.1 200 OK",
+    "A: B:C",
+    "D:E:F"
+  )
+  header <- charToRaw(paste(lines, collapse = "\n"))
+
+  expect_equal(parse_headers(header)[[1]]$headers$A, "B:C")
+  expect_equal(parse_headers(header)[[1]]$headers$D, "E:F")
+})
diff --git a/tests/testthat/test-http-condition.R b/tests/testthat/test-http-condition.R
new file mode 100644
index 0000000..cace1c4
--- /dev/null
+++ b/tests/testthat/test-http-condition.R
@@ -0,0 +1,15 @@
+context("http_condition")
+
+test_that("non failures passed through as is", {
+  expect_equal(stop_for_status(200), 200)
+})
+
+test_that("status converted to errors", {
+  expect_error(stop_for_status(300), "Multiple Choices (HTTP 300)", fixed = TRUE)
+  expect_error(stop_for_status(404), "Not Found (HTTP 404)", fixed = TRUE)
+  expect_error(stop_for_status(500), "Internal Server Error (HTTP 500)", fixed = TRUE)
+})
+
+test_that("task adds informative message", {
+  expect_error(stop_for_status(300, "download"), "Failed to download.")
+})
diff --git a/tests/testthat/test-http-error.R b/tests/testthat/test-http-error.R
new file mode 100644
index 0000000..e33397d
--- /dev/null
+++ b/tests/testthat/test-http-error.R
@@ -0,0 +1,19 @@
+context("http_error")
+
+test_that("http_error works with urls", {
+  expect_false(http_error("http://httpbin.org/status/200"))
+  expect_true(http_error("http://httpbin.org/status/404"))
+})
+
+test_that("http_error works with responses", {
+  r200 <- GET("http://httpbin.org/status/200")
+  expect_false(http_error(r200))
+
+  r404 <- GET("http://httpbin.org/status/404")
+  expect_true(http_error(r404))
+})
+
+test_that("http_error works with integers", {
+  expect_false(http_error(200L))
+  expect_true(http_error(404L))
+})
diff --git a/tests/testthat/test-oauth-cache.R b/tests/testthat/test-oauth-cache.R
new file mode 100644
index 0000000..03df708
--- /dev/null
+++ b/tests/testthat/test-oauth-cache.R
@@ -0,0 +1,41 @@
+context("OAuth cache")
+
+test_that("use_cache() returns NULL or filepath", {
+  expect_equal(use_cache(FALSE), NULL)
+  expect_equal(use_cache(TRUE), ".httr-oauth")
+  expect_equal(use_cache("xyz"), "xyz")
+})
+
+test_that("use_cache() respects options", {
+  old <- options()
+  on.exit(options(old))
+
+  options(httr_oauth_cache = FALSE)
+  expect_equal(use_cache(), NULL)
+
+  options(httr_oauth_cache = TRUE)
+  expect_equal(use_cache(), ".httr-oauth")
+})
+
+test_that("token saved to and restored from cache", {
+  on.exit(unlink(".tmp-cache"))
+
+  token_a <- Token2.0$new(
+    app = oauth_app("x", "y", "z"),
+    endpoint = oauth_endpoints("google"),
+    cache_path = ".tmp-cache",
+    credentials = list(a = 1)
+  )
+  token_a$cache()
+
+  token_b <- Token2.0$new(
+    app = oauth_app("x", "y", "z"),
+    endpoint = oauth_endpoints("google"),
+    cache_path = ".tmp-cache"
+  )
+
+  expect_true(token_b$load_from_cache())
+  expect_equal(token_b$app, token_a$app)
+  expect_equal(token_b$endpoint, token_a$endpoint)
+  expect_equal(token_b$credentials, token_a$credentials)
+})
diff --git a/tests/testthat/test-oauth-server-side.R b/tests/testthat/test-oauth-server-side.R
new file mode 100644
index 0000000..a6adf9d
--- /dev/null
+++ b/tests/testthat/test-oauth-server-side.R
@@ -0,0 +1,23 @@
+context("OAuth: server side")
+
+# Reference values from
+# https://developers.google.com/accounts/docs/OAuth2ServiceAccount
+
+test_that("reference header yields expected base64", {
+  expect_equal(jwt_base64(jwt_header()), "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9")
+})
+
+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
+  )
+  expected <- "eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU1NDM4NSwiaWF0IjoxMzI4NTUwNzg1fQ"
+
+  expect_equal(jwt_base64(cs), expected)
+})
+
+
diff --git a/tests/testthat/test-oauth.R b/tests/testthat/test-oauth.R
new file mode 100644
index 0000000..137cd14
--- /dev/null
+++ b/tests/testthat/test-oauth.R
@@ -0,0 +1,46 @@
+context("OAuth")
+
+test_that("oauth2.0 signing works", {
+  request_url <- "http://httpbin.org/headers"
+
+  token <- Token2.0$new(
+    app = oauth_app("x", "y", "z"),
+    endpoint = oauth_endpoints("google"),
+    credentials = list(access_token = "ofNoArms")
+  )
+
+  token$params$as_header <- TRUE
+  header_response <- GET(request_url, config(token = token))
+  response_content <- content(header_response)$headers
+  expect_equal("Bearer ofNoArms", response_content$Authorization)
+  expect_equal(request_url, header_response$url)
+
+  token$params$as_header <- FALSE
+  url_response <- GET(request_url, config(token = token))
+  response_content <- content(url_response)$headers
+  expect_equal(NULL, response_content$Authorization)
+  expect_equal(
+    parse_url(url_response$url)$query,
+    list(access_token = "ofNoArms")
+  )
+})
+
+test_that("partial OAuth1 flow works", {
+  skip_on_cran()
+  # From rfigshare
+
+  endpoint <- oauth_endpoint(
+    base_url = "http://api.figshare.com/v1/pbl/oauth",
+    "request_token", "authorize", "access_token"
+  )
+  myapp <- oauth_app("rfigshare",
+    key = "Kazwg91wCdBB9ggypFVVJg",
+    secret = "izgO06p1ymfgZTsdsZQbcA")
+  sig <- sign_oauth1.0(myapp,
+    token = "xdBjcKOiunwjiovwkfTF2QjGhROeLMw0y0nSCSgvg3YQxdBjcKOiunwjiovwkfTF2Q",
+    token_secret = "4mdM3pfekNGO16X4hsvZdg")
+
+  r <- GET("http://api.figshare.com/v1/my_data/articles", sig)
+  expect_equal(status_code(r), 200)
+})
+
diff --git a/tests/testthat/test-parse_media.R b/tests/testthat/test-parse_media.R
new file mode 100644
index 0000000..07518a5
--- /dev/null
+++ b/tests/testthat/test-parse_media.R
@@ -0,0 +1,17 @@
+context("parse_media")
+
+test_that("can parse bare media type", {
+  out <- parse_media("text/plain")
+
+  expect_equal(out$type, "text")
+  expect_equal(out$subtype, "plain")
+  expect_equal(out$params, list())
+})
+
+test_that("can parse params", {
+  out1 <- parse_media("text/plain; a=1")
+  out2 <- parse_media("text/plain; a=1; b=2")
+
+  expect_equal(out1$params, list(a = "1"))
+  expect_equal(out2$params, list(a = "1", b = "2"))
+})
diff --git a/tests/testthat/test-request.r b/tests/testthat/test-request.r
new file mode 100644
index 0000000..3146c82
--- /dev/null
+++ b/tests/testthat/test-request.r
@@ -0,0 +1,11 @@
+context("Request")
+
+test_that("c.request overwrites repeated options", {
+  expect_equal(c(request(url = "a"), request(url = "b")) ,
+    request(url = "b"))
+})
+
+test_that("c.request merges headers", {
+  expect_equal(c(request(headers = c("a" = "a")), request(headers = c("b" = "b"))),
+    request(headers = c("a" = "a", "b" = "b")))
+})
diff --git a/tests/testthat/test-response.r b/tests/testthat/test-response.r
new file mode 100644
index 0000000..ec9a71d
--- /dev/null
+++ b/tests/testthat/test-response.r
@@ -0,0 +1,57 @@
+context("Response")
+
+
+test_that("status codes returned as expected", {
+
+  expect_equal(GET("http://httpbin.org/status/320")$status_code, 320)
+  expect_equal(GET("http://httpbin.org/status/404")$status_code, 404)
+  expect_equal(GET("http://httpbin.org/status/418")$status_code, 418)
+
+})
+
+test_that("DELETE deletes", {
+  expect_equal(GET("http://httpbin.org/delete")$status_code, 405)
+  expect_equal(DELETE("http://httpbin.org/delete")$status_code, 200)
+})
+
+test_that("POST posts", {
+  expect_equal(GET("http://httpbin.org/post")$status_code, 405)
+  expect_equal(POST("http://httpbin.org/post")$status_code, 200)
+})
+
+test_that("PATCH patches", {
+  expect_equal(GET("http://httpbin.org/patch")$status_code, 405)
+  expect_equal(PATCH("http://httpbin.org/patch")$status_code, 200)
+})
+
+test_that("PUT puts", {
+  expect_equal(GET("http://httpbin.org/put")$status_code, 405)
+  expect_equal(PUT("http://httpbin.org/put")$status_code, 200)
+})
+
+test_that("headers returned as expected", {
+  round_trip <- function(...) {
+    req <- GET("http://httpbin.org/headers", add_headers(...))
+    headers <- content(req)$headers
+    names(headers) <- tolower(names(headers))
+    headers
+  }
+
+  expect_equal(round_trip(a = 1)$a, "1")
+  expect_equal(round_trip(a = "a + b")$a, "a + b")
+
+})
+
+test_that("application/json responses parsed as lists", {
+  test_user_agent <- function(user_agent = NULL) {
+    response <- GET("http://httpbin.org/user-agent",
+                    add_headers("user-agent" = user_agent))
+
+    expect_equal(response$status_code, 200)
+    expected_reply = list("user-agent" = user_agent %||% default_ua())
+    expect_equal(expected_reply, content(response))
+  }
+
+  test_user_agent()
+  test_user_agent("gunicorn-client/0.1")
+})
diff --git a/tests/testthat/test-ssl.R b/tests/testthat/test-ssl.R
new file mode 100644
index 0000000..bf279dd
--- /dev/null
+++ b/tests/testthat/test-ssl.R
@@ -0,0 +1,6 @@
+context("SSL")
+
+test_that("SSL certificates verified as expected", {
+  expect_equal(status_code(GET("https://google.com")), 200L)
+  expect_equal(status_code(GET("https://firefox.com")), 200L)
+})
diff --git a/tests/testthat/test-url.r b/tests/testthat/test-url.r
new file mode 100644
index 0000000..d5d2fa5
--- /dev/null
+++ b/tests/testthat/test-url.r
@@ -0,0 +1,93 @@
+context("URL parsing and building")
+
+test_that("parse_url works as expected", {
+
+  urls <- list(
+    "http://google.com/",
+    "http://google.com/path",
+    "http://google.com/path?a=1&b=2",
+    "http://google.com/path;param?a=1&b=2",
+    "http://google.com:80/path;param?a=1&b=2",
+    "http://google.com:80/path;param?a=1&b=2#frag",
+    "http://user@google.com:80/path;param?a=1&b=2",
+    "http://user:pass@google.com:80/path;param?a=1&b=2",
+    "svn+ssh://my.svn.server/repo/trunk"
+  )
+
+  expect_equal(lapply(urls, function(u) build_url(parse_url(u))),
+               urls)
+
+})
+
+
+test_that("empty queries not converted to NA", {
+  expect_equal(parse_url("http://x.com/?q=")$query, list(q = ""))
+  expect_equal(parse_url("http://x.com/?q")$query, list(q = ""))
+
+  expect_equal(parse_url("http://x.com/?a&q")$query, list(a = "", q = ""))
+})
+
+test_that("query strings escaped and unescaped correctly", {
+  url <- "http://x.com/?x%20y=a%20b"
+  parsed <- parse_url(url)
+  expect_equal(parsed$query, list("x y" = "a b"))
+  expect_equal(build_url(parsed), url)
+})
+
+test_that("password and no username is an error", {
+  url <- "http://www.example.com/"
+  parsed <- parse_url(url)
+  expect_equal(build_url(parsed), url)
+  parsed$password <- "secret"
+  expect_error(build_url(parsed), "password without username")
+})
+
+
+test_that("handle_url modifies url with named components", {
+  hu <- handle_url(NULL, "http://google.com")
+  expect_equal(hu$url, "http://google.com")
+
+  hu <- handle_url(NULL, "http://google.com", path = "abc")
+  expect_equal(hu$url, "http://google.com/abc")
+})
+
+test_that("handle_url ignores unnamed arguments", {
+  hu <- handle_url(NULL, "http://google.com", 1, 2, 3)
+  expect_equal(hu$url, "http://google.com")
+})
+
+test_that("build_url collapse path", {
+  url <- modify_url("http://google.com", path = c("one", "two"))
+  expect_equal(url, "http://google.com/one/two")
+})
+
+test_that("build_url drops leading / in path", {
+  url <- modify_url("http://google.com", path = "/one")
+  expect_equal(url, "http://google.com/one")
+})
+
+test_that("build_url drops null query", {
+  url <- modify_url("http://google.com", query = list(a = 1, b = NULL))
+  expect_equal(url, "http://google.com/?a=1")
+
+})
+
+test_that("parse_url pulls off domain correctly given query without trailing '/'", {
+   url <- modify_url('http://google.com?a=1', query = list(b = 2))
+   expect_equal(url, "http://google.com/?a=1&b=2")
+})
+
+test_that("parse_url preserves leading / in path", {
+  url <- parse_url("file:///tmp/foobar")
+  expect_equal(url$path, "/tmp/foobar")
+})
+
+# compose_query -----------------------------------------------------------
+
+test_that("I() prevents escaping", {
+  expect_equal(compose_query(list(x = I("&"))), "x=&")
+})
+
+test_that("null elements are dropped", {
+  expect_equal(compose_query(list(x = 1, y = NULL)), "x=1")
+})
diff --git a/vignettes/api-packages.Rmd b/vignettes/api-packages.Rmd
new file mode 100644
index 0000000..c44033c
--- /dev/null
+++ b/vignettes/api-packages.Rmd
@@ -0,0 +1,487 @@
+---
+title: "Best practices for writing an API package"
+author: "Hadley Wickham"
+date: "`r Sys.Date()`"
+output: rmarkdown::html_vignette
+vignette: >
+  %\VignetteIndexEntry{Best practices for writing an API package}
+  %\VignetteEngine{knitr::rmarkdown}
+  %\VignetteEncoding{UTF-8}
+---
+
+```{r setup, include = FALSE}
+library(httr)
+knitr::opts_chunk$set(comment = "#>", collapse = TRUE)
+```
+
+So you want to write an R client for a web API? This document walks through the key issues involved in writing API wrappers in R. If you're new to working with web APIs, you may want to start by reading "[An introduction to APIs](https://zapier.com/learn/apis)" by zapier.
+
+## Overall design
+
+APIs vary widely. Before starting to code, it is important to understand how the API you are working with handles important issues so that you can implement a complete and coherent R client for the API. 
+
+The key features of any API are the structure of the requests and the structure of the responses. An HTTP request consists of the following parts:
+
+1. HTTP verb (`GET`, `POST`, `DELETE`, etc.)
+1. The base URL for the API
+1. The URL path or endpoint
+1. URL query arguments (e.g., `?foo=bar`)
+1. Optional headers
+1. An optional request body
+
+An API package needs to be able to generate these components in order to perform the desired API call, which will typically involve some sort of authentication. 
+
+For example, to request that the GitHub API provides a list of all issues for the httr repo, we send an HTTP request that looks like:
+
+```
+-> GET /repos/hadley/httr HTTP/1.1
+-> Host: api.github.com
+-> Accept: application/vnd.github.v3+json
+```
+
+Here we're using a `GET` request to the host `api.github.com`. The url is `/repos/hadley/httr`, and we send an accept header that tells GitHub what sort of data we want.
+
+In response to this request, the API will return an HTTP response that includes:
+
+1. An HTTP status code.
+1. Headers, key-value pairs.
+1. A body typically consisting of XML, JSON, plain text, HTML, 
+   or some kind of binary representation. 
+   
+An API client needs to parse these responses, turning API errors into R errors, and return a useful object to the end user. For the previous HTTP request, GitHub returns:
+
+```
+<- HTTP/1.1 200 OK
+<- Server: GitHub.com
+<- Content-Type: application/json; charset=utf-8
+<- X-RateLimit-Limit: 5000
+<- X-RateLimit-Remaining: 4998
+<- X-RateLimit-Reset: 1459554901
+<- 
+<- {
+<-   "id": 2756403,
+<-   "name": "httr",
+<-   "full_name": "hadley/httr",
+<-   "owner": {
+<-     "login": "hadley",
+<-     "id": 4196,
+<-     "avatar_url": "https://avatars.githubusercontent.com/u/4196?v=3",
+<-     ...
+<-   },
+<-   "private": false,
+<-   "html_url": "https://github.com/hadley/httr",
+<-   "description": "httr: a friendly http package for R",
+<-   "fork": false,
+<-   "url": "https://api.github.com/repos/hadley/httr",
+<-   ...
+<-   "network_count": 1368,
+<-   "subscribers_count": 64
+<- }
+```
+
+Designing a good API client requires identifying how each of these API features is used to compose a request and what type of response is expected for each. It's best practice to insulate the end user from *how* the API works so they only need to understand how to use an R function, not the details of how APIs work. It's your job to suffer so that others don't have to!
+
+## First steps
+
+### Send a simple request
+
+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 `repos/hadley/httr`:
+
+```{R}
+library(httr)
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  GET(url)
+}
+
+resp <- github_api("/repos/hadley/httr")
+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. 
+
+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:
+
+```{r}
+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. 
+
+If you have a choice, choose json: it's usually much easier to work with than xml.
+
+Most APIs will return most or all useful information in the response body, which can be accessed using `content()`. To determine what type of information is returned, you can use `http_type()`
+
+```{r}
+http_type(resp)
+```
+
+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:
+
+```{r}
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  
+  resp <- GET(url)
+  if (http_type(resp) != "application/json") {
+    stop("API did not return json", call. = FALSE)
+  }
+  
+  resp
+}
+```
+
+NB: some poorly written APIs will say the content is type A, but it will actually be type B. In this case you should complain to the API authors, and until they fix the problem, simply drop the check for content type.
+
+Next we need to parse the output into an R object. httr provides some default parsers with `content(..., as = "auto")` but I don't recommend using them inside a package. Instead it's better to explicitly parse it yourself:
+
+1. To parse json, use `jsonlite` package.
+1. To parse xml, use the `xml2` package. 
+
+```{r}
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  
+  resp <- GET(url)
+  if (http_type(resp) != "application/json") {
+    stop("API did not return json", call. = FALSE)
+  }
+  
+  jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE)
+}
+```
+
+### Return a helpful object
+
+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.
+
+```{r}
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  
+  resp <- GET(url)
+  if (http_type(resp) != "application/json") {
+    stop("API did not return json", call. = FALSE)
+  }
+  
+  parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE)
+  
+  structure(
+    list(
+      content = parsed,
+      path = path,
+      response = resp
+    ),
+    class = "github_api"
+  )
+}
+
+print.github_api <- function(x, ...) {
+  cat("<GitHub ", x$path, ">\n", sep = "")
+  str(x$content)
+  invisible(x)
+}
+
+github_api("/users/hadley")
+```
+
+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.
+
+### 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:
+
+- Client-side exceptions
+- Network / communication exceptions
+- Server-side exceptions
+
+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!).
+
+```{r, error = TRUE}
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  
+  resp <- GET(url)
+  if (http_type(resp) != "application/json") {
+    stop("API did not return json", call. = FALSE)
+  }
+  
+  parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE)
+  
+  if (http_error(resp)) {
+    stop(
+      sprintf(
+        "GitHub API request failed [%s]\n%s\n<%s>", 
+        status_code(resp),
+        parsed$message,
+        parsed$documentation_url
+      ),
+      call. = FALSE
+    )
+  }
+  
+  structure(
+    list(
+      content = parsed,
+      path = path,
+      response = resp
+    ),
+    class = "github_api"
+  )
+}
+github_api("/user/hadley")
+```
+
+> Some poorly written APIs will return different types of response based on 
+> whether or not the request succeeded or failed. If your API does this you'll 
+> need to make your request function check the `status_code()` before parsing 
+> the response.
+
+For many APIs, the common approach is to retry API calls that return something in the 500 range. However, when doing this, it's **extremely** important to make sure to do this with some form of exponential backoff: if something's wrong on the server-side, hammering the server with retries may make things worse, and may lead to you exhausting quota (or hitting other sorts of rate limits). A common policy is to retry up to 5 times, starting at 1s, and each time doubling and adding a small  [...]
+
+### Set a user agent
+
+While we're in this function, there's one important header that you should set for every API wrapper: the user agent. The user agent is a string used to identify the client. This is most useful for the API owner as it allows them to see who is using the API. It's also useful for you if you have a contact on the inside as it often makes it easier for them to pull your requests from their logs and see what's going wrong. If you're hitting a commercial API, this also makes it easier for int [...]
+
+A good default for an R API package wrapper is to make it the URL to your GitHub repo:
+
+```{r}
+ua <- user_agent("http://github.com/hadley/httr")
+ua
+
+github_api <- function(path) {
+  url <- modify_url("https://api.github.com", path = path)
+  
+  resp <- GET(url, ua)
+  if (http_type(resp) != "application/json") {
+    stop("API did not return json", call. = FALSE)
+  }
+  
+  parsed <- jsonlite::fromJSON(content(resp, "text"), simplifyVector = FALSE)
+  
+  if (status_code(resp) != 200) {
+    stop(
+      sprintf(
+        "GitHub API request failed [%s]\n%s\n<%s>", 
+        status_code(resp),
+        parsed$message,
+        parsed$documentation_url
+      ),
+      call. = FALSE
+    )
+  }
+  
+  structure(
+    list(
+      content = parsed,
+      path = path,
+      response = resp
+    ),
+    class = "github_api"
+  )
+}
+```
+
+### Passing parameters
+
+Most APIs work by executing an HTTP method on a specified URL with some additional parameters. These parameters can be specified in a number of ways, including in the URL path, in URL query arguments, in HTTP headers, and in the request body itself.  These parameters can be controlled using httr functions:
+
+1. URL path: `modify_url()`
+2. Query arguments: The `query` argument to `GET()`, `POST()`, etc.
+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.
+
+```{r, eval = FALSE}
+# modify_url
+POST(modify_url("https://httpbin.org", path = "/post"))
+
+# query arguments
+POST("http://httpbin.org/post", query = list(foo = "bar"))
+
+# headers
+POST("http://httpbin.org/post", add_headers(foo = "bar"))
+
+# body
+## as form
+POST("http://httpbin.org/post", body = list(foo = "bar"), encode = "form")
+## as json
+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.)
+
+```{r}
+f <- function(x = c("apple", "banana", "orange")) {
+  match.arg(x)
+}
+f("a")
+```
+
+It is good practice to explicitly set default values for arguments that are not required to `NULL`. If there is a default value, it should be the first one listed in the vector of allowed arguments. 
+
+## Authentication
+
+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 
+    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"))`
+
+2.  *Basic authentication with an API key*: An alternative provided by many APIs 
+    is an API "key" or "token" which is passed as part of the request. It is 
+    better than a username/password combination because it can be 
+    regenerated independent of the username and password. 
+    
+    This API key can be specified in a number of different ways: in a URL query
+    argument, in an HTTP header such as the `Authorization` header, or in an
+    argument inside the request body.
+    
+3. *OAuth*: OAuth is a protocol for generating a user- or session-specific
+    authentication token to use in subsequent requests. (An early standard, 
+    OAuth 1.0, is not terribly common any more. See `oauth1.0_token()` for 
+    details.) The current OAuth 2.0 standard is very common in modern web apps. 
+    It involves a round trip between the client and server to establish if the 
+    API client has the authority to access the data. See `oauth2.0_token()`. 
+    It's ok to publish the app ID and app "secret" - these are not actually
+    important for security of user data. 
+
+> 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.
+
+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:
+
+```{r}
+github_pat <- function() {
+  pat <- Sys.getenv('GITHUB_PAT')
+  if (identical(pat, "")) {
+    stop("Please set env var GITHUB_PAT to your github personal access token",
+      call. = FALSE)
+  }
+
+  pat
+}
+```
+
+## Pagination (handling multi-page responses)
+
+One particularly frustrating aspect of many APIs is dealing with paginated responses. This is common in APIs that offer search functionality and have the potential to return a very large number of responses. Responses might be paginated because there is a large number of response elements or because elements are updated frequently. Often they will be sorted by an explicit or implicit argument specified in the request. 
+
+When a response is paginated, the API response will typically respond with a header or value specified in the body that contains one of the following:
+
+1. The total number of pages of responses
+2. The total number of response elements (with multiple elements per page)
+3. An indicator for whether any further elements or pages are available.
+4. A URL containing the next page
+
+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
+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.
+
+### Rate limiting
+
+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 `Sys.sleep()` that waits long enough.
+
+For example, we could implement a `rate_limit()` function that tells you how many calls against the github API are available to you.
+
+```{r}
+rate_limit <- function() {
+  github_api("/rate_limit")
+}
+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.
+
+```{r}
+rate_limit <- function() {
+  req <- github_api("/rate_limit")
+  core <- req$content$resources$core
+
+  reset <- as.POSIXct(core$reset, origin = "1970-01-01")
+  cat(core$remaining, " / ", core$limit,
+    " (Resets at ", strftime(reset, "%H:%M:%S"), ")\n", sep = "")
+}
+
+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
new file mode 100644
index 0000000..66f42fe
--- /dev/null
+++ b/vignettes/quickstart.Rmd
@@ -0,0 +1,251 @@
+<!--
+%\VignetteEngine{knitr::knitr}
+%\VignetteIndexEntry{httr quickstart guide}
+-->
+
+```{r, echo = FALSE}
+library(httr)
+knitr::opts_chunk$set(comment = "#>", collapse = TRUE)
+```
+
+# httr quickstart guide
+
+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 "[HTTP: The Protocol Every Web Developer Must Know][http-tutorial]" or "[HTTP made really easy](http://www.jmarshall.com/easy/http/)".
+
+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.
+
+## httr basics
+
+To make a request, first load httr, then call `GET()` with a url:
+
+```{r}
+library(httr)
+r <- GET("http://httpbin.org/get")
+```
+
+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.
+
+```{r}
+r
+```
+
+You can pull out important parts of the response with various helper methods, or dig directly into the object:
+
+```{r}
+status_code(r)
+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.
+
+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.
+
+## The response 
+
+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.
+
+### 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()`:
+
+```{r}
+r <- GET("http://httpbin.org/get")
+# Get an informative description:
+http_status(r)
+
+# Or just access the raw code:
+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).
+
+You can automatically throw a warning or raise an error if a request did not succeed:
+
+```{r}
+warn_for_status(r)
+stop_for_status(r)
+```
+
+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.
+
+### The body
+
+There are three ways to access the body of the request, all using `content()`:
+
+*   `content(r, "text")` accesses the body as a character vector:
+
+    ```{r}
+    r <- GET("http://httpbin.org/get")
+    content(r, "text")
+    ```
+
+    httr will automatically decode content from the server using the encoding 
+    supplied in the `content-type` HTTP header. Unfortunately you can't always 
+    trust what the server tells you, so you can override encoding if needed:
+
+    ```{r, eval = FALSE}
+    content(r, "text", encoding = "ISO-8859-1")
+    ```
+
+    If you're having problems figuring out what the correct encoding 
+    should be, try `stringi::stri_enc_detect(content(r, "raw"))`.
+
+*   For non-text requests, you can access the body of the request as a 
+    raw vector:
+
+    ```{r}
+    content(r, "raw")
+    ```
+    
+    This is exactly the sequence of bytes that the web server sent, so this is
+    the highest fidelity way of saving files to disk:
+    
+    ```{r, eval = FALSE}
+    bin <- content(r, "raw")
+    writeBin(bin, "myfile.txt")
+    ```
+
+*   httr provides a number of default parsers for common file types:
+
+    ```{r}
+    # JSON automatically parsed into named list
+    str(content(r, "parsed"))
+    ```
+    
+    See `?content` for a complete list.
+    
+    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.
+
+### The headers
+
+Access response headers with `headers()`:
+
+```{r}
+headers(r)
+```
+
+This is basically a named list, but because http headers are case insensitive, indexing this object ignores case:
+
+```{r}
+headers(r)$date
+headers(r)$DATE
+```
+
+### Cookies
+
+You can access cookies in a similar way:
+
+```{r}
+r <- GET("http://httpbin.org/cookies/set", query = list(a = 1))
+cookies(r)
+```
+
+Cookies are automatically persisted between requests to the same domain:
+
+```{r}
+r <- GET("http://httpbin.org/cookies/set", query = list(b = 1))
+cookies(r)
+```
+
+## The request
+
+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 `POST()`, `PUT()` and `PATCH()` requests.
+
+### The url query string
+
+A common way of sending simple key-value pairs to the server is the query string: e.g. `http://httpbin.org/get?key=val`. httr allows you to provide these arguments as a named list with the `query` argument. For example, if you wanted to pass `key1=value1` and `key2=value2` to `http://httpbin.org/get` you could do:
+
+```{r}
+r <- GET("http://httpbin.org/get", 
+  query = list(key1 = "value1", key2 = "value2")
+)
+content(r)$args
+```
+
+Any `NULL` elements are automatically dropped from the list, and both keys and values are escaped automatically.
+
+```{r}
+r <- GET("http://httpbin.org/get", 
+  query = list(key1 = "value 1", "key 2" = "value2", key2 = NULL))
+content(r)$args
+```
+
+### Custom headers
+
+You can add custom headers to a request with `add_headers()`:
+
+```{r}
+r <- GET("http://httpbin.org/get", add_headers(Name = "Hadley"))
+str(content(r)$headers)
+```
+
+(Note that `content(r)$header` retrieves the headers that httpbin received. `headers(r)` gives the headers that it sent back in its response.)
+
+## Cookies
+
+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 `set_cookies()`:
+
+```{r}
+r <- GET("http://httpbin.org/cookies", set_cookies("MeWant" = "cookies"))
+content(r)$cookies
+```
+
+Note that this response includes the `a` and `b` cookies that were added by the server earlier.
+
+### Request body
+
+When `POST()`ing, you can include data in the `body` of the request. httr allows you to supply this in a number of different ways. The most common way is a named list:
+
+```{r}
+r <- POST("http://httpbin.org/post", body = list(a = 1, b = 2, c = 3))
+```
+
+You can use the `encode` argument to determine how this data is sent to the server:
+
+```{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")
+```
+
+To see exactly what's being sent to the server, use `verbose()`. Unfortunately due to the way that `verbose()` works, knitr can't capture the messages, so you'll need to run these from an interactive console to see what's going on.
+
+```{r, eval = FALSE}
+POST(url, body = body, encode = "multipart", verbose()) # the default
+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()`.
+
+You can also send files off disk:
+
+```{r, eval = FALSE}
+POST(url, body = upload_file("mypath.txt"))
+POST(url, body = list(x = upload_file("mypath.txt")))
+```
+
+(`upload_file()` will guess the mime-type from the extension - using the `type` argument to override/supply yourself.)
+
+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.
+
+See `POST()` for more details on the other types of thing that you can send: no body, empty body, and character and raw vectors.
+
+##### Built with
+
+```{r}
+sessionInfo()
+```
+
+[http-tutorial]:http://code.tutsplus.com/tutorials/http-the-protocol-every-web-developer-must-know-part-1--net-31177

-- 
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