# Datasets_CreateDataset

`POST /v1/accounts/{account}/datasets`

Creates a mixture. Its sources are added with Mixtures.BatchCreateSources
(several at once, resolved together) or Mixtures.CreateSource; a mixture
with no sources reads nothing.

- 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.
- `requestId` (query, string): Idempotency key (AIP-155): a retry with the same id within 24 hours
  returns the first call's result instead of creating a second one.

## Request body

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

## Responses

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

## Schemas

```yaml
qualia.v1.Dataset:
  required:
    - displayName
    - kind
  type: object
  properties:
    name:
      type: string
      description: accounts/{account}/datasets/{dataset}
    displayName:
      type: string
      description: Human name shown in the dashboard.
    description:
      type: string
      description: Free text about the dataset.
    kind:
      enum:
        - KIND_UNSPECIFIED
        - UPLOAD
        - MIXTURE
        - MERGED
      type: string
      description: |-
        Upload, mixture or merged. Only MIXTURE can be created through
        CreateDataset; uploads arrive through the upload path and MERGED is made
        by MergeDatasets.
      format: enum
    state:
      readOnly: true
      enum:
        - STATE_UNSPECIFIED
        - UPLOADING
        - UPLOADED
        - UPDATED
        - REVIEWING
        - READY
      type: string
      description: Where the dataset stands (the UI calls it status).
      format: enum
    stateProgress:
      readOnly: true
      type: number
      description: |-
        How far along the current status is, 0 to 1 (share of the upload landed
        while UPLOADING, share of episodes no longer waiting while REVIEWING).
      format: double
    labels:
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Dataset_Labels"
      description: |-
        Session labels. Editable on an upload and on a merged dataset, derived
        (the commonest value over what it reads, a merged dataset's own for the
        uploads it holds) on a mixture. A merged dataset's are its own: chosen
        when the merge makes it (an answer, else its uploads' commonest), its
        collection tags seeded from its initial uploads and preserved on append.
        Every collection edit, including a compatibility method/axis edit, stays
        on the owner. Recording annotations on the uploads' takes stay separate.
    sourceRobot:
      type: string
      description: |-
        accounts/{account}/robots/{robot}: the fleet robot the episodes were
        recorded on. Empty for handheld data or when unmapped. A robot, not a
        gripper design (a Robot of kind GRIPPER); it is not looked up when set.
        A merged dataset's uploads share one, chosen at the merge; changing it
        changes it on every upload it holds.
    targetRobot:
      type: string
      description: |-
        accounts/{account}/robots/{robot}: the robot the episodes are played on. Set by default only on handheld uploads (method
        "handheld"), to the account's oldest robot (never a gripper design); empty
        otherwise until set.
        Changing it asks for no reach check; it closes the dataset's open
        out_of_reach issues, which were about the previous robot. Name a robot,
        not a gripper design (a Robot of kind GRIPPER): it is not looked up when
        set. Changing a merged dataset's changes it on every upload it holds; an
        upload merged away cannot be changed on its own.
    counts:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Dataset_Counts"
      description: Episode, time and standing counts.
    channels:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Dataset_Channels"
      description: What stable and head resolve to.
    drift:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Dataset_Drift"
      description: Head against the newest tag.
    robotInterface:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Dataset_InterfaceSummary"
      description: Robot interface and cameras.
    sourceDatasets:
      readOnly: true
      type: array
      items:
        type: string
      description: |-
        Lineage. Mixture: the uploads it reads at head, once each, an
        upload a merge took named by its merged dataset (once for all it holds). Merged: the uploads it holds, in the order they were merged.
        Upload: empty.
    readers:
      readOnly: true
      type: array
      items:
        type: string
      description: |-
        The mixtures that read this dataset directly: by name, or (for an
        upload or a merged dataset) through a label source it matches. A Task
        source is listed by the dataset's own Task; one that reads only takes
        linked to it here is found by ListDatasets' `reads` filter.
    posterUri:
      readOnly: true
      type: string
      description: |-
        A signed midpoint poster from the first episode with one, in source and
        member order, searching the first 128 members per visible source.
        Empty when no poster can be delivered within that search.
    repository:
      readOnly: true
      type: string
      description: "Upload only: the repository it was published into (the git-native view)."
    sourceFormat:
      readOnly: true
      enum:
        - SOURCE_FORMAT_UNSPECIFIED
        - LEROBOT_V2_1
        - LEROBOT_V3
        - MCAP_ROS2
      type: string
      description: "Upload only: persisted ingest format."
      format: enum
    verdict:
      readOnly: true
      enum:
        - VERDICT_UNSPECIFIED
        - PUBLISHED
        - DEGRADED
        - UNDERIVABLE
      type: string
      description: "Upload only: how the ingest ended."
      format: enum
    verdictReason:
      readOnly: true
      type: string
      description: 'Upload only: "<finding code>: <message>" when the verdict is not PUBLISHED.'
    creator:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Actor"
      description: Who created it (the uploader, or the person who made the mixture).
    createTime:
      readOnly: true
      type: string
      description: When the dataset was first published or created.
      format: date-time
    updateTime:
      readOnly: true
      type: string
      description: When anything about it last changed (a ruling, an issue, a source, a tag).
      format: date-time
    etag:
      type: string
      description: |-
        Sent back on UpdateDataset and DeleteDataset to guard against lost
        updates; a stale etag fails with ABORTED.
    slug:
      readOnly: true
      type: string
      description: |-
        The name a training run cites (Version.reference = "<slug>:<tag>"),
        fixed when the dataset is created and never changed by a rename (a rename
        changes display_name only). Made from display_name: lowercased, runs of
        anything but [a-z0-9] turned into "-", leading and trailing "-" trimmed,
        the first 8 characters of the dataset id when nothing is left. Unique per
        account: a collision at creation takes the next free "-2", "-3" suffix.
    posesAt:
      readOnly: true
      enum:
        - POSES_AT_UNSPECIFIED
        - ROOT
        - TCP
      type: string
      description: |-
        Upload only, and only a handheld (UMI) upload whose state ingest placed in
        the pair frame: where its poses are. ROOT or TCP; unspecified for any
        other dataset. Read from the published repository (meta/ingest.toml and
        calibration.json), so it is filled by GetDataset alone: unset on
        ListDatasets, CreateDataset and UpdateDataset.
      format: enum
    posesCalibration:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.TcpCalibration"
      description: |-
        Upload only: the calibration the TCP poses were resolved with, as it was
        when they were. Set when poses_at is TCP, and then only by GetDataset.
        Compare it with a rig's calibration to tell whether that rig's
        calibration has been replaced since.
    posesRig:
      readOnly: true
      type: string
      description: |-
        Upload only, with poses_at: the key of the rig ingest recognised the
        upload as recorded on ("umi-handheld"), which is the rig whose
        calibration a ResolvePoses takes. Ingest decides it from the recorder's
        robot_type, from the upload's meta/ingest.toml `[rig]`, or from the state
        channel names, so it can name a rig when Episode.rig (for a LeRobot
        upload only meta/info.json's robot_type) is empty or something else.
        Filled by GetDataset alone, like poses_at.
    posesUnavailable:
      readOnly: true
      type: boolean
      description: |-
        Upload only: true when GetDataset could not read the upload's repository
        for poses_at, poses_calibration and poses_rig, so they are unknown here
        and unspecified does not mean there are none. The rest of the dataset is
        answered as usual; read it again for the poses.
    mergedDataset:
      readOnly: true
      type: string
      description: |-
        Upload only: the merged dataset that holds it, once it was merged into
        one (MergeDatasets). Empty otherwise, including after member removal.
        While held, it leaves the unfiltered ListDatasets; its reads
        keep answering, by name and by `slug` (its versions, which keep resolving
        as `<slug>:<tag>`, its takes, issues, ingest report and re-ingests), and
        every write that names it, or an episode, issue or version under it, is
        FAILED_PRECONDITION naming the merged dataset, through which it is
        written.
    curatedFrom:
      type: string
      description: |-
        Mixture only: the upload version it curates from, as
        accounts/{account}/datasets/{dataset}/versions/{tag}; set on create
        (CreateDataset) and never changed. Such a branch reads that upload as the
        version froze it, through one source pinned there: the upload's rulings
        and blocking issues since reach neither it nor what reads it, its
        episodes read at the revisions the version pinned, its own keeps and
        rejects curate it, and edits are made in the upload. Empty for any other
        dataset.
    reachSettings:
      allOf:
        - $ref: "#/components/schemas/qualia.v1.ReachSettings"
      description: |-
        How the dataset's reach check is walked, as the dataset stores it: what
        is unset follows the target robot's model (see Checks.GetReachStatus for
        what a RunCheck would use now). Saving it starts nothing.
    linkedTask:
      type: string
      description: |-
        accounts/{account}/tasks/{task}: the Task an upload is recorded against, or empty when unlinked. A link, not a label: labels.task stays
        the text. Set on an upload (a task of its account, NOT_FOUND otherwise),
        or on a merged dataset, whose own it is (chosen when the merge makes it:
        an answer, else its uploads' commonest; its uploads keep theirs); a
        mixture shows the commonest link of the uploads it reads (a merged
        dataset's own for the uploads it holds). Linking by label
        (LinkTaskLabels, and publishing a task) links an upload or a merged
        dataset by its own labels.task, never an upload a merge took. Deleting
        the task unlinks it. A mixture source can follow every episode linked to
        one task (Source.linked_task). Every take of an upload
        or a merged dataset with no link of its own reads the dataset's
        (Episode.linked_task_inherited): a merged dataset's takes read
        its own, not their uploads', so setting it links them all at
        once; UpdateDatasetRequest.relink_episodes moves the takes linked to the
        old one as well.
    linkedTaskRevision:
      readOnly: true
      type: integer
      description: |-
        The revision of linked_task the link carries, and the one
        checks judge the dataset's takes against: the Task's newest published
        revision when the link was made. 0 when unlinked, when the Task had no
        published revision then (its draft is judged), and on a mixture, which
        carries no link of its own. Publishing a newer revision leaves it, and
        starts no check; RelinkTask moves it to latest_task_revision and reruns.
      format: int32
    latestTaskRevision:
      readOnly: true
      type: integer
      description: |-
        linked_task's newest published revision, 0 when it has none (or nothing
        is linked, or on a mixture). Above linked_task_revision: the link has
        drifted, and RelinkTask would relink it.
      format: int32
    interfaceSettlement:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Dataset_InterfaceSettlement"
      description: |-
        Merged dataset only: member interfaces ready to freeze as one, and the
        uploads still awaiting or differing from the first member's interface.
    workspaceId:
      readOnly: true
      type: string
      description: |-
        The project that owns the dataset, empty when it is in the account's
        default workspace. Gateway clients select it with X-Qualia-Workspace.
    mergedSettingsContext:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.MergedSettingsContext"
      description: |-
        No longer set: a merge is final and a merged dataset is not
        corrected through its uploads. It was GetDataset's merged membership for
        a member settings operation.
    posesEligible:
      readOnly: true
      type: boolean
      description: |-
        No longer set: a merged dataset's poses are not resolved
        through its uploads.
    settingsSettlement:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Dataset_InterfaceSettlement"
      description: |-
        No longer set: a merged dataset's uploads were settled at the
        merge (one contract, one pose frame); interface_settlement is its view of
        an interface still re-deriving.
    settingsUnavailableReason:
      readOnly: true
      type: string
      description: No longer set, with settings_settlement.
    taskReferences:
      readOnly: true
      type: array
      items:
        $ref: "#/components/schemas/qualia.v1.Dataset_TaskReferences"
      description: "GetDataset only: one per Task, by display name."
    taskReferencedEpisodeCount:
      readOnly: true
      type: integer
      description: |-
        GetDataset only: how many distinct episodes those Tasks reference (an
        episode two Tasks reference counts once).
      format: int32
    tags:
      type: array
      items:
        type: string
      description: |-
        This dataset's tags: up to 64 nonblank strings of at most 256 bytes.
        Recognized collection names are teleop, vr, umi, egocentric,
        policy-rollout, sim, real-world, human and gello; they are ordered first
        in that order, followed by free tags in lexical order. Environment,
        origin, method and controller derive from saved metadata. The tags mask
        may echo unchanged classifications and edit custom strings only; use
        typed metadata masks for classification changes or Unknown. A merged
        dataset owns its metadata, which is never written
        to its uploads or replaced when uploads are appended. A mixture reads
        the tags of its sources. Distinct from episode curation and version tags.
    posePreparation:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.MergedPosePreparation"
      description: |-
        GetDataset only, merged datasets: each upload's progress toward its own
        recognized rig's CURRENT capture calibration. Independent of playback
        robot selection. A failed read is unknown, never a completed preparation.
    composedUmiRig:
      readOnly: true
      type: string
      description: |-
        accounts/{account}/robots/{robot}: a Robot resource name, the UMI rig's
        gripper row (a Robot of kind GRIPPER with a rig kind). It is NOT the legacy
        accounts/{account}/rigs/{rig} Rig resource. The UMI rig this upload was
        composed with when it began: the gripper the uploader named, else
        the workspace's one UMI rig. Recorded at publication, so a later edit or
        deletion of the rig does not change it. A copy in the same workspace carries
        it; a move to a workspace where the rig does not exist clears it (it is not
        listed among the robots cleared, and a move back does not restore it).
        Set by each upload's publication from that upload's own begin, empty
        included; a re-ingest, resolve or cut leaves it.
        Empty when no rig row composed the upload: it predates this field; it was
        uploaded by an SDK that sends no transfer session; the workspace's rig was
        lent unasked and the tree kept its own TCP; it is a UMI recording only
        ingest recognises (channel names, a LeRobot robot_type); the merged dataset
        it appends to lent its members' rig, which is their config, not a row; it
        was copied across workspaces; it was recorded on a robot's joints (see
        source_robot); or it is not an upload. Not set on a merged dataset or a
        mixture.
    rigs:
      readOnly: true
      type: array
      items:
        $ref: "#/components/schemas/qualia.v1.DatasetRig"
      description: |-
        The rigs this dataset's uploads were recorded on, most uploads
        first; ties by resource name (a client that shows display names orders
        ties itself); the entry with no robot last whatever its count. An upload
        is recorded on one rig: a handheld (UMI) upload's is the UMI rig that
        composed it (composed_umi_rig), and any other upload's is its source_robot.
        A merged dataset lists the distinct rigs of the uploads it holds, and a
        mixture those of the uploads it read at its last resolve, each with how
        many were recorded on it. Computed when read, never stored, so it follows
        a robot assignment, an append, a member's removal and a revert.

        An upload with no rig is counted in the entry with no robot, never
        dropped. That is a handheld upload published before uploads recorded their
        rig, or whose latest upload composed none (a kept tree TCP, a rig lent by
        the merge target, an SDK that names no transfer session, a recording only
        ingest recognises as handheld), an upload copied across workspaces, and an
        upload of a robot's joints with no robot assigned.

        A handheld upload never lists source_robot, which is a robot, not a
        gripper design. Known limit: a merge's old "Recorded on" answer, and a
        source_robot written on a merged dataset, are written to every upload the
        dataset holds, and only arm uploads list it, so such a dataset lists one
        rig for arm uploads that may have been recorded on several.
  description: |-
    A dataset is an upload (one recording session, as it arrived), a mixture
    (kept up to date from other datasets) or a merge of uploads (several
    uploads reviewed, versioned and trained as one). All three have the same
    page and the same API: episodes, issues and versions nest under any of them.

    Served by qds-api.
qualia.v1.Dataset_Labels:
  type: object
  properties:
    task:
      type: string
      description: Task label, e.g. "restock shelf". Empty means unlabeled.
    robot:
      type: string
      description: |-
        The robot label as recorded (a robot type key such as "openarm"), or
        "umi-handheld" for handheld capture. Not the fleet robot; see source_robot.
    operator:
      type: string
      description: Operator label (a name or a hashed id).
    method:
      type: string
      description: |-
        Legacy single-method value: teleop, vr, handheld, egocentric,
        policy_rollout, umi, policy-rollout, sim, or empty. An upload's starts
        as its episodes' commonest.
    scene:
      type: string
      description: Scene label, e.g. "shelf".
    methods:
      type: array
      items:
        type: string
      description: |-
        Compatibility projection of Dataset.tags containing only the original
        six collection-method names, including saved axis aliases. Legacy
        method edits declare their human methods and positive axis aliases;
        existing saved independent axes and free names remain authoritative.
        Use typed axis paths or human_methods_only for independent edits.
    environment:
      enum:
        - ENVIRONMENT_UNSPECIFIED
        - ENVIRONMENT_UNKNOWN
        - REAL_WORLD
        - SIMULATION
      type: string
      description: |-
        Saved environment authority for derived real-world/sim tags. An axis edit
        changes only its pair; missing evidence does not prove real-world.
      format: enum
    origin:
      enum:
        - ORIGIN_UNSPECIFIED
        - ORIGIN_UNKNOWN
        - HUMAN_COLLECTED
        - POLICY_ROLLOUT
      type: string
      description: |-
        Saved origin authority for derived human/policy-rollout tags. Missing
        policy metadata never proves human-collected origin.
      format: enum
    teleopInput:
      enum:
        - TELEOP_INPUT_UNSPECIFIED
        - TELEOP_INPUT_UNKNOWN
        - VR
        - GELLO
      type: string
      description: |-
        Saved controller authority for derived vr/gello tags. A controller does
        not establish teleoperation, and absence establishes no controller.
      format: enum
  description: |-
    The session-level labels an upload travels with. A mixture source can
    follow every upload that carries one value (see Source.label).
qualia.v1.Dataset_Counts:
  type: object
  properties:
    episodes:
      readOnly: true
      type: string
      description: "Episodes in the dataset (for a mixture: in its resolved head)."
    frames:
      readOnly: true
      type: string
      description: Frames across every episode.
    durationSeconds:
      readOnly: true
      type: number
      description: Recorded time across every episode, in seconds.
      format: double
    kept:
      readOnly: true
      type: string
      description: Episodes standing KEPT here.
    unreviewed:
      readOnly: true
      type: string
      description: Episodes standing UNREVIEWED here.
    rejected:
      readOnly: true
      type: string
      description: Episodes standing REJECTED here (validation rejects included).
    rejectedValidation:
      readOnly: true
      type: string
      description: Of those, rejected by ingest validation.
    openIssues:
      readOnly: true
      type: string
      description: |-
        Open blocking issues in this dataset's scope, on the dataset itself or on
        episodes not rejected here.
    sizeBytes:
      readOnly: true
      type: string
      description: |-
        Upload only, and set by GetDataset alone (it sums every file): the
        bytes of every file in its repository as published.
    fileCount:
      readOnly: true
      type: string
      description: "Upload only, and set by GetDataset alone: how many files those are."
    ruled:
      readOnly: true
      type: string
      description: |-
        Episodes a person ruled on here: those a person's keep or
        reject in this dataset's scope (a merged dataset's: its uploads' too)
        stands on, a split take's on each of its pieces without a ruling of
        their own. An episode kept because nothing blocks it, and a validation
        reject, are not counted. At most `kept + rejected`.
  description: Row counts. Frame totals can exceed 2^31, so they are int64 (a JSON string).
qualia.v1.Dataset_Channels:
  type: object
  properties:
    stable:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Dataset_Ref"
      description: The newest tag; unset when there is none yet.
    head:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Dataset_Ref"
      description: What the dataset yields now; always set.
  description: What each channel resolves to right now.
qualia.v1.Dataset_Ref:
  type: object
  properties:
    version:
      readOnly: true
      type: string
      description: The version tag it stands at, e.g. "1.0"; empty for head.
    digest:
      readOnly: true
      type: string
      description: Content digest of the episode set, "b3:<hex>".
    episodeCount:
      readOnly: true
      type: string
      description: Episodes in that set.
  description: One resolved reference.
qualia.v1.Dataset_Drift:
  type: object
  properties:
    version:
      readOnly: true
      type: string
      description: The tag measured from; empty when the dataset has no tag yet.
    added:
      readOnly: true
      type: string
      description: Episodes head yields that the tag does not.
    removed:
      readOnly: true
      type: string
      description: Episodes the tag pins that head no longer yields.
    changed:
      readOnly: true
      type: string
      description: |-
        Episodes in both whose pinned revision differs from head's (an edit:
        trim, subtasks, tags). Training reads exact revisions, so these count.
  description: |-
    How far head has moved past the newest tag. A dataset whose
    drift is not empty lights its tag button.
qualia.v1.Dataset_InterfaceSummary:
  type: object
  properties:
    interfaceHash:
      readOnly: true
      type: string
      description: Canonical robot-interface hash `b3:<hex>`; empty until derived.
    stateChannels:
      readOnly: true
      type: array
      items:
        type: string
      description: Canonical state channel keys, e.g. "left.joint.1", "right.ee.x".
    actionChannels:
      readOnly: true
      type: array
      items:
        type: string
      description: Canonical action channel keys.
    cameras:
      readOnly: true
      type: array
      items:
        type: string
      description: Camera keys, e.g. "context", "wrist_left".
    fps:
      readOnly: true
      type: number
      description: Frames per second of the data rows.
      format: double
    mixed:
      readOnly: true
      type: boolean
      description: "Mixture only: the episodes it reads come from more than one interface."
    nativeStateChannels:
      readOnly: true
      type: array
      items:
        $ref: "#/components/schemas/qualia.v1.InterfaceSummary_Channel"
      description: |-
        Each state channel in vector order. A converted handheld upload's are
        the pair-frame channels its takes are sampled in. A merged dataset's are
        its uploads' (one entry per native channel, with `sides` where they
        resolve it differently), on GetDataset only. Empty on a mixture, whose
        uploads each place their channels their own way.
    nativeActionChannels:
      readOnly: true
      type: array
      items:
        $ref: "#/components/schemas/qualia.v1.InterfaceSummary_Channel"
      description: Each action channel in vector order, as `native_state_channels`.
    nativeCameras:
      readOnly: true
      type: array
      items:
        $ref: "#/components/schemas/qualia.v1.InterfaceSummary_Channel"
      description: |-
        Each recorded camera with the camera key it is read as: `native_name` is
        the recording's feature (e.g. "observation.images.top") and
        `canonical_key` the key. A person's channel mapping can key a camera
        otherwise than its name ("observation.images.top" as "cam.overhead").
        An upload's are its cameras in recording order; an entry without
        `sides` binds the upload's own episodes only. A merged dataset's or a
        mixture's are one per feature any upload it reads records, in the order
        the first such upload places it, always with `sides` naming those
        uploads, and `canonical_key` the commonest key. So an episode reads a
        camera from the feature whose entry gives it that key for the episode's
        upload, and records the camera nowhere when no entry does. A version can
        pin episodes of an upload the dataset no longer reads: no entry names
        it, so its cameras are recorded nowhere.
  description: |-
    What the compatibility check reads: the robot interface and the
    cameras. For a mixture it is the intersection over what it reads, with
    `mixed` set when the interfaces differ.
qualia.v1.InterfaceSummary_Channel:
  type: object
  properties:
    nativeIndex:
      readOnly: true
      type: integer
      description: |-
        Its position in the state or action vector: the n of the "state[n]"
        and "action[n]" keys Episodes.SampleEpisodeSeries returns. A camera's
        is its place among the recording's cameras.
      format: int32
    nativeName:
      readOnly: true
      type: string
      description: The name the recording gives it, e.g. "left_joint_1.pos".
    canonicalKey:
      readOnly: true
      type: string
      description: |-
        The canonical key it resolves to, e.g. "left.joint_1.pos";
        "unmapped:<native name>" when the name could not be read.
    unit:
      readOnly: true
      type: string
      description: '"rad", "m" or "norm"; empty when not known.'
    mapped:
      readOnly: true
      type: boolean
      description: True when a person's channel mapping resolved it, not the name.
    sides:
      readOnly: true
      type: array
      items:
        $ref: "#/components/schemas/qualia.v1.InterfaceSummary_KeySide"
      description: |-
        A merged dataset's state or action channel: each key its uploads
        resolve it to, with the uploads that give it, when they do not agree
        (a merged dataset made before its merge asked, or while a remap
        settles). Empty when every upload gives `canonical_key`.
        A camera (`native_cameras`): each key it is read as, with the uploads
        that read it so. Always set on a merged dataset's or a mixture's, even
        where its uploads agree. On an upload's, set only when the upload
        reads one feature as two keys.
  description: |-
    One state, action or camera channel of an upload: where it sits in the
    recording and the canonical key it resolves to.
qualia.v1.InterfaceSummary_KeySide:
  type: object
  properties:
    canonicalKey:
      readOnly: true
      type: string
      description: The canonical key, e.g. "gripper.pos".
    datasets:
      readOnly: true
      type: array
      items:
        type: string
      description: "accounts/{account}/datasets/{dataset}: the uploads that give it."
  description: |-
    One canonical key some of a merged dataset's (or, for a camera, a
    mixture's) uploads give a channel.
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.TcpCalibration:
  required:
    - filename
    - hands
  type: object
  properties:
    filename:
      type: string
      description: The uploaded file's name, e.g. "tcp_calibration.json".
    deriveDate:
      type: string
      description: |-
        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.
    hands:
      type: array
      items:
        $ref: "#/components/schemas/qualia.v1.TcpCalibration_Hand"
      description: Both hands, left then right.
    eeLink:
      type: string
      description: |-
        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_<side>_ee_base_link at
        link7, 20.5 mm behind the frame the calibration is in.
  description: |-
    A handheld rig's calibration of its trackers to the gripper's tool centre
    point (TCP, the midpoint of the closed fingertips). It is what a tcp_calibration.json holds as `<side>.controller_to_tcp`: the TCP's pose in
    the tracker's frame, so a pose recorded at a tracker becomes the pose of the
    TCP by composing it with this transform.

    A file that also names the gripper's end-effector link gives the same TCP in
    that link's frame (`ee_link`, `Hand.ee_to_tcp`), which is what a rig's
    gripper design (Rig.gripper) draws the TCP with. Those two are for display:
    poses are resolved and uploads converted with `tracker_to_tcp`, `filename`
    and `derive_date` alone, never with `ee_to_tcp`. So compare two calibrations
    on those, `hands[].side` and `hands[].tracker_to_tcp` only: a copy kept
    elsewhere (a dataset keeps the calibration its poses were resolved with)
    carries just those, and a whole-message comparison would call every
    calibration that has the end-effector fields replaced.
qualia.v1.TcpCalibration_Hand:
  required:
    - side
    - trackerToTcp
  type: object
  properties:
    side:
      enum:
        - SIDE_UNSPECIFIED
        - SIDE_LEFT
        - SIDE_RIGHT
      type: string
      description: Which hand.
      format: enum
    trackerToTcp:
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Transform"
      description: |-
        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.
    eeToTcp:
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Transform"
      description: |-
        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.
  description: One hand's transform.
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.ReachSettings:
  type: object
  properties:
    solver:
      enum:
        - SOLVER_UNSPECIFIED
        - PYROKI
        - DLS
        - MINK
      type: string
      description: |-
        The IK solver the dataset is solved with: always its target robot's, and
        set only on a status read (a run's resolved settings). A dataset does not
        pick one: a request that names a solver here (UpdateDataset, CreateDataset)
        is refused with INVALID_ARGUMENT ("a dataset uses its robot's solver;
        change it on the robot's Solver page"), and a solver stored on a dataset
        before that is ignored. A robot with no solver cannot be solved on.
      format: enum
    baseM:
      type: array
      items:
        type: number
        format: double
      description: |-
        Where the takes' start sits from the relaxed grippers' midpoint, metres
        in the robot's base frame (x forward, y left, z up): empty or exactly
        three numbers, each finite and within 5 m either way. Empty follows the
        target robot model's `reach_base`, else 0, 0, 0. Stored to 0.1 mm and
        applied to both hand paths as one rigid translation from the robot's home
        hand midpoint.
    homeHeadingRad:
      type: number
      description: |-
        Rotate both paired paths around world up before placing their first-row
        midpoint at the shared home reference. Radians; finite and within +/- pi.
        The robot base remains at world (0, 0, 0).
      format: double
    poseSource:
      readOnly: true
      enum:
        - POSES_AT_UNSPECIFIED
        - ROOT
        - TCP
      type: string
      description: |-
        Where the source episode's hand poses were recorded. Set by qds-api on
        reads; clients do not set it. TCP runs consume already-resolved TCP
        channels and never apply calibration again.
      format: enum
    calibrationDigest:
      readOnly: true
      type: string
      description: |-
        Digest of the TCP calibration already applied to the published upload,
        empty for root poses. Set by qds-api on reads.
  description: |-
    How a dataset's reach check is walked: where the takes
    start and which way they face. It is stored on the dataset, so two datasets
    over one robot keep their own, and it is part of the key a reach result is
    stored under: a result is shared by every dataset that asks the same
    question (same robot config, solver, base and walk), and a change here
    leaves the old results as they are until the next RunCheck on
    `checks/reach`. The solver is not a dataset's to choose: a dataset uses its
    robot's, tuned in the robot's solver file.

    Served by qds-api.
qualia.v1.Dataset_InterfaceSettlement:
  type: object
  properties:
    settledMembers:
      readOnly: true
      type: integer
      description: Number of merged members whose derived interfaces are settled.
      format: int32
    totalMembers:
      readOnly: true
      type: integer
      description: Total number of uploads in the merged dataset.
      format: int32
    outstandingUploads:
      readOnly: true
      type: array
      items:
        type: string
      description: Upload resource names whose interfaces are not settled yet.
  description: |-
    Progress toward one settled robot interface across a merged dataset's
    uploads. A member remains outstanding while its accepted mapping lens is
    still being re-derived, or when its derived interface differs from the
    merged dataset's first member.
qualia.v1.MergedSettingsContext:
  required:
    - mergedDataset
    - members
    - membershipRevision
    - etag
  type: object
  properties:
    mergedDataset:
      type: string
      description: The merged dataset whose membership was confirmed.
    members:
      type: array
      items:
        type: string
      description: Every upload it held, in merge order, including the selected upload.
    membershipRevision:
      type: string
      description: Opaque merge/undo epoch from GetDataset; never recapture it on retry.
    etag:
      type: string
      description: The dataset's editable etag; a member edit sends the selected upload's.
    sourceCommit:
      type: string
      description: |-
        The selected upload's exact paired head. Empty on the merged dataset's
        context, which is used only to fence global label edits.
  description: |-
    No longer answered: a merge is final and a merged dataset is not
    corrected through its uploads. One an older client still sends is checked:
    every resource in the caller's account/workspace, the exact ordered
    membership, the selected upload's etag and its published head. A stale
    context is ABORTED.
qualia.v1.Dataset_TaskReferences:
  type: object
  properties:
    task:
      readOnly: true
      type: string
      description: accounts/{account}/tasks/{task}
    displayName:
      readOnly: true
      type: string
      description: The Task's display name.
    episodeCount:
      readOnly: true
      type: integer
      description: How many of the dataset's episodes it references.
      format: int32
  description: |-
    The Tasks this dataset's episodes are references of: a
    reference names its episode where it was recorded, or the merged dataset
    that upload went into, so a mixture has none. Deleting the
    dataset removes those references (the Tasks stay), so DeleteDataset
    refuses until it is sent with force.
qualia.v1.MergedPosePreparation:
  type: object
  properties:
    membershipRevision:
      readOnly: true
      type: string
      description: |-
        Opaque ordered-membership epoch. Send it back on an explicit move or
        removal; a changed membership is ABORTED and must be confirmed again.
    currentMembers:
      readOnly: true
      type: integer
      description: Number already at their current capture calibration.
      format: int32
    totalMembers:
      readOnly: true
      type: integer
      description: Number of uploads in the current membership.
      format: int32
    members:
      readOnly: true
      type: array
      items:
        $ref: "#/components/schemas/qualia.v1.MemberPosePreparation"
      description: Preparation state for each upload, in membership order.
    applicable:
      readOnly: true
      type: boolean
      description: False when none of these recordings has handheld poses to prepare.
  description: Current TCP preparation of the uploads held by one merged dataset.
qualia.v1.MemberPosePreparation:
  type: object
  properties:
    upload:
      readOnly: true
      type: string
      description: The source upload resource.
    displayName:
      readOnly: true
      type: string
      description: The source upload's display name.
    state:
      readOnly: true
      enum:
        - STATE_UNSPECIFIED
        - CURRENT
        - READY
        - RUNNING
        - BLOCKED
        - FAILED
      type: string
      description: The upload's current preparation state.
      format: enum
    reason:
      readOnly: true
      type: string
      description: Human-readable explanation when applicable.
    operationId:
      readOnly: true
      type: string
      description: UUID polled at this upload's reingests/{operation_id} resource.
    rigKey:
      readOnly: true
      type: string
      description: Recording rig, never an editable collection tag or playback robot.
    calibrationGripper:
      readOnly: true
      type: string
      description: The capture gripper supplying the calibration.
    calibrationEtag:
      readOnly: true
      type: string
      description: The capture calibration's version.
    publishing:
      readOnly: true
      type: boolean
      description: Preparation is complete and its publication is pending; it cannot be cancelled.
  description: |-
    One upload's current state. Names and reasons are suitable for the merged
    dataset's pre-check; no source data or historical rulings are removed.
qualia.v1.DatasetRig:
  type: object
  properties:
    robot:
      readOnly: true
      type: string
      description: |-
        accounts/{account}/robots/{robot}: the rig's Robot (a UMI rig's gripper
        row, or the robot an arm upload was recorded on). A rig that no longer
        resolves to a robot is still named here. Empty counts the uploads whose rig
        was never recorded.
    uploadCount:
      readOnly: true
      type: integer
      description: How many of the dataset's uploads were recorded on it.
      format: int32
  description: "One rig a dataset's uploads were recorded on: see Dataset.rigs."
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.
```
