Cleanup menu
POST /v1/cleanupRemoves synced items that are not in the lists you send — the inverse
of sync. For each array you provide, you pass the externalIds to
keep; everything else that was synced from your system is deactivated
or deleted:
| Array provided | What happens to items NOT in the list |
|---|---|
productExternalIds | Products are deactivated (menuVisible: false) — recoverable by re-syncing |
categoryExternalIds | Categories are deleted; their products are kept and unlinked (no category) |
ingredientExternalIds | Ingredients are deleted, including from product compositions and modifier options |
Two important scoping rules:
- Only items that have an
externalId(i.e. came through the API) are affected. Items created manually in the DuckHub app are never touched. - Arrays you omit are left completely alone — send only the entity types you want to clean.
Sending no arrays at all is a no-op (success: true with a warning).
Deletes are permanent; empty lists need force
Category and ingredient deletion cannot be undone. An empty
array means "keep nothing" — it would wipe every synced item of
that type, so the API rejects it with 400 unless you also
send force: true. Deactivated products are the one soft
case: re-sync them to restore.
Request body#
| Field | Type | Required | Meaning |
|---|---|---|---|
productExternalIds | string[] | No | Products to keep visible |
categoryExternalIds | string[] | No | Categories to keep |
ingredientExternalIds | string[] | No | Ingredients to keep |
force | boolean | No | Default false; must be true when any provided array is empty |
Example#
Typical full-sync flow — after pushing the current catalog with
sync, clean up anything your POS no longer has, then
publish:
curl -X POST https://api.duck-hub.com/v1/cleanup \
-H "Authorization: Bearer dk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"productExternalIds": ["prod-margherita", "prod-pepperoni"],
"categoryExternalIds": ["cat-pizza"],
"ingredientExternalIds": ["ing-mozzarella", "ing-olives"]
}'const response = await fetch('https://api.duck-hub.com/v1/cleanup', {
method: 'POST',
headers: {
Authorization: 'Bearer dk_live_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
productExternalIds: ['prod-margherita', 'prod-pepperoni'],
categoryExternalIds: ['cat-pizza'],
ingredientExternalIds: ['ing-mozzarella', 'ing-olives'],
}),
})
const result = await response.json()Response#
201 Created:
{
"success": true,
"deactivatedProducts": 3,
"deletedCategories": 1,
"deletedIngredients": 2,
"warnings": []
}| Field | Meaning |
|---|---|
deactivatedProducts | Synced products hidden from the menu (menuVisible: false) |
deletedCategories | Synced categories removed (products unlinked first) |
deletedIngredients | Synced ingredients removed everywhere |
warnings | Non-fatal notes |
Cleanup changes the draft menu — run
POST /v1/publish afterwards to update what guests
see.
Errors#
400Passing an empty array would affect ALL items. Set force: true to confirm this action.— an empty array withoutforce401— see Authentication