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.
Add the crate
Section titled “Add the crate”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.
Connect
Section titled “Connect”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.
API parity
Section titled “API parity”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 streaming
Section titled “Capture streaming”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.
Canonical actions
Section titled “Canonical actions”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.
Partial updates
Section titled “Partial updates”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::Noneomits a non-nullable field;Option::Some(value)sets it.Change::Unchangedomits a nullable field.Change::Set(value)sets a nullable field.Change::Clearsends JSONnullfor a nullable field.
This prevents an accidental clear when a caller intended a true partial update.
Errors and validation
Section titled “Errors and validation”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.
Stability
Section titled “Stability”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.