Skip to content

Robots_CopyRobot

POST
/v1/accounts/{account}/robots/{robot}:copy
curl --request POST \
--url https://junction-api.qualiastudios.dev/v1/accounts/example/robots/example:copy \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <X-API-Key>' \
--data '{ "name": "example", "displayName": "example", "gripper": "GRIPPER_COPY_UNSPECIFIED", "requestId": "example" }'

Makes a new robot from an existing one, to try a change (another gripper, a mount, a jaw) without touching the original, and returns the copy. The source may be a robot template (kind ROBOT_TEMPLATE): that is how a rig is made, and the copy has source_template set to it; a template carries no solver, gripper, serial number or reach result, so the copy has none either (set them on the copy), gripper is ignored, and an unnamed copy is named like the template (templates and robots are named apart).

The copy takes the source’s robot type, URDF and meshes, arms (hand link, tool, start posture), solver, jaw, reach base and fitted capsules, and, with gripper KEEP, a gripper of its own copied from the one the source carries: its URDF, TCP, jaw and mounts as they are now. It is a snapshot and shares nothing with the source afterwards; until either is edited, the composed tcp of each arm and config_digest match the source’s, but for config_digest of a robot with fitted capsules (the digest covers the capsules’ path, which is the copy’s own). The copy is named display_name, or ” copy”; a name the workspace has gets the next free ” (2)”, ” (3)” … suffix. The source’s serial number, credentials, datasets played on it, reach results and history are not copied, and the copy is never the default target (that stays the oldest robot).

NOT_FOUND for a robot of another workspace. FAILED_PRECONDITION when the source is a gripper design (a gripper is copied by attaching it: SetRobotGripper copy_from; a UMI rig is made by CopyGripperAsUmiRig), when its model or its carried gripper’s is being imported, or while its capsules are being fitted: try again in a minute. ABORTED when the source changed while it was copied: try again. A retry with the same request_id returns the first call’s copy. The copy’s registration is recorded as an event whose subject is the copy and which names the source.

account
required
string

The account id.

robot
required
string

The robot id.

Media type application/json

Request for Robots.CopyRobot.

object
name
required

Accounts/{account}/robots/{robot} of kind ROBOT or ROBOT_TEMPLATE: the robot to copy.

string
displayName

The copy’s name, 1 to 64 characters. Empty: ” copy”, or the template’s own name for a copy of a robot template. A name the workspace has (among robots, apart from templates) takes the next free ” (2)”, ” (3)” … suffix.

string
gripper

What the copy takes of the source’s gripper. Empty reads as KEEP.

string format: enum
Allowed values: GRIPPER_COPY_UNSPECIFIED KEEP NONE
requestId

Idempotency key (AIP-155): a retry with the same id within 24 hours returns the first call’s copy instead of making a second one.

string

OK

Media type application/json

A robot the account keeps (fleet.robots). Episodes can be recorded on it (Dataset.source_robot) and played on it (Dataset.target_robot).

A gripper design (kind GRIPPER) is the same record: a URDF of one gripper with the TCP of each hand on it (gripper_tcp), which is also what a handheld rig’s trackers are calibrated to and what Rig.gripper points at. Everything that takes a robot name works on it (upload, model, delete), but it is no robot: ListRobots leaves it out unless asked for grippers, so it is never the default target, and Robot.config_digest is always empty (its RobotModel keeps one), so no reach result is keyed by it. It lives in one workspace. A workspace-level gripper is one of two things: a gripper template (gripper_tcp.rig_kind empty), the design a robot’s gripper or a UMI rig is copied from, or a UMI rig (gripper_tcp.rig_kind “umi-handheld”), the capture rig a handheld recording is converted with: made by CopyGripperAsUmiRig from a gripper template and calibrated with ImportGripperCalibration. A workspace has as many UMI rigs as someone made and none otherwise: nothing makes one on listing, and a UMI rig is deleted like any other robot.

A robot template (kind ROBOT_TEMPLATE) is the model of a robot that rigs are made from: a URDF with its meshes and arms, and nothing a rig adds (no solver, gripper, serial number, credentials, datasets or reach results). CopyRobot makes a rig from it. The copy is a snapshot and shares nothing with the template afterwards, and deleting the template never touches a rig.

A robot carries a gripper on its flanges (carried_gripper), set with SetRobotGripper, and owns it: attaching a gripper copies its URDF, TCP and jaw into a gripper design of the robot’s own (owner names the robot), so editing it changes that robot only. An owned gripper is carried by its owner alone, is included when listing grippers as a source to copy, is deleted with its robot, by detaching it, or by DeleteRobot on the gripper, and needs robots:write to change (a workspace-level gripper, a gripper template or a UMI rig, needs rigs:write). Everything that takes a gripper name works on it (GetRobot, the model, SetGripperTcp, ImportGripperCalibration).

Served by fleet.

object
name

Accounts/{account}/robots/{robot}

string
displayName
required

Unique per account.

string
robotType

The robot type key (fleet.robot_type.key), e.g. “openarm”, “franka_fr3”. Empty for a robot defined only by an imported URDF.

string
serialNumber

Serial number, free text.

string
hasModel

Whether the robot has a model (URDF) configured.

boolean
configDigest

RobotModel.config_digest, empty without a model: what reach results are keyed by. Always empty for a gripper design.

string
createTime

When it was registered.

string format: date-time
updateTime

When it last changed.

string format: date-time
etag

Sent back on UpdateRobot.

string
kind

Robot, robot template or gripper design. Set by CreateRobot (unset is ROBOT) and never changed.

string format: enum
Allowed values: KIND_UNSPECIFIED ROBOT GRIPPER ROBOT_TEMPLATE
gripperTcp

Kind GRIPPER only: its TCPs.

object
hands

The hands that have a TCP: none, one or both sides.

Array<object>

One hand’s TCP.

object
side

Which hand.

string format: enum
Allowed values: SIDE_UNSPECIFIED SIDE_LEFT SIDE_RIGHT
link

The link of the gripper’s URDF that tcp is expressed in, with the side resolved (“openarm_left_ee_base_link”). Empty when the hand has only a tracker_to_tcp.

string
tcp

The TCP’s pose in link’s frame. Empty when the hand has none.

object
m

M[0..3] is the first row, m[12..15] the last.

Array<number>
trackerToTcp

The TCP’s pose in the tracker’s frame: what uploads are resolved with. Unset when the hand has none, so it serves robots only.

object
xyz

X, y, z in metres.

Array<number>
rpy

Roll, pitch, yaw in radians.

Array<number>
calibration

The hands’ tracker_to_tcp as a calibration, the form Datasets.ResolvePoses takes. Set only when resolves_uploads.

object
filename
required

The uploaded file’s name, e.g. “tcp_calibration.json”.

string
deriveDate

When the calibration was made, “YYYY-MM-DD”: the leading date of the file’s calibrated or else its derived text. Empty when the file gives none.

string
hands
required

Both hands, left then right.

Array<object>

One hand’s transform.

object
side
required

Which hand.

string format: enum
Allowed values: SIDE_UNSPECIFIED SIDE_LEFT SIDE_RIGHT
trackerToTcp
required

The TCP’s pose in the tracker’s frame: xyz in metres and rpy in radians (URDF convention, extrinsic XYZ), the file’s rotation matrix converted. A rotation whose pitch is within a hair of +-90 degrees has no unique roll and yaw; it is stored with roll 0.

object
xyz

X, y, z in metres.

Array<number>
rpy

Roll, pitch, yaw in radians.

Array<number>
eeToTcp

The same TCP in the frame of the gripper design’s ee_link for this hand, in the form of tracker_to_tcp (so the same pitch limit). Display only. Set with ee_link or not at all, and only when it composes with the file’s controller_to_ee to the file’s controller_to_tcp, which is what makes the TCP drawn the TCP used.

object
xyz

X, y, z in metres.

Array<number>
rpy

Roll, pitch, yaw in radians.

Array<number>
eeLink

The link of the rig’s gripper design that Hand.ee_to_tcp is a pose in, with {side} for “left” or “right” (the gripper joint’s convention in RobotModel.Gripper), e.g. “openarm_{side}_ee_base_link”. Read from the file’s own description of its end-effector frame. Empty when the file names none, lacks the end-effector poses, or they disagree with its controller_to_tcp (then no ee_to_tcp is set either). Display only.

ee_to_tcp is drawn in this link’s own frame, which must be the plain end-effector frame the file means. An export whose link sits elsewhere draws the TCP off by the difference: an OpenArm exported with the xacro default collapse_internal_empty_links puts openarm__ee_base_link at link7, 20.5 mm behind the frame the calibration is in.

string
rigKey

The key a recording may name this gripper by, e.g. “umi-handheld” on a workspace’s first UMI rig, which also stands for the ingest rig kind of the same name (rig_kind). Unique among workspace-level grippers. Empty for a gripper that has none: a gripper template, a UMI rig made by CopyGripperAsUmiRig (named by its name or id), and the gripper a robot owns (see source_rig_key); a TCP on it serves robots only. Nothing makes a keyed gripper any more: the key stays on the ones that have it.

string
filename

The calibration file’s name, or “tcp-matrix:” for a matrix.

string
deriveDate

“YYYY-MM-DD”: the file’s, or the day the matrix was set (UTC). Empty when a file gave none.

string
stale

Whether the gripper’s URDF changed since a TCP in a link was set: a re-import changed its digest or took the link away. A stale TCP is shown with a warning and is never composed into a robot’s arm; set it again, every hand that has one in the same call.

boolean
tcpSet

Robots can use it: every hand has its TCP in a link and it is not stale.

boolean
resolvesUploads

Uploads can be resolved with it: both hands have a tracker_to_tcp, and the calibration has a file name and a date.

boolean
etag

Sent back on SetGripperTcp and ImportGripperCalibration.

string
sourceRigKey

The rig_kind (else the rig_key) of the gripper this one was copied from, e.g. “umi-handheld”, for the gripper a robot owns; empty otherwise. It says the gripper is a copy of a UMI rig’s gripper (and so has its controller to TCP transform), which a copy cannot claim as its own rig_kind: uploads are resolved with a UMI rig.

string
rigKind

The ingest rig kind this gripper converts a recording with, e.g. “umi-handheld”: what Dataset.poses_rig names. Set exactly on a UMI rig (a workspace-level gripper made by CopyGripperAsUmiRig, or the one a workspace had before: its rig_key is the same string). Several UMI rigs of one kind may exist in a workspace. Empty on a gripper template and on a gripper a robot owns.

string
carriedGripper

Kind ROBOT only: the gripper it carries, if any.

object
gripper

Accounts/{account}/robots/{robot} of kind GRIPPER, in the robot’s workspace: the robot’s own gripper (its owner is the robot).

string
mounts

One per arm side the gripper is mounted on.

Array<object>

Where a gripper’s hand link sits on one arm.

object
side
required

Which arm.

string format: enum
Allowed values: SIDE_UNSPECIFIED SIDE_LEFT SIDE_RIGHT
parentLink

The robot’s link the gripper hangs off: it must be joined to the arm’s hand_link by fixed joints only.

string
matrix

The pose of the gripper’s hand link (its TCP link) in parent_link’s frame. Ignored when baked. The mount is for the link the hand’s TCP is in when it is set, which must exist then; a TCP set in another link later composes nothing until the mount is set again.

object
m

M[0..3] is the first row, m[12..15] the last.

Array<number>
baked

The robot’s own URDF already has the gripper built in, as the gripper design has it: the mount is the identity on the robot’s link of the gripper’s link name. The server checks that the two URDFs agree on where that link is (FAILED_PRECONDITION when they do not) and sets parent_link and matrix itself.

boolean
guessed

The mount was filled in by a guess and has not been confirmed.

boolean
etag

Sent back on SetRobotGripper.

string
displayName

The gripper’s display name (the one it was copied from), so a robot can say “gripper: KIWI gripper pair” without reading the gripper.

string
owner

Kind GRIPPER only: accounts/{account}/robots/{robot} of the robot that owns this gripper. Empty for a workspace-level gripper (the handheld’s, or a library gripper). Set on CreateRobot (with kind GRIPPER) to make the named robot’s own gripper, which that robot carries from then on (with no mounts); never changed after.

string
stagedGripperSupported

The server supports exact staged gripper copy and fenced replacement. Absent on older servers; clients must not assume those guarantees.

boolean
thumbnailSha256

The sha256 (lowercase hex) of the robot’s thumbnail, a picture of its model at the default pose that fleet draws when the model is imported (the bytes are GetRobotThumbnail’s). Empty when it has none yet, or when the model was replaced since it was drawn, so a replaced model never shows its old picture.

string
solver

RobotModel.solver, so a reader of the robot knows whether it has one without reading the model (whose signed URLs cost fleet more than the answer is worth). SOLVER_UNSPECIFIED: the robot has no solver, or no model; has_model tells which. Always SOLVER_UNSPECIFIED for a gripper design. fleet always sets it, SOLVER_UNSPECIFIED included, so a reader that finds it unset is talking to an older fleet, which does not know the field, and reads the model’s solver instead.

string format: enum
Allowed values: SOLVER_UNSPECIFIED PYROKI DLS MINK
sourceTemplate

Accounts/{account}/robots/{robot} of the template this record was copied from: for a robot, the robot template CopyRobot copied (or the template of the robot it copied); for a gripper, the gripper template it was copied from (a UMI rig made by CopyGripperAsUmiRig, or the copy a robot owns). Empty when it was made another way, copied from something that was no template, or its template was deleted. It is provenance only: nothing is re-synced from it.

string
Example
{
"kind": "KIND_UNSPECIFIED",
"gripperTcp": {
"hands": [
{
"side": "SIDE_UNSPECIFIED"
}
],
"calibration": {
"hands": [
{
"side": "SIDE_UNSPECIFIED"
}
]
}
},
"carriedGripper": {
"mounts": [
{
"side": "SIDE_UNSPECIFIED"
}
]
},
"solver": "SOLVER_UNSPECIFIED"
}

Default error response

Media type application/json
One of:
API error

A refusal from the API itself.

object
code
required

The gRPC code name in upper case, e.g. NOT_FOUND.

string
message
required
string
retryable
required

Whether the same call may succeed if retried.

boolean
details
required

Empty, except when a dataset copy or move is refused: then one google.rpc.ErrorInfo with the refusal’s reason code, its domain and, in metadata, the datasets it names.

Array<object>
object
Example generated
{
"code": "example",
"message": "example",
"retryable": true,
"details": [
{}
]
}