Skip to content
Lenso
English
Esc
navigateopen⌘Jpreview
On this page

Call an upstream API

Give one Plugin an explicitly bound HTTP Client with exact-origin authority and bounded transport behavior.

Do not create an ambient HTTP client inside a business Plugin. Declare a requirement for lenso.http.client@1, bind it to one Egress Instance, and make the granted origins visible in the resolved App.

1. Configure exact-origin authority

An allowed origin is only scheme, host, and optional port. Paths, credentials, queries, and fragments are rejected:

{
  "allowed_origins": ["https://api.example.test"],
  "max_request_body_bytes": 1048576,
  "max_request_head_bytes": 16384,
  "max_response_body_bytes": 4194304,
  "max_response_head_bytes": 32768,
  "max_concurrent_requests": 64,
  "connect_timeout_millis": 5000,
  "request_timeout_millis": 30000
}

https://api.example.test/v1 is not a valid configured origin. The path is chosen per request after the exact origin has been admitted.

App Composition must bind the consumer’s one lenso.http.client@1 requirement to this specific Egress Instance. Linking Egress into the Host does not grant every Plugin outbound network access.

2. Resolve the generated Client once

Resolve ClientClient from the Plugin’s declared dependencies during activation and retain it on the provider. Do not rediscover a global client for each request:

use lenso_capability_http_client::{ClientClient, ClientInvocationError};
use lenso_kernel::{PluginDependencies, RuntimeFailure};

fn client(dependencies: &PluginDependencies) -> Result<ClientClient, RuntimeFailure> {
    ClientClient::from_dependencies(dependencies)
}

3. Send one bounded request

use lenso_capability_http_client::{SendRequest, SendRequestHeadersItem};

async fn create_order(client: &ClientClient) -> Result<i64, ClientInvocationError> {
    let response = client.send(SendRequest {
        method: "POST".into(),
        url: "https://api.example.test/orders".into(),
        headers: vec![SendRequestHeadersItem {
            name: "content-type".into(),
            value: "application/json".into(),
        }],
        body: br#"{"total_cents":4200}"#.as_slice().into(),
    }).await?;
    Ok(response.status)
}

The initial provider deliberately has no automatic redirects, retries, system proxy, Referer, cookie jar, or caller-controlled authority headers. Add credentials through an explicit Secrets dependency and request header; never put secret values in immutable Egress configuration.

4. Handle failure intentionally

The generated Domain Errors distinguish destination_not_allowed, invalid_request, request_too_large, response_too_large, timeout, and transport_failure. Decide at the business boundary whether an operation may retry; the transport will not repeat a request implicitly.

Test an allowed path, a different path on the same origin, a disallowed origin, oversized request and response, timeout, redirect response, and cancellation. Inspect lenso app show to prove the exact consumer-to-Egress binding before shipping.

See Web Capabilities for the complete wire and error contract.

Last updated on September 6, 2026

Was this page helpful?