---
title: "Update Card Positions"
method: PUT
path: "/api/v1/geo/hotels/{geo_hotel_id}/card-positions"
tags: ["geo-monitoring", "geo-hotels"]
---

# Update Card Positions

`PUT /api/v1/geo/hotels/{geo_hotel_id}/card-positions`

## Request

- Base URL: `https://api.getanana.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.getanana.com/api/v1/geo/hotels/{geo_hotel_id}/card-positions`
- Auth: none declared

Persist the Groups & Experiments grid layout for one hotel.

Writes ``position`` and NOTHING else. Experiment config (daily scan, samples,
model selection) stays on ``PUT /experiments/{id}``: dragging a card is a much
lighter action than reconfiguring an experiment, and layout must not widen the
write surface on those columns.

The layout is shared by every user of the tenant, scoped to this hotel — the
same model Reporting uses for ``Widget.position``.

``MANAGE_SETTINGS`` costs nothing in reach here: the Groups & Experiments page
is already gated on ``MANAGE_SETTINGS`` (the Visibility Lab area gate), so a
viewer without it never renders a card to drag in the first place. This gate is the server-side half of that same
rule, not a second, stricter one. (Reporting authorizes layout with a per-tab
ACL — ``can_edit = admin OR access_row.can_edit`` — so an admin there can grant
a named member edit access. AI Presence has no grant mechanism and the tenant
permission model is roles-only, but that difference only becomes visible if the
page-level gate is ever relaxed.)

Every id is validated against this hotel BEFORE anything is written, so a
request naming a foreign card changes nothing at all rather than applying
partially.

## Path parameters

- `geo_hotel_id` string, uuid, required

## Request body

- GeoCardPositionsUpdate: Bulk layout save for one hotel's Groups & Experiments grid. Deliberately carries ONLY positions. Experiment config (daily scan, samples, model selection) stays on ``PUT /experiments/{id}``: layout is a far lighter action than configuration and must not widen the write surface on those. Both lists are optional so a save can touch groups, experiments, or both.
  - `experiments` GeoCardPositionUpdate[]
    - `id` string, uuid, required
    - `position` object, required
  - `groups` GeoCardPositionUpdate[]
    - `id` string, uuid, required
    - `position` object, required

## Response `204`

Successful Response

## Other responses

- `404`: GEO hotel not found
- `422`: Validation Error

## Changes

> 55 revisions in range; 1 not diffed.

- **2026-09-03** `d65b8fefae34`: 2 info
  - added the optional property `detail/items/ctx` to the response with the `422` status
  - added the optional property `detail/items/input` to the response with the `422` status

[Operation history](https://skmtc.dev/getanana/apis/cleon-api/changes/api/v1/geo/hotels/:geo_hotel_id/card-positions/put.md)

---

[API](https://skmtc.dev/getanana/apis/cleon-api.md) · [All operations](https://skmtc.dev/getanana/apis/cleon-api/llms.txt) · [OpenAPI document](https://skmtc.dev/getanana/apis/cleon-api/revisions/4d9d38769a75?raw)
