---
title: "Render a report or export as a file"
method: POST
path: "/reports"
tags: ["Reports"]
---

# Render a report or export as a file

`POST /reports`

## Request

- Base URL: `https://tasg.app/api/v1`
- URL: `https://tasg.app/api/v1/reports`
- Auth: HTTP bearer

Builds the file on the server from the tasks you can see, stores it for 30 days and returns where to fetch it. At most 5000 rows per file. Cells that would execute as spreadsheet formulas are neutralised in CSV. This is the same engine behind the assistant's `create_report` tool.

## Request body

- ReportSpec
  - `template` 'task_list' | 'status_report' | 'workload': `task_list`: rows of tasks. `status_report`: headline figures, an optional narrative and tasks by section (needs `project_id`). `workload`: open tasks per person.
  - `format` 'xlsx' | 'csv' | 'pdf' | 'md'
  - `title` string: File and document title; defaults to the project or workspace name plus the template.
  - `narrative` string: Short prose placed above the tables (blank-line separated paragraphs).
  - `project_id` string, uuid: One project. Either this or `workspace_id` is required.
  - `workspace_id` string, uuid: A cross-project report over one workspace; ignored when `project_id` is set.
  - `filters` object
    - `status` 'any' | 'not_started' | 'in_progress' | 'blocked' | 'completed'
    - `assignee_id` string, uuid
    - `priority` 'any' | 'low' | 'medium' | 'high' | 'urgent'
    - `due_before` string, date: Calendar date, YYYY-MM-DD
    - `due_after` string, date: Calendar date, YYYY-MM-DD
    - `exclude_completed` boolean: Defaults to true for `workload`, false otherwise.
    - `include_subtasks` boolean
  - `group_by` 'section' | 'assignee' | 'status' | 'priority' | 'project' | 'none': Defaults to `section` for a project, `project` for a workspace.
  - `columns` string[]: Column pick for `task_list`; default omits description and url.
  - `deliver_to` 'chat' | 'task' | 'email': `chat`: stored only. `task`: also attached to `task_id`. `email`: also emailed to the caller (never anyone else).
  - `task_id` string, uuid: Required when `deliver_to` is `task`.

## Response `201`

Rendered and stored.

- ReportFile
  - `id` string, uuid, required
  - `filename` string, required
  - `mime_type` string, required
  - `file_size` integer, required: Bytes.
  - `row_count` integer, required
  - `template` 'task_list' | 'status_report' | 'workload', required
  - `format` 'xlsx' | 'csv' | 'pdf' | 'md', required
  - `delivered_to` 'chat' | 'task' | 'email', required
  - `url` string, required: Relative download path; send your bearer token.
  - `download_url` string, nullable, required: Absolute link that works WITHOUT a bearer token for 24 hours (signed, bound to this file). Null when the instance cannot sign links.
  - `created_at` string, date-time, required
  - `expires_at` string, date-time, required: Files are deleted after 30 days.
  - `attachment` object: Present when delivered to a task.
    - `id` string, uuid, required
    - `task_id` string, uuid, required
    - `task_url` string, nullable
  - `email` object: Present when delivery was email; `note` says why it was not sent.
    - `sent` boolean, required
    - `to` string
    - `note` string

## Other responses

- `400`: Validation failed: the body or query did not match the schema, or a business rule was violated. `error` says which.
- `401`: Missing, invalid, expired or revoked credentials.
- `403`: Authenticated, but not allowed: not a member, insufficient project role or the API token lacks the required scope.
- `404`: Not found, or not visible to the caller (tenancy is enforced: foreign resources look identical to missing ones).
- `429`: Rate limited. Back off and retry after the window in the `RateLimit-*` headers.

---

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