---
title: "What each log subject is, and what it can and cannot do"
method: GET
path: "/api/v1/logs/subjects"
tags: ["logs"]
---

# What each log subject is, and what it can and cannot do

`GET /api/v1/logs/subjects`

The subject registry as data, so the UI never hardcodes a capability.

Every gap in the capability matrix was introduced by a change that shipped one
channel and left the others, and the frontend then encoded that gap by hand:
``isTextReevalDisabled = channel === "text"`` outlived the PR that made text
re-evaluation work by five phases, and its tooltip still cited that PR as
pending. A control drawn from this response cannot outlive the truth, because
there is only one declaration behind it.

No tenant data is involved — the registry is identical for every caller — so
the response is a pure function of module-level declarations.

## Response `200`

Successful Response

- LogSubjectRegistryResponse — Every subject, in the order operators see them.
  - `subjects` LogSubjectDescriptor[], required
    - `key` 'voice' | 'text' | 'email' | 'booking', required
    - `label` string, required — Human-readable and singular, for operator-facing messages.
    - `id_field` string, required — The attribute a detail route addresses a row by. Voice is the only subject keyed on a provider id (`external_id`) rather than the primary key, and a client that assumes `id` breaks every existing shared call link.
    - `capabilities` string[], required — Capabilities this subject supports, sorted by value.
    - `unsupported_reason` object, required — Operator-facing copy for every capability NOT in `capabilities`. Together the two cover the capability enum exactly — the registry is total, so a client can always say *why* a control is missing rather than drawing a dead button.
    - `compiled_filters` string[], required — Universal filters this subject actually narrows on, sorted.
    - `unsupported_filters` object, required — Universal filters this subject accepts but does not narrow on, each with the reason the list echoes back as `ignored_filters`.
    - `unsupported_columns` object, required — Catalog columns this subject cannot populate, each with a reason. Withheld from the catalog so no column renders an em dash on every row.
    - `column_field_overrides` object, required — Catalog columns the list row carries under a different field name. Only the exceptions appear; the rule is that a column key is its own field.
    - `filters` FilterDescriptor[] — Every filter this subject offers, with its label, how its value is chosen, and its exact vocabulary. Served so the client renders what the backend accepts instead of keeping a hand-written copy that drifts — the copy that offered `status=completed`, a value no table has ever held, on all three conversation subjects at once.
      - `field` string, required — The query parameter, verbatim. The same snake_case vocabulary as `FilterClause.field` and the drill deep-links, so a filter means one thing across the product.
      - `label` string, required — Operator-facing name, singular.
      - `group` 'universal' | 'channel', required
      - `value_kind` 'boolean' | 'enum' | 'picker', required
      - `options` FilterOption[] — `enum` only — the exact vocabulary the column stores.
        - `value` string, required
        - `label` string, required
      - `option_source` 'country' | 'property' | 'agent' | 'owner' | 'tag' | 'booking_source' | 'text_widget_group' | 'metric', nullable — `picker` only — which of the tenant's own lists to offer.
      - `supports_exclude` boolean — Whether an `<field>_exclude` twin exists on the route, so the client may offer 'is not' as well as 'is'.
      - `multi` boolean — Whether the parameter takes a list, so the client offers several values at once rather than replacing the previous one. Declared here and checked against the route's annotation by `test_filter_catalog`, because the client cannot see the signature and guessing produced a UI that silently capped every list filter at one value.
  - `capability_keys` string[], required — Every capability name the registry knows, sorted. Served so a client can iterate the capability space without keeping its own copy of the enum — the copy that would silently miss a ninth member.

## Other responses

- `401` — Authentication required
- `403` — Missing the VIEW_CALL_LOGS permission

## Changes

- **2026-09-10** `5ebfe310b84c` — 1 info
  - added the optional property `subjects/items/filters` to the response with the `200` status

[Change history](https://skmtc.dev/getanana/apis/cleon-api/changes/api/v1/logs/subjects/get.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/d782acd3a884?raw)
