---
title: "Move Time Blocks"
method: POST
path: "/v1/time-blocks/move"
tags: ["time-blocks"]
---

# Move Time Blocks

`POST /v1/time-blocks/move`

## Request

- Base URL: `https://app.tokumalabs.com/api`
- URL: `https://app.tokumalabs.com/api/v1/time-blocks/move`
- Auth: OAuth 2
  - Password
  - Token URL: `v1/auth/token`

Bulk move of time blocks to one target entry (multi-select drag).

Moves 1..N blocks to one target entry (or detaches with ``null``)
in a single transaction. Per-block domain failures come back as
structured ``FailedBlockMove`` items; the whole request fails only
on a missing/released target entry or infrastructure errors.
``moved`` and ``affected_time_entries`` are in cursor-sync wire
shape for direct local-mirror apply.

## Request body

- MoveTimeBlocksRequest: Request model for the bulk time-block move. Backs the desktop client's multi-select drag; a single-block drag is a one-element list through the same path. All blocks move to ONE target (or detach together) — the drag gesture has exactly one drop target.
  - `time_block_ids` integer[], required: Time blocks to move
  - `time_entry_id` integer, nullable, required: Target time entry ID. Set to null to detach every block.

## Response `200`

Successful Response

- MoveTimeBlocksResponse: Partial-success envelope for the bulk block move. ``moved`` and ``affected_time_entries`` are cursor-sync wire rows (the desktop client force-applies them to its local SQLite mirror); ``failed`` carries per-block rejections with structured codes. A failed block ships no row data — the server never touched it, so the client's local row is already correct. ``moved_from`` lists the authoritative pre-move parents of the rows that actually changed (no-op members excluded) — the client's Undo source of truth.
  - `moved` TimeBlockSyncDto[]: Successfully moved blocks in cursor-sync wire shape
    - `id` integer, required
    - `organization_id` string, required
    - `user_id` string, required
    - `start_time` string, date-time, required
    - `end_time` string, date-time, required
    - `headline` string, required
    - `is_live` boolean, required
    - `app_id` string, nullable
    - `app_name` string, nullable
    - `segment_id` string, nullable
    - `matter_segment` MatterSegmentSummaryDto: Summary of matter and segment for time blocks.
      - `matter_id` string, required: Matter ID
      - `matter_name` string, required: Matter name
      - `segment_id` string, required: Segment ID
      - `segment_name` string, required: Segment name
      - `segment_order` integer, required: Display order of the segment within the matter
      - `is_root` boolean, required: Whether this is the root segment
      - `root_segment_id` string, nullable: Root segment ID of the matter
    - `activity_code` string, nullable
    - `time_entry_id` integer, nullable
    - `source_type` 'APP' | 'MS365_CALL' | 'MS365_EMAIL' | 'MS365_CALENDAR_EVENT', required: Source of a time block.
    - `headline_attempt_status` 'not_attempted' | 'succeeded' | 'skipped_no_signal' | 'failed_terminal': Vocabulary for `time_blocks.headline_attempt_status`. Stored as a `(str, Enum)` so the cursor-sync wire DTO can type the field as the enum and Pydantic v2 serializes to the bare value ('not_attempted' etc.) — the qualified-name footgun of plain `str(EnumMember)` is avoided.
    - `artifacts` BlockArtifactsDto: Artifacts for one block: a capped list plus the honest total. ``total`` is the pre-cap count of displayed (doc+email) artifacts — the same population as ``items`` — so a client showing the first N can render an honest "+more" without the wire ever implying the capped list is complete.
      - `items` ArtifactDto[]
        - `kind` 'doc' | 'email', required
        - `title` string, required
        - `location` string, nullable
      - `total` integer
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
    - `deleted_at` string, date-time, nullable
    - `version` integer, required
  - `failed` FailedBlockMove[]: Per-block rejections with structured codes
    - `time_block_id` integer, required: ID of the time block that failed to move
    - `code` 'INVALID_CREDENTIALS' | 'REFRESH_TOKEN_REQUIRED' | 'INVALID_REFRESH_TOKEN' | 'REFRESH_TOKEN_REUSE_DETECTED' | 'USER_NOT_FOUND' | 'NOT_AUTHENTICATED' | 'MS365_CONFIG_NOT_FOUND' | 'MS365_INVALID_STATE' | 'MS365_TOKEN_EXCHANGE_FAILED' | 'MS365_INVALID_USER' | 'MS365_GRAPH_REQUEST_FAILED' | 'MS365_CREDENTIALS_REJECTED' | 'MS365_PERMISSION_DENIED' | 'WRONG_AUTH_METHOD' | 'CAPABILITY_REQUIRED' | 'LAST_ADMINISTRATOR' | 'TOO_MANY_REQUESTS' | 'VALIDATION_ERROR' | 'NOT_FOUND' | 'CONFLICT' | 'ALREADY_EXISTS' | 'STALE_CURSOR' | 'INVALID_CURSOR' | 'DATA_WIPE_FORBIDDEN_ENV' | 'TIME_BLOCK_INVALID_TIME_RANGE' | 'TIME_BLOCK_INVALID_SEGMENT' | 'SEGMENT_NOT_ASSIGNABLE' | 'TIME_BLOCK_INVALID_ACTIVITY_CODE' | 'TIME_ENTRY_RELEASE_NOT_FOUND' | 'TIME_ENTRY_RELEASE_EMPTY' | 'TIME_ENTRY_RELEASE_NO_MATTER' | 'TIME_ENTRY_RELEASE_NO_ACTIVITY_CODE' | 'TIME_ENTRY_RELEASE_NARRATIVE_PENDING' | 'TIME_ENTRY_NO_BLOCKS' | 'TIME_ENTRY_MERGE_NOT_FOUND' | 'TIME_ENTRY_MERGE_RELEASED' | 'TIME_ENTRY_MERGE_DIFFERENT_DATE' | 'TIME_ENTRY_MERGE_SELF' | 'GENERATION_UNAVAILABLE' | 'GENERATION_AI_INVALID_RESPONSE' | 'GENERATION_AI_PROVIDER_ERROR' | 'GENERATION_INPUT_TOO_LARGE' | 'GENERATION_CONCURRENT' | 'GENERATION_IN_PROGRESS' | 'GENERATION_TIMEOUT_RECOVERED' | 'GENERATION_DISPATCH_FAILED' | 'GENERATION_DISPATCH_LOST' | 'GENERATION_FAILED' | 'SPAN_EVENT_DISPATCH_EXHAUSTED' | 'EXTRACTION_IMAGE_NOT_FOUND' | 'EXTRACTION_IMAGE_INVALID' | 'EXTRACTION_AI_INVALID_RESPONSE' | 'EXTRACTION_AI_PROVIDER_ERROR' | 'EXTRACTION_INPUT_TOO_LARGE' | 'REASSEMBLY_ENRICH_FAILED' | 'REASSEMBLY_REBUILD_FAILED' | 'REASSEMBLY_TIMEZONE_INVALID' | 'REASSEMBLY_FAILED' | 'SPAN_OWNERSHIP_MISMATCH' | 'SPAN_INGEST_FAILED' | 'CAPTURE_IMAGE_OWNERSHIP_MISMATCH' | 'DATABASE_UNAVAILABLE' | 'UNKNOWN_ERROR', required: Centralized error codes for the API.
    - `message` string, required: Default human-readable message paired with code
  - `affected_time_entries` TimeEntrySyncDto[]: Entries whose tracked_duration_minutes (or membership) changed
    - `id` integer, required
    - `organization_id` string, required
    - `user_id` string, required
    - `date` string, date, required
    - `legal_time_minutes` integer, required
    - `legal_time_is_computed` boolean, required
    - `tracked_duration_minutes` integer, required
    - `narrative` string, nullable, required
    - `narrative_suggestions` string[], nullable
    - `activity_code` string, nullable
    - `segment_id` string, nullable
    - `matter_segment` MatterSegmentSummaryDto: Summary of matter and segment for time blocks.
      - `matter_id` string, required: Matter ID
      - `matter_name` string, required: Matter name
      - `segment_id` string, required: Segment ID
      - `segment_name` string, required: Segment name
      - `segment_order` integer, required: Display order of the segment within the matter
      - `is_root` boolean, required: Whether this is the root segment
      - `root_segment_id` string, nullable: Root segment ID of the matter
    - `is_billable` boolean, required
    - `legal_time_pinned` boolean, required
    - `legal_time_resnapped_from` integer, nullable
    - `is_user_created` boolean, required
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
    - `deleted_at` string, date-time, nullable
    - `version` integer, required
  - `moved_from` BlockMoveOrigin[]: Pre-move parents of actually-moved blocks (no-op members excluded)
    - `time_block_id` integer, required: ID of the moved time block
    - `previous_time_entry_id` integer, nullable, required: Entry the block was under immediately before the move (null=Unassigned)

## Other responses

- `400`: Validation error or domain rule violation.
- `401`: Missing, expired, or invalid authentication.
- `403`: Authenticated but not permitted.
- `404`: Resource not found.
- `409`: Resource state conflict (duplicate, illegal transition).
- `422`: Request body failed schema validation.
- `500`: Server-side failure.

---

[API](https://skmtc.dev/tokumalabs/apis/tokuma-user-api.md) - [All operations](https://skmtc.dev/tokumalabs/apis/tokuma-user-api/llms.txt) - [OpenAPI document](https://skmtc.dev/tokumalabs/apis/tokuma-user-api/revisions/1f8201af459b?raw)
