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 SDK source is available under the MIT license in
rel-me/rel-tools. REL’s application
source and internal runtime implementation are not publicly distributed.
Related documents: CLI, MCP, and RPC.
Connect
Section titled “Connect”Until a crates.io release is announced, pin the public repository tag:
[dependencies]rel-client = { git = "https://github.com/rel-me/rel-tools", tag = "v0.1.1" }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 the 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.
SDK browser methods inherit the RPC session-selection behavior: when REL is inactive, the target session is selected by default without activating the app. Users can disable this with the General setting Follow browser commands.
API parity
Section titled “API parity”Each transport method maps to one public RPC route. read_page and
read_observation are documented composite helpers: the former obtains a new
semantic observation, while the latter re-queries a retained public snapshot
without navigating.
| Rust method | RPC operation |
|---|---|
health() |
GET /v1/health |
status() |
GET /v1/status |
model_providers() |
GET /v1/model-providers |
list_notifications() |
GET /v1/notifications |
navigate(&NavigateRequest) |
POST /v1/navigate |
navigate_and_observe(&NavigateObservationRequest) |
POST /v1/navigate/observe |
read_page(&PageReadRequest) |
semantic POST /v1/navigate/observe or POST /v1/observe |
perform(&PerformRequest) |
POST /v1/perform |
capture_current_page(&PageCaptureRequest) |
POST /v1/capture |
screenshot_current_page(&ScreenshotRequest) |
POST /v1/screenshot |
observe_current_page(&ObservationRequest) |
POST /v1/observe |
capture(&CaptureRequest) |
POST /v1/captures |
attach_page(&PageAttachRequest) |
POST /v1/pages |
perform_page_action(page_id, &PageActionRequest) |
POST /v1/pages/{page_id}/actions |
take_page_screenshot(page_id, &PageScreenshotRequest) |
POST /v1/pages/{page_id}/screenshot |
observe_page(page_id, &PageObservationRequest) |
POST /v1/pages/{page_id}/observe |
perform_observation_action(observation_id, &ObservationActionRequest) |
POST /v1/observations/{observation_id}/actions |
find_in_observation(observation_id, &ObservationFindRequest) |
POST /v1/observations/{observation_id}/find |
get_observation(observation_id) |
GET /v1/observations/{observation_id} |
read_observation(observation_id, &ObservationReadRequest) |
composite over GET /v1/observations/{observation_id} |
list_proxies() |
GET /v1/proxies |
get_proxy(alias) |
GET /v1/proxies/{alias} |
create_proxy(&ProxyCreateRequest) |
POST /v1/proxies |
update_proxy(alias, &ProxyUpdateRequest) |
PATCH /v1/proxies/{alias} |
delete_proxy(alias) |
DELETE /v1/proxies/{alias} |
rotate_proxy_session(alias) |
POST /v1/proxies/{alias}/rotate-session |
export_proxy_transfer(&ProxyTransferExportRequest) |
POST /v1/proxy-transfers/export |
import_proxy_transfer(&ProxyTransferImportRequest) |
POST /v1/proxy-transfers/import |
list_sessions() |
GET /v1/sessions |
get_session(id) |
GET /v1/sessions/{id} |
create_session(&SessionCreateRequest) |
POST /v1/sessions |
list_profiles() |
GET /v1/profiles |
create_profile(&ProfileCreateRequest) |
POST /v1/profiles |
update_profile_data(id, &ProfileDataUpdateRequest) |
PATCH /v1/profiles/{id} |
delete_profile(id) |
DELETE /v1/profiles/{id} |
export_profile_transfer(&ProfileTransferExportRequest) |
POST /v1/profile-transfers/export |
import_profile_transfer(&ProfileTransferImportRequest) |
POST /v1/profile-transfers/import |
update_session(id, &SessionUpdateRequest) |
PATCH /v1/sessions/{id} |
pause_session(id) |
POST /v1/sessions/{id}/pause |
play_session(id) |
POST /v1/sessions/{id}/play |
delete_session(id) |
DELETE /v1/sessions/{id} |
close_session_group(group) |
POST /v1/sessions/close |
Ordinary methods return RpcResponse<T>, preserving status, request_id,
and the typed data resource. Resources include Health, StatusReport,
BrowserNotification, PageOperationData, Proxy, and Session, with list/data wrapper types that
match RPC v1.
The rel_client::transfer module validates the size and SQLite header of
versioned .relprofile and .relproxy archives and provides their safe output
filenames. TransferExportData::contents() decodes the RPC’s base64 field, and
the transfer import request from_bytes helpers perform the inverse encoding.
The agent owns full schema validation, version checks, and protected Proxy
credential encryption. Both archive types share one five-table SQLite schema;
legacy JSON transfer documents are not supported. The 12 MiB transfer limit is
available as MAX_TRANSFER_FILE_BYTES.
pause_session and play_session return SessionNetworkStateData, containing
the canonical session_id and resulting network_paused value. Both methods
are idempotent; play reloads when the pause interrupted or deferred navigation.
If pause cancels navigation before the new document commits, REL restores the
previous URL and live document; play then resumes without reloading that page.
Health::build and StatusReport::build expose an optional BuildIdentity
with the installed bundle’s ID, configuration, worktree, branch, commit, and
dirty state. The field is None when the agent was not launched by a
metadata-bearing app bundle.
The bundled MCP adapter uses this same client for all fourteen tools. It
calls status, list_notifications, capture, attach_page,
read_page, perform_page_action, both screenshot methods, all observation methods,
list_sessions, close_session_group, and list_proxies; it does not maintain
alternate request types or bypass the RPC transport. For capture, it exhausts
and validates CaptureStream before returning one aggregated MCP result.
BrowserNotification title and body fields are untrusted website content.
Listing them never starts a model turn; agent clients must keep them in the same
untrusted-data boundary as page text and pixels.
Shorthand page workflow
Section titled “Shorthand page workflow”For retrieval without action refs or pixels, use PageReadRequest. Omit query
for an overview when the desired wording is unknown. Unqueried reads sample the
loaded document, including text without headings. Supply query to rank matching
semantic content and links; it is a lexical relevance filter, not a natural
language question answering operation. find_observation instead searches literal
substrings and roles in a retained observation.
Dense lists in navigation, sidebar, header or footer regions may be omitted from an overview when substantive page content is also present. The output reports the omitted block/link counts, and ordinary selection counts still include those omissions. A literal query can retrieve the omitted region. Sparse menus and pages consisting mainly of navigation keep their usual overview behavior.
Use a specific label, phrase or API identifier when known. Underscores and
qualified names stay intact: Archive.copy_into can match copy_into, but does
not become a broad query for Archive or into. A plain member is not expanded:
Archive.copy does not match an ordinary Copy control. Exact terms and
identifiers rank above ordinary word-prefix matches such as install/installation;
repeated boilerplate does not gain rank by repeating the same word. Both
read_page and read_observation select relevant content and its surrounding
context within character and section budgets before arranging it in document
order. A late precise match can therefore survive an early page full of weak
matches.
Reads preserve structural regions, list items, table rows, form text, and explicit advertisement labels. Adjacent blocks share their context label once. A heading already included in the content is not repeated in the supplementary outline. An exact, unambiguous text label can carry its link inline when its structural context matches. Duplicate links combine only when their label, destination, context, role, state, and value agree; distinct URLs or regions remain separate. Each link states whether any identical copy was in the captured viewport.
Table row markers and their captured cells form an intact record when their
context paths identify the row. List items and descendants in a named child
article/list-item context are selected together. Matching any member selects
that record; a record that cannot fit the character or section budget is omitted
whole, with an omission marker. max_sections and selected/available counts still
count captured content blocks: a row containing three blocks needs three slots.
Query and field selections also retain a table’s captured caption/name and first
captured row as explicitly labeled leading context. The first row is not assumed
to be a header: cell order and text remain verbatim, including in headerless
tables. A match in that leading context admits the captured table’s remaining
rows when budgets allow, so a column-label query can return associated values.
Rows and their required leading context must fit together; omitted rows remain
visible in the omission marker and coverage counts. Explicit new table markers
keep identically named tables separate. Overview sampling remains unchanged.
Ordinary prose retains bounded excerpts. Missing structural boundaries are not
inferred from wording: the current capture represents definition terms and
descriptions as ordinary text, and does not distinguish header cells from data
cells. Source clipping can already have removed part of a record; intact selection
does not restore uncaptured content or override source_truncated.
The Markdown records capture time, document sequence and viewport/document size. Source URL, observation ID, selected/available counts, and separate source/output truncation flags remain available in the result. Counts describe selected semantic records and unique links, not posts, complete page coverage, or content that might load after scrolling. Reading does not scroll a dynamic feed to completion.
use rel_client::{PageReadRequest, RelClient};
let client = RelClient::local();let read = client.read_page(&PageReadRequest { url: Some("https://example.com/docs".into()), query: Some("installation".into()), max_chars: Some(6_000), max_sections: Some(16), ..PageReadRequest::default()})?;println!("{}", read.data.markdown);# Ok::<(), rel_client::ClientError>(())Matched rating values retain their adjacent labels, semantic link categories such as genres and labels remain available, and link ranking does not treat a generic URL path segment as a label match. This helper still uses REL’s embedded Chromium and the public RPC observation routes. It does not fetch through a second HTTP client or browser backend.
The singular page methods can share the agent’s process-local current page. Set
the same session_id on each request to scope that page to one browser session:
use rel_client::{ Action, NavigateRequest, PageCaptureRequest, PerformRequest, RelClient,};
let client = RelClient::local();let session_id = "Session1".to_string();let mut navigate = NavigateRequest::new("https://example.com");navigate.session_id = Some(session_id.clone());client.navigate(&navigate)?;let mut perform = PerformRequest::new(vec![ Action::WaitFor { selector: "button.more".into(), timeout: None, }, Action::Click { selector: "button.more".into(), mouse_move: None, scroll: None, }, Action::Wait { seconds: 0.5 },]);perform.session_id = Some(session_id.clone());client.perform(&perform)?;let capture = client.capture_current_page(&PageCaptureRequest { session_id: Some(session_id), output: Some("/tmp/final.html".into()), ..PageCaptureRequest::default()})?;println!("{}", capture.data.capture.output_path);# Ok::<(), rel_client::ClientError>(())capture_current_page also refreshes the shorthand page binding from the
currently visible URL. Its returned page.url is authoritative after History
API, query, or fragment changes made by the page.
navigate becomes ready after the requested HTTP(S) main frame starts,
finishes, and has nonempty rendered source. Subframe and page-initiated
background loading does not hold the request open. Its wait value is a
bounded settling delay after final main-frame readiness. Use Action::Wait
when a workflow needs additional settling time.
Take a visual capture from the same current page with ScreenshotRequest, or
use PageScreenshotRequest with an explicit attached page ID:
use rel_client::{RelClient, ScreenshotFormat, ScreenshotRequest};
let client = RelClient::local();let screenshot = client.screenshot_current_page(&ScreenshotRequest { session_id: Some("Session1".into()), format: Some(ScreenshotFormat::Webp), quality: Some(80), full_page: true, ..ScreenshotRequest::default()})?;println!("{}", screenshot.data.screenshot.output_path);# Ok::<(), rel_client::ClientError>(())Request compact rendered semantics and typed element refs with
ObservationRequest. Hybrid adds a current-viewport PNG resource; visual keeps
semantics minimal. Optional context paths on content and elements preserve
their nearest landmark, form, dialog, list, table, and row relationships:
use rel_client::{ NavigateObservationRequest, ObservationAction, ObservationActionKind, ObservationActionRequest, ObservationFindRequest, ObservationMode, ObservationReadRequest, RelClient,};
let client = RelClient::local();let observed = client.navigate_and_observe(&NavigateObservationRequest { url: Some("https://example.com".into()), navigation: None, session_id: Some("Session1".into()), mode: Some(ObservationMode::Hybrid), profile: None, proxy: None, timeout: None, wait: None,})?;let first_ref = observed.data.observation.elements[0].element_ref.clone();let mut hover = ObservationAction::new(first_ref.clone(), ObservationActionKind::Hover);hover.scroll = Some(true);let next = client.perform_observation_action( &observed.data.observation.id, &ObservationActionRequest { actions: vec![ hover, ObservationAction::new(first_ref, ObservationActionKind::Click), ObservationAction::wait(0.25), ObservationAction::scroll(0, -600), ], mode: Some(ObservationMode::Semantic), timeout: None, wait: None, },)?;let found = client.find_in_observation( &next.data.observation.id, &ObservationFindRequest { query: Some("continue".into()), role: Some("button".into()), limit: Some(10), },)?;println!("{}", found.data.total_matches);let recalled = client.read_observation( &observed.data.observation.id, &ObservationReadRequest { query: Some("important facts and ratings".into()), max_chars: Some(6_000), max_sections: Some(20), },)?;println!("{}", recalled.data.markdown);# Ok::<(), rel_client::ClientError>(())Refs are scoped to one observation and document sequence. The agent retains
private locators and returns OBSERVATION_STALE instead of retargeting when the
document or element signature has changed. Observation actions execute in order,
stop at the first failure, and return one post-batch observation. Find searches
only the stored public snapshot and does not issue another browser read.
Navigation invalidates and erases private locators but retains the bounded public
snapshot for reading until the 32-observation registry evicts it, the session
closes, or the agent exits. Retained snapshots are evidence, not actionable page
state.
navigate returns ClientError::Rpc with ID UPSTREAM_UNAVAILABLE when the
main frame commits an HTTP 4xx or 5xx response. By default, detected Cloudflare
Turnstile and managed challenge pages first receive up to 15 seconds to
continue; this can be disabled in REL’s General settings. Error details include
the final url and exact target_http_status; the navigated session remains
selected.
The first navigation without a session ID reuses the first persisted session, creating one only when none exists; later unscoped requests use the most recent shorthand page. Session-scoped shorthand pages let clients operate concurrently across sessions. The state is cleared when the agent restarts or the session closes. Use explicit page methods for concurrent work within one session.
Reading an already captured snapshot
Section titled “Reading an already captured snapshot”ObservationOperationData::read(query, max_chars, max_sections) formats a captured
observation locally with the same matching, standard Markdown prelude and coverage
as RelClient::read_observation. Pass None for an overview or Some(query) for a
query-directed read. It uses the usual character and section limits and makes no
RPC calls. A caller can fetch one observation with get_observation, then produce
both ordinary reads and literal field selections from that same immutable snapshot.
Literal field selection from a captured snapshot
Section titled “Literal field selection from a captured snapshot”ObservationOperationData::read_field(field, max_chars, max_sections) is a pure
formatter: retrieve one observation, then select several fields locally without
additional RPC calls. It matches the full literal phrase after case and whitespace
normalization, preserving punctuation and identifier boundaries. It performs no
keyword splitting or synonym matching. For example, Price (incl. tax) does not
match Price (excl. tax), and Archive.copy does not match Archive.copy_into.
Field names must contain 1–128 characters after trimming; the usual character
and section limits apply.
let snapshot = client.get_observation("observation-id")?;for field in ["Price (incl. tax)", "UPC", "Availability"] { let selected = snapshot.data.read_field(field, 4_000, 100)?; println!("{}: {}", field, selected.markdown);}Unlike ordinary read Markdown, field Markdown contains only selected record and
context text, plus missing-match or budget-omission markers. It omits the page
title/source/snapshot prelude, supplementary heading outline and standalone link
list. The returned PageReadData still includes page identity, source URL, title,
observation ID and coverage flags, so callers can render source attribution once
around a field bundle. matched_query means the literal field was found, even
when its entire record was omitted by the budget; it does not guarantee a value
was returned. No match describes the captured snapshot only. Atomic field matches
retain their records plus the leading table context described above; matching a
leading row can return other captured rows from that table. Context is not an
additional literal field match, and the first captured row is not identified as a
header. Other neighboring records are excluded; ordinary prose still uses the
existing contextual window. Ordinary read_page and read_observation
queries keep their relevance-based matching and standard Markdown prelude.
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.
Capture and attached-page responses always expose an absolute filesystem path
in output_path, even when a request supplied a relative output path. The MCP
adapter maps these paths to file:/// URIs; the Rust SDK preserves the native
RPC path contract.
Dropping CaptureStream before capture.finished closes its HTTP connection
and cancels the matching agent and Chromium operation. The persistent session
and resident agent remain available.
Browser actions
Section titled “Browser actions”The public Action enum serializes directly to the RPC v1 object shapes. See
the Actions reference for every variant, a Rust example, selector
constraints, defaults, and failure behavior. Browser sessions controlled while
not visible use the global Background Browser Size preset; it is not
duplicated as an SDK field.
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 the proxy_alias for a session.
use rel_client::{Change, RelClient, SessionUpdateRequest};
let request = SessionUpdateRequest { name: Some("Research".into()), proxy_alias: Change::Clear, ..SessionUpdateRequest::default()};RelClient::local().update_session("Session12", &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.
Session profiles
Section titled “Session profiles”Session creation opens a new page tab in the oldest compatible existing tab group.
Compatibility includes the selected profile and browser-data source, proxy,
fingerprint configuration, image/ad blocking, model, and viewport settings.
The existing group’s identity, storage, fingerprint seed, and lifetime are preserved.
An explicit name or group label must match to reuse a group; an explicit lifetime
must also match. If no compatible group exists, REL creates one.
The response’s session ID identifies the reused group, so repeated creation can
return the same ID. reused_group reports whether a group was reused.
Set SessionCreateRequest::new_group to Some(true) to force a separate group.
SessionCreateRequest::default() serializes to {}, so the agent uses the
configured Default Profile, or Custom when the preference is unset.
Custom uses direct networking, AdBlock on, all images allowed, and Private.
Set profile to a case-insensitively unique saved configuration name. Explicit proxy
and filtering fields override the selected profile; use
Change::Set("alias".into()) for a proxy or Change::Clear for direct
networking:
use rel_client::{Change, RelClient, SessionCreateRequest};
let request = SessionCreateRequest { group: Some("pgm".into()), adblock_enabled: Some(true), proxy_alias: Change::Clear, ..SessionCreateRequest::default()};RelClient::local().create_session(&request)?;RelClient::local().close_session_group("pgm")?;# Ok::<(), rel_client::ClientError>(())Profile contains its public id, unique name, proxy and filtering policy,
browser-data inclusion flags, optional fingerprint identity template, built-in
status, and creation time.
ProfileCreateRequest creates a custom settings template; browser-data import
itself remains app-owned. ProfileDataUpdateRequest updates the two inclusion
flags after REL.app stages an import; no cookie or password values cross RPC.
Re-importing a selected category replaces that category in the template.
There are no built-in profiles. ImageBlockingMode::None allows every
image without disabling AdBlock. Existing sessions retain copied settings and
data after their source profile is changed or deleted. REL Free can create one
persistent Session and one custom Profile; REL Pro removes those limits.
ProfileCreateRequest::fingerprint_profile uses Change::Unchanged to select
the default compatibility template, Change::Clear for native Chromium, and
Change::Set(profile) for explicit identity settings. REL.app preserves those
settings but generates a fresh seed whenever it creates a session from the
profile. New configurations use Private; explicit native identity stays native.
Session::profile exposes the source profile name and
Session::profile_data_id identifies the custom browser-data template copied
at creation, when any. CaptureRequest, NavigateRequest, and
PageAttachRequest accept profile only when session_id is absent.
CaptureRequest and PageAttachRequest also accept group, so an implicitly
created session can join a group. Group matching is case-insensitive; closing
an empty group succeeds with an empty deleted_ids vector.
ProxyCreateRequest.locale: Option<String> configures an optional BCP-47 locale
for the proxy. ProxyUpdateRequest.locale: Change<String> supports set, clear,
and unchanged. Proxy.locale and Session.proxy_locale return it. This preference is independent of any
country selection, and travels with proxy/profile exports.
FingerprintProfile.chrome_version optionally adds a Chrome identity. Set
chrome_version_follows_engine to Some(true) to follow REL’s bundled Chromium
version, including future upgrades, or Some(false) to keep an explicit fixed
version. Canonical profiles retain this choice when decoded and serialized.
With both fields omitted, Chromium retains its native branding. The server also
resolves the fingerprint’s engine version and matching User-Agent token to the
bundled runtime; unrelated privacy settings and browser data stay unchanged.
FingerprintProfile.locale_mode accepts FingerprintLocaleMode::Automatic or
Custom; the SDK also preserves the optional overrides list on round-trip.
Automatic uses the configured proxy locale, then the macOS user’s preferred
locale. Custom uses the explicit fingerprint locale ahead of those defaults.
Only a value different from native Chromium is applied as an override.
ProxyCreateRequest requires an immutable, unique alias. The typed proxy
methods and the capture/page proxy field accept only that alias; public proxy
resources never expose or accept numeric IDs or UUIDs. Proxy creation, update,
rotation, assignment, and use require REL Pro. On Free, those calls return a
non-retryable PRO_REQUIRED error with feature and plan details.
Sessions similarly expose their immutable canonical id (for example,
Session12) as their sole public identifier. The typed session
methods accept that string, and Session and session deletion responses do not
expose numeric database IDs.
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, 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.code or failure.error.id. RpcError preserves the
numeric code, string id, message, retryable, and optional object-valued
details. The rpc_error_codes module exports constants for every standard
code; all application codes are 10,000 or greater and are unrelated to HTTP
statuses.
The client validates Content-Type, requires X-Request-Id, and checks it
against the envelope or every NDJSON event.
Stability
Section titled “Stability”The SDK targets RPC v1 only. Removing legacy CLI syntax does not change this wire contract. SDK versions are distributed alongside compatible REL releases.
Per-proxy CA certificates
Section titled “Per-proxy CA certificates”ProxyCreateRequest and ProxyUpdateRequest expose tls: Option<ProxyTls>:
use rel_client::{ProxyTls, ProxyUpdateRequest};let request = ProxyUpdateRequest { tls: Some(ProxyTls::Custom { certificate_pem: std::fs::read_to_string("company-root-ca.pem")?, }), ..Default::default()};ProxyTls::System clears added roots, and ProxyTls::BrightData uses REL’s bundled root for brd.superproxy.io:44445. None preserves trust on update and selects system trust on create. Proxy responses include the selected tls configuration. All certificate validation and session scoping is performed by the agent and embedded browser.
Database recovery status
Section titled “Database recovery status”Health.database_recovery is an optional DatabaseRecoverySummary. It preserves
the schema version, original backup and report paths, recovery item count, and
retained session count. Older agents may omit it. See the health contract
and app recovery guide for semantics.
Session lifetimes
Section titled “Session lifetimes”SessionCreateRequest::lifetime accepts
SessionLifetime::Inactivity { timeout_seconds: 300 } or
SessionLifetime::Indefinite. Leaving it None uses the server default of 120
seconds of inactivity. RelClient::ping_session(id) refreshes the timer without
browser work and returns SessionData. The Session response exposes the
policy and last_activity_at. Keep idle clients alive by pinging well before the
timeout. Session listing and background page traffic do not refresh activity.
Query context in semantic reads
Section titled “Query context in semantic reads”Query-directed reads keep a matching block with its nearby heading and preceding
text label, when available. They include two following blocks and continuing
prose or list items from the same structural region, stopping at the next text
label, heading, landmark or region change. This preserves definition descriptions,
defaults and caveats that follow an API signature. The character and section
budgets bound these windows; an oversized prose block can still be clipped.
Structurally identified table/list records are selected intact or omitted whole.
When the query matches the literal name of a table, section, form or region, its
captured descendants also qualify.
This keeps table rows and form values associated with their caption or label,
even when the values use different words. Matching considers the region name,
not generic path roles such as main or table. Query output states how many
candidate blocks were selected from the captured page; selected/available counts describe that query,
not complete page coverage. Omit the query when the desired text is unknown.
The Rust client exposes activate_license(&key) for POST /v1/license/activate.
Pass keys securely and never log them. This activates the owning app’s REL Pro
license using its existing validation and persistence; existing licenses must be
managed in the app. See the CLI guide for stdin usage.
Use RelClient::update_app() to request the owning app’s native update flow.
The returned AppUpdateData acknowledges the request and reports the selected
channel and required user interaction. It does not report installation completion.