diff --git a/README.md b/README.md
index ad22789..bd62949 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ axoupdater can also be used as a library within your own applications in order t
 To check for updates and notify the user:
 
 ```rust
-if AxoUpdater::new_for("axolotlsay").load_receipt()?.is_update_needed()? {
+if AxoUpdater::new_for("axolotlsay").load_receipt()?.is_update_needed_sync()? {
     eprintln!("axolotlsay is outdated; please upgrade!");
 }
 ```
@@ -25,7 +25,17 @@ if AxoUpdater::new_for("axolotlsay").load_receipt()?.is_update_needed()? {
 To automatically perform an update if the program isn't up to date:
 
 ```rust
-if AxoUpdater::new_for("axolotlsay").load_receipt()?.run()? {
+if AxoUpdater::new_for("axolotlsay").load_receipt()?.run_sync()? {
+    eprintln!("Update installed!");
+} else {
+    eprintln!("axolotlsay already up to date");
+}
+```
+
+Asynchronous versions of `is_update_needed()` and `run()` are also provided:
+
+```rust
+if AxoUpdater::new_for("axolotlsay").load_receipt()?.run().await? {
     eprintln!("Update installed!");
 } else {
     eprintln!("axolotlsay already up to date");
@@ -47,7 +57,7 @@ To build as a standalone binary, follow these steps:
 
 Licensed under either of
 
-* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0))
-* MIT license ([LICENSE-MIT](LICENSE-MIT) or [opensource.org/licenses/MIT](https://opensource.org/licenses/MIT))
+- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0))
+- MIT license ([LICENSE-MIT](LICENSE-MIT) or [opensource.org/licenses/MIT](https://opensource.org/licenses/MIT))
 
 at your option.
diff --git a/axoupdater-cli/Cargo.toml b/axoupdater-cli/Cargo.toml
index f42ed5a..570680f 100644
--- a/axoupdater-cli/Cargo.toml
+++ b/axoupdater-cli/Cargo.toml
@@ -12,7 +12,7 @@ readme = "../README.md"
 
 [dependencies]
 axocli = "0.2.0"
-axoupdater = { version = "=0.1.0", path = "../axoupdater" }
+axoupdater = { version = "=0.1.0", path = "../axoupdater", features = ["blocking"] }
 
 # errors
 miette = "7.1.0"
diff --git a/axoupdater-cli/src/bin/axoupdater/main.rs b/axoupdater-cli/src/bin/axoupdater/main.rs
index 8bd4316..94546f9 100644
--- a/axoupdater-cli/src/bin/axoupdater/main.rs
+++ b/axoupdater-cli/src/bin/axoupdater/main.rs
@@ -8,7 +8,7 @@ fn real_main(_cli: &CliApp<CliArgs>) -> Result<(), miette::Report> {
 
     if AxoUpdater::new_for_updater_executable()?
         .load_receipt()?
-        .run()?
+        .run_sync()?
     {
         eprintln!("New release installed!")
     } else {
diff --git a/axoupdater/Cargo.toml b/axoupdater/Cargo.toml
index 4268a71..0f66e0b 100644
--- a/axoupdater/Cargo.toml
+++ b/axoupdater/Cargo.toml
@@ -17,6 +17,7 @@ path = "src/lib.rs"
 [features]
 default = ["axo_releases", "github_releases"]
 axo_releases = ["gazenot", "tokio"]
+blocking = ["tokio"]
 github_releases = ["reqwest"]
 
 [dependencies]
@@ -35,7 +36,6 @@ tokio = { version = "1.36.0", features = ["full"], optional = true }
 
 # github releases
 reqwest = { version = "0.11", default-features = false, features = [
-    "blocking",
     "rustls-tls",
     "json",
 ], optional = true }
diff --git a/axoupdater/src/lib.rs b/axoupdater/src/lib.rs
index bdd298a..a726647 100644
--- a/axoupdater/src/lib.rs
+++ b/axoupdater/src/lib.rs
@@ -118,7 +118,7 @@ impl AxoUpdater {
     /// This can only be performed if the `current_version` field has been
     /// set, either by loading the install receipt or by specifying it using
     /// `set_current_version`.
-    pub fn is_update_needed(&mut self) -> AxoupdateResult<bool> {
+    pub async fn is_update_needed(&mut self) -> AxoupdateResult<bool> {
         let Some(current_version) = self.current_version.to_owned() else {
             return Err(AxoupdateError::NotConfigured {
                 missing_field: "current_version".to_owned(),
@@ -128,7 +128,7 @@ impl AxoUpdater {
         let release = match &self.latest_release {
             Some(r) => r,
             None => {
-                self.fetch_latest_release()?;
+                self.fetch_latest_release().await?;
                 self.latest_release.as_ref().unwrap()
             }
         };
@@ -136,19 +136,30 @@ impl AxoUpdater {
         Ok(current_version != release.version())
     }
 
+    /// Identical to Axoupdater::is_update_needed(), but performed synchronously.
+    pub fn is_update_needed_sync(&mut self) -> AxoupdateResult<bool> {
+        tokio::runtime::Builder::new_current_thread()
+            .worker_threads(1)
+            .max_blocking_threads(128)
+            .enable_all()
+            .build()
+            .expect("Initializing tokio runtime failed")
+            .block_on(self.is_update_needed())
+    }
+
     /// Attempts to perform an update. The return value specifies whether an
     /// update was actually performed or not; false indicates "no update was
     /// needed", while an error indicates that an update couldn't be performed
     /// due to an error.
-    pub fn run(&mut self) -> AxoupdateResult<bool> {
-        if !self.is_update_needed()? {
+    pub async fn run(&mut self) -> AxoupdateResult<bool> {
+        if !self.is_update_needed().await? {
             return Ok(false);
         }
 
         let release = match &self.latest_release {
             Some(r) => r,
             None => {
-                self.fetch_latest_release()?;
+                self.fetch_latest_release().await?;
                 self.latest_release.as_ref().unwrap()
             }
         };
@@ -184,12 +195,14 @@ impl AxoUpdater {
             installer_file.set_permissions(perms)?;
         }
 
-        let client = reqwest::blocking::Client::new();
+        let client = reqwest::Client::new();
         let download = client
             .get(&installer_url.browser_download_url)
             .header(ACCEPT, "application/octet-stream")
-            .send()?
-            .text()?;
+            .send()
+            .await?
+            .text()
+            .await?;
 
         LocalAsset::write_new_all(&download, &installer_path)?;
 
@@ -207,7 +220,18 @@ impl AxoUpdater {
         Ok(true)
     }
 
-    fn fetch_latest_release(&mut self) -> AxoupdateResult<()> {
+    /// Identical to Axoupdater::run(), but performed synchronously.
+    pub fn run_sync(&mut self) -> AxoupdateResult<bool> {
+        tokio::runtime::Builder::new_current_thread()
+            .worker_threads(1)
+            .max_blocking_threads(128)
+            .enable_all()
+            .build()
+            .expect("Initializing tokio runtime failed")
+            .block_on(self.run())
+    }
+
+    async fn fetch_latest_release(&mut self) -> AxoupdateResult<()> {
         let Some(app_name) = &self.name else {
             return Err(AxoupdateError::NotConfigured {
                 missing_field: "app_name".to_owned(),
@@ -224,7 +248,8 @@ impl AxoUpdater {
             &source.owner,
             &source.app_name,
             &source.release_type,
-        )?
+        )
+        .await?
         else {
             return Err(AxoupdateError::NoStableReleases {
                 app_name: app_name.to_owned(),
@@ -451,8 +476,12 @@ pub struct InstallReceipt {
 }
 
 #[cfg(feature = "github_releases")]
-fn get_github_releases(name: &str, owner: &str, app_name: &str) -> AxoupdateResult<Vec<Release>> {
-    let client = reqwest::blocking::Client::new();
+async fn get_github_releases(
+    name: &str,
+    owner: &str,
+    app_name: &str,
+) -> AxoupdateResult<Vec<Release>> {
+    let client = reqwest::Client::new();
     let resp: Vec<Release> = client
         .get(format!("{GITHUB_API}/repos/{owner}/{name}/releases"))
         .header(ACCEPT, "application/json")
@@ -460,8 +489,10 @@ fn get_github_releases(name: &str, owner: &str, app_name: &str) -> AxoupdateResu
             USER_AGENT,
             format!("axoupdate/{}", env!("CARGO_PKG_VERSION")),
         )
-        .send()?
-        .json()?;
+        .send()
+        .await?
+        .json()
+        .await?;
 
     Ok(resp
         .into_iter()
@@ -474,15 +505,13 @@ fn get_github_releases(name: &str, owner: &str, app_name: &str) -> AxoupdateResu
 }
 
 #[cfg(feature = "axo_releases")]
-fn get_axo_releases(name: &str, owner: &str, app_name: &str) -> AxoupdateResult<Vec<Release>> {
+async fn get_axo_releases(
+    name: &str,
+    owner: &str,
+    app_name: &str,
+) -> AxoupdateResult<Vec<Release>> {
     let abyss = Gazenot::new_unauthed("github".to_string(), owner)?;
-    let release_lists = tokio::runtime::Builder::new_current_thread()
-        .worker_threads(1)
-        .max_blocking_threads(128)
-        .enable_all()
-        .build()
-        .expect("Initializing tokio runtime failed")
-        .block_on(abyss.list_releases_many(vec![app_name.to_owned()]))?;
+    let release_lists = abyss.list_releases_many(vec![app_name.to_owned()]).await?;
     let Some(our_release) = release_lists.iter().find(|rl| rl.package_name == app_name) else {
         return Err(AxoupdateError::ReleaseNotFound {
             name: name.to_owned(),
@@ -501,7 +530,7 @@ fn get_axo_releases(name: &str, owner: &str, app_name: &str) -> AxoupdateResult<
     Ok(releases)
 }
 
-fn get_latest_stable_release(
+async fn get_latest_stable_release(
     name: &str,
     owner: &str,
     app_name: &str,
@@ -509,7 +538,7 @@ fn get_latest_stable_release(
 ) -> AxoupdateResult<Option<Release>> {
     let releases = match release_type {
         #[cfg(feature = "github_releases")]
-        ReleaseSourceType::GitHub => get_github_releases(name, owner, app_name)?,
+        ReleaseSourceType::GitHub => get_github_releases(name, owner, app_name).await?,
         #[cfg(not(feature = "github_releases"))]
         ReleaseSourceType::GitHub => {
             return Err(AxoupdateError::BackendDisabled {
@@ -517,7 +546,7 @@ fn get_latest_stable_release(
             })
         }
         #[cfg(feature = "axo_releases")]
-        ReleaseSourceType::Axo => get_axo_releases(name, owner, app_name)?,
+        ReleaseSourceType::Axo => get_axo_releases(name, owner, app_name).await?,
         #[cfg(not(feature = "axo_releases"))]
         ReleaseSourceType::Axo => {
             return Err(AxoupdateError::BackendDisabled {