---
title: "Bulk Delete Logs"
method: POST
path: "/api/v1/logs/bulk-delete"
tags: ["logs"]
---

# Bulk Delete Logs

`POST /api/v1/logs/bulk-delete`

Soft-delete selected logs (sets deleted_at; excluded from metrics/dashboards/list).

Idempotent — re-deleting already-deleted logs is a no-op.

## Request body

- BulkDeleteRequest — Request body for POST /logs/bulk-delete. PR 26 adds ``subject`` + ``ids``, matching the shape the other bulk routes use. The three per-channel lists stay accepted for one deploy so a client on the previous bundle keeps working; PR 50 drops them. PR 44 adds ``selection`` — "delete everything matching this filter" — which the other bulk routes express through ``TargetedBulkRequest``. This one cannot inherit it while the three deprecated per-channel lists survive, so it restates the field and defers the resolution to the same ``resolve_selection``. PR 50 drops the legacy lists and this becomes a ``TargetedBulkRequest`` like its four siblings.
  - `subject` 'voice' | 'text' | 'email' | 'booking', nullable
  - `ids` string[]
  - `selection` BulkSelection — Either explicit ids or a filter, never both, never neither. **Fail closed.** An explicitly empty ``ids`` matches NOTHING; it never widens to "everything in the view". That is the existing contract for exports (``TestSelectedIdsFailClosed``) and the stakes are higher for a mutation: a buggy caller that sends ``[]`` must delete nothing, not delete the tenant's entire log history.
    - `ids` string[], nullable
    - `filters` LogListParams — The universal filter set, shared by all four log lists. Subject-native parameters (voice's transfer telemetry, email's ``include_spam``, bookings' stay axis) stay declared on their own routes — see the plan's §3.4. This model covers only what all four mean the same way. Pagination is intentionally defaulted to ``None`` rather than a number: the four routes have different historical page sizes (voice 10, text 20, bookings 50) and silently changing one would re-page every existing caller. Each route passes its own default to :meth:`resolve_pagination`.
      - `limit` integer, nullable
      - `offset` integer, nullable
      - `page` integer, nullable — Deprecated — use `offset`.
      - `per_page` integer, nullable — Deprecated — use `limit`.
      - `start_date` string, date, nullable
      - `end_date` string, date, nullable
      - `date_field` string, nullable
      - `date_from` string, date-time, nullable — Deprecated — use `start_date`.
      - `date_to` string, date-time, nullable — Deprecated — use `end_date`.
      - `use_created_at` boolean, nullable — Deprecated — use `date_field=created_at`.
      - `property_ids` string[], nullable — Filter to logs associated with any of these hotels.
      - `property_ids_exclude` string[], nullable — Exclude logs associated with any of these hotels; logs with no hotel are kept, mirroring the widget aggregate.
      - `include_considered_hotels` boolean, nullable — Whether the hotel filter also matches hotels the conversation was merely CONSIDERED for, as well as the hotel it was attributed to. Applies to both property_ids and property_ids_exclude. Unset keeps the historical any-association behaviour during the rollout.
      - `agent_id` string, uuid, nullable — Filter by the agent that handled the log.
      - `outcome_agent_ref` string, nullable — Filter by outcome owner, as 'ai_voice:<uuid>' / 'ai_text:<uuid>' / 'human:<uuid>'. Use '__null__' for unassigned owners.
      - `outcome_agent_refs` string[], nullable — Filter to logs owned by any of these refs.
      - `outcome_agent_refs_exclude` string[], nullable — Exclude logs owned by any of these refs; unassigned owners are kept, mirroring the widget aggregate.
      - `outcome_agent_kind` 'ai_voice' | 'ai_text' | 'human', nullable — Filter by the outcome owner's kind.
      - `reviewed` boolean, nullable
      - `status` string, nullable
      - `has_booking_intent` boolean, nullable — Filter to logs with a recorded booking inquiry.
      - `has_booking` boolean, nullable
      - `booking_made` boolean, nullable
      - `booking_attributed` boolean, nullable
      - `tag_ids` string[], nullable
      - `country` string, nullable
      - `is_after_hours` boolean, nullable
      - `population_filters` string[], nullable — Named conversation-population filters (business_hours, min_call_25s, real_customer_message, exclude_spam_test, dedupe_24h). Unknown names are a 422. A name that does not apply to this subject is a no-op.
      - `search` string, nullable
      - `standard_filters` string, nullable — JSON array of {field, value} for standard-column filters.
      - `metric_filters` string, nullable — JSON array of [{metric_id, outcome}] for multi-metric AND filtering.
      - `sort_by` string, nullable
      - `sort_order` 'asc' | 'desc', nullable
      - `strict` boolean
      - `voice_agent_id` string, uuid, nullable — Deprecated — use `agent_id`.
      - `metric_id` string, uuid, nullable — Deprecated — use `metric_filters`.
      - `metric_state` 'pass' | 'fail' | 'na' | 'not_run' | 'error', nullable — Deprecated — use `metric_filters`.
      - `booking_intent` boolean, nullable — Deprecated — use `has_booking_intent`.
    - `exclude_ids` string[]
    - `booking_filters` BookingListSelectors — The booking-only query parameters of the ``/api/v1/logs/booking`` list. Bookings are the one subject whose filters are mostly NOT in ``LogListParams``: status code, ingestion origin, attribution tier, the provider source id, the discrepancy flag and the two legacy date axes are all booking vocabulary, and the universal params have no spelling for any of them. That matters the moment a mutation acts over "everything matching the current filter" (PR 44). A filter-scoped bulk action carrying only ``LogListParams`` would resolve a **wider** population than the screen showed — the operator filters to ``origin=agent_tool``, and the tag lands on every booking in the date range. So the selectors travel with the selection, and this model is the shape they travel in. Field names follow the route's query parameters, with **two deliberate exceptions**: the route spells the hotel filter ``property`` / ``property_exclude`` (short, because it is typed into URLs) while this model and ``BookingListFilters`` both spell it ``property_codes`` / ``property_codes_exclude`` — the name that says what it holds, since bookings scope by the provider's property CODE rather than by hotel UUID like the three conversation subjects. The booking page maps the alias; everything downstream reads the explicit name. Everywhere else the two agree, so a URL, a saved view and a bulk payload spell a booking filter the same way.
      - `property_codes` string[], nullable
      - `property_codes_exclude` string[], nullable
      - `originating_channel` OriginatingChannel[], nullable
      - `originating_channel_exclude` OriginatingChannel[], nullable
      - `wh_source_id` string, nullable
      - `channelstream` boolean, nullable
      - `status_code` BookingStatusCode[], nullable
      - `origin` IngestionSource[], nullable
      - `attribution_tier` AttributionTier[], nullable
      - `booked_from` string, date-time, nullable
      - `booked_to` string, date-time, nullable
      - `stay_from` string, date, nullable
      - `stay_to` string, date, nullable
      - `cancelled_from` string, date-time, nullable
      - `cancelled_to` string, date-time, nullable
      - `discrepancy` 'feed_conflicts_human' — Booking-anchored discrepancy filter values (PR 6/PR 7). ``judged_without_reservation`` is log-anchored (a stamped log with NO canonical reservation) and therefore lives on the logs surface — a bookings list cannot show a booking that doesn't exist.
      - `unknown_commission` boolean, nullable
  - `voice_call_ids` string[]
  - `text_session_ids` string[]
  - `email_thread_ids` string[]

## Response `200`

Successful Response

- BulkDeleteResponse — Response for POST /logs/bulk-delete.
  - `deleted_count` integer, required
  - `matched_count` integer

## Other responses

- `401` — Authentication required
- `409` — This subject does not support deletion
- `422` — Validation Error

---

[API](https://skmtc.dev/getanana/apis/cleon-api.md) · [All operations](https://skmtc.dev/getanana/apis/cleon-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/getanana/cleon-api/revisions/c0f54348e251/schema)
