---
title: "Upsert shifts by external id"
method: POST
path: "/v1/shifts/upsert-by-external-id"
tags: ["Shifts"]
---

# Upsert shifts by external id

`POST /v1/shifts/upsert-by-external-id`

## Request

- Base URL: `https://api.sandbox.usenash.com`
- URL: `https://api.sandbox.usenash.com/v1/shifts/upsert-by-external-id`
- Auth: none declared

Atomically create, update or no-op up to 500 shifts keyed by your external_id, in one transaction: any rejected item leaves every other item unwritten. Each shift is a complete planning snapshot (omitted fields are stored as defaults) and may set its courier, vehicle, store location and optimization_params.vehicle_config. Only PLANNED shifts can be written.

## Request body

- UpsertShiftsByExternalIdRequest
  - `shifts` ShiftUpsertInput[], required
    - `external_id` string, required
    - `status` 'PLANNED' | 'ACTIVE' | 'INACTIVE' | 'ON_BREAK' | 'COMPLETED' | 'CANCELLED'
    - `store_location_id` string, nullable
    - `zone_ids` string[]
    - `courier_id` string, nullable
    - `vehicle_id` string, nullable
    - `name` string, nullable
    - `planned_start` string, date-time, required
    - `planned_end` string, date-time, required
    - `planned_break_duration` integer
    - `optimization_params` ShiftOptimizationParamsInput: Typed replacement for the free-form `optimization_params` blob. Every value this schema admits converts cleanly through `ShiftOptimizationParams`, which stays the single normalization authority — `to_optimization_params_dict` delegates to it rather than re-deriving the stored shape, so a snapshot replayed unchanged keeps comparing equal to what a previous request persisted.
      - `serviceTimeRelativeEfficiency` number, nullable
      - `plannedBreaks` ShiftBreakConfigurationInput[]
        - `timeWindow` unknown[], required
          - unknown
        - `breakDuration` number, required
      - `breakConfig` BreakConfig
        - `anchor` 'shift_start', nullable
        - `preferOffRoute` boolean: Prefer breaks at the store when anchor is shift_start.
      - `departureDelay` number, nullable
      - `shiftEndServiceTimeMinutes` number, nullable
      - `vehicleConfig` ShiftVehicleConfigInput: Per-shift vehicle overrides. Field names and defaults mirror `nash.vehicles.VehicleConfig`.
        - `weight` integer
        - `height` integer
        - `length` integer
        - `width` integer
        - `axleCount` integer
        - `trailerCount` integer
        - `weightUnit` 'kg' | 'lbs'
        - `lengthUnit` 'meter' | 'in'
        - `maxStops` integer
        - `maxOrdersAtOnce` integer
        - `maxDistance` integer
        - `maxDuration` integer
        - `speedFactor` number
        - `capabilities` string[]
        - `fixedCost` number, nullable
        - `costPerUnitDistance` number, nullable
        - `costPerUnitTime` number, nullable
        - `vehicleProfile` string, nullable
        - `minOrdersPerRoute` integer, nullable
        - `parkingTime` number, nullable
    - `shift_metadata` object

## Response `200`

OK

- UpsertShiftsByExternalIdResponse
  - `results` ShiftUpsertReceipt[], required
    - `index` integer, required
    - `external_id` string, required
    - `id` string, required
    - `outcome` 'created' | 'updated' | 'unchanged', required
    - `shift` ShiftRead, required
      - `id` string, required
      - `organization_id` string, required
      - `external_id` string, nullable
      - `route_id` string, nullable
      - `store_location_id` string, nullable
      - `zone_ids` string[]
      - `courier_id` string, nullable
      - `vehicle_id` string, nullable
      - `name` string, nullable
      - `status` 'PLANNED' | 'ACTIVE' | 'INACTIVE' | 'ON_BREAK' | 'COMPLETED' | 'CANCELLED', required
      - `timezone_id` string, nullable
      - `planned_start` string, date-time, nullable
      - `planned_end` string, date-time, nullable
      - `actual_start` string, date-time, nullable
      - `actual_end` string, date-time, nullable
      - `planned_breaks` PlannedBreakWindow[]
        - `start_offset` string, duration, required
        - `end_offset` string, duration, required
        - `duration` string, duration, required
      - `taken_break_count` integer
      - `vehicle_config` ShiftVehicleConfig: The optimization-relevant vehicle settings a shift carries. Derived from `shift_metadata.optimization_params.vehicle_config`, which is where Coles keeps them and where the main optimize path reads them from. Only the fields a re-solve or re-time needs; `speed_factor` and `parking_time` (hours) keep travel times honest. HERE truck dimensions deliberately do not come from here — the shift's raw weight/length fields carry their own units, and the original solve reads dimensions from the contract.
        - `vehicle_profile` string, nullable
        - `speed_factor` number, nullable
        - `parking_time` number, nullable

## Other responses

- `422`: Unprocessable Content

---

[API](https://skmtc.dev/nash/apis/nash-api.md) - [All operations](https://skmtc.dev/nash/apis/nash-api/llms.txt) - [OpenAPI document](https://skmtc.dev/nash/apis/nash-api/revisions/e3713837c5c9?raw)
