---
title: "Export Cohort Csv"
method: POST
path: "/analytics/sales-report/export"
tags: ["Analytics"]
---

# Export Cohort Csv

`POST /analytics/sales-report/export`

## Request

- Base URL: `https://be.graph8.com/api/v1`
- URL: `https://be.graph8.com/api/v1/analytics/sales-report/export`
- Auth: HTTP bearer

Download one section of the report - or the cohort drill-down - as CSV.

Takes the `/cohort` filter plus `section`; an absent or unknown section is
the persons drill-down. The export is the SAME cohort the report shows,
narrowed by the same deal and lead scopes, so it can never be a wider
download than the screen. Person rows are withheld exactly where `/cohort`
withholds them.

Reads; writes nothing. A POST only because the filter is a tree - tiered
`read`.

## Query parameters

- `tz` string: IANA timezone the local-day window bounds are computed in, e.g. `Asia/Tokyo`. Changes which deals fall inside the date range.

## Headers

- `Authorization` string, nullable
- `X-Target-Org-Id` string, nullable

## Request body

- ExportRequestDto: Export body — the cohort filter plus the section selector. The FE sends ``section`` in the JSON body alongside the filter (see ``sales-report.service.ts``). Absent / unknown values fall back to the persons drill-down CSV, so older clients keep working unchanged. Inherits every AC-3 filter cap from ``FilterQueryDto``, including the cross-group ``_bound_total_filter_values`` validator — the export streams the SAME cohort the screen shows, so an uncapped ``/export`` would be a download-shaped bypass of the cap ``/cohort`` applies.
  - `date` DateRangeDto, required
    - `from` string, date, required
    - `to` string, date, required
  - `rep` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `pipeline` UuidFilterGroupDto: `pipeline` / `stage` — `CBDeal.pipeline_id` and `.stage_id` are real `PostgresUUID` columns, so a non-UUID can only ever fail open.
    - `in` string[]
    - `not` boolean
  - `stage` UuidFilterGroupDto: `pipeline` / `stage` — `CBDeal.pipeline_id` and `.stage_id` are real `PostgresUUID` columns, so a non-UUID can only ever fail open.
    - `in` string[]
    - `not` boolean
  - `lifecycle` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `audiences` IntIdFilterGroupDto: `rec_type` / `audiences` — `Lead.rec_type` is `SmallInteger` and `audience_id` is `Integer`; both appliers coerce and skip what will not parse.
    - `in` string[]
    - `not` boolean
  - `rec_type` IntIdFilterGroupDto: `rec_type` / `audiences` — `Lead.rec_type` is `SmallInteger` and `audience_id` is `Integer`; both appliers coerce and skip what will not parse.
    - `in` string[]
    - `not` boolean
  - `signals_email` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `signals_voice` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `signals_linkedin` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `signals_sms` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `signals_forms` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `signals_website` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `signals_calendar` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `signals_chat` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `signals_intent` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `signals_crm` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `signals_sequence` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `signals_enrichment` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `channel` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `campaign` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `country` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `industry` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `headcount` HeadcountFilterGroupDto: `headcount` — a CLOSED vocabulary (`HEADCOUNT_BANDS`), and the exact list `/filter-options` hands the client.
    - `in` string[]
    - `not` boolean
  - `grading_bucket` GradingBucketFilterGroupDto: `grading_bucket` — a CLOSED vocabulary (`GRADING_BUCKETS`). An unknown bucket produces no band clause and widens to the full book.
    - `in` string[]
    - `not` boolean
  - `objections` ObjectionCodeFilterGroupDto: `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.
    - `in` string[]
    - `not` boolean
  - `tier` FilterGroupDto: 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.
    - `in` string[]
    - `not` boolean
  - `pipeline_target` 'prospects_meeting' | 'prospects_revenue' | 'customers_revenue', nullable
  - `granularity` 'day' | 'week' | 'month', nullable
  - `section` string, nullable

## Response `200`

One report section as CSV.

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/graph8/apis/graph8-developer-api.md) · [All operations](https://skmtc.dev/graph8/apis/graph8-developer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/graph8/apis/graph8-developer-api/revisions/2e7cfa0ca921?raw)
