Skip to content

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.

  • Base URL: http://127.0.0.1:17319/v1
  • REL_AGENT_PORT overrides port 17319.
  • HTTP/1.1, one request per connection, Connection: close.
  • JSON request limit: 16 MiB.
  • Ordinary responses use application/json.
  • Capture streams use application/x-ndjson and 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.

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.

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.

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.

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.

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.

{
"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:

  1. capture.started
  2. capture.browser_requested
  3. capture.page_ready
  4. capture.rendered
  5. capture.writing
  6. capture.retrying when applicable
  7. capture.traffic
  8. capture.completed or capture.failed
  9. capture.finished, containing exit_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.

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

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

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/proxies returns data.proxies, ordered by numeric ID.
  • GET /v1/proxies/{id} returns data.proxy.
  • POST /v1/proxies requires upstream_host and upstream_port. Optional write fields are name, username, password, oxylabs_enabled, oxylabs_location_parameter, and oxylabs_location_value.
  • PATCH /v1/proxies/{id} is a true partial update. Missing fields are retained. username:null or password:null clears that value.
  • DELETE /v1/proxies/{id} detaches it from all sessions, then returns data.deleted_id.
  • POST /v1/proxies/{id}/rotate-session requires an Oxylabs-enabled proxy and returns data.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.

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/sessions returns data.sessions, ordered by ID.
  • GET /v1/sessions/{id} returns data.session.
  • POST /v1/sessions accepts optional name, proxy_id, adblock_enabled, image_blocking_mode, and image_size_limit_kb; returns data.session.
  • PATCH /v1/sessions/{id} is partial and returns data.session.
  • DELETE /v1/sessions/{id} returns data.deleted_id and 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.

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.