PATCH /moves/:id/workflows/:slot/data
Merge integration-owned keys into a move's workflow data for a given slot. Editability is driven by the move's workflow definition: a key can be written through this endpoint only when the corresponding step in the move's pickup_workflow.steps or delivery_workflow.steps has config.allow_server_edits set to true.
The pickup workflow reads these keys to gate driver actions (e.g., a loaner cannot leave the lot until consumer-info-complete is true).
This endpoint is idempotent and safe to retry.
Terminology: the move's "rooftop" and the move's
customer_idrefer to the same thing. The bearer token'sx-hasura-allowed-customersclaim is checked against this value.
curl --request PATCH \
--url https://api.hopdrive.com/v1/moves/:id/workflows/:slot/data \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <your token>' \
--data '{ "consumer-info-complete": true }'
Example Request
{
"consumer-info-complete": true
}
Example Request — Multiple Keys
{
"consumer-info-complete": true,
"internal-reference": "ABC-123"
}
URL Params
| Field | Type | Required? | Description |
|---|---|---|---|
| id | Number | Required | The move's numeric ID. |
| slot | String | Required | The workflow slot to merge into. "pickup" or "delivery". |
Headers
| Header | Required? | Description |
|---|---|---|
| Content-Type | Required | Must be application/json. |
| Authorization | Required | Bearer token from POST /v1/authorize. |
Body
The body is a flat JSON object whose keys map to editable step IDs in the slot's workflow. A step is editable only when its config.allow_server_edits is true. The value's type must match the step's type (boolean, string, number, or integer); a wrong-typed value returns 422.
To discover which keys are editable for a given move, call GET /v1/moves/:id/workflows/:slot and inspect the returned steps[*] for entries whose config.allow_server_edits is true. Each such entry's id is a writable key in this endpoint, and its type constrains the value.
In the standard pickup workflow, consumer-info-complete is opted in this way (boolean). Body keys without a matching server-editable step are returned in the response's ignored_keys array — useful for catching typos.
Behavior
- The lifecycle lock is per-slot:
- Pickup slot is editable while the move is pre-pickup. Once the status reaches
pickup successful,delivery started,delivery arrived, ordelivery successful, pickup writes are rejected with409. - Delivery slot is editable through the in-flight statuses (
pickup successful,delivery started,delivery arrived) so integrators can update consumer info while the driver is en route. Onlydelivery successfullocks the delivery slot.
- Pickup slot is editable while the move is pre-pickup. Once the status reaches
- A move with a non-null
cancel_statusrejects writes to either slot. - If the slot's column is
null, it is initialized with the provided keys. - If the column already contains other keys, the provided keys are merged in without disturbing them.
- Body keys that don't match a server-editable step are surfaced in
ignored_keys. If every key in the body is ignored, the request is rejected with422and the ignored keys are listed in the error message.
Idempotency
This endpoint is idempotent. Resending the same body produces the same state. If every key in the request already matches the stored value, the response returns 200 with the existing updated_at — no row write, and no downstream triggers fire. Safe to retry on transport errors.
Concurrency
Last writer wins on overlapping keys; non-overlapping keys are preserved. There is a small TOCTOU window between the read and the write: if the column is null at read time and a concurrent write populates it before this request commits, the concurrent value can be clobbered. In practice this is only a concern if integration writes race against driver writes on the same move and slot — avoid that pattern.
Example Response
{
"id": 10033,
"slot": "pickup",
"workflow_data": {
"consumer-info-complete": true
},
"ignored_keys": [],
"updated_at": "2026-04-28T14:05:00+00:00"
}
Response Fields
| Field | Type | Description |
|---|---|---|
| id | Number | The move's numeric ID. |
| slot | String | Echoes the slot URL parameter ("pickup" or "delivery"). |
| workflow_data | Object | The slot's workflow data, filtered to keys whose corresponding step has allow_server_edits: true. Driver-recorded keys are not surfaced. |
| ignored_keys | Array | Body keys that did not match a server-editable step in the slot's workflow. Use to catch typos. Empty when all body keys matched. |
| updated_at | String | ISO 8601 timestamp of the move's last update. Unchanged on idempotent no-op writes. |
Error Responses
All errors follow this envelope:
{
"errors": [
{ "type": "invalid_request_error", "code": "REQUEST_INVALID", "message": "..." }
]
}
| Status | Description |
|---|---|
| 400 | Invalid move ID. |
| 401 | Missing or invalid authorization token. |
| 403 | The bearer token is malformed, or its allowed-customers claim does not include the move's rooftop. |
| 404 | Move not found, or slot is not a known workflow slot. |
| 409 | The slot's lifecycle is locked: pickup is past pickup-successful, delivery is at delivery-successful, or the move has been canceled. |
| 422 | Request body has no editable fields after matching against the workflow steps, or a value's type doesn't match the step's type. When all body keys were ignored as typos, the message lists them. |
| 500 | Unexpected server error. |