RPC v1
Rel exposes one local, versioned JSON API. This document is the supported wire contract; unversioned routes and legacy response shapes are not supported.
Related documents: CLI, Rust SDK, Services, and Architecture.
Transport
Section titled “Transport”- Base URL:
http://127.0.0.1:17319/v1 REL_AGENT_PORToverrides port17319.- HTTP/1.1, one request per connection,
Connection: close. - JSON request limit: 16 MiB.
- Ordinary responses use
application/json. - Capture streams use
application/x-ndjsonand terminate at connection close. - The agent is loopback-only but currently has no client authentication.
Every parsed request receives an opaque ID. Ordinary responses include it in the
X-Request-Id header and body. Every capture-stream line includes the same ID.
Response envelope
Section titled “Response envelope”Every successful ordinary response is:
{ "status": "ok", "request_id": "req_01J...", "data": {}}Every failure is:
{ "status": "error", "request_id": "req_01J...", "error": { "id": "SESSION_NOT_FOUND", "http_code": 404, "message": "Session 42 was not found.", "retryable": false, "details": { "session_id": 42 } }}id, http_code, message, and retryable are required. details is an
optional JSON object. Clients must branch on id, never parse message.
http_code always equals the actual HTTP status for ordinary responses; it is
also retained so the identical error object works in NDJSON and the private pipe
protocol.
retryable:true means retrying the same idempotent operation may succeed without
user correction. It does not mean every mutation is automatically safe to
repeat.
Standard error IDs
Section titled “Standard error IDs”| ID | HTTP | Retryable | Meaning |
|---|---|---|---|
INVALID_REQUEST |
400 | no | Malformed HTTP or JSON |
ROUTE_NOT_FOUND |
404 | no | No v1 route matches |
METHOD_NOT_ALLOWED |
405 | no | Resource exists but method is unsupported |
PAYLOAD_TOO_LARGE |
413 | no | Request body exceeds 16 MiB |
UNSUPPORTED_MEDIA_TYPE |
415 | no | JSON endpoint received unsupported content |
VALIDATION_FAILED |
422 | no | Parsed request violates field constraints |
SESSION_NOT_FOUND |
404 | no | Session ID does not exist |
PAGE_NOT_FOUND |
404 | no | Ephemeral attached page does not exist |
PAGE_MISMATCH |
409 | no | Attached page state no longer matches the request |
PROXY_NOT_FOUND |
404 | no | Proxy does not exist |
CONFLICT |
409 | no | Name/state/last-session conflict |
BROWSER_BUSY |
409 | yes | Chromium is servicing incompatible work |
NETWORK_PAUSED |
409 | no | Session networking is paused |
ACTION_TARGET_NOT_FOUND |
422 | no | Click target could not be found |
REQUEST_CANCELLED |
409 | yes | Browser work was cancelled |
RATE_LIMITED |
429 | yes | Rel itself is rate limiting the caller |
UPSTREAM_UNAVAILABLE |
502 | yes | Browser/proxy received an invalid upstream result |
BROWSER_UNAVAILABLE |
503 | yes | Required Chromium service is unavailable |
AGENT_UNHEALTHY |
503 | yes | The serialized control worker missed its health deadline |
TIMEOUT |
504 | yes | Rel’s operation deadline expired |
INTERNAL_ERROR |
500 | no | Unexpected internal failure |
A target website returning 404 or 429 is not a Rel RPC error. Its status is
reported as target_http_status in capture data.
Routes
Section titled “Routes”| Method | Path | Purpose |
|---|---|---|
GET |
/v1/health |
Readiness of the agent control worker |
GET |
/v1/status |
App, agent, proxy, and Chromium diagnostic report |
POST |
/v1/captures |
Capture rendered HTML as an NDJSON operation |
POST |
/v1/pages |
Attach an ephemeral automation page |
POST |
/v1/pages/{page_id}/actions |
Perform one action on an attached page |
GET |
/v1/proxies |
List proxies |
POST |
/v1/proxies |
Create a proxy |
GET |
/v1/proxies/{id} |
Read one proxy |
PATCH |
/v1/proxies/{id} |
Partially update a proxy |
DELETE |
/v1/proxies/{id} |
Delete and detach a proxy |
POST |
/v1/proxies/{id}/rotate-session |
Rotate an Oxylabs session |
GET |
/v1/sessions |
List persistent browser sessions |
POST |
/v1/sessions |
Create a browser session |
GET |
/v1/sessions/{id} |
Read one browser session |
PATCH |
/v1/sessions/{id} |
Partially update a browser session |
DELETE |
/v1/sessions/{id} |
Delete a browser session |
There are deliberately no log read, clear, or ingestion routes. Rel processes share local NDJSON log files; see Services.
The rel-client Rust crate exposes one typed method for every route
in this table. The bundled CLI is built on that crate and uses resource commands
such as rel capture, rel page, rel proxy, and rel session; it has no
direct database or log-file command path.
Health
Section titled “Health”GET /v1/health
Section titled “GET /v1/health”HTTP 200 while the worker is ready or operating within its deadline:
{ "status": "ok", "request_id": "req_...", "data": { "version": "0.1.7", "pid": 123, "browser_proxy_port": 17400, "worker": { "state": "idle" } }}Worker state is starting, idle, or busy. A startup/operation deadline
violation or failed worker returns HTTP 503 AGENT_UNHEALTHY, with the worker
snapshot in error.details.worker. Health deadlines diagnose stalls; they do not
cancel the active request.
GET /v1/status
Section titled “GET /v1/status”The diagnostic call succeeds with HTTP 200 even when a component is down:
{ "status": "ok", "request_id": "req_...", "data": { "overall_status": "ok", "running_count": 4, "total_count": 4, "checks": [ { "id": "agent", "name": "Agent", "kind": "service", "running": true, "status": "running", "detail": "pid 123; worker idle", "pids": [123] } ] }}Check IDs are rel_app, agent, browser_proxy, and chromium_bridge.
Captures
Section titled “Captures”POST /v1/captures
Section titled “POST /v1/captures”{ "url": "https://example.com", "output": "/optional/page.html", "timeout": 90, "wait": 1, "actions": [], "session_id": "machine-....Session12", "proxy": 3, "retry": 1, "retry_delay": 3}| Field | Contract |
|---|---|
url |
Required HTTP(S) URL; scheme-less input is normalized by the agent. |
output |
Optional nonempty path or null; generated when absent. |
timeout |
Finite seconds greater than zero; default 90. |
wait |
Finite seconds at least zero; default 1. |
actions |
Optional array of canonical action objects. |
session_id |
Optional existing federated ID. Omission creates a session. |
proxy |
Optional positive ID, UUID, or unique name selector. |
retry |
Integer 0 through 100; default 1. |
retry_delay |
Finite seconds 0 through 86400; default 3. |
The RPC accepts only action objects:
{ "action": "click", "selector": "button.more" }{ "action": "wait", "seconds": 0.5 }{ "action": "click-link", "link": "https://example.com/next", "match": { "type": "fuzzy-link", "threshold": 0.9 }}The legacy output_mode field and function-like action strings are rejected.
Preflight failures use the ordinary error response. Once accepted, Rel returns
HTTP 200 application/x-ndjson. Each physical line is one complete object; there
is no encoded stdout/stderr layer:
{ "status": "ok", "request_id": "req_...", "event": "capture.started", "data": { "url": "https://example.com/", "session_id": "machine-....Session12" }}Events, in normal order:
capture.startedcapture.browser_requestedcapture.page_readycapture.renderedcapture.writingcapture.retryingwhen applicablecapture.trafficcapture.completedorcapture.failedcapture.finished, containingexit_code
capture.failed uses the standard nested error object. capture.completed
contains output path, bytes, final URL, optional target_http_status, session ID,
capture ID, and proxy traffic. A target status at least 400 is a completed
capture with outcome:"target_error" and CLI exit code 1; it is not an API
error.
Attached pages
Section titled “Attached pages”POST /v1/pages
Section titled “POST /v1/pages”{ "url": "https://example.com", "session_id": "machine-....Session12", "proxy": 3, "output": "/optional/page.html", "timeout": 90, "wait": 1}Omitting session creates one. The final normalized browser URL must equal the requested URL. Success data:
{ "page": { "id": "page_...", "session_id": "machine-....Session12", "url": "https://example.com/" }, "capture": { "output_path": "tmp/captures/...html", "bytesize": 1234, "target_http_status": 200 }}Page IDs are process-local and disappear when the agent restarts.
POST /v1/pages/{page_id}/actions
Section titled “POST /v1/pages/{page_id}/actions”{ "action": { "action": "click", "selector": "button" }, "output": "/optional/page.html", "timeout": 90, "wait": 1}The response uses the same page/capture data. URL, proxy, and session come from the attached page and cannot be overridden.
Proxies
Section titled “Proxies”A proxy resource is:
{ "id": 1, "uuid": "...", "name": "Office", "upstream_host": "proxy.example.com", "upstream_port": 8000, "username": "optional", "password_set": true, "oxylabs": { "enabled": false, "session_id": null, "location_parameter": null, "location_value": null }}If no Oxylabs configuration exists for a proxy, oxylabs is omitted.
Passwords are accepted on writes but never returned.
GET /v1/proxiesreturnsdata.proxies, ordered by numeric ID.GET /v1/proxies/{id}returnsdata.proxy.POST /v1/proxiesrequiresupstream_hostandupstream_port. Optional write fields arename,username,password,oxylabs_enabled,oxylabs_location_parameter, andoxylabs_location_value.PATCH /v1/proxies/{id}is a true partial update. Missing fields are retained.username:nullorpassword:nullclears that value.DELETE /v1/proxies/{id}detaches it from all sessions, then returnsdata.deleted_id.POST /v1/proxies/{id}/rotate-sessionrequires an Oxylabs-enabled proxy and returnsdata.proxy.
Names are case-insensitively unique and cannot be positive integers or UUIDs.
Oxylabs location requires both parameter and value; parameter is cc, country,
or st. oxylabs.session_id is generated by Rel and is read-only; rotate it
with the dedicated rotate-session operation.
Sessions
Section titled “Sessions”A session resource is:
{ "id": 12, "session_id": "machine-<uuid>.Session12", "name": "Session12", "proxy_id": null, "adblock_enabled": true, "image_blocking_mode": "over_limit", "image_size_limit_kb": 100, "created_at": 1785860000}GET /v1/sessionsreturnsdata.sessions, ordered by ID.GET /v1/sessions/{id}returnsdata.session.POST /v1/sessionsaccepts optionalname,proxy_id,adblock_enabled,image_blocking_mode, andimage_size_limit_kb; returnsdata.session.PATCH /v1/sessions/{id}is partial and returnsdata.session.DELETE /v1/sessions/{id}returnsdata.deleted_idand refuses to remove the last session.
image_blocking_mode is all or over_limit. The legacy block_images alias
is rejected. Size is 1 through 1,048,576 kB. The visible name is editable and
case-insensitively unique; the federated session_id is immutable.
Private Chromium pipe RPC
Section titled “Private Chromium pipe RPC”Rel.app supervises the agent with REL_CHROMIUM_BRIDGE=stdio. Protocol v4 uses a
four-byte unsigned big-endian length followed by 1–128 MiB of UTF-8 JSON.
Request:
{ "version": 4, "id": "bridge_...", "operation": "capture", "browser_profile": {}, "url": "https://example.com/", "actions": [], "timeout": 90, "wait": 1}Operation is capture, browser_attach, or browser_action.
Success:
{ "version": 4, "id": "bridge_...", "status": "ok", "data": { "url": "https://example.com/", "html": "<html>...</html>", "target_http_status": 200 }}Failure reuses the standard error object:
{ "version": 4, "id": "bridge_...", "status": "error", "error": { "id": "BROWSER_UNAVAILABLE", "http_code": 503, "message": "Chromium is unavailable.", "retryable": true }}Both sides validate protocol version, frame size, response ID, status, and the required success/error payload.