Skip to main content

create-customer-subscription

dci create-customer-subscription [body] [flags]

Creates one dashboard, report, or commitment subscription with one, two, or three scheduled subscriptions: dashboard-subscription, report-subscription, and commitment-manager. Create does not accept alert or budget. Each type appears at most once. The response is that subscription. Nested entries are ordered by type. name is \{source name\} (Dashboard Subscription), (Report Subscription), or (Commitment Subscription), taken from the nested type that sorts first. createdBy is the caller. subscriptions must contain one to three entries. A repeated type is 422 customers.duplicate_subscription_type. An alert or budget type, an empty subscriptions array, or any server-set field (id, name, createdBy, etag, permissions, source.name, schedule.nextAt) is 422 validation_failed. source.id must belong to the tenant. A missing or foreign source is 422 validation_failed. Recipient and schedule value rules are on CustomerSubscriptionRecipients and CustomerSubscriptionScheduleInput. The caller must be allowed to manage every sent type: a DoiT employee, Manage Settings, or the permission for that type. Users Manager alone grants no access. Idempotency-Key is required. The same key, caller, customer, method, path, and body replays the stored success response. A different body for that key is 422 idempotency_key_reused. A missing key is 400 idempotency_key_required. While the first request is in progress, another request with that key is 409 idempotency_request_in_progress. A 4xx response releases the key. dryRun=true does not reserve the key. It returns the would-be subscription with id and etag null and writes nothing. Reads are immediately consistent. After a 201, a GET of that id returns the created subscription. The first delivery can take up to an hour.

Pass the request body as name: value arguments or pipe JSON on stdin — see Command structure.

Request​

Content-Type: application/json

FieldTypeRequiredDescription
recipientsobjectyesDelivery targets. Both arrays are required.
recipients.emailsarray of string (email)yesEmail destinations in stored order. Reads return each address trimmed and lowercase. Addresses must belong to the organization's approved domains and must be a user or a pending invite. doit.com and doit-intl.com pass the domain check. A blank email, an email with no domain, or a domain the tenant does not allow is refused with 422 and code DOMAIN_NOT_APPROVED. An email that is not a user or a pending invite is refused with 422 and code NOT_INVITED_USER. The same email twice, ignoring case, is refused with 422 and code customers.duplicate_recipient. An address already stored, and still present, is not checked again.
recipients.slackChannelsarray of stringyesSlack channel ids in stored order. Reads return the public id. Ids are compared exactly after trim. A duplicate id is refused with 422 and code customers.duplicate_recipient. A blank or unconnected id is refused with 422 and code SLACK_CHANNEL_NOT_CONNECTED. This API does not list eligible channels.
subscriptionsarray of objectyesScheduled subscriptions to store on the new subscription. The server sorts them by type ascending. Request order is not stored and is not significant. A change of only that order does not write. The response is ordered by type, not by this request.
subscriptions[].typestringyesScheduled subscription type.
subscriptions[].sourceobjectyesSource to attach. Must belong to the tenant.
subscriptions[].source.idstringyesID of the dashboard, report, or commitment. Must belong to the tenant. Report ids come from GET /analytics/v1/reports and GET /analytics/v1/reports/\{id\}. Commitment ids come from GET /analytics/v1/commitment-manager and GET /analytics/v1/commitment-manager/\{id\}. Create checks that id against the customer's dashboards and the preset dashboards.
subscriptions[].scheduleobjectyesDelivery schedule.
subscriptions[].schedule.timeZonestringyesIANA time zone for this schedule.
subscriptions[].schedule.startDatestring (date-time)yesWhen this schedule starts. The start day is the calendar date of this instant in timeZone, not its UTC date. To start on 2026-11-03 in America/Los_Angeles, send 2026-11-03T00:00:00-08:00 (2026-11-03T08:00:00Z). A read returns the stored instant as UTC RFC3339 (Z).
subscriptions[].schedule.timestring (date-time)yesDelivery clock to store. The value is an instant. A read returns it as UTC RFC3339 (Z) and does not preserve an offset. The calendar date is stored and is not the start date. Deliveries use only the hour and minute of that instant, interpreted in timeZone.
subscriptions[].schedule.frequencystringyesDelivery frequency to store.
subscriptions[].schedule.customPeriodAmountintegerNumber of customPeriodUnit between deliveries. Required when frequency is Custom. At least 1.
subscriptions[].schedule.customPeriodUnitstringUnit for customPeriodAmount. Required when frequency is Custom.
subscriptions[].attachmentsarray of stringFiles to attach to the report email. Report only. Omitted stores an empty list.
subscriptions[].pdfSettingsobjectPDF rendering options. Report only. Omitted stores all true.
subscriptions[].pdfSettings.showFiltersbooleanyesInclude the report filters summary in the PDF.
subscriptions[].pdfSettings.includeTablebooleanyesInclude the data table in the PDF.
subscriptions[].pdfSettings.includeAnnotationsbooleanyesInclude annotations in the PDF time range.

Example body (JSON)​

{
"recipients": {
"emails": [
],
"slackChannels": [
"C0123ABCD"
]
},
"subscriptions": [
{
"type": "dashboard-subscription",
"source": {
"id": "dashboard_123"
},
"schedule": {
"timeZone": "Europe/Warsaw",
"startDate": "2026-10-23T00:00:00Z",
"time": "2026-10-23T08:00:00Z",
"frequency": "Weekly"
}
}
]
}
Raw JSON schema
{
"type": "object",
"additionalProperties": false,
"description": "Fields of a new subscription. Unknown fields and server-set fields are rejected. `subscriptions` has one, two, or three scheduled types, each at most once. Both recipient arrays are required, and at least one address is required.",
"required": [
"recipients",
"subscriptions"
],
"properties": {
"recipients": {
"description": "Delivery targets. Both arrays are required.",
"allOf": [
{
"type": "object",
"additionalProperties": false,
"description": "Delivery targets. This object reads and writes email addresses and Slack channel ids only. Microsoft Teams and the in-app bell are not returned and cannot be set. A Teams destination stored in the Console still counts as a destination, so clearing email and Slack on that subscription does not return `409` `customers.last_destination`. Both arrays are always present on read. An unused list is an empty array, never omitted and never `null`. Emails are trimmed and stored lowercase. Channel ids are trimmed. A replaced list is stored in the order sent. Reads do not sort. A disconnected Slack channel stays in the list at its stored id. Create requires at least one email or Slack channel. A stored alert or budget may have both lists empty.",
"required": [
"emails",
"slackChannels"
],
"properties": {
"emails": {
"type": "array",
"minItems": 0,
"description": "Email destinations in stored order. Reads return each address trimmed and lowercase. Addresses must belong to the organization's approved domains and must be a user or a pending invite. `doit.com` and `doit-intl.com` pass the domain check. A blank email, an email with no domain, or a domain the tenant does not allow is refused with `422` and code `DOMAIN_NOT_APPROVED`. An email that is not a user or a pending invite is refused with `422` and code `NOT_INVITED_USER`. The same email twice, ignoring case, is refused with `422` and code `customers.duplicate_recipient`. An address already stored, and still present, is not checked again.",
"items": {
"type": "string",
"format": "email",
"minLength": 1,
"x-doit-pii": true
},
"example": [
]
},
"slackChannels": {
"type": "array",
"minItems": 0,
"description": "Slack channel ids in stored order. Reads return the public id. Ids are compared exactly after trim. A duplicate id is refused with `422` and code `customers.duplicate_recipient`. A blank or unconnected id is refused with `422` and code `SLACK_CHANNEL_NOT_CONNECTED`. This API does not list eligible channels.",
"items": {
"type": "string",
"minLength": 1
},
"example": [
"C0123ABCD"
]
}
},
"example": {
"emails": [
],
"slackChannels": [
"C0123ABCD"
]
}
}
]
},
"subscriptions": {
"type": "array",
"format": "set",
"minItems": 1,
"maxItems": 3,
"description": "Scheduled subscriptions to store on the new subscription. The server sorts them by `type` ascending. Request order is not stored and is not significant. A change of only that order does not write. The response is ordered by `type`, not by this request.",
"items": {
"type": "object",
"additionalProperties": false,
"description": "One scheduled subscription to create. `type` is `dashboard-subscription`, `report-subscription`, or `commitment-manager`, and each type appears once. `attachments` and `pdfSettings` are accepted only when `type` is `report-subscription`. Omitted `attachments` stores `[]`. Omitted `pdfSettings` stores all `true`. A duplicate or unknown attachment value is `422`.",
"required": [
"type",
"source",
"schedule"
],
"properties": {
"type": {
"description": "Scheduled subscription type.",
"allOf": [
{
"type": "string",
"description": "Scheduled type that create and a patch of a scheduled subscription can write. `alert` and `budget` are rejected.",
"example": "dashboard-subscription",
"enum": [
"commitment-manager",
"dashboard-subscription",
"report-subscription"
],
"x-enumDescriptions": {
"commitment-manager": "Commitment subscription. Scheduled delivery of a commitment in Commitment manager.",
"dashboard-subscription": "Dashboard subscription. Scheduled email or Slack delivery of a dashboard.",
"report-subscription": "Report subscription. Scheduled email or Slack delivery of a report."
}
}
],
"example": "dashboard-subscription"
},
"source": {
"description": "Source to attach. Must belong to the tenant.",
"allOf": [
{
"type": "object",
"additionalProperties": false,
"description": "Source to attach. `id` is the same property GET returns. `name` is server-set and rejected.",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "ID of the dashboard, report, or commitment. Must belong to the tenant. Report ids come from `GET /analytics/v1/reports` and `GET /analytics/v1/reports/{id}`. Commitment ids come from `GET /analytics/v1/commitment-manager` and `GET /analytics/v1/commitment-manager/{id}`. Create checks that id against the customer's dashboards and the preset dashboards.",
"example": "dashboard_123"
}
},
"example": {
"id": "dashboard_123"
}
}
]
},
"schedule": {
"description": "Delivery schedule.",
"allOf": [
{
"type": "object",
"additionalProperties": false,
"description": "Schedule fields the client sends. `nextAt` is server-set and rejected. `customPeriodAmount` and `customPeriodUnit` are required when `frequency` is `Custom` and rejected otherwise.",
"required": [
"timeZone",
"startDate",
"time",
"frequency"
],
"properties": {
"timeZone": {
"type": "string",
"minLength": 1,
"description": "IANA time zone for this schedule.",
"example": "Europe/Warsaw"
},
"startDate": {
"type": "string",
"format": "date-time",
"description": "When this schedule starts. The start day is the calendar date of this instant in `timeZone`, not its UTC date. To start on 2026-11-03 in `America/Los_Angeles`, send `2026-11-03T00:00:00-08:00` (`2026-11-03T08:00:00Z`). A read returns the stored instant as UTC RFC3339 (`Z`).",
"example": "2026-11-03T08:00:00Z"
},
"time": {
"type": "string",
"format": "date-time",
"description": "Delivery clock to store. The value is an instant. A read returns it as UTC RFC3339 (`Z`) and does not preserve an offset. The calendar date is stored and is not the start date. Deliveries use only the hour and minute of that instant, interpreted in `timeZone`.",
"example": "2026-10-23T08:00:00Z"
},
"frequency": {
"description": "Delivery frequency to store.",
"allOf": [
{
"type": "string",
"description": "Console delivery frequency. Wire values match the Console labels.",
"example": "Weekly",
"enum": [
"Daily",
"Weekly",
"Monthly",
"Annually",
"Custom"
],
"x-enumDescriptions": {
"Daily": "Deliver every day at the configured time.",
"Weekly": "Deliver once a week at the configured time.",
"Monthly": "Deliver once a month at the configured time.",
"Annually": "Deliver once a year at the configured time.",
"Custom": "Deliver on the interval in `customPeriodAmount` and `customPeriodUnit`."
}
}
],
"example": "Weekly"
},
"customPeriodAmount": {
"type": "integer",
"minimum": 1,
"description": "Number of `customPeriodUnit` between deliveries. Required when `frequency` is `Custom`. At least 1.",
"example": 2
},
"customPeriodUnit": {
"description": "Unit for `customPeriodAmount`. Required when `frequency` is `Custom`.",
"allOf": [
{
"type": "string",
"description": "Unit for a `Custom` frequency.",
"example": "Month",
"enum": [
"Day",
"Week",
"Month",
"Year"
],
"x-enumDescriptions": {
"Day": "The custom interval is a number of days.",
"Week": "The custom interval is a number of weeks.",
"Month": "The custom interval is a number of months.",
"Year": "The custom interval is a number of years."
}
}
],
"example": "Month"
}
},
"example": {
"timeZone": "America/Los_Angeles",
"startDate": "2026-11-03T08:00:00Z",
"time": "2026-11-03T16:00:00Z",
"frequency": "Weekly"
}
}
]
},
"attachments": {
"type": "array",
"uniqueItems": true,
"description": "Files to attach to the report email. Report only. Omitted stores an empty list.",
"items": {
"type": "string",
"description": "File to attach to a report subscription email. Values may appear at most once. Attachments are email-only: Slack-only subscriptions may still store the choice, and delivery skips the files when there is no email recipient. An oversized file is omitted from that email with a note in the body. This API does not upload or return file bytes.",
"example": "pdf",
"enum": [
"pdf",
"csv"
],
"x-enumDescriptions": {
"pdf": "Attach a PDF of the report to the email.",
"csv": "Attach a CSV export of the report to the email."
}
},
"example": [
"pdf"
]
},
"pdfSettings": {
"description": "PDF rendering options. Report only. Omitted stores all `true`.",
"allOf": [
{
"type": "object",
"additionalProperties": false,
"description": "PDF rendering options for a report subscription. Present on every `report-subscription` read. A missing stored object reads as all `true`. The flags apply only when `pdf` is in `attachments`. They may still be stored while PDF is off.",
"required": [
"showFilters",
"includeTable",
"includeAnnotations"
],
"properties": {
"showFilters": {
"type": "boolean",
"description": "Include the report filters summary in the PDF.",
"example": true
},
"includeTable": {
"type": "boolean",
"description": "Include the data table in the PDF.",
"example": true
},
"includeAnnotations": {
"type": "boolean",
"description": "Include annotations in the PDF time range.",
"example": true
}
},
"example": {
"showFilters": true,
"includeTable": true,
"includeAnnotations": true
}
}
]
}
},
"example": {
"type": "dashboard-subscription",
"source": {
"id": "dashboard_123"
},
"schedule": {
"timeZone": "Europe/Warsaw",
"startDate": "2026-10-23T00:00:00Z",
"time": "2026-10-23T08:00:00Z",
"frequency": "Weekly"
}
}
}
}
},
"example": {
"recipients": {
"emails": [
],
"slackChannels": [
"C0123ABCD"
]
},
"subscriptions": [
{
"type": "dashboard-subscription",
"source": {
"id": "dashboard_123"
},
"schedule": {
"timeZone": "Europe/Warsaw",
"startDate": "2026-10-23T00:00:00Z",
"time": "2026-10-23T08:00:00Z",
"frequency": "Weekly"
}
}
]
}
}

Flags​

FlagTypeDefaultExampleDescription
--dry-runbooleanfalseIf true, validates the request and returns the would-be result without applying it. The response then carries the X-Dry-Run header.

Every command also accepts the CLI-wide flags for output shaping — see Output formats and Table output options.

Output​

OK - Dry run only (dryRun=true). Every check of a real create passed and nothing was written. id and etag are null.

By default dci renders the result as a table. Use --output json to get the full structure described below — see Output formats.

FieldTypeDescription
idstringServer-assigned subscription id. null only on a dry-run create.
namestringDisplay name. For a scheduled subscription, \{source name\} plus (Dashboard Subscription), (Report Subscription), or (Commitment Subscription), from the nested type that sorts first. For an alert or a budget, the source name.
createdBystring (email)Email address of the subscription creator. On create, the caller.
recipientsobjectDelivery targets shared by every nested subscription on this item.
recipients.emailsarray of string (email)Email destinations in stored order. Reads return each address trimmed and lowercase. Addresses must belong to the organization's approved domains and must be a user or a pending invite. doit.com and doit-intl.com pass the domain check. A blank email, an email with no domain, or a domain the tenant does not allow is refused with 422 and code DOMAIN_NOT_APPROVED. An email that is not a user or a pending invite is refused with 422 and code NOT_INVITED_USER. The same email twice, ignoring case, is refused with 422 and code customers.duplicate_recipient. An address already stored, and still present, is not checked again.
recipients.slackChannelsarray of stringSlack channel ids in stored order. Reads return the public id. Ids are compared exactly after trim. A duplicate id is refused with 422 and code customers.duplicate_recipient. A blank or unconnected id is refused with 422 and code SLACK_CHANNEL_NOT_CONNECTED. This API does not list eligible channels.
subscriptionsarray of objectNested subscriptions ordered by type ascending. The server sorts them. One entry per visible type. A scheduled subscription has one, two, or three scheduled types. An alert or a budget has exactly one entry.
subscriptions[].typestringKind of nested subscription.
subscriptions[].sourceobjectSource resource for this nested subscription.
subscriptions[].source.idstringID of the dashboard, report, commitment, alert, or budget. Writable on create and on a patch of a scheduled subscription. On an alert or a budget it is the resource id. Report ids come from GET /analytics/v1/reports and GET /analytics/v1/reports/\{id\}. Commitment ids come from GET /analytics/v1/commitment-manager and GET /analytics/v1/commitment-manager/\{id\}.
subscriptions[].source.namestringDisplay name of the source resource.
subscriptions[].scheduleobjectDelivery schedule. Omitted for an alert or a budget. Never null.
subscriptions[].schedule.timeZonestringIANA time zone used to calculate deliveries.
subscriptions[].schedule.startDatestring (date-time)When this schedule starts. The start day is the calendar date of this instant in timeZone, not its UTC date. To start on 2026-11-03 in America/Los_Angeles, send 2026-11-03T00:00:00-08:00 (2026-11-03T08:00:00Z). A read returns the stored instant as UTC RFC3339 (Z).
subscriptions[].schedule.timestring (date-time)Stored instant of the delivery clock, returned as UTC RFC3339 (Z). An offset in the request is not preserved. 2026-10-23T10:00:00+02:00 is returned as 2026-10-23T08:00:00Z. The calendar date is the date of that instant. It is stored and returned, and it is not the subscription start date. Deliveries use only the hour and minute of that instant, interpreted in timeZone. Sending time already in Z makes the next read match the sent date and clock.
subscriptions[].schedule.frequencystringConsole delivery frequency.
subscriptions[].schedule.nextAtstring (date-time)Next calculated delivery, returned as UTC RFC3339 (Z). The server sets it. A schedule change recalculates it.
subscriptions[].schedule.customPeriodAmountintegerNumber of customPeriodUnit between deliveries. Present only when frequency is Custom. At least 1.
subscriptions[].schedule.customPeriodUnitstringUnit for customPeriodAmount. Present only when frequency is Custom.
subscriptions[].attachmentsarray of stringFiles to attach to the report subscription email. Present only for report-subscription. Always an array on read. Empty means no file. Rejected on any other type with 422.
subscriptions[].pdfSettingsobjectPDF rendering options. Present only for report-subscription. Always present on a report read. Rejected on any other type with 422.
subscriptions[].pdfSettings.showFiltersbooleanInclude the report filters summary in the PDF.
subscriptions[].pdfSettings.includeTablebooleanInclude the data table in the PDF.
subscriptions[].pdfSettings.includeAnnotationsbooleanInclude annotations in the PDF time range.
subscriptions[].permissionsobjectActions the authenticated caller may perform on this nested subscription.
subscriptions[].permissions.canEditRecipientsbooleanWhether the caller may replace recipients.
subscriptions[].permissions.canDeletebooleanWhether the caller may delete this subscription. Always false for an alert or a budget.
subscriptions[].permissions.canUnsubscribebooleanWhether the caller may remove their own recipient email.
etagstringOpaque version token for optimistic concurrency. 64 lowercase hex characters when set. The response ETag header is this value wrapped in double quotes. Send If-Match: "<etag>". null only on a dry-run create. It only covers fields this API can write.

Example response (--output json)​

{
"id": null,
"name": "Executive costs (Dashboard Subscription)",
"createdBy": "[email protected]",
"recipients": {
"emails": [
],
"slackChannels": [
"C0123ABCD"
]
},
"subscriptions": [
{
"type": "dashboard-subscription",
"source": {
"id": "dashboard_123",
"name": "Executive costs"
},
"schedule": {
"timeZone": "Europe/Warsaw",
"startDate": "2026-10-23T00:00:00Z",
"time": "2026-10-23T08:00:00Z",
"frequency": "Weekly",
"nextAt": "2026-10-30T08:00:00Z"
},
"permissions": {
"canEditRecipients": true,
"canDelete": true,
"canUnsubscribe": false
}
},
{
"type": "report-subscription",
"source": {
"id": "report_456",
"name": "Monthly costs"
},
"schedule": {
"timeZone": "Europe/Warsaw",
"startDate": "2026-11-01T00:00:00Z",
"time": "2026-11-01T08:00:00Z",
"frequency": "Monthly",
"nextAt": "2026-12-01T08:00:00Z"
},
"permissions": {
"canEditRecipients": true,
"canDelete": true,
"canUnsubscribe": false
}
}
],
"etag": null
}
Raw JSON schema
{
"type": "object",
"additionalProperties": false,
"description": "One subscription. A scheduled subscription, an alert, and a budget use this same schema. `etag` is the opaque version token. Quote it when sending `If-Match` (`If-Match: \"<etag>\"`) on update and delete. The `ETag` response header carries that quoted form. `id` and `etag` are null only on a dry-run create.",
"required": [
"id",
"name",
"createdBy",
"recipients",
"subscriptions",
"etag"
],
"properties": {
"id": {
"type": "string",
"readOnly": true,
"x-terraform-computed": true,
"nullable": true,
"description": "Server-assigned subscription id. `null` only on a dry-run create.",
"example": "notif_01HX9P2KQVJ8Z3M4T5N6V7W8Y9"
},
"name": {
"type": "string",
"readOnly": true,
"x-terraform-computed": true,
"description": "Display name. For a scheduled subscription, `{source name}` plus `(Dashboard Subscription)`, `(Report Subscription)`, or `(Commitment Subscription)`, from the nested type that sorts first. For an alert or a budget, the source name.",
"example": "Executive costs (Dashboard Subscription)"
},
"createdBy": {
"type": "string",
"format": "email",
"readOnly": true,
"x-terraform-computed": true,
"x-doit-pii": true,
"description": "Email address of the subscription creator. On create, the caller.",
"example": "[email protected]"
},
"recipients": {
"description": "Delivery targets shared by every nested subscription on this item.",
"allOf": [
{
"type": "object",
"additionalProperties": false,
"description": "Delivery targets. This object reads and writes email addresses and Slack channel ids only. Microsoft Teams and the in-app bell are not returned and cannot be set. A Teams destination stored in the Console still counts as a destination, so clearing email and Slack on that subscription does not return `409` `customers.last_destination`. Both arrays are always present on read. An unused list is an empty array, never omitted and never `null`. Emails are trimmed and stored lowercase. Channel ids are trimmed. A replaced list is stored in the order sent. Reads do not sort. A disconnected Slack channel stays in the list at its stored id. Create requires at least one email or Slack channel. A stored alert or budget may have both lists empty.",
"required": [
"emails",
"slackChannels"
],
"properties": {
"emails": {
"type": "array",
"minItems": 0,
"description": "Email destinations in stored order. Reads return each address trimmed and lowercase. Addresses must belong to the organization's approved domains and must be a user or a pending invite. `doit.com` and `doit-intl.com` pass the domain check. A blank email, an email with no domain, or a domain the tenant does not allow is refused with `422` and code `DOMAIN_NOT_APPROVED`. An email that is not a user or a pending invite is refused with `422` and code `NOT_INVITED_USER`. The same email twice, ignoring case, is refused with `422` and code `customers.duplicate_recipient`. An address already stored, and still present, is not checked again.",
"items": {
"type": "string",
"format": "email",
"minLength": 1,
"x-doit-pii": true
},
"example": [
]
},
"slackChannels": {
"type": "array",
"minItems": 0,
"description": "Slack channel ids in stored order. Reads return the public id. Ids are compared exactly after trim. A duplicate id is refused with `422` and code `customers.duplicate_recipient`. A blank or unconnected id is refused with `422` and code `SLACK_CHANNEL_NOT_CONNECTED`. This API does not list eligible channels.",
"items": {
"type": "string",
"minLength": 1
},
"example": [
"C0123ABCD"
]
}
},
"example": {
"emails": [
],
"slackChannels": [
"C0123ABCD"
]
}
}
]
},
"subscriptions": {
"type": "array",
"format": "set",
"minItems": 1,
"description": "Nested subscriptions ordered by `type` ascending. The server sorts them. One entry per visible type. A scheduled subscription has one, two, or three scheduled types. An alert or a budget has exactly one entry.",
"items": {
"type": "object",
"additionalProperties": false,
"description": "One nested subscription. `schedule` is present for a dashboard, report, or commitment subscription and omitted for an alert or a budget. `attachments` and `pdfSettings` are present only for a `report-subscription` and omitted for every other type.",
"required": [
"type",
"source",
"permissions"
],
"properties": {
"type": {
"description": "Kind of nested subscription.",
"allOf": [
{
"type": "string",
"description": "Kind of subscription nested on one item. One id namespace is shared by a scheduled subscription, an alert, and a budget. Create accepts only `dashboard-subscription`, `report-subscription`, and `commitment-manager`. GET returns `alert` and `budget`. PATCH may replace their recipient lists, and that is the one write this API makes on them. Those lists are the same ones written by `PATCH /analytics/v1/alerts/{id}` and `PATCH /analytics/v1/budgets/{id}`. DELETE of an alert or a budget returns `409` `customers.entry_is_resource`. `EntryPatch.type` includes `alert` and `budget` so an echo of the stored entry validates.",
"example": "dashboard-subscription",
"enum": [
"alert",
"budget",
"commitment-manager",
"dashboard-subscription",
"report-subscription"
],
"x-enumDescriptions": {
"alert": "Alert entry. One item, and it cannot be deleted through this API.",
"budget": "Budget entry. One item, and it cannot be deleted through this API.",
"commitment-manager": "Commitment subscription. Scheduled delivery of a commitment in Commitment manager.",
"dashboard-subscription": "Dashboard subscription. Scheduled email or Slack delivery of a dashboard.",
"report-subscription": "Report subscription. Scheduled email or Slack delivery of a report."
}
}
],
"example": "dashboard-subscription"
},
"source": {
"description": "Source resource for this nested subscription.",
"allOf": [
{
"type": "object",
"additionalProperties": false,
"description": "Source resource for one nested subscription. `name` is read live from the source and can change without a write to this API.",
"required": [
"id",
"name"
],
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "ID of the dashboard, report, commitment, alert, or budget. Writable on create and on a patch of a scheduled subscription. On an alert or a budget it is the resource id. Report ids come from `GET /analytics/v1/reports` and `GET /analytics/v1/reports/{id}`. Commitment ids come from `GET /analytics/v1/commitment-manager` and `GET /analytics/v1/commitment-manager/{id}`.",
"example": "dashboard_123"
},
"name": {
"type": "string",
"readOnly": true,
"x-terraform-computed": true,
"description": "Display name of the source resource.",
"example": "Executive costs"
}
},
"example": {
"id": "dashboard_123",
"name": "Executive costs"
}
}
]
},
"schedule": {
"description": "Delivery schedule. Omitted for an alert or a budget. Never `null`.",
"allOf": [
{
"type": "object",
"additionalProperties": false,
"description": "Delivery schedule for a dashboard, report, or commitment subscription. Omitted on an alert or a budget, never `null`. `customPeriodAmount` and `customPeriodUnit` are omitted unless `frequency` is `Custom`. `nextAt` moves when the next delivery is calculated.",
"required": [
"timeZone",
"startDate",
"time",
"frequency",
"nextAt"
],
"properties": {
"timeZone": {
"type": "string",
"minLength": 1,
"description": "IANA time zone used to calculate deliveries.",
"example": "Europe/Warsaw"
},
"startDate": {
"type": "string",
"format": "date-time",
"description": "When this schedule starts. The start day is the calendar date of this instant in `timeZone`, not its UTC date. To start on 2026-11-03 in `America/Los_Angeles`, send `2026-11-03T00:00:00-08:00` (`2026-11-03T08:00:00Z`). A read returns the stored instant as UTC RFC3339 (`Z`).",
"example": "2026-11-03T08:00:00Z"
},
"time": {
"type": "string",
"format": "date-time",
"description": "Stored instant of the delivery clock, returned as UTC RFC3339 (`Z`). An offset in the request is not preserved. `2026-10-23T10:00:00+02:00` is returned as `2026-10-23T08:00:00Z`. The calendar date is the date of that instant. It is stored and returned, and it is not the subscription start date. Deliveries use only the hour and minute of that instant, interpreted in `timeZone`. Sending `time` already in `Z` makes the next read match the sent date and clock.",
"example": "2026-10-23T08:00:00Z"
},
"frequency": {
"description": "Console delivery frequency.",
"allOf": [
{
"type": "string",
"description": "Console delivery frequency. Wire values match the Console labels.",
"example": "Weekly",
"enum": [
"Daily",
"Weekly",
"Monthly",
"Annually",
"Custom"
],
"x-enumDescriptions": {
"Daily": "Deliver every day at the configured time.",
"Weekly": "Deliver once a week at the configured time.",
"Monthly": "Deliver once a month at the configured time.",
"Annually": "Deliver once a year at the configured time.",
"Custom": "Deliver on the interval in `customPeriodAmount` and `customPeriodUnit`."
}
}
],
"example": "Weekly"
},
"nextAt": {
"type": "string",
"format": "date-time",
"readOnly": true,
"x-terraform-computed": true,
"description": "Next calculated delivery, returned as UTC RFC3339 (`Z`). The server sets it. A schedule change recalculates it.",
"example": "2026-10-30T08:00:00Z"
},
"customPeriodAmount": {
"type": "integer",
"minimum": 1,
"description": "Number of `customPeriodUnit` between deliveries. Present only when `frequency` is `Custom`. At least 1.",
"example": 2
},
"customPeriodUnit": {
"description": "Unit for `customPeriodAmount`. Present only when `frequency` is `Custom`.",
"allOf": [
{
"type": "string",
"description": "Unit for a `Custom` frequency.",
"example": "Month",
"enum": [
"Day",
"Week",
"Month",
"Year"
],
"x-enumDescriptions": {
"Day": "The custom interval is a number of days.",
"Week": "The custom interval is a number of weeks.",
"Month": "The custom interval is a number of months.",
"Year": "The custom interval is a number of years."
}
}
],
"example": "Month"
}
},
"example": {
"timeZone": "Europe/Warsaw",
"startDate": "2026-10-23T00:00:00Z",
"time": "2026-10-23T08:00:00Z",
"frequency": "Weekly",
"nextAt": "2026-10-30T08:00:00Z"
}
}
]
},
"attachments": {
"type": "array",
"uniqueItems": true,
"description": "Files to attach to the report subscription email. Present only for `report-subscription`. Always an array on read. Empty means no file. Rejected on any other type with `422`.",
"items": {
"type": "string",
"description": "File to attach to a report subscription email. Values may appear at most once. Attachments are email-only: Slack-only subscriptions may still store the choice, and delivery skips the files when there is no email recipient. An oversized file is omitted from that email with a note in the body. This API does not upload or return file bytes.",
"example": "pdf",
"enum": [
"pdf",
"csv"
],
"x-enumDescriptions": {
"pdf": "Attach a PDF of the report to the email.",
"csv": "Attach a CSV export of the report to the email."
}
},
"example": [
"pdf",
"csv"
]
},
"pdfSettings": {
"description": "PDF rendering options. Present only for `report-subscription`. Always present on a report read. Rejected on any other type with `422`.",
"allOf": [
{
"type": "object",
"additionalProperties": false,
"description": "PDF rendering options for a report subscription. Present on every `report-subscription` read. A missing stored object reads as all `true`. The flags apply only when `pdf` is in `attachments`. They may still be stored while PDF is off.",
"required": [
"showFilters",
"includeTable",
"includeAnnotations"
],
"properties": {
"showFilters": {
"type": "boolean",
"description": "Include the report filters summary in the PDF.",
"example": true
},
"includeTable": {
"type": "boolean",
"description": "Include the data table in the PDF.",
"example": true
},
"includeAnnotations": {
"type": "boolean",
"description": "Include annotations in the PDF time range.",
"example": true
}
},
"example": {
"showFilters": true,
"includeTable": true,
"includeAnnotations": true
}
}
]
},
"permissions": {
"description": "Actions the authenticated caller may perform on this nested subscription.",
"readOnly": true,
"x-terraform-computed": true,
"allOf": [
{
"type": "object",
"additionalProperties": false,
"description": "Actions the authenticated caller may perform on this nested subscription. Computed per request; a different token can see different values.",
"readOnly": true,
"x-terraform-computed": true,
"required": [
"canEditRecipients",
"canDelete",
"canUnsubscribe"
],
"properties": {
"canEditRecipients": {
"type": "boolean",
"description": "Whether the caller may replace recipients.",
"example": true
},
"canDelete": {
"type": "boolean",
"description": "Whether the caller may delete this subscription. Always `false` for an alert or a budget.",
"example": true
},
"canUnsubscribe": {
"type": "boolean",
"description": "Whether the caller may remove their own recipient email.",
"example": false
}
},
"example": {
"canEditRecipients": true,
"canDelete": true,
"canUnsubscribe": false
}
}
]
}
},
"example": {
"type": "dashboard-subscription",
"source": {
"id": "dashboard_123",
"name": "Executive costs"
},
"schedule": {
"timeZone": "Europe/Warsaw",
"startDate": "2026-10-23T00:00:00Z",
"time": "2026-10-23T08:00:00Z",
"frequency": "Weekly",
"nextAt": "2026-10-30T08:00:00Z"
},
"permissions": {
"canEditRecipients": true,
"canDelete": true,
"canUnsubscribe": false
}
}
}
},
"etag": {
"type": "string",
"readOnly": true,
"x-terraform-computed": true,
"nullable": true,
"description": "Opaque version token for optimistic concurrency. 64 lowercase hex characters when set. The response `ETag` header is this value wrapped in double quotes. Send `If-Match: \"<etag>\"`. `null` only on a dry-run create. It only covers fields this API can write.",
"example": "3f7a1c9e2b4d6f8a0c1e3a5b7d9f1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a3c5e"
}
},
"example": {
"id": "notif_01HX9P2KQVJ8Z3M4T5N6V7W8Y9",
"name": "Executive costs (Dashboard Subscription)",
"createdBy": "[email protected]",
"recipients": {
"emails": [
],
"slackChannels": [
"C0123ABCD"
]
},
"subscriptions": [
{
"type": "dashboard-subscription",
"source": {
"id": "dashboard_123",
"name": "Executive costs"
},
"schedule": {
"timeZone": "Europe/Warsaw",
"startDate": "2026-10-23T00:00:00Z",
"time": "2026-10-23T08:00:00Z",
"frequency": "Weekly",
"nextAt": "2026-10-30T08:00:00Z"
},
"permissions": {
"canEditRecipients": true,
"canDelete": true,
"canUnsubscribe": false
}
}
],
"etag": "3f7a1c9e2b4d6f8a0c1e3a5b7d9f1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a3c5e"
}
}

Errors​

On failure, dci prints a single error message — with a hint when one is available — and exits with a typed code your scripts can branch on. See Errors and exit codes for the full contract.

HTTP status to exit code mapping
HTTP statusExit codeError codeMeaning
400, 42230VALIDATION_ERRORThe arguments or request body were rejected. Review the command's flags and payload.
40110AUTHENTICATION_FAILEDNot signed in, or the API token is invalid. Run dci login or check DCI_API_KEY.
40311PERMISSION_DENIEDThe DoiT user or the active customer context does not have access.
40921RESOURCE_CONFLICTThe operation conflicts with the resource's current state.
50040API_SERVER_ERRORThe API failed to process the request. Retryable; contact DoiT support if it persists.

Aliases: createcustomersubscription