---
title: "Schedule a report for delivery by email"
method: POST
path: "/schedules/create"
tags: ["Schedules"]
---

# Schedule a report for delivery by email

`POST /schedules/create`

## Request

- Base URL: `https://api.reportingninja.com/v1`
- URL: `https://api.reportingninja.com/v1/schedules/create`
- Auth: HTTP bearer

Configures the standing delivery of a report by email, or sends it once with frequency now. A report has at most one standing schedule: when it already has an active one the call is refused with 409 SCHEDULE_ALREADY_EXISTS, and when it has a stopped one this call reconfigures it and shows what it looked like before. The message comes either from an email template of the account or from the six email fields, never from both. A template cannot be scheduled. This endpoint ALWAYS asks for a confirmation, and it spends one unit of the daily budget of calls that send email. Refusals: 400 VALIDATION_FAILED, UNKNOWN_PARAM, MISSING_REQUIRED_FIELD or INVALID_VALUE, 403 WRITE_ACCESS_DISABLED, 404 NOT_FOUND for the report, the email template or the language file it names, 409 SCHEDULE_ALREADY_EXISTS, SCHEDULE_ALREADY_QUEUED, SCHEDULE_QUEUED, NOT_EDITABLE_BY_API, SCHEDULE_USES_SYSTEM_TEMPLATE (when the stopped schedule this call would configure is built on the system email template) or ACCOUNT_HAS_NO_OWNER plus the confirmation and idempotency conflicts, 429 EMAIL_QUOTA_REACHED. Two warnings travel in a successful answer: REPORT_WILL_BE_SHARED_PUBLICLY when the link format will create and enable a public link without a password, and MAY_BE_DELIVERED_AGAIN_TODAY when the hour of the schedule has already passed today.

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

## Request body

- SchedulesCreateRequest: The delivery to configure. A report has at most one standing schedule; when it already has one and it is delivering, use /v1/schedules/update. What is checked is the RESULT: `time` must end up set unless the frequency is now, and at least one recipient must end up in recipients_to. When this call is configuring a schedule that had been stopped, a field the body leaves out keeps the value it already had, so the two can come from there instead of from this body.
  - `report_key` string, required: The report to deliver. It cannot be a template.
  - `frequency` 'daily' | 'weekly' | 'monthly' | 'now', required: How often to deliver it. `now` sends it once, as soon as the next delivery pass runs, and leaves no standing order behind. `none` is not accepted here: use /v1/schedules/delete to stop a delivery.
  - `frequency_details` string: REQUIRED with weekly and with monthly, rejected with any other frequency. With weekly it is the weekday in upper case: MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY or SUNDAY. With monthly it is the day of the month written as a string, from "1" to "31".
  - `time` string: Hour of the delivery in the time zone of the account, as HH:00. REQUIRED except with frequency now, which rejects it.
  - `recipients_to` string[]: At least one address, at most 20, each at most 255 characters.
  - `recipients_cc` string[]
  - `recipients_bcc` string[]
  - `report_format` 'pdf' | 'xls' | 'link': How the report travels. Default pdf. `link` puts a link in the body instead of an attachment and needs the report link token in it.
  - `email_template_key` string, nullable: An email template of the account, which then carries the whole message. Explicit null means no template. When a template is named, the six email fields below are rejected by name.
  - `format` 'html' | 'plain_text': Body of the email. REQUIRED when no email_template_key is named.
  - `sender` string: Display name of the sender. REQUIRED when no email_template_key is named.
  - `sender_email` string: REQUIRED when no email_template_key is named. It must be the address of a user of the account, or of a domain the account already sends from.
  - `reply_to_email` string: REQUIRED when no email_template_key is named, under the same restriction as sender_email.
  - `subject` string: REQUIRED when no email_template_key is named.
  - `body` string: REQUIRED when no email_template_key is named.
  - `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.
  - `confirmation_token` string: The token the previous call answered. Sending the same body with it performs the write.

## Response `200`

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

- SchedulesCreateResponse: 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, and the call that asks for a confirmation answers WriteConfirmationRequiredData. Those two carry the preview of this operation alongside their own fields, so an answer can match more than one of these shapes.
    - SchedulesCreateData: The schedule as it now stands, with the title of its report and, when there was a stopped row to reconfigure, what it looked like before. A one-off delivery answers a different shape: schedule_key, report_key, frequency now, transient: true and will_send_now_to.
      - `schedule_key` string, nullable, required: The key that addresses this schedule.
      - `report_key` string, nullable, required: The report that is delivered.
      - `frequency` 'daily' | 'weekly' | 'monthly' | 'none' | 'now', nullable, required: How often the report is sent. `none` is a schedule that is kept and does not deliver; `now` appears only in the answer of a one-off delivery.
      - `frequency_details` string, nullable: The weekday in upper case for `weekly`, and the day of the month as a string from "1" to "31" for `monthly`.
      - `time` string, nullable: Hour of the delivery in the time zone of the account, as HH:00.
      - `recipients_to` string[]
      - `recipients_cc` string[]
      - `recipients_bcc` string[]
      - `report_format` 'pdf' | 'xls' | 'link', nullable: How the report travels: attached as a PDF, attached as a spreadsheet, or as a link in the body.
      - `email_template_key` string, nullable: The email template of the account this delivery uses, or null when it uses none and when it uses the system one.
      - `uses_system_template` boolean: True when the delivery is built from the system template in the language of the user, which this version cannot express and cannot update.
      - `format` 'html' | 'plain_text', nullable: Body of the email.
      - `subject` string, nullable
      - `sender` string, nullable: Display name of the sender.
      - `sender_email` string, nullable
      - `reply_to_email` string, nullable
      - `origin` 'app' | 'portal' | 'api', nullable: Where the schedule was created.
      - `user_key` string, nullable: The user the delivery is attributed to.
      - `conditional_execution_enabled` boolean: True when the delivery only goes out if a condition holds. This version cannot express the condition, and refuses to change a schedule that has one.
      - `queued` boolean: True while this delivery is being sent. A schedule cannot be changed or stopped while it is true.
      - `report_name` string, nullable: Title of the report that is delivered.
      - `before` Schedule: One scheduled delivery of a report by email.
        - `schedule_key` string, nullable, required: The key that addresses this schedule.
        - `report_key` string, nullable, required: The report that is delivered.
        - `frequency` 'daily' | 'weekly' | 'monthly' | 'none' | 'now', nullable, required: How often the report is sent. `none` is a schedule that is kept and does not deliver; `now` appears only in the answer of a one-off delivery.
        - `frequency_details` string, nullable: The weekday in upper case for `weekly`, and the day of the month as a string from "1" to "31" for `monthly`.
        - `time` string, nullable: Hour of the delivery in the time zone of the account, as HH:00.
        - `recipients_to` string[], required
        - `recipients_cc` string[], required
        - `recipients_bcc` string[], required
        - `report_format` 'pdf' | 'xls' | 'link', nullable: How the report travels: attached as a PDF, attached as a spreadsheet, or as a link in the body.
        - `email_template_key` string, nullable: The email template of the account this delivery uses, or null when it uses none and when it uses the system one.
        - `uses_system_template` boolean, required: True when the delivery is built from the system template in the language of the user, which this version cannot express and cannot update.
        - `format` 'html' | 'plain_text', nullable: Body of the email.
        - `subject` string, nullable
        - `sender` string, nullable: Display name of the sender.
        - `sender_email` string, nullable
        - `reply_to_email` string, nullable
        - `origin` 'app' | 'portal' | 'api', nullable: Where the schedule was created.
        - `user_key` string, nullable: The user the delivery is attributed to.
        - `conditional_execution_enabled` boolean: True when the delivery only goes out if a condition holds. This version cannot express the condition, and refuses to change a schedule that has one.
        - `last_execution_at` string, nullable: When it was last delivered, in UTC.
        - `queued` boolean: True while this delivery is being sent. A schedule cannot be changed or stopped while it is true.
      - `transient` boolean: Present only for frequency now: the delivery is one-off and leaves no standing order.
      - `will_send_now_to` string[]: Present only for frequency now: the addresses the one-off delivery goes to.
      - `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.
    - WriteConfirmationRequiredData: The answer of the first call to an endpoint that asks for a confirmation. Nothing was written. The fields of the endpoint's own preview travel alongside these three, so the caller can show what is about to happen. The token is good for one use, for this account, this endpoint and this exact body.
      - `confirmation_required` true, required: Always true in this shape.
      - `confirmation_token` string, required: Send the same body again with this token to perform the write.
      - `expires_at` string, date-time, required: When the token stops being accepted, 10 minutes after it was issued.
  - `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)
