---
title: "Rebind By Remote"
method: PUT
path: "/datamate-project-bindings/by-remote"
tags: ["DatamateProjectBindings"]
---

# Rebind By Remote

`PUT /datamate-project-bindings/by-remote`

Atomically move an active binding from its current workspace to a new one.

Holds a row-level ``FOR UPDATE`` lock on the existing binding for the
duration of the transaction so two concurrent PUTs against the same
remote serialize cleanly. When ``expected_current_datamate_id`` is
provided, it must equal the currently-bound workspace's id or the swap
is rejected with 412 (optimistic concurrency); when absent, the swap is
unconditional — the caller is explicitly opting into last-writer-wins.

Authorization: the caller must be the binding's own creator, and must be
able to see the target workspace (the same rule as ``GET /datamates/``).
Visibility collapse applies: an invisible source or target answers 404
rather than 403, so the endpoint does not double as an existence oracle.

Owning the source workspace is deliberately NOT sufficient — it would
allow re-pointing a binding someone else created. The binder is checked
before the source-visibility gate on purpose, so that losing access to the
source (a revoked share) cannot strand a project the caller bound
themselves. See ``_acquire_rebind_locks``.

The row is mutated in place rather than delete+insert; ``created_at`` keeps
reflecting the original binding, and ``user_id`` is left alone because
moving a binding does not transfer it.

## Request body

- RebindByRemoteRequest — Body for PUT /datamate-project-bindings/by-remote (atomic re-link). Mutates the existing active binding for ``repo_remote`` **in place**, re-pointing its ``datamate_id`` at ``target_datamate_id``. Runs in a single transaction under a ``SELECT ... FOR UPDATE`` row lock so two concurrent PUTs against the same remote serialize cleanly. When ``expected_current_datamate_id`` is provided, it must equal the currently -bound workspace's id or the swap is rejected with 412 (optimistic concurrency). When absent, the swap is unconditional — the caller explicitly opts into last-writer-wins. Note: the binding row is mutated in place rather than delete-then-insert (consensus review m5). ``created_at`` therefore reflects the original binding's creation, not the rebind moment.
  - `repo_remote` string, required
  - `target_datamate_id` integer, required
  - `expected_current_datamate_id` integer, nullable

## Response `200`

Successful Response

- BindingResponse — Response for the bind-existing and rebind endpoints.
  - `binding` BindingModel, required — Wire shape returned to the CLI for a single binding row. Either ``repo_remote`` or ``project_path`` is present (at least one; the row's CHECK constraint enforces this). Projects with a git remote use ``repo_remote``; projects without one (materialized sample scaffolds, fresh scratch dirs) use ``project_path`` as the fallback identifier.
    - `id` integer, required
    - `datamate_id` integer, required
    - `datamate_name` string, required
    - `repo_remote` string, nullable
    - `project_path` string, nullable
    - `created_at` string, date-time, nullable

## Other responses

- `400` — Invalid request
- `403` — Not the current or target owner
- `404` — No existing binding for this remote
- `412` — Current binding does not match expected_current_datamate_id
- `422` — Validation Error

## Changes

- **2026-08-21** `b9cc757ca795` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/myaltimate/apis/fastapi/changes/datamate-project-bindings/by-remote/put.md)

---

[API](https://skmtc.dev/myaltimate/apis/fastapi.md) · [All operations](https://skmtc.dev/myaltimate/apis/fastapi/llms.txt) · [OpenAPI document](https://skmtc.dev/myaltimate/apis/fastapi/revisions/4dfe86ff412c?raw)
