Post Data Quality
POST /analytics/sales-report/data-quality
The same diagnostic, narrowed to the deals one filter resolves.
A SECOND VERB rather than a changed one: the GET above is unchanged, so “omit the filter and get today’s answer” stays a structural fact rather than a claim about a defaulted parameter.
Only the DEAL-anchored figures move. The transcript counters, the 90-day event total and the tier-rules flag come back org-wide on this path too, because no filter on this report can reach them.
Authorizations
Parameters
Query Parameters
IANA timezone the local-day window bounds are computed in, e.g. Asia/Tokyo. Changes which deals fall inside the date range.
IANA timezone the local-day window bounds are computed in, e.g. Asia/Tokyo. Changes which deals fall inside the date range.
Header Parameters
Request Body required
JSON shape consumed by POST /api/sales-report/cohort.
Empty groups are omitted by callers; absent groups treated as empty.
object
rec_type / audiences — Lead.rec_type is SmallInteger and
audience_id is Integer; both appliers coerce and skip what will not
parse.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
object
grading_bucket — a CLOSED vocabulary (GRADING_BUCKETS). An unknown
bucket produces no band clause and widens to the full book.
object
headcount — a CLOSED vocabulary (HEADCOUNT_BANDS), and the exact list
/filter-options hands the client.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
objections — codes live in the PER-ORG cb_objection_codes catalog, so
only the SHAPE can be checked here. Blank is the one thing the applier
provably cannot use.
object
pipeline / stage — CBDeal.pipeline_id and .stage_id are real
PostgresUUID columns, so a non-UUID can only ever fail open.
object
rec_type / audiences — Lead.rec_type is SmallInteger and
audience_id is Integer; both appliers coerce and skip what will not
parse.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
pipeline / stage — CBDeal.pipeline_id and .stage_id are real
PostgresUUID columns, so a non-UUID can only ever fail open.
object
One filter group — list of values + optional NOT toggle.
in_ is capped at MAX_FILTER_VALUES_PER_GROUP items of
MAX_FILTER_VALUE_LENGTH characters each. Over-cap input raises during
body parsing, so FastAPI answers 422 with the offending group named in
detail[].loc (["body", "<group>", "in"]) and the cap in
detail[].msg — no handler code, and therefore no SQL, runs.
THIS SHAPE DOES NOT NEST. in_ holds scalars and there is no
group-of-groups member, so a caller cannot vary the tree depth: it is fixed
at 2 (query -> group -> values) by construction, and extra="forbid" on
both models keeps it that way. A depth check here would be validation that
can never fire. Add one WITH the member that introduces nesting, not before
it.
Do not confuse this with SegmentFilterGroup
(contacts/domain/value_objects/ag_grid_models.py:111), which IS
recursive and is where AC-3’s “tree depth” belongs. Different model,
different route, still unbounded — see the scope note above the constants.
object
Responses
200
Successful Response
object
An OPEN envelope around one report’s answer.
The app wraps every one of these reports in {success, data, meta}, where
meta carries the cache state, the window and - on the agency reports -
failed_org_ids, the orgs that contributed NOTHING to the totals above.
That field is the difference between “this client sent no email” and “we
could not query this client”, so it is forwarded rather than flattened
away.
Open on purpose: data is dict | list in the app and its contents grow
with the product. A closed model would turn every new metric into a
silently dropped one.
object
object
Whether there are more pages
Items per page
Current page number (1-indexed)
Total number of items
422
Validation Error