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 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.

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.

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.

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.

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 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.

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.

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::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.

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.

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.

The SDK targets RPC v1 only. Removing legacy CLI syntax does not change this wire contract. SDK versions are distributed alongside compatible REL releases.

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.

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.

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-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.