---
title: "Change the settings of a report"
method: POST
path: "/reports/update"
tags: ["Reports"]
---

# Change the settings of a report

`POST /reports/update`

## Request

- Base URL: `https://api.reportingninja.com/v1`
- URL: `https://api.reportingninja.com/v1/reports/update`
- Auth: HTTP bearer

Changes the fields of a report that already exists, the period of every one of its sections and the whole settings window of the report. It is a partial update: only the fields the body carries are written, and inside a block only the keys it carries, so everything it leaves out is left exactly as it is. /v1/reports/get publishes the same settings under `settings`, key by key, which is the object this call takes back.

`date_range` is not a field of the report: the period belongs to each SECTION, and what this field does is what the application's own button does, applying one range to ALL of them at once. It writes the range and nothing else, so the header of each section goes on showing whatever it showed. The answer lists the sections it wrote, and the cached results of the whole report are dropped so the next read is of the new period.

`data_sources` rebinds the report as a whole and is for the reports whose `report_level_data_sources` is true. On a report that binds a connection per widget it is refused with 400 APPEND_NOT_ALLOWED and reason per_widget_data_sources; those are rebound with /v1/reports/set-connections.

`share` is the public link of the report. The id is minted by the call the first time the report is shared and again whenever `regenerate` is true, which invalidates the previous link; a password is stored hashed and `null` empties it. A template has no public link and the field is refused on one.

The settings window is the rest of the body. `brand_template` and `language_file` take an empty string for the system one. A template carries no brand of its own: on one `brand_template` takes only the empty string, and the key a template still stores is accepted and changes nothing. `front_cover` carries the type, the texts and the date line, and the TYPE commands the file: a type that prints an uploaded PDF is refused on a report that carries none, and a type that prints none removes the file the report carried, which the answer says with a COVER_FILE_REMOVED warning -- a dry run says it too -- and `before.front_cover.file`. `localization` either follows the account or belongs to the report: the six settings under it exist only in the second case, sending one of them with `use: account` is refused, and a call that writes that branch asks for the six together. `ai_brief` has three states: absent keeps it, an empty string removes it, a text replaces it.

The block /v1/reports/get answers under `settings` is this body, whole: sending it back writes what the report already has and changes nothing. A key carrying null means the column has no value and is left as it is; an empty string is a value. The three keys only a read can answer -- `brand_template_name`, `language_file_name` and `front_cover.file` -- are accepted and ignored, each with a READ_ONLY_FIELD_IGNORED warning naming it. `updated_fields` and `before` carry the fields the call really wrote, so a block that changes nothing appears in neither.

Changing the language file, the localization mode or the date format drops the cached results of the report so the next read is of the new setting; nothing else drops them, and nothing is executed by this call.

The uploaded PDF of the cover is the one thing of that window this call does not write: it is a file, and it has a route of its own, /v1/reports/set-cover-file, which writes the cover type with it.

What it never writes: the level at which the report binds its data sources (moving a report to per widget bindings deletes its binding rows), its identity, structure and provenance, and the settings of the ACCOUNT.

There is no guard against concurrent edits: the call writes over whatever the report holds when it lands, a change made in the application since the client read the report included.

Refusals: 400 for the framework and the body, 400 APPEND_NOT_ALLOWED with reason per_widget_data_sources or template_based, 403 WRITE_ACCESS_DISABLED, 404 NOT_FOUND and 409 REPORT_IN_TRASH.

Every write endpoint reads dry_run and idempotency_key; see the Write guarantees section of the introduction.

## Request body

- ReportsUpdateRequest: Which report to change, and which of its settings. Everything the body does not carry is left exactly as it is.
  - `report_key` string, required: The report to change, from /v1/reports/list.
  - `name` string: The title of the report, 1 to 60 characters: the name the cover paints. It is what /v1/reports/get answers as `name`.
  - `internal_name` string: The name the list of the application shows, 1 to 100 characters. Left out, the one the report has is kept.
  - `subtitle` string: The subtitle under the title. An empty string clears it.
  - `internal_notes` string: Notes only the account sees. An empty string clears them.
  - `share` object: The public /share link of the report. On a report that has NO link yet the block has to carry `enabled`: the id is minted by this call, and a body that only set the other fields would publish a report nobody asked to publish. Refused on a template, which has no public link.
    - `enabled` boolean: Whether the link answers. Defaults to true the first time the report is shared.
    - `password` string, nullable: The password the link asks for, stored hashed. null removes it and leaves the link unprotected; an empty string is refused; leaving the field out keeps the password the link already had.
    - `allow_date_range` boolean: Whether whoever opens the link may change the period. Defaults to false.
    - `regenerate` boolean: Mint a new id, which invalidates the link that was given out. Default false.
  - `data_sources` DataSourceInput[]: The report level bindings, one entry per integration, as /v1/reports/create takes them. Refused on a report that assigns a connection per widget.
    - `integration_id` string, required: Which integration this binding is for. Two entries may not name the same one.
    - `connection_key` string, required: One of the connection keys of /v1/connections.
    - `account_id` string, required: One of the account ids of that connection.
    - `entity` 'campaign' | 'list': REQUIRED for mailchimp and rejected for every other integration: which of its two levels this binding is for.
  - `date_range` CompositionDateRange: The period a report is built for. Its vocabulary is not the one of /v1/query: the presets a report has no equivalent for (lastweeksunsat, lastweekmonsun, thismonthtoyesterday, thisyear, lastyear, lastyeartodate, lastxweeksiso, lastxyears) are refused with the problem code DATE_PRESET_NOT_IN_REPORTS and the list of the ones that do exist.
    - `preset` 'today' | 'yesterday' | 'lastweek' | 'thisweek' | 'lastmonth' | 'thismonth' | 'lastxdays' | 'lastxweeks' | 'lastxmonths' | 'yeartolastmonth' | 'yeartolastweek' | 'custom', required: The period of the report.
    - `x` integer: How many days, weeks or months. Required by the lastx* presets.
    - `start` string: First day of a custom period, as yyyy-MM-dd.
    - `end` string: Last day of a custom period, as yyyy-MM-dd.
  - `brand_template` string: The brand template the report is printed with: a key of a brand template of the account, or an empty string for the system one. A template carries no brand of its own and takes only the empty string, or the key it still stores, which changes nothing.
  - `brand_template_name` string: Published by /v1/reports/get and written by nothing. It is accepted here so the whole `settings` block a read answers can be sent straight back as a body; the key is ignored and the answer carries a READ_ONLY_FIELD_IGNORED warning naming it.
  - `front_cover` ReportFrontCoverInput: The front cover of the report. Every key is optional and what the body does not carry is left as it is; so is a key carrying null, which is what a read answers for a text the report does not have. The uploaded PDF itself is not written here: it has a route of its own, /v1/reports/set-cover-file.
    - `type` 'none' | 'report' | 'pdf' | 'pdf_and_report' | 'custom' | 'pdf_and_custom': What the front cover of the PDF is. `none` prints none, `report` draws the generated cover, `pdf` prints the uploaded file and `pdf_and_report` draws the generated text block over it. The `custom` pair is the legacy cover with texts of its own: it is kept and not offered, so only a report already printed with one of them is written with it again, and a cover that prints texts of its own asks for a title and a subtitle, in the body or already stored. The type commands the file: one that prints an uploaded PDF is refused on a report that carries none, and one that prints none removes the file the report carried, which the answer reports as a COVER_FILE_REMOVED warning.
    - `title` string, nullable: The title the generated cover prints, at most 60 characters. An empty string clears it; null leaves it as it is.
    - `subtitle` string, nullable: The line under it, at most 60 characters. An empty string clears it; null leaves it as it is.
    - `date_line` 'none' | 'generation_date' | 'current_period' | 'current_and_past_period': The date line of the cover, of the hero of the public link and of the header of the client portal.
    - `file` ReportCoverFile: The PDF file the front cover prints, by name. Its content is never published.
      - `name` string, required: The name of the uploaded file.
  - `pdf` ReportPdfOptions: What the generated PDF carries besides the report itself.
    - `table_of_contents` boolean: Whether the PDF opens with a table of contents.
    - `page_numbers` boolean: Whether its pages are numbered.
    - `title_in_pdf` boolean: Whether the title of the report is printed on each page.
  - `localization` ReportLocalizationInput: A report either follows the localization of the ACCOUNT or carries its own, which is what `use` says. The six settings below exist only in the second case: sending one of them with `use: account` is refused instead of being ignored, and a call that WRITES this branch asks for the six together, in the body or already stored. Once they are all there each of them can be written on its own, and a block that gives no value at all changes nothing and asks for nothing -- which is what makes the block a read answers for a legacy report, whose six may be null, a body this call takes back. A key carrying null (or an empty string) gives no value and leaves that column exactly as it is; none of the six is ever written as null. The time zone and the time format are not settings of a report: they are the account's, and they are copied from it every time this branch is written.
    - `use` 'account' | 'report': Whether the report follows the localization of the account or carries its own.
    - `date_format` 'DDMMYYYY' | 'MMDDYYYY' | 'YYYYMMDD', nullable: How dates are printed: `DDMMYYYY` is dd/MM/yyyy, `MMDDYYYY` is MM/dd/yyyy and `YYYYMMDD` is yyyy/MM/dd.
    - `week_starts_on` 'monday' | 'sunday', nullable: The first day of a week, which decides how weekly series are bucketed.
    - `decimal_separator` '.' | ',', nullable: The separator every figure of the report is printed with.
    - `currency` string, nullable: The currency symbol printed beside monetary figures, 1 to 3 characters.
    - `currency_symbol_position` 'left' | 'right', nullable: Which side of the figure the symbol is printed on.
    - `pdf_page_size` 'A4' | 'Letter', nullable: The page size of the generated PDF.
  - `language_file` string: The language file the report is printed with: an empty string for the system default, one of the sixteen system files (`language_file_en`, `language_file_es`, ...) or the key of a language file of the account.
  - `language_file_name` string: Published by /v1/reports/get and written by nothing. It is accepted here so the whole `settings` block a read answers can be sent straight back as a body; the key is ignored and the answer carries a READ_ONLY_FIELD_IGNORED warning naming it.
  - `substitution_variables` string[]: The five substitution variables of the report, positional: the list carries exactly five values, one per %%CUSTOM_VAR_n%%, and an unused one is an empty string. Each of them is at most 800 characters.
  - `widgets_show` ReportWidgetsShow: What the header of every widget of the report shows.
    - `data_source_name` boolean: Whether the name of the data source is shown.
    - `data_source_icon` boolean: Whether its icon is shown.
    - `date_range` boolean: Whether the period of the widget is shown.
  - `snapshots_with_scheduled_executions` boolean: Whether a snapshot of the report is stored every time it is delivered by a schedule.
  - `ai_brief` string, nullable: What the AI summaries of this report should know about the client. Three states: leaving the field out -- or sending null, which is what a read answers for a report that has none -- keeps the brief the report has, an empty string removes it, and a text replaces it. At most 2000 characters. A brief that could not be written comes back as an AI_BRIEF_NOT_SAVED warning of a 200, with `ai_brief` left out of `updated_fields`.
  - `dry_run` boolean: Validate and answer what WOULD happen, without writing anything. Default false.
  - `idempotency_key` string: Makes a retry safe: the answer of the first call is replayed for 24 hours to any later call with the same key and the same body.

## Response `200`

The call succeeded. This endpoint reports its errors with a real HTTP status, so a 200 always carries status=ok.

- ReportsUpdateResponse: The 200 of this write, in whichever of its shapes the call asked for.
  - `status` 'ok', required
  - `data` union, required: The answer of the write. A call sent with dry_run: true answers WriteDryRunData instead.
    - ReportsUpdateData: The report as a read publishes it, and what this call changed.
      - `report` ReportHeader, required: What every listing and every read of a report says about it.
        - `report_key` string, required: The key that addresses this report in every other endpoint.
        - `name` string, nullable, required: Title of the report: the name the cover paints, and the one every write takes as `name`. At most 60 characters.
        - `internal_name` string, nullable, required: The OTHER name of the report: the one the list of the application shows. A report has two and both are writable. At most 100 characters.
        - `subtitle` string, nullable, required: The line under the title of the report, as /v1/reports/update writes it. Null when the report has none.
        - `internal_notes` string, nullable, required: The note the account keeps about this report. It is never shown to a client of the account: it belongs to whoever builds the report.
        - `type` 'report' | 'template' | 'template_based', required: `report` is a normal report, `template` one of the account's own templates, `template_based` a report whose sections are its template's.
        - `based_on_template_key` string, nullable, required: The template a template_based report follows. Null for the other two types.
        - `catalog` object, nullable, required: The catalog entry this report was materialized from, or null when it was not.
          - `slug` string, required
          - `version` integer, nullable, required
        - `created_via` 'api' | 'ui', required: Whether the report was created through this API or in the app.
        - `version` integer, required: Version of the sections structure. It is what if_version compares against, and it moves only when the list of sections changes.
        - `created_at` string, nullable, required: When the report was created, in UTC.
        - `report_level_data_sources` boolean, required: True when every widget reads from the bindings of data_sources; false when each widget carries its own connection, which is a report the API cannot extend.
        - `data_sources` ReportDataSource[], required: The report level bindings, ordered by integration. A binding without an account is not published.
          - `integration_id` string, nullable, required: Public id of the integration, or null when this source has no public id and cannot be addressed through the API.
          - `integration` string: Internal name of the source. Present only when integration_id is null.
          - `connection_key` string, nullable, required: The connection this report reads that integration from.
          - `account_id` string, nullable, required: The account inside that connection.
          - `entity` 'campaign' | 'list': mailchimp only: which of its two levels this binding is for.
        - `share_link_enabled` boolean, required: Whether the public share link of the report is on.
        - `share_link_id` string, nullable, required: The id in the public /share URL, or null when the report has never been shared.
        - `share_url` string, nullable, required: The public URL of the report, built on the account's own portal domain when it has one. Null when the report has no link.
        - `app_url` string, nullable, required: The link that opens this report in the Reporting Ninja application. Opening it requires a signed-in user of the account, so it is not the public link: `share_url` is the one anybody holding it reads. Null for a report in the trash, which the application does not open.
        - `share_link_password_set` boolean, required: Whether the link asks for a password. The password itself is never published.
        - `share_link_allow_date_range` boolean, required: Whether whoever opens the link may change the period.
        - `settings` ReportSettings: The settings window of the report, in the same shape /v1/reports/update takes it: what a read answers here is what a write sends back, whole, with no key removed and no value rewritten. Three of its keys -- `brand_template_name`, `language_file_name` and `front_cover.file` -- are published here and written by nothing: that call accepts them, ignores them, and names each one in a READ_ONLY_FIELD_IGNORED warning. Everywhere else a null means the column has no value and a write that carries it back leaves that column exactly as it is.
          - `brand_template` string, required: The brand template the report is printed with: a key of a brand template of the account, or an empty string for the system one.
          - `brand_template_name` string, nullable, required: The name of that brand template, so naming one does not need a second call. Null when the report points at a brand template that no longer exists.
          - `front_cover` ReportFrontCover, required: The front cover of the report as a read publishes it, which is the object /v1/reports/update takes back: `file` is published and not written, and it is accepted and ignored so the object can be replayed as it is.
            - `type` 'none' | 'report' | 'pdf' | 'pdf_and_report' | 'custom' | 'pdf_and_custom', required: What the front cover of the PDF is. `none` prints none, `report` draws the generated cover, `pdf` prints the uploaded file and `pdf_and_report` draws the generated text block over it. The `custom` pair is the legacy cover with texts of its own, kept and not offered: only a report already printed with one of them can be written with it again. The type commands the file: one that prints an uploaded PDF is refused on a report that carries none, and one that prints none removes the file the report carried, which the answer reports as a COVER_FILE_REMOVED warning.
            - `title` string, nullable, required: The title the generated cover prints. An empty string is a title the report does not have; null is a column that was never written.
            - `subtitle` string, nullable, required: The line under it. An empty string is a subtitle the report does not have; null is a column that was never written.
            - `date_line` 'none' | 'generation_date' | 'current_period' | 'current_and_past_period', required: The date line of the cover, of the hero of the public link and of the header of the client portal.
            - `file` ReportCoverFile, required: The PDF file the front cover prints, by name. Its content is never published.
              - …
          - `pdf` ReportPdfOptions, required: What the generated PDF carries besides the report itself.
            - `table_of_contents` boolean: Whether the PDF opens with a table of contents.
            - `page_numbers` boolean: Whether its pages are numbered.
            - `title_in_pdf` boolean: Whether the title of the report is printed on each page.
          - `localization` ReportLocalization, required: The localization of the report as a read publishes it. The six settings travel only when `use` is `report`: with `account` the report is printed with the account's, and they are the body that would put it back.
            - `use` 'account' | 'report', required: Whether the report follows the localization of the account or carries its own.
            - `date_format` 'DDMMYYYY' | 'MMDDYYYY' | 'YYYYMMDD', nullable: How dates are printed.
            - `week_starts_on` 'monday' | 'sunday', nullable: The first day of a week.
            - `decimal_separator` '.' | ',', nullable: The separator of every figure.
            - `currency` string, nullable: The currency symbol printed beside monetary figures.
            - `currency_symbol_position` 'left' | 'right', nullable: Which side of the figure the symbol is printed on.
            - `pdf_page_size` 'A4' | 'Letter', nullable: The page size of the generated PDF.
          - `language_file` string, required: The language file the report is printed with: an empty string for the system default, one of the sixteen system files (`language_file_en`, `language_file_es`, ...) or the key of a language file of the account.
          - `language_file_name` string, nullable, required: The name of that language file; a report that names none is printed with the English system file and that is the name answered here. Null when the report points at a language file of the account that no longer exists.
          - `substitution_variables` string[], required: The five substitution variables of the report, positional. An unused one is an empty string.
          - `widgets_show` ReportWidgetsShow, required: What the header of every widget of the report shows.
            - `data_source_name` boolean: Whether the name of the data source is shown.
            - `data_source_icon` boolean: Whether its icon is shown.
            - `date_range` boolean: Whether the period of the widget is shown.
          - `snapshots_with_scheduled_executions` boolean, required: Whether a snapshot of the report is stored every time it is delivered by a schedule.
          - `ai_brief` string, nullable, required: What the AI summaries of this report know about the client, or null when the report carries no brief. It is the middle of the three levels a summary reads: account, report and widget.
      - `updated_fields` string[], required: The fields of the body this call wrote, in the order it read them.
      - `before` object, required: The previous value of every field this call touched, which is the body that undoes it. date_range is not in it: the period belongs to each section and there is no single previous value to give back.
      - `sections_updated` string[]: Present only when the body carried a date_range: the keys of the sections that were moved to it.
      - `atomic` boolean: Present with sections_updated, and always false: the sections are written one after another with no transaction across them.
      - `unfulfilled` string[], required: What was asked for and could not be expressed; the API returns an empty list.
      - `warnings` Problem[], required: Things worth knowing about a write that DID happen. They are never errors.
        - `path` string, required: Dotted path of the offending field inside the request body, empty for a problem about the body as a whole.
        - `code` 'MISSING_REQUIRED_FIELD' | 'INVALID_VALUE' | 'UNKNOWN_PARAM' | 'INVALID_DATE_RANGE' | 'INTEGRATION_NOT_SUPPORTED' | 'CONNECTION_NOT_FOUND' | 'ACCOUNT_NOT_FOUND' | 'ONE_CONNECTION_PER_INTEGRATION' | 'DATE_PRESET_NOT_IN_REPORTS' | 'TEMPLATE_NOT_FOUND' | 'TEMPLATE_PARAMETERIZED' | 'ROW_RULE' | 'POSITION_CHANGED' | 'SECTION_CHANGED' | 'TOO_MANY_PROBLEMS' | 'DATA_SOURCES_PARTIALLY_BOUND' | 'WELCOME_EMAIL_FAILED' | 'REPORT_WILL_BE_SHARED_PUBLICLY' | 'MAY_BE_DELIVERED_AGAIN_TODAY' | 'SUMMARY_SECTION_SKIPPED' | 'COVER_FILE_REMOVED' | 'COVER_TYPE_SET' | 'LOGO_REMOVED' | 'READ_ONLY_FIELD_IGNORED' | 'AI_BRIEF_NOT_SAVED' | 'SPENDS_AI_CREDITS' | 'DROPS_ACCOUNT_CACHE' | 'CHART_TYPE_NOT_FOR_THIS_SHAPE' | 'CHART_TYPE_NOT_FOR_TREND' | 'CONTENT_NOT_FOR_THIS_SHAPE' | 'FULL_WIDTH_NOT_FOR_KPI' | 'DATA_VIEW_FIELDS_PER_ACCOUNT' | 'DATA_VIEW_NOT_AVAILABLE' | 'DATA_VIEW_NOT_EMPTY' | 'DATA_VIEW_NOT_OF_THE_SECTION' | 'DATA_VIEW_REQUIRED' | 'DATA_VIEW_UNKNOWN' | 'DATE_RANGE_CUSTOM_INCOMPLETE' | 'DATE_RANGE_CUSTOM_INVALID' | 'DATE_RANGE_CUSTOM_INVERTED' | 'DATE_RANGE_PRESET_UNKNOWN' | 'DATE_RANGE_X_REQUIRED' | 'DIMENSION_DUPLICATED' | 'DIMENSION_IS_A_METRIC' | 'DIMENSION_REQUIRED' | 'DIMENSION_UNKNOWN' | 'DSL_INVALID' | 'FIELD_NOT_IN_AD_TYPES' | 'FILTERS_NOT_SUPPORTED' | 'FILTER_COMBINATION_NOT_SUPPORTED' | 'FILTER_CONDITIONS_MIXED_KINDS' | 'FILTER_FIELD_NOT_A_COLUMN' | 'FILTER_FIELD_NOT_IN_DATA_VIEW' | 'FILTER_FIELD_UNKNOWN' | 'FILTER_LIKE_NEEDS_WILDCARD' | 'FILTER_OPERATOR_INVALID' | 'FILTER_TYPE_OF_FIELD_MISMATCH' | 'FILTER_TYPE_OF_FIELD_NOT_USED' | 'FILTER_TYPE_OF_FIELD_REQUIRED' | 'FILTER_VALUE_EMPTY' | 'FILTER_VALUE_NOT_ALLOWED' | 'FILTER_VALUE_NOT_NUMERIC' | 'GRAPH_EXTRA_DIMENSIONS' | 'GRAPH_TIME_DIMENSION_MIXED' | 'INTEGRATION_UNKNOWN' | 'INTENT_MISSING' | 'KPI_NEEDS_ONE_METRIC' | 'KPI_TAKES_NO_DIMENSIONS' | 'LIMIT_IGNORED_IN_TREND' | 'LIMIT_OUT_OF_RANGE' | 'METRICS_SUMMARY_TAKES_NO_DIMENSIONS' | 'METRIC_DUPLICATED' | 'METRIC_IS_A_DIMENSION' | 'METRIC_UNKNOWN' | 'METRICS_REQUIRED' | 'NAME_EMPTY' | 'NAME_NOT_PLAIN_TEXT' | 'NAME_TOO_LONG' | 'PIE_TAKES_ONE_DIMENSION' | 'PREVIOUS_PERIOD_NOT_AVAILABLE_FOR_THIS_TABLE' | 'COMPARISON_NOT_AVAILABLE_FOR_THIS_WIDGET' | 'PREVIOUS_PERIOD_NOT_FOR_THIS_SHAPE' | 'SHOW_CHANGE_NOT_FOR_THIS_SHAPE' | 'SHOW_CHANGE_NEEDS_PREVIOUS_PERIOD' | 'SETTING_DUPLICATED' | 'SETTING_NOT_FOR_DATA_VIEW' | 'SETTING_NOT_IN_WIDGET' | 'SETTING_UNKNOWN' | 'SETTING_VALUE_INVALID' | 'SORT_FIELD_NOT_SELECTED' | 'TABLE_TIME_DIMENSION_NOT_DRAWABLE' | 'TEXT_CONTENT_REQUIRED' | 'TEXT_CONTENT_TOO_LONG' | 'TEXT_NOT_A_WIDGET_TYPE' | 'TEXT_TAKES_NO_DATA' | 'TOO_MANY_DIMENSIONS' | 'TOO_MANY_METRICS' | 'TRANSLATION_FAILED' | 'TREND_ORDER_IS_THE_TIME_AXIS' | 'TREND_TAKES_NO_DIMENSIONS' | 'VISUALIZATION_NOT_AVAILABLE' | 'VISUALIZATION_NOT_OF_REQUESTED_TYPE' | 'FIELD_NOT_AVAILABLE' | 'FILTER_CONDITIONS_MIXED_LEVELS' | 'FILTER_LEVEL_MISMATCH' | 'FORMULA_TOKEN_UNKNOWN' | 'FORMULA_INVALID' | 'LANGUAGE_FILE_KEYS_IGNORED' | 'SELECTION_EMPTY' | 'FILTER_KEY_AND_FILTERS' | 'FILTER_KEY_NOT_FOR_THIS_SHAPE' | 'KEY_NOT_FOR_GENERATION' | 'COMPARE_NOT_FOR_THIS_SHAPE' | 'COMPARE_NOT_FOR_INTEGRATION' | 'COMPARE_ITEMS_CARDINALITY' | 'COMPARE_METRICS_CARDINALITY' | 'COMPARE_OVER_TIME_NOT_AVAILABLE' | 'COMPARE_METRIC_HAS_NO_BREAKDOWN' | 'COMPARE_KEY_NOT_FOR_THIS_COMPARATOR' | 'COMPARATOR_CLASS_FIXED', required: What is wrong with that field, or what is worth knowing about it. The first group is the vocabulary of the body itself; the eleven that follow it appear only in `warnings[]`, bar TOO_MANY_PROBLEMS, which closes a `problems` list that was truncated; and the rest are the codes of the widget DSL, which travel here when a composition or a widget carries a dsl that does not validate.
        - `message` string, required: Human-readable explanation. Not stable across versions.
        - `suggestions` string[]: The accepted values, when the field takes a closed list.
    - WriteDryRunData: The answer of a call sent with dry_run: true. Nothing was written, no idempotency key was consumed and no confirmation token was burnt. When valid is true, the fields of the endpoint's own preview travel alongside these two; when it is false, `message` and `problems` do instead. The HTTP status is 200 in both cases: a rehearsal that says the body is wrong is a successful rehearsal. Two refusals are NOT translated and arrive with their real status: a 429 (the budgets are spent either way) and any 5xx.
      - `dry_run` true, required: Always true in this shape.
      - `valid` boolean, required: Whether the body would be accepted.
      - `message` string: Why the body would be refused. Present only when valid is false.
      - `problems` Problem[]: Everything that is wrong with the body. Present only when valid is false.
        - `path` string, required: Dotted path of the offending field inside the request body, empty for a problem about the body as a whole.
        - `code` 'MISSING_REQUIRED_FIELD' | 'INVALID_VALUE' | 'UNKNOWN_PARAM' | 'INVALID_DATE_RANGE' | 'INTEGRATION_NOT_SUPPORTED' | 'CONNECTION_NOT_FOUND' | 'ACCOUNT_NOT_FOUND' | 'ONE_CONNECTION_PER_INTEGRATION' | 'DATE_PRESET_NOT_IN_REPORTS' | 'TEMPLATE_NOT_FOUND' | 'TEMPLATE_PARAMETERIZED' | 'ROW_RULE' | 'POSITION_CHANGED' | 'SECTION_CHANGED' | 'TOO_MANY_PROBLEMS' | 'DATA_SOURCES_PARTIALLY_BOUND' | 'WELCOME_EMAIL_FAILED' | 'REPORT_WILL_BE_SHARED_PUBLICLY' | 'MAY_BE_DELIVERED_AGAIN_TODAY' | 'SUMMARY_SECTION_SKIPPED' | 'COVER_FILE_REMOVED' | 'COVER_TYPE_SET' | 'LOGO_REMOVED' | 'READ_ONLY_FIELD_IGNORED' | 'AI_BRIEF_NOT_SAVED' | 'SPENDS_AI_CREDITS' | 'DROPS_ACCOUNT_CACHE' | 'CHART_TYPE_NOT_FOR_THIS_SHAPE' | 'CHART_TYPE_NOT_FOR_TREND' | 'CONTENT_NOT_FOR_THIS_SHAPE' | 'FULL_WIDTH_NOT_FOR_KPI' | 'DATA_VIEW_FIELDS_PER_ACCOUNT' | 'DATA_VIEW_NOT_AVAILABLE' | 'DATA_VIEW_NOT_EMPTY' | 'DATA_VIEW_NOT_OF_THE_SECTION' | 'DATA_VIEW_REQUIRED' | 'DATA_VIEW_UNKNOWN' | 'DATE_RANGE_CUSTOM_INCOMPLETE' | 'DATE_RANGE_CUSTOM_INVALID' | 'DATE_RANGE_CUSTOM_INVERTED' | 'DATE_RANGE_PRESET_UNKNOWN' | 'DATE_RANGE_X_REQUIRED' | 'DIMENSION_DUPLICATED' | 'DIMENSION_IS_A_METRIC' | 'DIMENSION_REQUIRED' | 'DIMENSION_UNKNOWN' | 'DSL_INVALID' | 'FIELD_NOT_IN_AD_TYPES' | 'FILTERS_NOT_SUPPORTED' | 'FILTER_COMBINATION_NOT_SUPPORTED' | 'FILTER_CONDITIONS_MIXED_KINDS' | 'FILTER_FIELD_NOT_A_COLUMN' | 'FILTER_FIELD_NOT_IN_DATA_VIEW' | 'FILTER_FIELD_UNKNOWN' | 'FILTER_LIKE_NEEDS_WILDCARD' | 'FILTER_OPERATOR_INVALID' | 'FILTER_TYPE_OF_FIELD_MISMATCH' | 'FILTER_TYPE_OF_FIELD_NOT_USED' | 'FILTER_TYPE_OF_FIELD_REQUIRED' | 'FILTER_VALUE_EMPTY' | 'FILTER_VALUE_NOT_ALLOWED' | 'FILTER_VALUE_NOT_NUMERIC' | 'GRAPH_EXTRA_DIMENSIONS' | 'GRAPH_TIME_DIMENSION_MIXED' | 'INTEGRATION_UNKNOWN' | 'INTENT_MISSING' | 'KPI_NEEDS_ONE_METRIC' | 'KPI_TAKES_NO_DIMENSIONS' | 'LIMIT_IGNORED_IN_TREND' | 'LIMIT_OUT_OF_RANGE' | 'METRICS_SUMMARY_TAKES_NO_DIMENSIONS' | 'METRIC_DUPLICATED' | 'METRIC_IS_A_DIMENSION' | 'METRIC_UNKNOWN' | 'METRICS_REQUIRED' | 'NAME_EMPTY' | 'NAME_NOT_PLAIN_TEXT' | 'NAME_TOO_LONG' | 'PIE_TAKES_ONE_DIMENSION' | 'PREVIOUS_PERIOD_NOT_AVAILABLE_FOR_THIS_TABLE' | 'COMPARISON_NOT_AVAILABLE_FOR_THIS_WIDGET' | 'PREVIOUS_PERIOD_NOT_FOR_THIS_SHAPE' | 'SHOW_CHANGE_NOT_FOR_THIS_SHAPE' | 'SHOW_CHANGE_NEEDS_PREVIOUS_PERIOD' | 'SETTING_DUPLICATED' | 'SETTING_NOT_FOR_DATA_VIEW' | 'SETTING_NOT_IN_WIDGET' | 'SETTING_UNKNOWN' | 'SETTING_VALUE_INVALID' | 'SORT_FIELD_NOT_SELECTED' | 'TABLE_TIME_DIMENSION_NOT_DRAWABLE' | 'TEXT_CONTENT_REQUIRED' | 'TEXT_CONTENT_TOO_LONG' | 'TEXT_NOT_A_WIDGET_TYPE' | 'TEXT_TAKES_NO_DATA' | 'TOO_MANY_DIMENSIONS' | 'TOO_MANY_METRICS' | 'TRANSLATION_FAILED' | 'TREND_ORDER_IS_THE_TIME_AXIS' | 'TREND_TAKES_NO_DIMENSIONS' | 'VISUALIZATION_NOT_AVAILABLE' | 'VISUALIZATION_NOT_OF_REQUESTED_TYPE' | 'FIELD_NOT_AVAILABLE' | 'FILTER_CONDITIONS_MIXED_LEVELS' | 'FILTER_LEVEL_MISMATCH' | 'FORMULA_TOKEN_UNKNOWN' | 'FORMULA_INVALID' | 'LANGUAGE_FILE_KEYS_IGNORED' | 'SELECTION_EMPTY' | 'FILTER_KEY_AND_FILTERS' | 'FILTER_KEY_NOT_FOR_THIS_SHAPE' | 'KEY_NOT_FOR_GENERATION' | 'COMPARE_NOT_FOR_THIS_SHAPE' | 'COMPARE_NOT_FOR_INTEGRATION' | 'COMPARE_ITEMS_CARDINALITY' | 'COMPARE_METRICS_CARDINALITY' | 'COMPARE_OVER_TIME_NOT_AVAILABLE' | 'COMPARE_METRIC_HAS_NO_BREAKDOWN' | 'COMPARE_KEY_NOT_FOR_THIS_COMPARATOR' | 'COMPARATOR_CLASS_FIXED', required: What is wrong with that field, or what is worth knowing about it. The first group is the vocabulary of the body itself; the eleven that follow it appear only in `warnings[]`, bar TOO_MANY_PROBLEMS, which closes a `problems` list that was truncated; and the rest are the codes of the widget DSL, which travel here when a composition or a widget carries a dsl that does not validate.
        - `message` string, required: Human-readable explanation. Not stable across versions.
        - `suggestions` string[]: The accepted values, when the field takes a closed list.
  - `meta` WriteMeta, required: The meta of a write answer. A replay carries two extra fields so it can be told apart from a first call and still traced back to it.
    - `request_id` string, required: Id of THIS request, including on a replay.
    - `timestamp` string, date-time, required
    - `replayed` boolean: True when this answer is the stored answer of an earlier call with the same idempotency_key and the same body. Absent on a first call.
    - `replayed_from_request_id` string, nullable: The request_id of the call that actually did the work. Present only on a replay, and null when the stored answer carried no id.

## Other responses

- `400`: The request was refused before anything was written. INVALID_REQUEST covers the framework refusals of every endpoint (the Content-Type is not application/json, the body could not be read, the body is not valid JSON). The report endpoints add their own: VALIDATION_FAILED with details.problems, UNKNOWN_PARAM with the path of the field the endpoint does not read, and the single-field refusals MISSING_REQUIRED_FIELD, INVALID_VALUE and APPEND_NOT_ALLOWED. The description of each operation lists the ones it emits. Everything else a body can get wrong arrives as an entry of details.problems rather than as the code of the envelope.
- `401`: Invalid or missing Bearer token.
- `403`: The account may not perform this write. WRITE_ACCESS_DISABLED means write access has not been enabled for the API key in the REST API tab of the app. PLAN_LIMIT_REACHED means the plan has no room left for another report or template, and is emitted only by the endpoints that create one.
- `404`: A key of the request does not name anything this account owns. NOT_FOUND covers reports, sections, widgets, schedules, portal views, brand templates, email templates and language files; TEMPLATE_NOT_FOUND covers a catalog slug, version or section position that stopped being published between the validation and the write. An entity of another account is reported the same way as one that does not exist.
- `409`: The request is well formed and the current state does not allow it. Every write can answer IDEMPOTENCY_CONFLICT (the same idempotency_key with a different body) and IDEMPOTENCY_IN_PROGRESS (an identical call is running, with a Retry-After). The endpoints that ask for a confirmation add CONFIRMATION_EXPIRED and CONFIRMATION_MISMATCH; the ones that never confirm cannot answer either. Each operation lists the domain conflicts it adds on top of those.
- `413`: The request body is over 1 MB. The cap is applied before authentication, so this answer is refused without the key being read, and it carries the usual error envelope.
- `429`: A budget was spent. RATE_LIMITED is the per-key read budget (300/minute, 5000/day) or the per-account write budget (60/minute, 1000/day). EMAIL_QUOTA_REACHED is the separate budget of 50 calls per account per day that send email, and its Retry-After points at the next UTC midnight. The Retry-After header gives the seconds until the more restrictive window resets. The X-RateLimit-* headers report the read budget, except on the 429 of a write, where they report the write budget.
- `500`: Internal server error.
- `503`: Writes are paused, or the store the write guarantees rely on is not answering. Nothing was written and the call is worth retrying later. Reads are unaffected.

---

[API](https://skmtc.dev/reportingninja/apis/reporting-ninja-api.md) · [All operations](https://skmtc.dev/reportingninja/apis/reporting-ninja-api/llms.txt) · [OpenAPI document](https://skmtc.dev/reportingninja/apis/reporting-ninja-api/revisions/13876c71be34?raw)
