# Robots_UpdateRobotModel

`PATCH /v1/accounts/{account}/robots/{robot}/model`

Edits arms, solver, gripper, reach_base, mink_parameters or
pyroki_parameters. A robot template takes `arms` and `reach_base` only:
INVALID_ARGUMENT for a solver or parameters, since a template has none
(they are set on the rig made from it), and for a gripper that differs from
the stock jaw its model already has (the import guessed it): a client
sending back what GetRobotModel returned is not refused. A MINK solver's links
(shoulder_link, elbow_link, wrist_link, `{side}` read as left or right)
must exist in the model's URDF: INVALID_ARGUMENT names the missing one.

- Base URL: `https://junction-api.qualiastudios.dev`
- Authentication: an API key in the `X-API-Key` header

## Parameters

- `account` (path, string, required): The account id.
- `robot` (path, string, required): The robot id.
- `updateMask` (query, string (field-mask)): Which fields to change: arms, solver (PYROKI, DLS or MINK; a solver cannot
  be cleared, so naming it with SOLVER_UNSPECIFIED is INVALID_ARGUMENT, while
  `*` with SOLVER_UNSPECIFIED leaves the solver as it is), gripper, reach_base
  (naming it with none sent clears it), mink_parameters (a MINK model needs
  them whole; INVALID_ARGUMENT says which one a request gets wrong),
  pyroki_parameters (a PYROKI model needs them whole, the same way; sent with
  `solver` PYROKI). Sending the parameters of another solver than the one the
  model ends with is INVALID_ARGUMENT; naming that path with none sent drops
  them (a model keeps only its own solver's).

## Request body

Required: `application/json` `qualia.v1.RobotModel`

## Responses

- `200` OK: `application/json` `qualia.v1.RobotModel`
- `default` Default error response: `application/json` `google.rpc.Status`

## Schemas

```yaml
qualia.v1.RobotModel:
  type: object
  properties:
    name:
      type: string
      description: accounts/{account}/robots/{robot}/model
    urdfRobot:
      readOnly: true
      type: string
      description: The URDF's own robot name.
    urdfUri:
      readOnly: true
      type: string
      description: "Signed URI of the browser-safe URDF: meshes point at the GLBs below."
    meshes:
      readOnly: true
      type: array
      items:
        $ref: "#/components/schemas/qualia.v1.RobotModel_Mesh"
      description: The meshes.
    digest:
      readOnly: true
      type: string
      description: Content digest of the imported URDF and meshes.
    arms:
      type: array
      items:
        $ref: "#/components/schemas/qualia.v1.RobotModel_Arm"
      description: |-
        Its arms: guessed at import (the deepest link named for each side, else
        one right arm), then edited.
    solver:
      enum:
        - SOLVER_UNSPECIFIED
        - PYROKI
        - DLS
        - MINK
      type: string
      description: |-
        The IK solver; SOLVER_UNSPECIFIED when none has been set (a new model has
        none). Once set it can be changed to another, never cleared: naming
        `solver` in the update mask with SOLVER_UNSPECIFIED, or with a value this
        server does not know, is INVALID_ARGUMENT.
      format: enum
    gripper:
      allOf:
        - $ref: "#/components/schemas/qualia.v1.RobotModel_Gripper"
      description: The gripper joint; unset when the robot has none.
    capsuleState:
      readOnly: true
      enum:
        - CAPSULE_STATE_UNSPECIFIED
        - FITTING
        - FITTED
        - UNAVAILABLE
      type: string
      description: Self-collision capsules.
      format: enum
    capsulesUri:
      readOnly: true
      type: string
      description: Signed URI of the capsules JSON (pyroki's export), when fitted.
    configDigest:
      readOnly: true
      type: string
      description: |-
        Digest over digest, arms, solver, capsules, gripper and the solver's
        parameters (mink_parameters for MINK, pyroki_parameters for PYROKI, the
        legacy numbers excepted). Reach results
        are keyed by it: after a change the stored results are for the old
        config, and nothing re-runs them until a reach check is asked for.
    editor:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Actor"
      description: Who last changed it.
    updateTime:
      readOnly: true
      type: string
      description: When it last changed.
      format: date-time
    etag:
      type: string
      description: Sent back on UpdateRobotModel.
    importState:
      readOnly: true
      enum:
        - IMPORT_STATE_UNSPECIFIED
        - PENDING
        - CONVERTING
        - READY
        - FAILED
      type: string
      description: |-
        Where the latest import is. Until it is READY, the fields above describe
        the previous model (empty when there is none).
      format: enum
    importFailureReason:
      readOnly: true
      type: string
      description: |-
        Why the latest import FAILED, e.g. "no link is reached through three
        turning joints".
    reachBase:
      type: array
      items:
        type: number
        format: double
      description: |-
        Where a dataset's reach check starts when the dataset names no base of
        its own (ReachSettings.base_m): empty or exactly three numbers, metres in
        the robot's base frame, each within 5 m either way. Not part of
        config_digest, so changing it makes no stored result stale; a dataset that
        follows it gets a new reach key on its next RunCheck. The reach op does
        not apply a base yet; see ReachSettings.base_m.
    tcpDigest:
      readOnly: true
      type: string
      description: |-
        Digest of every arm's TCP (Arm.tcp), empty when no arm has one. Reach
        results walked at the TCP are keyed by it beside config_digest, so a
        change to the carried gripper, its mount, its TCP or either URDF makes
        them stale; results walked at the gripper root do not depend on it.
    minkParameters:
      allOf:
        - $ref: "#/components/schemas/qualia.v1.RobotModel_MinkParameters"
      description: |-
        What Mink solves with: set exactly when the solver is MINK, and then whole
        (see MinkParameters). Set with UpdateRobotModel (update_mask
        `mink_parameters`) together with solver MINK; any other solver clears
        them. Part of config_digest, so changing them makes stored reach results
        stale.
    pyrokiParameters:
      allOf:
        - $ref: "#/components/schemas/qualia.v1.RobotModel_PyrokiParameters"
      description: |-
        What PyRoKi solves with: set exactly when the solver is PYROKI, and then
        whole (see PyrokiParameters). Set with UpdateRobotModel (update_mask
        `pyroki_parameters`) together with solver PYROKI, as `mink_parameters` is
        with MINK; any other solver clears them. A PYROKI robot that has run before
        holds the numbers it has always run. Part of config_digest, except that the
        numbers every PyRoKi robot ran before it had a file add nothing, so a robot
        that still holds them keeps the digest (and the stored reach results) it
        had.
  description: |-
    A robot's kinematic model and the config every consumer shares: reach
    checks, retargeting, forward kinematics for trajectories, and the 3D
    gripper. One per robot.

    Served by fleet.
qualia.v1.RobotModel_Mesh:
  type: object
  properties:
    path:
      readOnly: true
      type: string
      description: The path in the served URDF (package:// resolved to this relative path).
    uri:
      readOnly: true
      type: string
      description: Signed URI of the GLB.
  description: One mesh, converted to GLB, at the path the served URDF references.
qualia.v1.RobotModel_Arm:
  required:
    - side
    - handLink
  type: object
  properties:
    side:
      enum:
        - SIDE_UNSPECIFIED
        - SIDE_LEFT
        - SIDE_RIGHT
      type: string
      description: Which side.
      format: enum
    handLink:
      type: string
      description: The link the arm ends at.
    tool:
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Transform"
      description: The tool frame fixed in the hand link, where a handheld gripper root is held.
    startPosture:
      type: array
      items:
        type: number
        format: double
      description: |-
        The posture a replay starts from: one angle per turning joint of the
        chain, radians, clamped to the limits.
    joints:
      readOnly: true
      type: array
      items:
        $ref: "#/components/schemas/qualia.v1.RobotModel_Joint"
      description: The chain's turning joints, base to hand, with their limits.
    tcp:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Matrix4"
      description: |-
        The arm's TCP in the hand link's frame, the one every reader of a robot's
        TCP uses (the reach solve at the TCP, the dashboard). A robot carrying a
        gripper (Robot.carried_gripper): the hand link to the mount's parent
        link, then the mount, then the gripper's own TCP; empty when the gripper
        has no TCP for this side or it is stale, or the mount's parent link is
        not joined to the hand link by fixed joints. A robot carrying none: the
        TCP its URDF names, the first link called `grasp_frame`, `hand_tcp` or
        `tcp` (or ending `_` and one of those) fixed below the hand link; empty
        where it names none.
    startPostureConfigured:
      type: boolean
      description: |-
        Explicitly use this complete, in-limit start_posture as configured
        home for contact exclusions. Absent/false retains the replay rest seed
        without claiming imported, padded or clipped defaults are verified home.
  description: One arm.
qualia.v1.Transform:
  type: object
  properties:
    xyz:
      type: array
      items:
        type: number
        format: double
      description: x, y, z in metres.
    rpy:
      type: array
      items:
        type: number
        format: double
      description: roll, pitch, yaw in radians.
  description: |-
    A 3D rigid transform: translation in metres and roll, pitch, yaw in radians
    (URDF convention, extrinsic XYZ).
qualia.v1.RobotModel_Joint:
  type: object
  properties:
    joint:
      readOnly: true
      type: string
      description: Joint name.
    lower:
      readOnly: true
      type: number
      description: Lower limit, radians (or metres for prismatic).
      format: double
    upper:
      readOnly: true
      type: number
      description: Upper limit.
      format: double
  description: One turning joint, read off the URDF.
qualia.v1.Matrix4:
  type: object
  properties:
    m:
      type: array
      items:
        type: number
        format: double
      description: m[0..3] is the first row, m[12..15] the last.
  description: |-
    A 3D rigid transform as a 4x4 matrix: 16 numbers, row-major, translation in
    metres in the last column, the bottom row 0 0 0 1. Where it is used says
    whose pose it is and in which frame (Robot.GripperTcp.Hand.tcp: the TCP in
    a link's frame). Unlike Transform it has no pitch pole to lose a rotation at.
qualia.v1.RobotModel_Gripper:
  required:
    - joint
    - openPosition
    - closedPosition
  type: object
  properties:
    joint:
      type: string
      description: Joint name; `{side}` is replaced by "left" or "right".
    openPosition:
      type: number
      description: Its position fully open.
      format: double
    closedPosition:
      type: number
      description: Its position shut.
      format: double
    homePosition:
      type: number
      description: |-
        Explicit jaw position at the configured arm home, mirrored with the
        open/closed range where the side's URDF limits require it. Absence means
        unknown, including on legacy profiles; never infer open, closed or zero.
      format: double
  description: |-
    The joint that opens the gripper. A robot that carries a gripper
    (Robot.carried_gripper) has that gripper's own jaw here, none when the gripper
    has none, not the stock fingers its URDF came on; taking the gripper off gives
    the robot's own jaw back. The gripper's own model holds the same jaw.
qualia.v1.Actor:
  type: object
  properties:
    kind:
      readOnly: true
      enum:
        - KIND_UNSPECIFIED
        - PERSON
        - VALIDATION
        - RULE
        - MODEL
        - OPERATOR
        - SYSTEM
      type: string
      description: What kind of actor it is.
      format: enum
    user:
      readOnly: true
      type: string
      description: The person's user id when `kind` is PERSON; empty otherwise.
    displayName:
      readOnly: true
      type: string
      description: The person's display name at the time, for rendering without a lookup.
    agent:
      readOnly: true
      type: string
      description: |-
        The rule code, or for a MODEL the Qualia model tier (e.g. "qualia-vlm";
        "qualia" for any other model), when not a person.
    run:
      readOnly: true
      type: string
      description: The run a model or rule finding came from, when it has one.
    apiKey:
      readOnly: true
      type: string
      description: |-
        The API key's id when a person acted through one; empty for a session.
        Never the key itself.
    attribution:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Attribution"
      description: |-
        Signed identity evidence recorded with the change, kept apart from the
        principal above. Unset on older records and wherever none was signed.
  description: Who did something. Every decision, issue and reply names one.
qualia.v1.Attribution:
  type: object
  properties:
    authentication:
      readOnly: true
      enum:
        - AUTHENTICATION_UNSPECIFIED
        - SESSION
        - API_KEY
        - SERVICE
      type: string
      description: How the principal authenticated.
      format: enum
    initiator:
      readOnly: true
      type: string
      description: |-
        The user id of a person verified as starting the work: the person of a
        session, or the person a trusted service's signed job record names.
        Empty when nobody was verified.
    initiatorDisplayName:
      readOnly: true
      type: string
      description: The initiator's display name at the time, never their email.
    credentialCreator:
      readOnly: true
      type: string
      description: The user id of the key's creator at the time. Not who used the key.
    credentialOwner:
      readOnly: true
      enum:
        - CREDENTIAL_OWNER_UNSPECIFIED
        - PERSON
        - SPACE
      type: string
      description: Who owned the key at the time.
      format: enum
    credentialOwnerSpace:
      readOnly: true
      type: string
      description: |-
        The owning space's id (a workspace or the organization) when
        `credential_owner` is SPACE.
  description: |-
    How a change was authenticated and who verifiably started it.
    Only the trusted mesh signs it. It never authorizes anything, and a key's
    creator or owner is never the person who used the key.
qualia.v1.RobotModel_MinkParameters:
  type: object
  properties:
    posCost:
      type: number
      description: |-
        Each arm's frame task on its tool frame: its position cost, 0 or more.
        pos_cost and ori_cost are not both 0.
      format: double
    oriCost:
      type: number
      description: The frame task's orientation cost, 0 or more.
      format: double
    frameLmDamping:
      type: number
      description: The frame task's Levenberg-Marquardt damping, 0 or more.
      format: double
    postureCost:
      type: number
      description: The posture task's cost on the arm joints, 0 or more.
      format: double
    postureTarget:
      enum:
        - POSTURE_TARGET_UNSPECIFIED
        - ENGAGE
        - ROW
      type: string
      description: What the posture task pulls the arm joints toward.
      format: enum
    smoothnessCost:
      type: number
      description: The smoothness task's cost, 0 or more; 0 turns it off.
      format: double
    elbowDownCost:
      type: number
      description: The elbow-down task's cost, 0 or more; 0 turns it off.
      format: double
    singularityThreshold:
      type: number
      description: The singularity task's threshold, 0 or more; 0 turns it off.
      format: double
    singularityDamping:
      type: number
      description: The singularity task's damping, 0 or more; 0 turns it off.
      format: double
    dt:
      type: number
      description: The QP's step, above 0.
      format: double
    damping:
      type: number
      description: The QP's Levenberg-Marquardt damping, 0 or more.
      format: double
    maxIters:
      type: integer
      description: The most QP steps a row takes, 1 to 1000.
      format: int32
    posTol:
      type: number
      description: |-
        A row's steps stop once every arm's position error is within this and
        its orientation error within ori_tol; a 0 tolerance is never met.
      format: double
    oriTol:
      type: number
      description: The orientation tolerance that goes with pos_tol; 0 is never met.
      format: double
    stallTol:
      type: number
      description: |-
        A row's steps stop once a step moves the joints less than this; 0
        never stops one.
      format: double
    progressWindow:
      type: integer
      description: |-
        A row's steps stop once its error shrank by less than min_progress over
        this many steps, 0 to 1000; 0 never stops one.
      format: int32
    minProgress:
      type: number
      description: |-
        The share of its error a row must lose over progress_window steps to
        keep stepping, 0 or more and below 1.
      format: double
    checkedSteps:
      type: boolean
      description: q-research's checked step (inference) instead of mink.solve_ik (teleop).
    maxJointVelocity:
      type: number
      description: The arm joints' speed limit in the QP, 0 or more; 0 is no limit.
      format: double
    maxJointSpeedPerRow:
      type: number
      description: |-
        The arm joints' speed limit from one row of the take to the next, 0 or
        more; 0 is no limit.
      format: double
    selfCollision:
      type: boolean
      description: Whether the collision limit, on the robot's capsules, is on.
    collisionPairs:
      enum:
        - COLLISION_PAIRS_UNSPECIFIED
        - ALL
        - AVOIDED
      type: string
      description: The capsule pairs the collision limit watches.
      format: enum
    collisionMinDistance:
      type: number
      description: The collision limit's minimum distance, 0 or more.
      format: double
    collisionDetectionDistance:
      type: number
      description: The collision limit's detection distance, above collision_min_distance.
      format: double
    collisionGain:
      type: number
      description: The collision limit's gain, above 0 and at most 1.
      format: double
    collisionBoundRelaxation:
      type: number
      description: The collision limit's bound relaxation, 0 or more.
      format: double
    shoulderLink:
      type: string
      description: |-
        The shoulder link's name ("{side}" stands for left or right, at most 256
        characters); empty is the arm chain's 2nd joint's child link.
    elbowLink:
      type: string
      description: |-
        The elbow link's name, like shoulder_link; empty is the 4th joint's
        child link.
    wristLink:
      type: string
      description: |-
        The wrist link's name, like shoulder_link; empty is the 5th joint's
        child link.
  description: |-
    What Mink solves with (Solver MINK): schema 1 of the reach op's contract
    (op_reach.contract.MinkParameters). Every field is required: there are no
    defaults and no built-in sets. Metres, radians and seconds.
qualia.v1.RobotModel_PyrokiParameters:
  type: object
  properties:
    posWeight:
      type: number
      description: |-
        The pose cost's weight on each hand's position, 0 to 1e6. pos_weight and
        ori_weight are not both 0.
      format: double
    oriWeight:
      type: number
      description: The pose cost's weight on each hand's orientation, 0 to 1e6.
      format: double
    lastWeight:
      type: number
      description: |-
        The weight pulling each row toward the last answer, 0 to 1e6. It pulls
        toward the same value as anchor_weight, so only their sum of squares
        matters and tuning either has the same effect.
      format: double
    anchorWeight:
      type: number
      description: |-
        The weight pulling each row toward the row before it, 0 to 1e6. Not toward
        home: see last_weight.
      format: double
    manipulabilityWeight:
      type: number
      description: The weight on the arm's manipulability, 0 to 1e6.
      format: double
    limitWeight:
      type: number
      description: The weight keeping the joints inside their limits, 0 to 1e6.
      format: double
    collisionWeight:
      type: number
      description: The weight keeping the arms' capsules apart, 0 to 1e6.
      format: double
    collisionMargin:
      type: number
      description: |-
        How near two capsules may come before the collision cost acts, in metres,
        0 to 1.
      format: double
    maxIterations:
      type: integer
      description: The most solver iterations a row takes, 1 to 50.
      format: int32
  description: |-
    What PyRoKi solves with (Solver PYROKI): schema 1 of the PyRoKi solver
    file, as fleet checks it (q_fleet_api.services.pyroki_parameters). qds sends
    it to the reach op with each solve, as it sends a Mink robot's parameters. Every field is required: there are no defaults and no built-in
    sets. Weights are unitless, the margin is metres.
    Each robot has its own: PyRoKi on one robot and PyRoKi on another run the same
    solver and are tuned apart.
google.rpc.Status:
  oneOf:
    - $ref: "#/components/schemas/ApiError"
    - $ref: "#/components/schemas/GatewayError"
  description: "The body of a failed call: one of the two shapes below."
ApiError:
  title: API error
  type: object
  required:
    - code
    - message
    - retryable
    - details
  properties:
    code:
      type: string
      description: The gRPC code name in upper case, e.g. `NOT_FOUND`.
    message:
      type: string
    retryable:
      type: boolean
      description: Whether the same call may succeed if retried.
    details:
      type: array
      items:
        type: object
      description: "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."
  description: A refusal from the API itself.
GatewayError:
  title: Gateway error
  type: object
  required:
    - detail
  properties:
    detail:
      type: string
      description: What went wrong.
  description: A refusal from the gateway before the call reaches the API, for example `401` bad or missing API key, `403` key lacks the scope or environment closed, `413` body too large.
```
