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

# Move Time Entries

`POST /v1/time-entries/move`

## Request

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

Bulk segment reassignment of time entries (multi-select drag).

Moves 1..N entries to one target segment (or Unassigned with
``null``) in a single transaction, re-deriving each entry's
billability from the target segment. Per-entry domain failures come
back as structured ``FailedEntryMove`` items; the whole request
fails only on a missing target segment or infrastructure errors.
``moved`` rows are in cursor-sync wire shape for direct local-mirror
apply.

## Request body

- MoveTimeEntriesRequest: Request model for bulk segment reassignment of time entries. Backs the desktop client's multi-select drag; a single-entry drag is a one-element list through the same path. Billability is deliberately absent — the server derives it from the target segment (``resolve_entry_billability``).
  - `time_entry_ids` integer[], required: Time entries to move
  - `segment_id` string, nullable, required: Target segment ID. Set to null to move to Unassigned.
  - `restore_legal_times` EntryLegalTimeRestore[]: Undo only: typed legal times to put back verbatim instead of re-snapping, echoed from a prior response's ``moved_from``
    - `time_entry_id` integer, required: Entry to restore; ignored if not being moved
    - `legal_time_minutes` integer, required: The figure the entry held before the move

## Response `200`

Successful Response

- MoveTimeEntriesResponse: Partial-success envelope for the bulk entry move. ``moved`` carries the updated entries in cursor-sync wire shape (the desktop client force-applies them to its local SQLite mirror); ``failed`` carries per-entry rejections with structured codes. A failed entry 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. ``resnapped`` names the entries whose **typed** legal time the destination Matter's Billing rounding moved, so the client can say so rather than changing a figure someone typed in silence. The new value is already on that entry's ``moved`` row; this list only says which changed and why. An older build that does not model the field drops it, and the move still lands correctly — the snap is the server's, not the client's.
  - `moved` TimeEntrySyncDto[]: Successfully moved entries in cursor-sync wire shape
    - `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
  - `failed` FailedEntryMove[]: Per-entry rejections with structured codes
    - `time_entry_id` integer, required: ID of the time entry 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
  - `moved_from` EntryMoveOrigin[]: Pre-move parents of actually-moved entries (no-op members excluded)
    - `time_entry_id` integer, required: ID of the moved time entry
    - `previous_segment_id` string, nullable, required: Segment the entry was in immediately before the move (null=Unassigned)
    - `previous_legal_time_minutes` integer, nullable: The typed legal time the move re-snapped away, for Undo to echo back in ``restore_legal_times``. Null when nothing was re-snapped
  - `resnapped` integer[]: Entries whose typed legal time the destination Matter's Billing rounding moved. The new figure is on the matching ``moved`` row

## 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)
