Update a catalog entry
PATCH/v1/catalog/entities/{entityId}
Edits a catalog entry, as the Catalog's drawer does. Send only what changes: content MERGES into the entry's (an absent field stays; send the empty value — "", [] or null where allowed — to clear one). images REPLACES the list: send it to reorder, re-describe (alt) or remove photos, or to attach an image uploaded through this API (by assetId); it cannot add an external URL. Move the entry in the tree with content.partOf. The kind never changes. Pass the updatedAt you read, exactly as you read it, as expectedUpdatedAt to refuse a stale write with a 409. Undo with POST /changes/{changeId}/revert.
content fields by kind — use exactly these names; an unknown field is refused. text fields accept plain text (one paragraph per line). partOf is the parent entry's id: a punto hangs from a destino; nothing hangs from a punto.
Every kind also takes: category: string, features: string[], altNames: string[], estado: borrador|migrado|revisado|publicado|obsoleto, verifiedAt: string (pattern)|null, source: string, reviewNotes: string.
- destino: partOf: uuid|null, intro: text, gettingAround: text, food: text, whenToGo: text, zoneNote: string
- punto: partOf: uuid|null, description: text, tips: text, warnings: text, durationMin: number, hours: {state: unknown|always|scheduled, rules: string, display: string, note: string}, closedWeekdays: mon|tue|wed|thu|fri|sat|sun[], momentsOfDay: amanecer|manana|mediodia|tarde|atardecer|noche[], bestSeasons: primavera|sakura|verano|otono|koyo|invierno|iluminaciones|todo_el_ano[], intensity: baja|media|alta, indoor: boolean, accessibility: unknown|accessible|limited|not, profiles: primera_vez|repetidor|familia_ninos|pareja|grupo|senior|movilidad_reducida|cultural|gastronomico|naturaleza|fotografia|otaku|compras|presupuesto_ajustado|premium[], priority: 1|2|3, paidEntry: boolean, priceNote: string, bookingUrl: string, arrival: text, mapUrl: string, googleFeatureId: string
- alojamiento: partOf: uuid|null, description: text, tips: text, warnings: text, includes: text, accessibility: unknown|accessible|limited|not, checkInWindow: {from: string (pattern), to: string (pattern)}, checkOutTime: string (pattern), mealTimes: {breakfast: string (pattern), dinner: string (pattern), note: string}
- actividad: partOf: uuid|null, description: text, tips: text, warnings: text, durationMin: number|null, hours: {state: unknown|always|scheduled, rules: string, display: string, note: string}, closedWeekdays: mon|tue|wed|thu|fri|sat|sun[], momentsOfDay: amanecer|manana|mediodia|tarde|atardecer|noche[], bestSeasons: primavera|sakura|verano|otono|koyo|invierno|iluminaciones|todo_el_ano[], intensity: baja|media|alta, indoor: boolean, accessibility: unknown|accessible|limited|not, profiles: primera_vez|repetidor|familia_ninos|pareja|grupo|senior|movilidad_reducida|cultural|gastronomico|naturaleza|fotografia|otaku|compras|presupuesto_ajustado|premium[], includes: text, meetingPoint: string, requiresReservation: boolean, fixedTime: boolean, providers: {key: string, name: string, place: object, partOf: uuid|null, notes: string}[], beforeYouGo: text
- servicio: partOf: uuid|null, description: text, tips: text, warnings: text, includes: text, provider: string
Permissions: catalog:write · Kind: write · Cost: 1 unit · MCP tool update_catalog_entity
Undoable: the response carries Bymundi-Change-Id; revert it with POST /v1/changes/{changeId}/revert.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
entityId |
uuid | yes |
Body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | max 200 chars | |
placeId |
string | The Google place id. Required for alojamiento. max 300 chars |
|
cityKey |
string | A normalized city key; derived from the name when absent. max 200 chars | |
place |
object | Where it is. The Google place id goes in placeId, not here. |
|
place.lat |
number | null | ||
place.lng |
number | null | ||
place.formattedAddress |
string | max 500 chars | |
content |
object | The kind's own fields — see the list in the description. | |
tags |
string[] | Lowercased and de-duplicated; at most 12. max 12 items | |
externalId |
string | Your system's id for this entry (e.g. a supplier's product code). Unique among the agency's live entries; "" clears it. max 200 chars | |
images |
object[] | The entry's whole photo list, in order. max 30 items | |
images[].assetId |
uuid | An image of this entry, or one uploaded through this API. | |
images[].url |
string | An image of this entry, by the url it already has. max 2000 chars | |
images[].alt |
string | A description of the photo for screen readers. max 300 chars | |
expectedUpdatedAt |
datetime | The entry's updatedAt as you read it. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
object |
"catalog_entity" | yes | |
id |
uuid | yes | |
kind |
"destino" | "punto" | "alojamiento" | "actividad" | "servicio" | yes | |
name |
string | yes | |
placeId |
string | yes | |
cityKey |
string | yes | |
externalId |
string | yes | |
place |
object | yes | |
images |
object[] | yes | |
tags |
string[] | yes | |
origin |
"human" | "ai" | "api" | "import" | yes | |
content |
object | yes | |
updatedAt |
string | yes | |
deletedAt |
string | null | yes |
Errors
Errors are problem details. Besides the refusals described above, any call like this one can return:
invalid_request— Invalid requestunauthorized— Missing or invalid API keyinsufficient_scope— Missing permissionrate_limited— Rate limit reachednot_found— Not foundidempotency_key_reused— Idempotency-Key reusedrequest_in_progress— Request in progress