diff --git a/.github/workflows/default.yml b/.github/workflows/default.yml
index 4e82e36..24abd1e 100644
--- a/.github/workflows/default.yml
+++ b/.github/workflows/default.yml
@@ -51,7 +51,7 @@ jobs:
         uses: actions-rs/cargo@v1
         with:
           command: check
-          args: --workspace --target wasm32-unknown-unknown
+          args: -p roctogen-common -p roctogen-github-update-issue-bot -p roctogen-jwt-example --target wasm32-unknown-unknown
 
       - name: Run cargo check min-req-adapter
         uses: actions-rs/cargo@v1
@@ -59,6 +59,12 @@ jobs:
           command: check
           args: -p min-req-adapter
           
+      - name: Run cargo check search
+        uses: actions-rs/cargo@v1
+        with:
+          command: check
+          args: -p search
+          
   test:
     name: Test Suite
     runs-on: ubuntu-latest
diff --git a/Cargo.toml b/Cargo.toml
index d30518b..bf5a55a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,6 +15,7 @@ edition = "2021"
 members = [
   "examples/auth/*",
   "examples/min-req-adapter",
+  "examples/search",
 ]
 exclude = ["codegen/**"]
 
diff --git a/examples/min-req-adapter/README.md b/examples/min-req-adapter/README.md
new file mode 100644
index 0000000..c8b0a4e
--- /dev/null
+++ b/examples/min-req-adapter/README.md
@@ -0,0 +1 @@
+Example of externally extending the adapter interface
diff --git a/examples/min-req-adapter/src/main.rs b/examples/min-req-adapter/src/main.rs
index 2061918..127cf12 100644
--- a/examples/min-req-adapter/src/main.rs
+++ b/examples/min-req-adapter/src/main.rs
@@ -113,39 +113,4 @@ impl GitHubResponseExt for Response {
     }
 }
 
-// impl<C: super::Client> GitHubRequestBuilder<Vec<u8>, C> for http::Request<Vec<u8>> {
-//     fn build(req: GitHubRequest<Vec<u8>>, client: &C) -> Result<Self, AdapterError> {
-//         let mut builder = http::Request::builder();
-
-//         builder = builder
-//             .uri(req.uri)
-//             .method(req.method)
-//             .header(ACCEPT, "application/vnd.github.v3+json")
-//             .header(USER_AGENT, "roctogen")
-//             .header(CONTENT_TYPE, "application/json");
-
-//         for header in req.headers.iter() {
-//             builder = builder.header(header.0, header.1);
-//         }
-
-//         builder = match client.get_auth() {
-//             Auth::Basic { user, pass } => {
-//                 let creds = format!("{}:{}", user, pass);
-//                 builder.header(
-//                     AUTHORIZATION,
-//                     format!("Basic {}", BASE64_STANDARD.encode(creds.as_bytes())),
-//                 )
-//             }
-//             Auth::Token(token) => builder.header(AUTHORIZATION, format!("token {}", token)),
-//             Auth::Bearer(bearer) => builder.header(AUTHORIZATION, format!("Bearer {}", bearer)),
-//             Auth::None => builder,
-//         };
-
-//         Ok(RequestWithBody {
-//             req: builder,
-//             body: req.body,
-//         })
-//     }
-// }
-
 fn main() {}
diff --git a/examples/search/Cargo.toml b/examples/search/Cargo.toml
new file mode 100644
index 0000000..055d92d
--- /dev/null
+++ b/examples/search/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "search"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+bytes = "1"
+chrono = "0.4"
+futures = { version = "0.3" }
+futures-util = { version = "0.3" }
+hyper = "1"
+hyper-util = { version = "0.1", features = ["http1", "client-legacy", "tokio"] }
+hyper-rustls = "0.27"
+http-body-util = "0.1"
+rustls = "0.23"
+tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
+thiserror.workspace = true
+log.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+base64.workspace = true
+roctogen = { path = "../../" }
diff --git a/examples/search/README.md b/examples/search/README.md
new file mode 100644
index 0000000..f1e4b78
--- /dev/null
+++ b/examples/search/README.md
@@ -0,0 +1 @@
+Example of handling rate-limiting and building a streaming iterator over paginated results.
diff --git a/examples/search/src/main.rs b/examples/search/src/main.rs
new file mode 100644
index 0000000..ac13a0a
--- /dev/null
+++ b/examples/search/src/main.rs
@@ -0,0 +1,259 @@
+use std::time::Duration;
+
+use base64::{prelude::BASE64_STANDARD, Engine};
+use chrono::DateTime;
+use chrono::Utc;
+use futures::FutureExt;
+use futures::StreamExt;
+use futures::TryFutureExt;
+use futures::TryStreamExt;
+use futures_util::stream;
+use http_body_util::BodyExt;
+use http_body_util::Empty;
+use http_body_util::Full;
+use hyper::body::Buf;
+use hyper::body::Bytes;
+use hyper::body::Incoming;
+use hyper::header::ToStrError;
+use hyper::header::ACCEPT;
+use hyper::header::AUTHORIZATION;
+use hyper::header::CONTENT_TYPE;
+use hyper::header::USER_AGENT;
+use hyper_rustls::ConfigBuilderExt;
+use hyper_util::client::legacy::connect::HttpConnector;
+use log::debug;
+use roctogen::adapters::GitHubRequest;
+use roctogen::adapters::GitHubResponseExt;
+use roctogen::api::search;
+use roctogen::api::PerPage;
+use roctogen::auth::Auth;
+use serde::de::Error;
+use serde::{ser, Deserialize};
+
+// Jitter adds a number of seconds to the GitHub reset header
+// to adjust timings in favour of request/response completion
+const JITTER: u64 = 2;
+
+#[derive(thiserror::Error, Debug)]
+pub enum AdapterError {
+    #[error(transparent)]
+    Hyper(#[from] hyper::Error),
+    #[error(transparent)]
+    HyperUtil(#[from] hyper_util::client::legacy::Error),
+    #[error(transparent)]
+    Http(#[from] hyper::http::Error),
+    #[error(transparent)]
+    Serde(#[from] serde_json::Error),
+    #[error(transparent)]
+    IOError(#[from] std::io::Error),
+    #[error("Ureq adapter only has sync fetch implemented")]
+    UnimplementedSync,
+    #[error(transparent)]
+    ToStrError(#[from] ToStrError),
+    #[error(transparent)]
+    ParseIntError(#[from] std::num::ParseIntError),
+}
+
+impl From<AdapterError> for roctogen::adapters::AdapterError {
+    fn from(err: AdapterError) -> Self {
+        Self::Client {
+            description: err.to_string(),
+            source: Some(Box::new(err)),
+        }
+    }
+}
+
+struct Response {
+    pub inner: hyper::Response<Incoming>,
+}
+
+impl roctogen::adapters::Client for Client {
+    type Req = hyper::Request<Full<Bytes>>;
+    type Err = AdapterError where roctogen::adapters::AdapterError: From<Self::Err>;
+    type Body = Empty<Bytes>;
+
+    fn new(auth: &Auth) -> Result<Self, Self::Err> {
+        let tls = rustls::ClientConfig::builder()
+            .with_native_roots()?
+            .with_no_client_auth();
+        let connector = hyper_rustls::HttpsConnectorBuilder::new()
+            .with_tls_config(tls)
+            .https_or_http()
+            .enable_http1()
+            .build();
+        let client_builder =
+            hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new());
+        let pool: hyper_util::client::legacy::Client<_, _> = client_builder.build(connector);
+        Ok(Self {
+            auth: auth.to_owned(),
+            pool,
+        })
+    }
+
+    fn fetch(&self, _req: Self::Req) -> Result<impl GitHubResponseExt, Self::Err> {
+        Err::<Response, _>(AdapterError::UnimplementedSync)
+    }
+
+    async fn fetch_async(&self, req: Self::Req) -> Result<impl GitHubResponseExt, Self::Err> {
+        let res = self.pool.request(req).await?;
+        if let (Some(reset), Some(remaining)) = (
+            res.headers().get("x-ratelimit-reset"),
+            res.headers().get("x-ratelimit-remaining"),
+        ) {
+            let reset = DateTime::from_timestamp(reset.to_str()?.parse()?, 0)
+                .unwrap_or(chrono::DateTime::<Utc>::MAX_UTC);
+
+            let _reset_rfc = reset.to_rfc3339();
+
+            let remaining: u64 = remaining.to_str()?.parse()?;
+
+            let time_to_reset = reset.signed_duration_since(Utc::now()).abs().num_seconds() as u64;
+            let time_to_wait = Duration::from_secs((time_to_reset + JITTER) / remaining);
+
+            println!(
+                "GitHub will reset it's rate-limiting window for this token in {} seconds, you have {} remaining requests within that window. Sleeping {} seconds to prevent rate-limiting.",
+                time_to_reset,
+                remaining,
+                time_to_wait.as_secs()
+            );
+
+            // Sleep so we don't trigger GitHub rate limiting
+            tokio::time::sleep(time_to_wait).await;
+        }
+
+        Ok(Response { inner: res })
+    }
+
+    fn build(&self, req: GitHubRequest<Self::Body>) -> Result<Self::Req, Self::Err> {
+        let mut builder = hyper::Request::builder();
+
+        builder = builder
+            .uri(req.uri)
+            .method(req.method)
+            .header(ACCEPT, "application/vnd.github.v3+json")
+            .header(USER_AGENT, "roctogen")
+            .header(CONTENT_TYPE, "application/json");
+
+        for header in req.headers.iter() {
+            builder = builder.header(header.0, header.1);
+        }
+
+        builder = match &self.auth {
+            Auth::Basic { user, pass } => {
+                let creds = format!("{}:{}", user, pass);
+                builder.header(
+                    AUTHORIZATION,
+                    format!("Basic {}", BASE64_STANDARD.encode(creds.as_bytes())),
+                )
+            }
+            Auth::Token(token) => builder.header(AUTHORIZATION, format!("token {}", token)),
+            Auth::Bearer(bearer) => builder.header(AUTHORIZATION, format!("Bearer {}", bearer)),
+            Auth::None => builder,
+        };
+
+        Ok(hyper::Request::from(builder.body(Full::new(Bytes::new()))?))
+    }
+
+    fn from_json<E: ser::Serialize>(_model: E) -> Result<Self::Body, Self::Err> {
+        Ok(Empty::new())
+    }
+}
+
+pub struct Client {
+    auth: Auth,
+    pool: hyper_util::client::legacy::Client<
+        hyper_rustls::HttpsConnector<HttpConnector>,
+        Full<Bytes>,
+    >,
+}
+
+impl GitHubResponseExt for Response {
+    fn is_success(&self) -> bool {
+        self.inner.status().is_success()
+    }
+
+    fn status_code(&self) -> u16 {
+        self.inner.status().as_u16()
+    }
+
+    fn to_json<E: for<'de> Deserialize<'de> + std::fmt::Debug>(
+        self,
+    ) -> Result<E, serde_json::Error> {
+        unimplemented!("hyper adapter only has async json conversion implemented");
+    }
+
+    async fn to_json_async<E: for<'de> Deserialize<'de> + Unpin + std::fmt::Debug>(
+        self,
+    ) -> Result<E, serde_json::Error> {
+        let body = self
+            .inner
+            .collect()
+            .await
+            .map_err(|e| serde_json::Error::custom(format!("{}", e)))?
+            .aggregate();
+
+        let json = serde_json::from_reader(body.reader())?;
+
+        debug!("Body: {:?}", json);
+
+        Ok(json)
+    }
+}
+
+#[derive(Clone, Copy, Debug)]
+struct IterableState {
+    page: u16,
+    count: i64,
+    total: i64,
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let client: Client = <Client as roctogen::adapters::Client>::new(&Auth::None).unwrap();
+    let per_page = roctogen::api::PerPage::new(100);
+    let search = roctogen::api::search::new(&client);
+    let stream = unfold(&search, per_page.as_ref());
+
+    let lst = stream.try_collect::<Vec<_>>().await?;
+
+    println!("{:?}", lst);
+
+    Ok(())
+}
+
+fn unfold<'a>(
+    search: &'a search::Search<Client>,
+    per_page: &'a PerPage,
+) -> impl stream::Stream<
+    Item = Result<roctogen::models::IssueSearchResultItem, roctogen::adapters::AdapterError>,
+> + 'a {
+    let state = IterableState {
+        page: 1,
+        count: 0,
+        total: 1,
+    };
+
+    stream::try_unfold(state, move |acc| {
+        if acc.count >= acc.total {
+            futures::future::ok(None).left_future()
+        } else {
+            let mut params: search::SearchIssuesAndPullRequestsParams = per_page.into();
+            params = params.q("org:fussybeaver");
+            params = params.page(acc.page);
+
+            println!("Requesting page {}", acc.page);
+            let fut = search.issues_and_pull_requests_async(params);
+
+            fut.map_ok(move |res| {
+                let total = res.total_count.unwrap_or(1);
+                let items = res.items.unwrap_or_else(Vec::new);
+                let count = acc.count + items.len() as i64;
+                let page = acc.page + 1;
+                Some((stream::iter(items), IterableState { page, count, total }))
+            })
+            .right_future()
+        }
+    })
+    .map_ok(|v| v.map(|v| Ok(v)))
+    .try_flatten()
+}
diff --git a/src/endpoints/apps.rs b/src/endpoints/apps.rs
index 339468b..6d08fe6 100644
--- a/src/endpoints/apps.rs
+++ b/src/endpoints/apps.rs
@@ -1813,8 +1813,6 @@ impl<'api, C: Client> Apps<'api, C> where AdapterError: From<<C as Client>::Err>
     /// 
     /// Optionally, use the `permissions` body parameter to specify the permissions that the installation access token should have. If `permissions` is not specified, the installation access token will have all of the permissions that were granted to the app. The installation access token cannot be granted permissions that the app was not granted.
     /// 
-    /// When using the repository or permission parameters to reduce the access of the token, the complexity of the token is increased due to both the number of permissions in the request and the number of repositories the token will have access to. If the complexity is too large, the token will fail to be issued. If this occurs, the error message will indicate the maximum number of repositories that should be requested. For the average application requesting 8 permissions, this limit is around 5000 repositories. With fewer permissions requested, more repositories are supported.
-    /// 
     /// You must use a [JWT](https://docs.github.com/apps/building-github-apps/authenticating-with-github-apps/#authenticating-as-a-github-app) to access this endpoint.
     ///
     /// [GitHub API docs for create_installation_access_token](https://docs.github.com/rest/apps/apps#create-an-installation-access-token-for-an-app)
@@ -1863,8 +1861,6 @@ impl<'api, C: Client> Apps<'api, C> where AdapterError: From<<C as Client>::Err>
     /// 
     /// Optionally, use the `permissions` body parameter to specify the permissions that the installation access token should have. If `permissions` is not specified, the installation access token will have all of the permissions that were granted to the app. The installation access token cannot be granted permissions that the app was not granted.
     /// 
-    /// When using the repository or permission parameters to reduce the access of the token, the complexity of the token is increased due to both the number of permissions in the request and the number of repositories the token will have access to. If the complexity is too large, the token will fail to be issued. If this occurs, the error message will indicate the maximum number of repositories that should be requested. For the average application requesting 8 permissions, this limit is around 5000 repositories. With fewer permissions requested, more repositories are supported.
-    /// 
     /// You must use a [JWT](https://docs.github.com/apps/building-github-apps/authenticating-with-github-apps/#authenticating-as-a-github-app) to access this endpoint.
     ///
     /// [GitHub API docs for create_installation_access_token](https://docs.github.com/rest/apps/apps#create-an-installation-access-token-for-an-app)
diff --git a/src/endpoints/repos.rs b/src/endpoints/repos.rs
index b14757b..d8f2182 100644
--- a/src/endpoints/repos.rs
+++ b/src/endpoints/repos.rs
@@ -17739,7 +17739,13 @@ impl<'api, C: Client> Repos<'api, C> where AdapterError: From<<C as Client>::Err
     ///
     /// # Get a release asset
     ///
-    /// To download the asset's binary content, set the `Accept` header of the request to [`application/octet-stream`](https://docs.github.com/rest/using-the-rest-api/getting-started-with-the-rest-api#media-types). The API will either redirect the client to the location, or stream it directly if possible. API clients should handle both a `200` or `302` response.
+    /// To download the asset's binary content:
+    /// 
+    /// - If within a browser, fetch the location specified in the `browser_download_url` key provided in the response.
+    /// - Alternatively, set the `Accept` header of the request to 
+    ///   [`application/octet-stream`](https://docs.github.com/rest/using-the-rest-api/getting-started-with-the-rest-api#media-types). 
+    ///   The API will either redirect the client to the location, or stream it directly if possible.
+    ///   API clients should handle both a `200` or `302` response.
     ///
     /// [GitHub API docs for get_release_asset](https://docs.github.com/rest/releases/assets#get-a-release-asset)
     ///
@@ -17779,7 +17785,13 @@ impl<'api, C: Client> Repos<'api, C> where AdapterError: From<<C as Client>::Err
     ///
     /// # Get a release asset
     ///
-    /// To download the asset's binary content, set the `Accept` header of the request to [`application/octet-stream`](https://docs.github.com/rest/using-the-rest-api/getting-started-with-the-rest-api#media-types). The API will either redirect the client to the location, or stream it directly if possible. API clients should handle both a `200` or `302` response.
+    /// To download the asset's binary content:
+    /// 
+    /// - If within a browser, fetch the location specified in the `browser_download_url` key provided in the response.
+    /// - Alternatively, set the `Accept` header of the request to 
+    ///   [`application/octet-stream`](https://docs.github.com/rest/using-the-rest-api/getting-started-with-the-rest-api#media-types). 
+    ///   The API will either redirect the client to the location, or stream it directly if possible.
+    ///   API clients should handle both a `200` or `302` response.
     ///
     /// [GitHub API docs for get_release_asset](https://docs.github.com/rest/releases/assets#get-a-release-asset)
     ///
diff --git a/src/models.rs b/src/models.rs
index 5c9c07d..90719ac 100644
--- a/src/models.rs
+++ b/src/models.rs
@@ -9280,6 +9280,9 @@ pub struct CustomProperty {
     /// The URL that can be used to fetch, update, or delete info about this property via the API.
     #[serde(skip_serializing_if="Option::is_none")]
     pub url: Option<String>,
+    /// The source type of the property
+    #[serde(skip_serializing_if="Option::is_none")]
+    pub source_type: Option<String>,
     /// The type of the value for the property
     #[serde(skip_serializing_if="Option::is_none")]
     pub value_type: Option<String>,
@@ -18251,6 +18254,12 @@ pub struct OrganizationRole {
     /// A short description about who this role is for or what permissions it grants.
     #[serde(skip_serializing_if="Option::is_none")]
     pub description: Option<String>,
+    /// The system role from which this role inherits permissions.
+    #[serde(skip_serializing_if="Option::is_none")]
+    pub base_role: Option<String>,
+    /// Source answers the question, \"where did this role come from?\"
+    #[serde(skip_serializing_if="Option::is_none")]
+    pub source: Option<String>,
     /// A list of permissions included in this role.
     #[serde(skip_serializing_if="Option::is_none")]
     pub permissions: Option<Vec<String>>,
@@ -18307,6 +18316,14 @@ pub struct OrganizationSecretScanningAlert {
     /// The time that push protection was bypassed in ISO 8601 format: `YYYY-MM-DDTHH:MM:SSZ`.
     #[serde(skip_serializing_if="Option::is_none")]
     pub push_protection_bypassed_at: Option<chrono::DateTime<chrono::Utc>>,
+    #[serde(skip_serializing_if="Option::is_none")]
+    pub push_protection_bypass_request_reviewer: Option<NullableSimpleUser>,
+    /// An optional comment when requesting a push protection bypass.
+    #[serde(skip_serializing_if="Option::is_none")]
+    pub push_protection_bypass_request_comment: Option<String>,
+    /// The URL to a push protection bypass request.
+    #[serde(skip_serializing_if="Option::is_none")]
+    pub push_protection_bypass_request_html_url: Option<String>,
     /// The comment that was optionally added when this alert was closed
     #[serde(skip_serializing_if="Option::is_none")]
     pub resolution_comment: Option<String>,
@@ -27785,6 +27802,14 @@ pub struct SecretScanningAlert {
     /// The time that push protection was bypassed in ISO 8601 format: `YYYY-MM-DDTHH:MM:SSZ`.
     #[serde(skip_serializing_if="Option::is_none")]
     pub push_protection_bypassed_at: Option<chrono::DateTime<chrono::Utc>>,
+    #[serde(skip_serializing_if="Option::is_none")]
+    pub push_protection_bypass_request_reviewer: Option<NullableSimpleUser>,
+    /// An optional comment when requesting a push protection bypass.
+    #[serde(skip_serializing_if="Option::is_none")]
+    pub push_protection_bypass_request_comment: Option<String>,
+    /// The URL to a push protection bypass request.
+    #[serde(skip_serializing_if="Option::is_none")]
+    pub push_protection_bypass_request_html_url: Option<String>,
     /// The token status as of the latest validity check.
     #[serde(skip_serializing_if="Option::is_none")]
     pub validity: Option<String>,
@@ -27969,6 +27994,14 @@ pub struct SecretScanningAlertWebhook {
     /// The time that push protection was bypassed in ISO 8601 format: `YYYY-MM-DDTHH:MM:SSZ`.
     #[serde(skip_serializing_if="Option::is_none")]
     pub push_protection_bypassed_at: Option<chrono::DateTime<chrono::Utc>>,
+    #[serde(skip_serializing_if="Option::is_none")]
+    pub push_protection_bypass_request_reviewer: Option<NullableSimpleUser>,
+    /// An optional comment when requesting a push protection bypass.
+    #[serde(skip_serializing_if="Option::is_none")]
+    pub push_protection_bypass_request_comment: Option<String>,
+    /// The URL to a push protection bypass request.
+    #[serde(skip_serializing_if="Option::is_none")]
+    pub push_protection_bypass_request_html_url: Option<String>,
     /// Whether the detected secret was publicly leaked.
     #[serde(skip_serializing_if="Option::is_none")]
     pub publicly_leaked: Option<bool>,
@@ -30162,6 +30195,9 @@ pub struct TeamRepository {
 /// The Relationship a Team has with a role.
 #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
 pub struct TeamRoleAssignment {
+    /// Determines if the team has a direct, indirect, or mixed relationship to a role
+    #[serde(skip_serializing_if="Option::is_none")]
+    pub assignment: Option<String>,
     #[serde(skip_serializing_if="Option::is_none")]
     pub id: Option<i64>,
     #[serde(skip_serializing_if="Option::is_none")]
@@ -31256,6 +31292,12 @@ pub struct UserMarketplacePurchase {
 /// The Relationship a User has with a role.
 #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
 pub struct UserRoleAssignment {
+    /// Determines if the user has a direct, indirect, or mixed relationship to a role
+    #[serde(skip_serializing_if="Option::is_none")]
+    pub assignment: Option<String>,
+    /// Team the user has gotten the role through
+    #[serde(skip_serializing_if="Option::is_none")]
+    pub inherited_from: Option<Vec<TeamSimple>>,
     #[serde(skip_serializing_if="Option::is_none")]
     pub name: Option<String>,
     #[serde(skip_serializing_if="Option::is_none")]