Skip to content

Rust SDK

rel-client is the typed Rust client for every public Rel RPC v1 operation. The rel CLI uses this crate rather than maintaining a separate transport or request model.

The crate lives at crates/rel-client. It has no SQLite, desktop-app lifecycle, browser implementation, or local-log dependency, making it suitable for publication and reuse by other Rust programs.

Related documents: CLI, RPC, Services, and Architecture.

From this repository, use the workspace dependency:

[dependencies]
rel-client = { path = "crates/rel-client" }

Until the crate is published to crates.io, an external project can depend on the repository:

[dependencies]
rel-client = { git = "https://github.com/gabriel/rel" }

The Rust module name is rel_client.

use rel_client::RelClient;
let client = RelClient::local();
let status = client.status()?;
println!("{}", status.data.overall_status);
# Ok::<(), rel_client::ClientError>(())

RelClient::local() connects to http://127.0.0.1:17319/v1 and honors REL_AGENT_PORT. RelClient::new(base_url) accepts an explicit RPC v1 base URL. with_request_timeout(Duration) changes the ten-second timeout used by ordinary requests. Capture and page methods derive longer deadlines from their operation timeout, wait, retry count, and retry delay.

The SDK is transport-only: it never launches Rel.app, reads Rel’s SQLite database, or tails log files. The caller is responsible for ensuring that the installed app and agent are running. The bundled CLI adds app-launch behavior for Chromium and mutation commands around this same client.

Each method maps to one public RPC route:

Rust method RPC operation
health() GET /v1/health
status() GET /v1/status
capture(&CaptureRequest) POST /v1/captures
attach_page(&PageAttachRequest) POST /v1/pages
perform_page_action(page_id, &PageActionRequest) POST /v1/pages/{page_id}/actions
list_proxies() GET /v1/proxies
get_proxy(id) GET /v1/proxies/{id}
create_proxy(&ProxyCreateRequest) POST /v1/proxies
update_proxy(id, &ProxyUpdateRequest) PATCH /v1/proxies/{id}
delete_proxy(id) DELETE /v1/proxies/{id}
rotate_proxy_session(id) POST /v1/proxies/{id}/rotate-session
list_sessions() GET /v1/sessions
get_session(id) GET /v1/sessions/{id}
create_session(&SessionCreateRequest) POST /v1/sessions
update_session(id, &SessionUpdateRequest) PATCH /v1/sessions/{id}
delete_session(id) DELETE /v1/sessions/{id}

Ordinary methods return RpcResponse<T>, preserving status, request_id, and the typed data resource. Resources include Health, StatusReport, PageOperationData, Proxy, and Session, with list/data wrapper types that match RPC v1.

capture returns a lazy CaptureStream, an iterator of validated Result<CaptureEvent, ClientError> values:

use rel_client::{Action, CaptureRequest, RelClient};
let client = RelClient::local();
let mut request = CaptureRequest::new("https://example.com");
request.output = Some("/tmp/example.html".into());
request.actions.push(Action::Wait { seconds: 0.5 });
let mut stream = client.capture(&request)?;
for event in stream.by_ref() {
let event = event?;
println!("{}", event.event);
}
if !stream.is_finished() {
return Err("capture stream ended before capture.finished".into());
}
println!("exit code: {}", stream.exit_code().unwrap_or(1));
# Ok::<(), Box<dyn std::error::Error>>(())

request_id() exposes the response request ID. is_finished() becomes true only after a valid capture.finished event has been read; exit_code() is then available. The iterator rejects malformed JSON, invalid event envelopes, request-ID mismatches, and a terminal event without an integer exit code.

The public Action enum serializes directly to the RPC v1 object shapes:

use rel_client::{Action, FuzzyLinkMatch};
let click = Action::Click {
selector: "button.more".into(),
};
let wait = Action::Wait { seconds: 0.5 };
let link = Action::ClickLink {
link: "https://example.com/more".into(),
match_rule: FuzzyLinkMatch::new(0.9),
};

There are no function-style action strings or compatibility action shapes in the SDK.

Non-nullable PATCH fields use Option<T>: None omits a field and Some(value) sets it. Nullable fields use Change<T> so callers can also send an explicit JSON null. Those fields are proxy username, password, and Oxylabs location, plus a session’s proxy_id.

use rel_client::{Change, RelClient, SessionUpdateRequest};
let request = SessionUpdateRequest {
name: Some("Research".into()),
proxy_id: Change::Clear,
..SessionUpdateRequest::default()
};
RelClient::local().update_session(12, &request)?;
# Ok::<(), rel_client::ClientError>(())
  • Option::None omits a non-nullable field; Option::Some(value) sets it.
  • Change::Unchanged omits a nullable field.
  • Change::Set(value) sets a nullable field.
  • Change::Clear sends JSON null for a nullable field.

This prevents an accidental clear when a caller intended a true partial update.

SDK failures use one ClientError type:

Variant Meaning
Transport The agent could not be reached or the HTTP exchange failed.
Protocol Content type, request ID, envelope, HTTP status, or event shape violated RPC v1.
Rpc(RpcFailure) Rel returned the standard structured RPC error envelope.
Io Reading a response or capture stream failed.
Json JSON serialization or deserialization failed.

Use ClientError::rpc_failure() to inspect an optional RpcFailure, then branch on failure.error.id. RpcError preserves http_code, message, retryable, and optional object-valued details.

The client validates Content-Type, requires X-Request-Id, and checks it against the envelope or every NDJSON event. For ordinary errors it also checks that the HTTP status equals error.http_code.

The SDK targets RPC v1 only. Removing legacy CLI syntax does not change this wire contract. Before publishing the crate, its package version and public Rust API should follow semantic versioning independently of the Rel app bundle.