Offline programming HTTP API
On this page
- Quick start
- Conventions
- Errors
- Target fencing
- Routes at a glance
- Service
- Authoring and planning
- Plan a program
- Cells and targets
- Machine control
- Observe
- Home
- Arm and disarm
- Heartbeat
- Load, Play, Stop
- Joint jog
- Joint move
- Cartesian jog
- Cartesian move
- Local simulator
- Program catalog
- Legacy and disabled routes
- Error codes
- Related pages
The offline programming (OLP) server is the backend of the OLP web app and of the Steam Deck v5 pendant. It serves CAD import, seam authoring and weld planning, a local simulator, and machine control: Home, Arm, joint and Cartesian jog, joint and Cartesian moves, Load, Play and Stop on a selected cell, through rt-control.
All routes are under /api/offline-programming/v1 on 127.0.0.1:8794 by default (serve --listen). The server has no authentication. It is meant to be reached from the same host, by the UI's dev proxy or by a pendant's local process. Don't expose it on a network.
Warning
Energised motion. This moves the robot. RosieOS has no software E-stop: keep the hardware E-stop within reach and clear the cell before you arm. Jog, moves and Home are checked against joint limits only, not against collisions. See the safety model.
Tip
Machine-readable. This page's routes, request fields and error codes as an OpenAPI 3.1 document, generated from this page.
Quick start#
This selects the local simulated cell that the dev stack provides, homes it, arms it, jogs J1 for a moment, and stops.
OLP=http://127.0.0.1:8794/api/offline-programming/v1
# 1. Pick a cell. The dev stack lists "local-simulation" first.
curl -s $OLP/targets
curl -s -X POST $OLP/targets/select -d '{"cell_id":"local-simulation","model_id":"rosie_1400_v3"}'
# 2. Every mutating dense-execution call carries the selection it was made against.
STATUS=$(curl -s $OLP/dense-execution/status)
GEN=$(echo "$STATUS" | jq -r .target.selection_generation)
CELL=$(echo "$STATUS" | jq -r .target.cell_id)
FENCE=(-H "X-RT-Target-Generation: $GEN" -H "X-RT-Target-Cell: $CELL")
# 3. Home, then arm. Arm acquires the rt-control lease.
curl -s -X POST $OLP/dense-execution/home "${FENCE[@]}"
curl -s -X POST $OLP/dense-execution/arm "${FENCE[@]}" -d '{"armed":true}'
# 4. Jog J1 at 10 % for one hold. A real client repeats "update" every 50 ms.
TARGET=$(curl -s $OLP/dense-execution/capabilities | jq -r .cell)
SESSION=$(curl -s $OLP/dense-execution/status | jq -r .session.id)
REV=$(curl -s $OLP/dense-execution/jog/state | jq -r .revision)
JOG="{\"target_id\":\"$TARGET\",\"session_id\":\"$SESSION\",\"revision\":$REV,\"axis\":0,\"direction\":1,\"fraction\":0.1}"
curl -s -X POST $OLP/dense-execution/jog/begin "${FENCE[@]}" -d "$JOG"
curl -s -X POST $OLP/dense-execution/jog/end -d "$JOG"
# 5. Stop always works and needs no fence. It also releases the lease.
curl -s -X POST $OLP/dense-execution/stopWhile OLP holds the lease, send a heartbeat at least every 5 s, or OLP stops the machine with ui_heartbeat_lost.
Conventions#
- Request and response bodies are JSON unless a route says otherwise. Unknown request fields are ignored; trailing data after the JSON value is refused.
- Units are in the field names:
_rad,_deg,_mm,_m,_s,_ms,_ns. Where a name has no unit, the table says. - Request body limits: 64 KiB for dense-execution routes, 12 MiB for
/cadquery/topologyand/seam, 24 MiB for/weld-plan. - Responses are gzip-compressed when the client accepts it.
Errors#
Two error shapes are in use.
Machine-control routes (/dense-execution/*, /targets/select):
{"error": "home_required", "detail": "home_required: establish the current joint position reference in Home before loading the program"}error is a stable code. A refusal from rt-control keeps rt-control's reason as the code. A limit refusal can add limit_violation: {kind, segment, sample, axis, value, limit, unit}.
Authoring and service routes:
{"ok": false, "code": "seam_worker_unavailable", "error": "the seam worker is unavailable, so no plan request can be packed"}Target fencing#
When the server runs with a cell catalogue (OFFLINE_PROGRAMMING_CELLS), every POST under /dense-execution/ must name the selection it was issued against:
| Header | Value |
|---|---|
X-RT-Target-Generation | status.target.selection_generation, a decimal integer (sent as a string in JSON) |
X-RT-Target-Cell | status.target.cell_id |
If another client has selected a different cell since, the request is refused with 409 target_changed and nothing is sent to the robot.
These routes are exempt, so they always work: /stop, /pause, /heartbeat, /target, /cells, /jog/stop, /jog/end, and /arm with {"armed": false}. /cartesian/stop and /cartesian/halt are not exempt.
Without a catalogue (a single target from --rt-core-config), there is no selection and no fencing.
Routes at a glance#
| Method and path | Purpose |
|---|---|
GET /health | Server status |
GET /capabilities | Feature flags |
GET /robots, POST /robots/capture-model | Robot catalogue; compiled URDF for a recorded pose |
POST /cadquery/topology | STEP topology and tessellation |
POST /seam | Seam worker operations |
POST /weld-plan, POST /weld-plan/export | Plan a program; export the .weldplan |
GET /targets, POST /targets/select | List and select cells |
GET /dense-execution/cells, POST /dense-execution/cells, DELETE /dense-execution/cells/{id}, POST /dense-execution/target | Cell registry |
GET /dense-execution/capabilities, GET /dense-execution/status, GET /dense-execution/cell, GET /dense-execution/telemetry/window | Observe the selected cell |
POST /dense-execution/home, POST /dense-execution/arm | Home and arm |
POST /dense-execution/load, play, pause, stop, heartbeat | Run a planned program |
GET /dense-execution/jog/state, POST /dense-execution/jog/{begin,update,end} | Joint jog |
POST /dense-execution/move | Joint move |
GET /dense-execution/cartesian/state, POST /dense-execution/cartesian/{start,intent,stop,halt} | Cartesian jog |
POST /dense-execution/cartesian/move | Cartesian move |
POST /dense-execution/go-home | Not available on rt_core |
/local-simulator/* | The loopback simulator and its preview jog |
/programs* | Program catalog proxy |
Service#
/api/offline-programming/v1/health Server status. Always 200.
{"ok": true, "schema": "offline-programming.server-status.v1", "mode": "offline_preview",
"network_required": false, "connected": false, "connected_targets": 0,
"local_simulator": { … }, "local_ready": true, "execution_enabled": false,
"target_planning": false, "teleop_enabled": false,
"program_catalog": {"available": false, "owner": "motion-server"}}execution_enabled and connected_targets describe the legacy connected-execution path, not dense execution.
/api/offline-programming/v1/capabilities Feature flags, schema offline-programming.capabilities.v1: whether the seam worker is wired (weld_planner.enabled), the local simulator's availability, and the planning flags.
/api/offline-programming/v1/robots The robot descriptions this server can plan against: the workstation's own, plus any fetched from cells, by identity.
{"robots": [{"model_id": "rosie_1400_v3", "robot_description_sha256": "sha256:…",
"frames": {"base": "world", "tool": "tool0", "work": "positioner_table_a_top",
"work_world_m": [0, -0.622, 0.1], "arm_base_world_m": [ … ]},
"axes": {"driven": ["J1", "J2", "J3", "J4", "J5", "J6", "J7"], "held": {"J8": 0, "J9": 0}},
"reset_pose": { … }}],
"problems": []}problems names each description that failed to load. A 500 with robot_store_unreadable means the description directory itself could not be read.
/api/offline-programming/v1/robots/capture-model Compiles the URDF for a model at a given description and cell calibration, for recording a pose.
| Field | Type | Required | Description |
|---|---|---|---|
model_id | string | yes | Robot model |
robot_description_sha256 | string | yes | Description identity, sha256:<64 hex> |
machine_planning_calibration_base64 | string | no | The cell's machine_planning_calibration.json, base64. Empty uses the empty calibration. |
Returns {"urdf": "<xml>", "identity": …}. A failure returns 400 {"error": "…"}. If the description is neither local, cached nor advertised by the selected cell: adopt the matching robot description before recording.
Authoring and planning#
/api/offline-programming/v1/cadquery/topology Forwards the body to the bounded CadQuery worker (one subprocess per request) and returns its JSON.
The worker operations are extract_topology, build_seam, build_sequence and tessellate. Error codes are the same set as the seam worker's, below.
/api/offline-programming/v1/seam Forwards the body to the seam worker, python -m seam_worker.workers --stdin, and returns its JSON.
The body names the operation: {"operation": "detect_joints", …}. Operations: detect_joints, check_torch_fits, plan_torch_path, sample_seam_frames, build_plan_request. The response header X-Offline-Seam-Part identifies the part the answer is about.
| Code | HTTP | Meaning |
|---|---|---|
invalid_request | 400 | The worker refused the request |
payload_too_large | 413 | Body over 12 MiB |
busy | 429 | No worker free |
canceled | 408 | The client went away |
timeout | 504 | The worker ran out of time |
unavailable | 503 | The worker's pixi environment is missing |
worker_failed, invalid_response | 502 | The worker crashed or answered with invalid JSON |
See the weld program format for the operations' payloads.
Plan a program#
/api/offline-programming/v1/weld-plan Packs a .weldplan, sends it to the weld planner, and brings back the planner's result and the dense trajectory the robot will play. One plan at a time; up to 30 minutes.
| Field | Type | Required | Description |
|---|---|---|---|
program | object | yes | A robot.v4.program.v2 document |
tooling | object | yes | The fitted torch, packed as tooling.json |
step_base64 | string | yes | The workpiece STEP file, base64. Empty only when workpiece_absent is true. |
step_filename | string | no | Its file name |
workpiece_absent | boolean | no | Plan with no workpiece |
cell_id | string | yes | The robot model whose description to plan against |
free_space_backend | string | no | Passed to the planner: bspline, curobo or legacy |
machine_planning_calibration_base64 | string | no | Only read when no cell is connected. While a cell is selected, the server fetches the cell's own calibration. |
program_id | string | no | Stamped into the plan identity |
program_digest | string | no | Echoed in the response |
manifest_revision, plan_revision | integer | no | Stamped into the plan identity |
fixtures | object | no | Workcell bodies in the cell's world frame, packed as fixtures.json |
The server always asks the planner for weld and connecting trajectory optimisation, verification and a dense trajectory.
{"result": { … }, "weldplan_sha256": "…", "program_digest": "sha256:…",
"dense": {"trajectory_digest": "sha256:…", "plan_id": "bracket_fillet:3f1c0a9d2b7e", … },
"dense_blob_base64": "…"}| Field | Description |
|---|---|
result | The planner's result document, amr-weld-planner-v1.motion-plan-result.v1, unchanged. See the Weld planner HTTP API. |
weldplan_sha256 | SHA-256 of the packed request |
dense | The dense trajectory summary: digest, plan_id, samples, segments, robot_cell |
dense_blob_base64 | The .rdt bytes, base64, so the project keeps a copy |
| Code | HTTP | Meaning |
|---|---|---|
weld_plan_invalid | 400 | Invalid JSON, or missing STEP |
seam_worker_unavailable, motion_origin_unavailable | 503 | The seam worker or OFFLINE_PROGRAMMING_WELD_PLANNER_MOTION_ORIGIN is not configured |
motion_plan_seam_refused | 502 | One or more welds could not be planned |
motion_plan_unjoined | 502 | The welds planned but the connecting moves did not |
motion_plan_no_trajectory | 502 | No dense trajectory was produced; dense_error gives {reason, detail, segment_index} |
motion_plan_failed, motion_plan_invalid_response, dense_blob_unreachable | 502 | The planner call failed |
| seam worker codes | as above | Packing failed |
/api/offline-programming/v1/weld-plan/export Packs the same .weldplan without planning it. Same body as /weld-plan.
Returns the container bytes as an attachment named <program_id>.weldplan, with its hash in X-Weldplan-SHA256.
Cells and targets#
A cell is a commissioned machine in the server's catalogue. Selecting one creates the controller for it and bumps the selection generation used for target fencing. Selection grants no authority; Arm or Load does. See Connect to a cell for the catalogue file.
/api/offline-programming/v1/targets Lists the catalogue's cells with what each one reports about itself.
{"targets": [{"cell_id": "local-simulation", "label": "Local simulation", "role": "LOCAL SIMULATION",
"models": ["rosie_1400_v3"], "requested_mode": "simulation",
"backend": "simulation", "simulation": true, "host": "localhost"}],
"selected": null}A cell that cannot be used has backend: "unreachable" and a reason, for example cell_configuration_missing, cell_mode_mismatch or cell_unreachable.
/api/offline-programming/v1/targets/select Selects a cell. Stops and releases any session on the previous cell first.
| Field | Type | Required | Description |
|---|---|---|---|
cell_id | string | yes | A catalogue cell |
model_id | string | yes | One of that cell's models |
Returns {"selected": {cell_id, model_id}, "target": {…view…}}. Refusals, all 409: cell_switch_busy, cell_switch_while_playing, cell_model_mismatch, cell_id_unknown, cell_mode_mismatch (the cell's backend is not the requested mode), cell_previous_stop_failed, cell_backend_unavailable, cell_configuration_mismatch, cell_description_invalid, cell_backend_mismatch, cell_unreachable. Without a catalogue: 503 cell_catalogue_unavailable.
/api/offline-programming/v1/dense-execution/target The Cells dialog's selection. Body {"cell": "<id>"}; an empty string clears the selection. Returns the dense-execution capabilities. Not fenced.
/api/offline-programming/v1/dense-execution/cells The registered cells, with reachability: {"cells": [{id, label, address, host, credential_ref, model_id, backend, configuration_digest, source, reachable, error}], "active_cell": "…"}.
POST /dense-execution/cells only accepts a cell that matches a commissioned server-side binding (otherwise cell_not_commissioned or cell_binding_pinned). DELETE /dense-execution/cells/{id} always refuses with cell_commissioning_required, or dense_cell_active for the selected cell: commissioned cells are removed from the catalogue file, not from the UI.
Machine control#
Every route below answers 503 dense_target_unavailable when no cell is selected. Every mutating route except the exempt ones needs the fencing headers.
Observe#
/api/offline-programming/v1/dense-execution/capabilities What the selected controller is and whether it is reachable.
{"backend": "rt_core", "available": true, "cell": "local-dev", "controller_id": "offline-programming:server-3f9a1c",
"daemon_reachable": true, "rt_core": {"description": { … }, "address_host": ""},
"active_cell": "local-simulation", "cells": [ … ]}cell is the cell's pair ID. Joint and Cartesian jog and moves send it as target_id. rt_core.description is rt-control's Describe. reason explains an unreachable controller, for example rt_core_identity_mismatch.
/api/offline-programming/v1/dense-execution/status The machine and session state. Takes no authority. Poll it.
{
"backend": "rt_core", "available": true,
"target": {"selection_generation": "4", "cell_id": "local-simulation", "model_id": "rosie_1400_v3",
"label": "Local simulation", "backend": "simulation", "simulation": true, "host": "localhost"},
"session": {"id": "ds-9b1e4f07a2c3", "kind": "trajectory", "state": "loaded", "dry_run": true,
"trajectory_digest": "sha256:…", "plan_id": "bracket_fillet:3f1c0a9d2b7e",
"lease": {"held": true, "expires_in_ms": 0},
"heartbeat_age_ms": 820, "heartbeat_deadline_ms": 5000,
"stop_reason": "", "last_error": ""},
"armed": true,
"robot_cell": {"configured": true, "valid": true, "model_id": "rosie_1400_v3",
"robot_description_sha256": "sha256:…", "machine_planning_calibration_sha256": "sha256:…",
"machine_planning_calibration_present": false, "error": ""},
"rt_core": {"inhibited": false, "stop_uncertain": false, "recovery_action": "",
"refusal": "", "refusal_kind": "", "reason": "", "status": { … }, "events": [ … ], "description": { … }},
"round_trip_ns": 180000, "link_lease_ns": 500000000
}| Field | Description |
|---|---|
target | The selection this status belongs to. Use it for the fencing headers. |
session.id | The session to name in heartbeat, Play, jog and moves |
session.kind | authority (after Arm or Home), trajectory (after Load) |
session.state | idle, loaded, playing or stopped |
session.lease.held | OLP holds the rt-control lease |
session.stop_reason, last_error | Why the session stopped |
armed | Armed, lease held and no refusal pending |
robot_cell | Which robot the machine says it is. See Robot and cell identity. |
rt_core.status | rt-control's full status. See status. |
rt_core.events | Events since the last poll |
rt_core.refusal, refusal_kind | The last refused operation and which kind it was |
rt_core.inhibited, stop_uncertain, recovery_action | A Stop or Release was not confirmed. recovery_action says what to do: retry POST /dense-execution/stop. |
round_trip_ns, link_lease_ns | Measured round trip to rt-control, and the effective lease |
/api/offline-programming/v1/dense-execution/cell The selected machine's robot identity, its machine_planning_calibration.json as base64, and its execution limits. This is what a plan request for this cell carries.
/api/offline-programming/v1/dense-execution/telemetry/window The newest seconds of the machine's telemetry ring, thinned.
| Query | Type | Default | Description |
|---|---|---|---|
seconds | number, s | 3 | In (0, 30] |
stride | integer | 1 | Keep every N-th record, 1–100 |
Returns {daemon_incarnation, configuration_sha256, cycle_period_ns, axis_count, stride, seconds, first_sequence, last_sequence, head_estimate, fetched_records, fetch_ms, records: [...]}. One second of 1 kHz telemetry is about 4.9 MB from the cell before thinning. Errors: 400 telemetry_window_invalid, 503 telemetry_unavailable, 409 telemetry_identity_changed.
Home#
/api/offline-programming/v1/dense-execution/home Runs native Home and waits until Home is valid on the named axes. Acquires the lease if OLP does not hold it. Does not arm.
| Field | Type | Required | Description |
|---|---|---|---|
axes | integer array | no | Axis indices, 0–15, a subset of the cell's axis mask. Omit the body, or send [], to home every configured axis. |
Returns the status body. If an axis has a latched reference fault whose recovery is a reset, Home resets it first. Other latched faults refuse with rt_core_fault. Errors: 400 home_axes_invalid, 409 capability_unimplemented, and rt-control reasons.
Arm and disarm#
/api/offline-programming/v1/dense-execution/arm {"armed": true} acquires the lease, enables and arms, and waits until every configured axis is ready. {"armed": false} runs Stop and Release.
| Field | Type | Required | Description |
|---|---|---|---|
armed | boolean | yes | Arm or disarm |
{"ok": true, "accepted": true, "status": { … }}If a latched execution fault can be cleared by a reset (recovery class reset_clears or reset_after_condition_clears), Arm resets it first, then re-acquires and arms, so one Arm recovers the machine. Faults that need a re-Home or a restart are left to their refusal. Errors: 400 dense_arm_invalid, 409 rt_core_arm_refused, 409 rt_core_arm_timeout, and rt-control reasons such as control_already_owned or not_ready.
Heartbeat#
/api/offline-programming/v1/dense-execution/heartbeat Tells OLP a supervising client is still there. Send it at least every 5 s while OLP holds the lease. Not fenced.
{"session_id": "ds-9b1e4f07a2c3"}{"ok": true, "deadline_ms": 5000, "age_ms": 1004}age_ms is how long the previous beat had stood. If no beat arrives for 5 s, OLP stops the machine with ui_heartbeat_lost, except while an accepted Cartesian move is finishing. Errors: 400 dense_session_required, 409 dense_session_mismatch.
Load, Play, Stop#
/api/offline-programming/v1/dense-execution/load Fetches a verified .rdt from the weld planner by digest, checks it, and prepares it on rt-control.
{
"trajectory_digest": "sha256:…",
"plan_id": "bracket_fillet:3f1c0a9d2b7e",
"program_id": "bracket_fillet",
"program_digest": "sha256:…",
"manifest_revision": 1,
"plan_revision": 3,
"dry_run": true
}| Field | Type | Required | Description |
|---|---|---|---|
trajectory_digest | string | yes | sha256:<64 hex>. The server fetches GET /api/motion/dense/{digest} from the weld planner. |
plan_id, program_id, program_digest | string | yes | Must equal the .rdt header exactly |
manifest_revision, plan_revision | integer | yes | Must equal the header. JSON integers. |
dry_run | boolean | no | Strip the torch bits and process markers before upload. The planner sets the torch bit on weld segments unless the program's execution_mode is dry_run, and a program that needs process outputs is refused, so set this for any planned weld program. |
Load runs these checks in order, before any byte reaches the robot:
- The identity matches the header (
dense_identity_mismatch). - The plan's
robot_cellmatches the machine (robot_cell_missing,robot_cell_mismatch,robot_cell_unavailable). - Every configured axis has valid Home (
home_required). - OLP acquires the lease and calls
prepare_program.rt-controlapplies its admission checks. - The prepared identity, axis mask and process requirement match (
program_identity_or_process_mismatch).
{"ok": true, "session_id": "ds-9b1e4f07a2c3",
"trajectory": {"trajectory_digest": "sha256:…", "total_sample_count": 4210, "total_duration_s": 42.09, "dt_s": 0.01,
"segments": [{"index": 0, "kind": "freespace", "source_id": "approach-1", "sample_count": 350, "duration_s": 3.49}]},
"status": { … },
"notes": ["the machine's configuration changed since this plan was made (…); the robot description and planning calibration still match, so it loads"]}Other errors: 400 dense_identity_invalid, 400 dense_trajectory_invalid, 404 dense_blob_not_found, 503 dense_blob_source_unavailable, 409 program_already_executing, and rt-control reasons with an optional limit_violation.
/api/offline-programming/v1/dense-execution/play Starts the loaded program. Body {"session_id": "…"} from the Load answer. Waits until the machine is armed and every axis ready, then sends start_program. Returns the status body.
Refused with control_session_stale unless the session is the loaded one and OLP holds the lease. Arm before Play.
/api/offline-programming/v1/dense-execution/stop Stops the machine now, then releases the lease. Needs no body and no fence. Retry it until it succeeds.
{"ok": true, "stopped": true, "attempts": 1, "status": { … }}note: "nothing_held" means there was nothing to stop. If Stop or Release cannot be confirmed, the answer is an error that still carries stopped, attempts and status, and further motion is blocked (rt_core_inhibited) until a Stop succeeds. A receipt does not prove the robot is standing still; see Software stops.
/api/offline-programming/v1/dense-execution/pause Always refused on rt_core with capability_unimplemented. Use Stop.
/api/offline-programming/v1/dense-execution/go-home Always refused on rt_core with capability_unimplemented. Use Home, or a joint move.
Joint jog#
/api/offline-programming/v1/dense-execution/jog/{action} Holds one axis at a fraction of its described velocity. action is begin, update or end. Needs an armed session. GET /dense-execution/jog/state returns the current state.
| Field | Type | Required | Description |
|---|---|---|---|
target_id | string | yes | The cell's pair ID, capabilities.cell |
session_id | string | yes | status.session.id |
revision | integer | yes | jog/state.revision. It advances when a jog ends or a move starts, so a late request from an old hold is refused. |
axis | integer | yes | Axis index, within the cell's axis mask |
direction | integer | yes | 1 or -1 |
fraction | number | yes | (0, 1]. The axis moves at fraction × max_velocity × 0.75. |
Send begin, then update every 50 ms while the operator holds the control, then end. An update may not change axis, direction or fraction; end the hold and begin a new one. Each update lives for the cell's jog input age (250 ms on a LAN cell). If updates stop, the jog ends with jog_input_deadline_expired and the axis ramps to a hold.
{"active": true, "revision": 12, "generation": 3, "sequence": 41, "axis": 0, "reason": "", "cadence_ms": 50,
"move": {"axis": 0, "state": "done", "reason": "", "revision": 11}}Errors (409 unless noted): jog_session_stale, jog_mode_conflict (a move, a program or another hold is active), no_grant, jog_not_ready, invalid_axis_mask, 400 jog_invalid_vector, jog_input_deadline_expired, jog_transport_congested, dense_target_unavailable, and native jog reasons.
Joint move#
/api/offline-programming/v1/dense-execution/move Moves one or more joints by a bounded amount and returns when the move is done. Needs an armed session.
{"target_id": "local-dev", "session_id": "ds-9b1e4f07a2c3", "revision": 12, "fraction": 0.2,
"axis": 0, "degrees": 0,
"axes": [{"axis": 0, "position_rad": 0}, {"axis": 1, "position_rad": 0}, {"axis": 2, "position_rad": 0},
{"axis": 3, "position_rad": 0}, {"axis": 4, "position_rad": 0}, {"axis": 5, "position_rad": 0}]}| Field | Type | Required | Description |
|---|---|---|---|
target_id, session_id, revision | yes | As for joint jog | |
axis | integer | yes | The joint, for a single-joint move |
degrees | number, degrees | yes | Signed relative move for a single joint. Non-zero. |
fraction | number | yes | (0, 1] of each joint's velocity and acceleration |
axes[] | array | no | A synchronised move of several joints. Each entry has axis and either degrees (relative, degrees) or position_rad (absolute, radians, from the held position). When present, it replaces axis and degrees. |
Each joint follows its own trapezoid at fraction × max_velocity × 0.75 and fraction × its jog acceleration (or its described maximum acceleration, if lower). Shorter profiles are stretched so all joints start and finish together. The points are sampled at the core's cycle and sent with prepare_trajectory and start_trajectory. The target must be inside the joint's limits.
{"axis": 0, "state": "done", "reason": "", "revision": 13, "axes": [0, 1, 2, 3, 4, 5]}Errors: joint_move_invalid, joint_move_limits, joint_move_units, joint_move_description_invalid, joint_move_too_many_points, joint_move_prepare_timeout, joint_move_prepare_busy, joint_move_interrupted, joint_move_completion_timeout, joint_move_feedback_invalid, client_disconnected, plus the joint jog errors. If the HTTP client disconnects, OLP stops the move.
Cartesian jog#
/api/offline-programming/v1/dense-execution/cartesian/{action} Jogs the tool in the base or tool frame. action is start, intent, stop or halt (stop and halt both end the hold). Same session, revision and timing rules as joint jog. GET /dense-execution/cartesian/state returns the jog state.
| Field | Type | Required | Description |
|---|---|---|---|
target_id, session_id, revision | yes | As for joint jog | |
frame | string | yes | base or tool |
twist | 6 numbers | yes | X, Y, Z in m/s (each |v| ≤ 1), RX, RY, RZ in rad/s (each |ω| ≤ π). At least one non-zero. |
fraction | number | yes | (0, 1] |
OLP resolves the twist into joint velocities from the measured pose with robot-v4-cartesiand --resolve-only, then scales the whole vector so no joint exceeds fraction × max_velocity × 0.75. The twist sets the direction; the joint ceilings usually set the speed. A step that would leave a joint's limits within one input lifetime is refused with joint_limit.
Errors: 400 cartesian_input_invalid, cartesian_resolver_unavailable, cartesian_resolver_invalid, cartesian_pose_stale, cartesian_axes_invalid, joint_limit, jacobian_gate, ik_no_solution, plus the joint jog errors.
Cartesian move#
/api/offline-programming/v1/dense-execution/cartesian/move Moves the tool a bounded distance along one axis of the base or tool frame, in a straight line, and returns when done.
| Field | Type | Required | Description |
|---|---|---|---|
target_id, session_id, revision | yes | As for joint jog | |
frame | string | yes | base or tool |
axis | integer | yes | 0–2 for X, Y, Z; 3–5 for RX, RY, RZ |
distance_mm | number, mm | for axes 0–2 | Non-zero for a linear axis, zero otherwise. At most 1,000 mm. |
angle_degrees | number, degrees | for axes 3–5 | Non-zero for a rotary axis, zero otherwise. At most 180°. |
fraction | number | yes | (0, 1] |
The resolver solves IK along the line at 1 mm or 0.25° spacing and checks each waypoint against joint limits and the singularity gate. OLP then times the path with a smooth (quintic) progress clock, so that no joint exceeds fraction × max_velocity × 0.75, reduced further near a singularity, or fraction × its acceleration limit. It sends the result as one trajectory. An accepted move finishes even if the HTTP client disconnects; Stop still cancels it.
Errors: cartesian_input_invalid, cartesian_reach, ik_no_solution, joint_limit, cartesian_pose_stale, cartesian_resolver_invalid, joint_move_too_many_points, plus the joint move errors.
Local simulator#
The server can own a loopback-only rt-core simulator for preview. It requires a loopback --listen address and is on by default (--enable-local-simulator).
| Method and path | Description |
|---|---|
GET /local-simulator/status | Simulator phase and readiness |
POST /local-simulator/start, POST /local-simulator/stop | Start or stop it. No request body. |
POST /local-simulator/plans | Plan a saved program against the simulator |
POST /local-simulator/jog/start, /jog/stop | Start or stop the preview jog runtime |
POST /local-simulator/jog/intent | {mode: "base"|"tool"|"joint", axes: [6 values in -1..1], selectedJoint, speedScale: 0..1} |
GET /local-simulator/jog/state | Joints in rad, axis names, running flag |
POST /local-simulator/jog/home, /jog/halt | Home or halt the preview |
POST /local-simulator/jog/set-zero | {axisMask} |
POST /local-simulator/jog/step | {joint, deltaDeg} (degrees); answers 202 |
Once a machine is selected, preview jog requests are refused with target_changed, so a preview cannot run beside a selected controller.
Program catalog#
GET/POST /programs, GET/PUT /programs/{program_id} and GET /programs/{program_id}/revisions proxy to an external program catalog set with --program-catalog-origin. Without one they answer catalog_unavailable. The OLP app stores projects in the browser (IndexedDB) and does not need the catalog.
Legacy and disabled routes#
connected-targets,execution-gateway,execution,targets/{profile}/plansandplans/{plan_id}belong to the older connected-execution path over NATS. They stay off unless the server is started with--enable-connected-planningor--enable-connected-execution. Don't use them for new work.POST /connect,POST /executeandPOST /teleopare retired and answer 403{"ok": false, "enabled": false}.
Error codes#
Codes specific to machine control, with their usual HTTP status. rt-control reasons such as control_already_owned, not_ready, native_limit_exceeded or mode_conflict pass through with 409; see Error codes.
| Code | HTTP | Meaning |
|---|---|---|
dense_target_unavailable | 503, 409 | No cell selected, or the backend cannot do this |
target_changed | 409 | The fencing headers name an old selection |
ui_heartbeat_lost | 409 | OLP stopped the machine: no heartbeat for 5 s |
rt_core_inhibited | 409 | A previous Stop or Release was not confirmed. Retry Stop. |
rt_core_identity_mismatch | 409 | Describe no longer matches the cell's pinned identity |
rt_core_request_slow | 409 | rt-control did not answer within the call budget. The lease is kept; retry. |
rt_core_connection_reopened | 409 | The request was not sent; the client reconnected. Retry. |
rt_core_transport_lost, rt_core_protocol_error, rt_core_closed, rt_core_cancelled, rt_core_failure | 409 | Transport or protocol failures |
rt_core_incarnation_changed, rt_core_event | 409 | rt-control restarted, or an event (a fault, a lost grant, dropped events) ended the session |
control_renewal_lost | 409 | Lease renewal failed; OLP stopped |
control_session_stale | 409 | The session was stopped or replaced before this request ran |
capability_unimplemented | 409 | Pause, go-home, or a missing rt-control capability |
dense_session_required, dense_session_mismatch | 400, 409 | Missing or unknown session_id |
dense_internal | 500 | Unexpected server error |