> ## Documentation Index
> Fetch the complete documentation index at: https://teardowns.aero/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Transition a teardown

> Move a teardown through the lifecycle state machine.

Drives a teardown between lifecycle states. Use this not PATCH to
change a teardown's `status`. Each action is validated against the
state machine; trying an invalid transition returns `400` with a clear
message.

See [status lifecycle](/concepts/status-lifecycle) for the full state
diagram.

## Headers

<ParamField header="Authorization" type="string" required>
  `Bearer tdao_live_…`
</ParamField>

<ParamField header="X-Organization-Id" type="string" required>
  Your organization's UUID.
</ParamField>

<ParamField header="Content-Type" type="string" required>
  `application/json`
</ParamField>

## Path parameters

<ParamField path="teardown_id" type="string (UUID)" required />

## Body

<ParamField body="action" type="string" required>
  The transition action to perform. See the table below.
</ParamField>

<ParamField body="reason" type="string">
  Required when `action="reject"`. Optional otherwise. Stored in the
  audit row's metadata.
</ParamField>

## Actions

| Action             | Valid from          | Result                  | Requires |
| ------------------ | ------------------- | ----------------------- | -------- |
| `start_processing` | `active_starting`   | `active_in_process`     | `seller` |
| `complete`         | `active_in_process` | `active_completed`      | `seller` |
| `unpublish`        | any `active_*`      | `draft` (UI-only state) | `seller` |
| `archive`          | any                 | `archived`              | `seller` |
| `restore`          | `archived`          | `draft`                 | `seller` |
| `submit`           | `draft`             | `active_starting`       | `seller` |

## Response

`200 OK`. A small transition response:

<ResponseField name="id" type="string (UUID)">
  The teardown's id.
</ResponseField>

<ResponseField name="status" type="string">
  The new status after the transition.
</ResponseField>

<ResponseField name="previous_status" type="string | null">
  The prior status. Populated for admin-only transitions; usually
  `null` for the seller-facing actions listed above.
</ResponseField>

<Note>
  The transition endpoint currently returns the **internal** field names
  (`id`, `status`). The other teardown endpoints have moved to the
  partner-facing names (`teardown_id`, etc.). We'll align this in a
  future release.
</Note>

<RequestExample>
  ```bash start_processing theme={null}
  # active_starting -> active_in_process
  curl -X POST "$base_url/public/v1/teardowns/transition/$teardown_id" \
    -H "Authorization: Bearer $api_key" \
    -H "X-Organization-Id: $org_id" \
    -H "Content-Type: application/json" \
    -d '{"action": "start_processing"}'
  ```

  ```bash complete theme={null}
  # active_in_process -> active_completed
  curl -X POST "$base_url/public/v1/teardowns/transition/$teardown_id" \
    -H "Authorization: Bearer $api_key" \
    -H "X-Organization-Id: $org_id" \
    -H "Content-Type: application/json" \
    -d '{"action": "complete"}'
  ```

  ```bash unpublish theme={null}
  # any active_* -> draft
  curl -X POST "$base_url/public/v1/teardowns/transition/$teardown_id" \
    -H "Authorization: Bearer $api_key" \
    -H "X-Organization-Id: $org_id" \
    -H "Content-Type: application/json" \
    -d '{"action": "unpublish"}'
  ```

  ```bash archive theme={null}
  # any -> archived (soft delete)
  curl -X POST "$base_url/public/v1/teardowns/transition/$teardown_id" \
    -H "Authorization: Bearer $api_key" \
    -H "X-Organization-Id: $org_id" \
    -H "Content-Type: application/json" \
    -d '{"action": "archive"}'
  ```

  ```bash restore theme={null}
  # archived -> draft
  curl -X POST "$base_url/public/v1/teardowns/transition/$teardown_id" \
    -H "Authorization: Bearer $api_key" \
    -H "X-Organization-Id: $org_id" \
    -H "Content-Type: application/json" \
    -d '{"action": "restore"}'
  ```

  ```bash submit theme={null}
  # draft -> active_starting
  curl -X POST "$base_url/public/v1/teardowns/transition/$teardown_id" \
    -H "Authorization: Bearer $api_key" \
    -H "X-Organization-Id: $org_id" \
    -H "Content-Type: application/json" \
    -d '{"action": "submit"}'
  ```

  ```python python theme={null}
  import requests

  def transition(teardown_id: str, action: str) -> dict:
      resp = requests.post(
          f"{base_url}/public/v1/teardowns/transition/{teardown_id}",
          headers={
              "Authorization": f"Bearer {api_key}",
              "X-Organization-Id": org_id,
          },
          json={"action": action},
      )
      resp.raise_for_status()
      return resp.json()

  # Drive a teardown through the full lifecycle
  transition(teardown_id, "start_processing")  # -> active_in_process
  transition(teardown_id, "complete")          # -> active_completed
  transition(teardown_id, "archive")           # -> archived
  transition(teardown_id, "restore")           # -> draft
  transition(teardown_id, "submit")            # -> active_starting
  transition(teardown_id, "unpublish")         # -> draft
  ```
</RequestExample>

<ResponseExample>
  ```jsonc 200 OK theme={null}
  {
    "id": "3c707051-021d-4d04-a8e7-4eb254e80858",
    "status": "active_in_process",
    "previous_status": null
  }
  ```

  ```jsonc 400 Invalid transition theme={null}
  {
    "detail": "Cannot perform 'complete' from status 'active_starting'"
  }
  ```

  ```jsonc 400 Admin-only action theme={null}
  {
    "detail": "Action 'approve' requires admin role"
  }
  ```

  ```jsonc 400 Reject without reason theme={null}
  {
    "detail": "Reason required for rejection"
  }
  ```
</ResponseExample>

## Audit trail

Every transition writes one audit row:

* `action` = `teardown.<your-action>` (e.g., `teardown.complete`,
  `teardown.archive`).
* `previous_state` = `{ "status": "<old-status>" }`
* `new_state` = `{ "status": "<new-status>" }`
* `metadata.via_api = true` plus the standard API-key tags.
* For `reject`: `metadata.reason` carries your reason string.

## See also

* [Status lifecycle](/concepts/status-lifecycle) for the full state
  diagram and the conceptual model.
* [Delete a teardown](/api-reference/teardowns/delete) for hard delete
  vs. archive trade-offs.
* [Update a teardown](/api-reference/teardowns/update) for changing
  fields other than `status`.
