openapi: 3.2.0 info: version: 1.0.0 title: Customer.io App Newsletter Metrics API description: 'Our App API provides ways to trigger messages and retrieve information about people, campaigns, broadcasts, and more. # Overview The App API provides methods to send newsletters, transactional messages, and API-triggered broadcasts. You can create newsletters from scratch and update transactional messages and API-triggered broadcasts. For transactional messages and API-triggered broadcasts, your payload acts as a message "trigger" and can contain `data` that you reference in your messages using liquid—`{{trigger.}}`. The other endpoints help you retrieve information about people, segments, campaigns, broadcasts, etc; it also lets you update campaign actions, messages, newsletter variants, etc. Aside from the [API-triggered broadcast](#triggerBroadcast) (1 per 10 seconds) and [Transactional](#sendEmail) (100 per second) endpoints, requests are limited to 10 per second. # Use our Postman collection We''ve generated a Postman collection to help you get started with our APIs. If you fork this collection, you might want to disable the *Watch original collection* option. We automatically update our Postman collection whenever we release changes to our documentation, even if we don''t change our APIs—which happens daily! Rather than being flooded with Postman notifications, you can check out our [Release Notes](/release-notes/) for updates to our APIs. **NOTE**: Postman endpoints default to our US APIs. If you''re in our European (EU) region, you''ll need to add `-eu` to the server variables (`track_api_url` and `app_api_url`). [Run In Postman](https://god.gw.postman.com/run-collection/23697545-2931c004-e63d-4cdc-bf4b-e685ba6da42d?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D23697545-2931c004-e63d-4cdc-bf4b-e685ba6da42d%26entityType%3Dcollection%26workspaceId%3Db886877f-fc09-475f-84fe-6221a98f4d18#?env%5BCustomer.io%20API%20Environment%5D=W3sia2V5IjoidHJhY2tfYXBpX3VybCIsInR5cGUiOiJkZWZhdWx0IiwidmFsdWUiOiJ0cmFjay5jdXN0b21lci5pbyIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoiYXBwX2FwaV91cmwiLCJ0eXBlIjoiZGVmYXVsdCIsInZhbHVlIjoiYXBpLmN1c3RvbWVyLmlvIiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJzaXRlX2lkIiwidHlwZSI6ImRlZmF1bHQiLCJ2YWx1ZSI6IiIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoiYXBpX2tleSIsInR5cGUiOiJzZWNyZXQiLCJ2YWx1ZSI6IiIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoiYmVhcmVyIiwidHlwZSI6ImRlZmF1bHQiLCJ2YWx1ZSI6IiIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoiYnJvYWRjYXN0X2lkIiwidHlwZSI6ImRlZmF1bHQiLCJ2YWx1ZSI6IiIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoiaW1wb3J0X2lkIiwidHlwZSI6ImRlZmF1bHQiLCJ2YWx1ZSI6IiIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoiZW1haWxfYWRkcmVzcyIsInR5cGUiOiJkZWZhdWx0IiwidmFsdWUiOiIiLCJlbmFibGVkIjp0cnVlfSx7ImtleSI6InN1cHByZXNzaW9uX3R5cGUiLCJ0eXBlIjoiZGVmYXVsdCIsInZhbHVlIjoiIiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJjb2xsZWN0aW9uX2lkIiwidHlwZSI6ImRlZmF1bHQiLCJ2YWx1ZSI6IiIsImVuYWJsZWQiOnRydWV9LHsia2V5Ijoic25pcHBldF9uYW1lIiwidHlwZSI6ImRlZmF1bHQiLCJ2YWx1ZSI6IiIsImVuYWJsZWQiOnRydWV9LHsia2V5Ijoid2ViaG9va19pZCIsInR5cGUiOiJkZWZhdWx0IiwidmFsdWUiOiIiLCJlbmFibGVkIjp0cnVlfSx7ImtleSI6InNlbmRlcl9pZCIsInR5cGUiOiJkZWZhdWx0IiwidmFsdWUiOiIiLCJlbmFibGVkIjp0cnVlfSx7ImtleSI6ImV4cG9ydF9pZCIsInR5cGUiOiJkZWZhdWx0IiwidmFsdWUiOiIiLCJlbmFibGVkIjp0cnVlfSx7ImtleSI6Im1lc3NhZ2VfaWQiLCJ0eXBlIjoiZGVmYXVsdCIsInZhbHVlIjoiIiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJzZWdtZW50X2lkIiwidHlwZSI6ImRlZmF1bHQiLCJ2YWx1ZSI6IiIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoibmV3c2xldHRlcl9pZCIsInR5cGUiOiJkZWZhdWx0IiwidmFsdWUiOiIiLCJlbmFibGVkIjp0cnVlfSx7ImtleSI6ImNvbnRlbnRfaWQiLCJ0eXBlIjoiZGVmYXVsdCIsInZhbHVlIjoiIiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJjYW1wYWlnbl9pZCIsInR5cGUiOiJkZWZhdWx0IiwidmFsdWUiOiIiLCJlbmFibGVkIjp0cnVlfSx7ImtleSI6ImFjdGlvbl9pZCIsInR5cGUiOiJkZWZhdWx0IiwidmFsdWUiOiIiLCJlbmFibGVkIjp0cnVlfSx7ImtleSI6ImN1c3RvbWVyX2lkIiwidHlwZSI6ImRlZmF1bHQiLCJ2YWx1ZSI6IiIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoidHJhbnNhY3Rpb25hbF9pZCIsInR5cGUiOiJkZWZhdWx0IiwidmFsdWUiOiIiLCJlbmFibGVkIjp0cnVlfSx7ImtleSI6InRyaWdnZXJfaWQiLCJ0eXBlIjoiZGVmYXVsdCIsInZhbHVlIjoiIiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJmb3JtX2lkIiwidHlwZSI6ImRlZmF1bHQiLCJ2YWx1ZSI6IiIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoiaWRlbnRpZmllciIsInR5cGUiOiJkZWZhdWx0IiwidmFsdWUiOiIiLCJlbmFibGVkIjp0cnVlfSx7ImtleSI6ImRldmljZV9pZCIsInR5cGUiOiJkZWZhdWx0IiwidmFsdWUiOiIiLCJlbmFibGVkIjp0cnVlfSx7ImtleSI6ImRlbGl2ZXJ5X2lkIiwidHlwZSI6ImRlZmF1bHQiLCJ2YWx1ZSI6IiIsImVuYWJsZWQiOnRydWV9XQ==) # Server addresses: US and EU Customer.io hosts services in the United States (US) and European Union. Select the appropriate server address for your region. | Region | Server Address | | :-- | :-- | | US | https://api.customer.io | | EU | https://api-eu.customer.io | # Authentication All requests to the Customer.io App API use an [App API Key](#App-API-Key). To authenticate, provide your key as a Bearer token in a HTTP Authorization header. You can create and manage your API keys—including keys with different scopes—in [your account settings page](https://fly.customer.io/settings/api_credentials?keyType=app). Each operation on this page references the authorization header it requires. # Rate Limits Most endpoints on this page are limited to 10 requests per second. The exceptions are: * The [transactional email](#operation/sendEmail) endpoint is limited to 100 requests per second. * The [API-triggered broadcast endpoint](#operation/triggerBroadcast) is limited to 1 request every 10 seconds. **Rate limits are subject to change. We may adjust these thresholds to ensure stable performance for all customers.** ' servers: - url: https://api.customer.io description: The base URL for broadcasts, transactional messages, and data-retrieval APIs. These endpoints use bearer authorization, and require a [token that you generate in the UI](https://fly.customer.io/settings/api_credentials?keyType=app). - url: https://api-eu.customer.io description: The base URL for broadcasts, transactional messages, and data-retrieval APIs (EU region). These endpoints use bearer authorization, and require a [token that you generate in the UI](https://fly.customer.io/settings/api_credentials?keyType=app). tags: - name: Newsletter Metrics description: 'Newsletter metrics include metrics for translations, A/B tests, and links. These endpoints return information about newsletter metrics including metrics for translations and A/B tests. You can update variants in newsletters from these endpoints, but you must perform all other create, update, and/or delete operations through the UI. ' paths: /v1/newsletters/{newsletter_id}/metrics: servers: - url: https://api.customer.io description: This API uses bearer authorization, requiring a [token that you generate in the UI](https://fly.customer.io/settings/api_credentials?keyType=app). parameters: - name: newsletter_id in: path required: true description: The identifier of a newsletter. schema: type: integer - name: period in: query required: false description: The unit of time for your report. schema: type: string default: days enum: - hours - days - weeks - months - name: steps in: query required: false description: The number of periods you want to return. Defaults to the maximum available, or `12` if the period is in `months`. Maximums are 24 hours, 45 days, 12 weeks, or 121 months. Days start at 00:00 EST. Weeks start at 00:00 EST on Sunday. Months start at 00:00 EST on the 1st of the month. schema: type: integer - name: type in: query required: false description: The type of item you want to return metrics for. When empty, the response contains metrics for all possible types. schema: type: string enum: - email - webhook - twilio - push - in_app - inbox get: summary: Get newsletter metrics operationId: getNewsletterMetrics security: - Bearer-Auth: [] description: 'Returns a list of metrics for an individual newsletter in `steps` (days, weeks, etc). We return metrics from oldest to newest (i.e. the 0-index for any result is the oldest step/period). You cannot request fewer than 2 steps of any period (2 hours, 2 days, 2 weeks, or 2 months). For instance, `?period=days&steps=1` means two days - the 48 hours before the API request was made. `?period=days&steps=0` returns the same as the maximum of the period - `?period=days&steps=45`. See the `steps` parameter below for the maximum count of each period. ' tags: - Newsletter Metrics responses: '200': description: Returns newsletter metrics by `series` (with increments are based on the `period` and `step` in your request) for the newsletter. content: application/json: schema: type: object properties: metric: type: object properties: type: x-scalar-ignore: true description: Channel type for a newsletter or newsletter content variant. type: string enum: - email - webhook - twilio - push - in_app - inbox readOnly: true example: email series: allOf: - x-scalar-ignore: true type: object description: Metrics grouped by the requested resolution. Each property is an array where each entry represents one period, such as one day. properties: 2xx: type: array items: type: integer description: 2xx responses by period, representative of webhook performance. 3xx: type: array items: type: integer description: 3xx responses by period, representative of webhook performance. 4xx: type: array items: type: integer description: 4xx responses by period, representative of webhook performance. 5xx: type: array items: type: integer description: 5xx responses by period, representative of webhook performance. - x-scalar-ignore: true description: Metrics grouped by the requested resolution. Each property is an array where each entry represents one period, such as one day. type: object properties: attempted: type: array items: type: integer description: The number of `attempted` messages. bounced: type: array items: type: integer description: The number of `bounced` messages. clicked: type: array items: type: integer description: The number of `clicked` messages. human_clicked: type: array items: type: integer description: The number of `clicked` emails excluding machine clicks. This metric is reliable starting April 20, 2025. [Learn more](/journeys/metrics/analytics/#delivery-metrics). prefetch_clicked: type: array items: type: integer description: The number of `clicked` emails attributed to machines. This metric is reliable starting April 20, 2025. converted: type: array items: type: integer description: The number of `converted` messages. created: type: array items: type: integer description: The number of `created` messages. deferred: type: array items: type: integer description: The number of `deferred` messages. delivered: type: array items: type: integer description: The number of `delivered` messages. drafted: type: array items: type: integer description: The number of `drafted` messages. failed: type: array items: type: integer description: The number of `failed` messages. opened: type: array items: type: integer description: The number of `opened` messages. human_opened: type: array items: type: integer description: The number of `opened` emails excluding machine opens. This metric is reliable starting March 20, 2025. [Learn more](/journeys/metrics/analytics/#delivery-metrics). prefetch_opened: type: array items: type: integer description: The number of `opened` emails attributed to machines. This metric is reliable starting March 20, 2025. sent: type: array items: type: integer description: The number of sent messages. spammed: type: array items: type: integer description: The number of spam complaints. suppressed: type: array items: type: integer description: The number of `suppressed` messages. undeliverable: type: array items: type: integer description: The number of `undeliverable` messages. topic_unsubscribed: type: array items: type: integer description: The number of topic unsubscribes in a given period. unsubscribed: type: array items: type: integer description: The number of unsubscribes attributed to the campaign or message. '404': description: The newsletter you requested does not exist. '429': description: Your request is over the 10-per-second limit. x-codeSamples: - lang: Shell + Curl source: "curl --request GET \\\n --header 'Authorization: Bearer REPLACE_BEARER_TOKEN' \\\n --url https://api.customer.io/v1/newsletters/{newsletter_id}/metrics" - lang: Node + Native source: "const http = require(\"https\");\n\nconst options = {\n \"method\": \"GET\",\n \"hostname\": \"api.customer.io\",\n \"port\": null,\n \"path\": \"/v1/newsletters/%7Bnewsletter_id%7D/metrics\",\n \"headers\": {}\n};\n\nconst req = http.request(options, function (res) {\n const chunks = [];\n\n res.on(\"data\", function (chunk) {\n chunks.push(chunk);\n });\n\n res.on(\"end\", function () {\n const body = Buffer.concat(chunks);\n console.log(body.toString());\n });\n});\n\nreq.end();" - lang: Ruby + Native source: 'require ''uri'' require ''net/http'' require ''openssl'' url = URI("https://api.customer.io/v1/newsletters/%7Bnewsletter_id%7D/metrics") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE request = Net::HTTP::Get.new(url) response = http.request(request) puts response.read_body' - lang: Python + Python3 source: 'import http.client conn = http.client.HTTPSConnection("api.customer.io") conn.request("GET", "/v1/newsletters/%7Bnewsletter_id%7D/metrics") res = conn.getresponse() data = res.read() print(data.decode("utf-8"))' - lang: Go + Native source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.customer.io/v1/newsletters/%7Bnewsletter_id%7D/metrics\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}" /v1/newsletters/{newsletter_id}/metrics/links: servers: - url: https://api.customer.io description: This API uses bearer authorization, requiring a [token that you generate in the UI](https://fly.customer.io/settings/api_credentials?keyType=app). parameters: - name: newsletter_id in: path required: true description: The identifier of a newsletter. schema: type: integer - name: period in: query required: false description: The unit of time for your report. schema: type: string default: days enum: - hours - days - weeks - months - name: steps in: query required: false description: The number of periods you want to return. Defaults to the maximum available, or `12` if the period is in `months`. Maximums are 24 hours, 45 days, 12 weeks, or 121 months. Days start at 00:00 EST. Weeks start at 00:00 EST on Sunday. Months start at 00:00 EST on the 1st of the month. schema: type: integer - name: unique in: query required: false description: If true, the response contains only unique customer results, i.e. a customer who clicks a link twice is only counted once. If false, the response contains the total number of results without regard to uniqueness. schema: type: boolean default: false get: summary: Get click metrics for newsletter links operationId: getNewsletterLinks security: - Bearer-Auth: [] description: 'Returns metrics for link clicks within a newsletter, both in total and in `series` periods (days, weeks, etc). `series` metrics are ordered oldest to newest (i.e. the 0-index for any result is the oldest step/period). You cannot request fewer than 2 steps of any period (2 hours, 2 days, 2 weeks, or 2 months). For instance, `?period=days&steps=1` means two days - the 48 hours before the API request was made. `?period=days&steps=0` returns the same as the maximum of the period - `?period=days&steps=45`. See the `steps` parameter below for the maximum count of each period. ' tags: - Newsletter Metrics responses: '200': description: Returns an array of link objects. Each object represents a different link in your newsletter and contains independent metrics. content: application/json: schema: type: object properties: links: type: array items: x-scalar-ignore: true type: object properties: link: type: object properties: id: type: integer description: The ID of the link. example: 1234 href: type: string description: The link destination—a URL, mailto, etc. example: https://docs.customer.io metric: type: object description: Contains metrics for the link. properties: series: type: object properties: clicked: type: array description: An array of results from oldest to newest, where each result indicates a period. items: type: integer example: - 1 - 3 - 5 - 7 human_clicked: type: array description: An array of human click counts (clicks excluding machine clicks) from oldest to newest, where each result indicates a period. This metric is reliable starting April 20, 2025. [Learn more](/journeys/metrics/analytics/#delivery-metrics). items: type: integer example: - 1 - 2 - 3 - 4 machine_clicked: type: array description: An array of machine click counts (clicks attributed to bots or prefetching) from oldest to newest, where each result indicates a period. This metric is reliable starting April 20, 2025. [Learn more](/journeys/metrics/analytics/#delivery-metrics). items: type: integer example: - 0 - 1 - 1 - 2 '404': description: The newsletter you requested does not exist. '429': description: Your request is over the 10-per-second limit. x-codeSamples: - lang: Shell + Curl source: "curl --request GET \\\n --header 'Authorization: Bearer REPLACE_BEARER_TOKEN' \\\n --url https://api.customer.io/v1/newsletters/{newsletter_id}/metrics/links" - lang: Node + Native source: "const http = require(\"https\");\n\nconst options = {\n \"method\": \"GET\",\n \"hostname\": \"api.customer.io\",\n \"port\": null,\n \"path\": \"/v1/newsletters/%7Bnewsletter_id%7D/metrics/links\",\n \"headers\": {}\n};\n\nconst req = http.request(options, function (res) {\n const chunks = [];\n\n res.on(\"data\", function (chunk) {\n chunks.push(chunk);\n });\n\n res.on(\"end\", function () {\n const body = Buffer.concat(chunks);\n console.log(body.toString());\n });\n});\n\nreq.end();" - lang: Ruby + Native source: 'require ''uri'' require ''net/http'' require ''openssl'' url = URI("https://api.customer.io/v1/newsletters/%7Bnewsletter_id%7D/metrics/links") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE request = Net::HTTP::Get.new(url) response = http.request(request) puts response.read_body' - lang: Python + Python3 source: 'import http.client conn = http.client.HTTPSConnection("api.customer.io") conn.request("GET", "/v1/newsletters/%7Bnewsletter_id%7D/metrics/links") res = conn.getresponse() data = res.read() print(data.decode("utf-8"))' - lang: Go + Native source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.customer.io/v1/newsletters/%7Bnewsletter_id%7D/metrics/links\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}" /v1/newsletters/{newsletter_id}/messages: servers: - url: https://api.customer.io description: This API uses bearer authorization, requiring a [token that you generate in the UI](https://fly.customer.io/settings/api_credentials?keyType=app). parameters: - name: newsletter_id in: path required: true description: The identifier of a newsletter. schema: type: integer - name: start in: query required: false description: The token for the page of results you want to return. Responses contain a `next` property. Use this property as the `start` value to return the next page of results. schema: type: string - name: limit in: query required: false description: The maximum number of results you want to retrieve per page. schema: type: integer - name: metric in: query required: false description: Determines the metric(s) you want to return. schema: type: string enum: - attempted - sent - delivered - opened - clicked - converted - bounced - spammed - unsubscribed - dropped - failed - undeliverable - name: start_ts in: query required: false description: The beginning timestamp for your query. schema: type: integer format: unix timestamp - name: end_ts in: query required: false description: The ending timestamp for your query. schema: type: integer format: unix timestamp - name: get_tracked_responses in: query required: false description: If true, the response includes `tracked_responses` for each message—an object containing tracked response option names for in-app survey responses. schema: type: boolean default: false get: summary: Get delivery data for a newsletter operationId: getNewsletterMsgMeta security: - Bearer-Auth: [] description: 'Returns information about the "deliveries" (rendered messages) sent to your recipients for a specific newsletter. Provide query parameters to refine the metrics you want to return. Use `start_ts` and `end_ts` to find messages within a time range. If your request doesn''t include `start_ts` and `end_ts` parameters, we''ll return up to 6 months of results beginning with the first delivery generated from the newsletter. If your `start_ts` and `end_ts` range is more than 12 months, we''ll return 12 months of data from the most recent timestamp in your request. Timestamps reflect when deliveries were created in our system, not when they were actually sent to recipients. There may be a delay between creation and sending. ' tags: - Newsletter Metrics responses: '200': description: Returns an array of `messages`. Each object represents a different delivery to a recipient. content: application/json: schema: type: object properties: messages: type: array items: x-scalar-ignore: true description: Each object is a delivery of a newsletter. allOf: - x-scalar-ignore: true type: object description: Describes an individual message delivery. The object contains keys for all possible parents of the message (`newsletter_id`, `broadcast_id`, etc) but only the parents of the delivery are populated. Other parent IDs are null. properties: id: x-scalar-ignore: true description: The identifier for a delivery—the instance of a message intended for an individual recipient. type: string readOnly: true example: dgOq6QWq6QUBAAF4_CGoeVX7mFkDbRFu7ek= deduplicate_id: x-scalar-ignore: true type: string readOnly: true description: An identifier in the format `id:timestamp` where the id is for the object you're working with (Campaigns, Deliveries, Exports, Identities, Newsletters, Segments, and Templates), and the timestamp is the last time the object was updated. example: 15:1492548073 message_template_id: x-scalar-ignore: true description: The identifier of the message template used to create a message. type: integer readOnly: true deprecated: true customer_id: x-scalar-ignore: true type: - string - 'null' description: The ID of a customer profile, analogous to a "person" in the UI. If your workspace supports multiple identifiers (email and ID), this value can be null. example: '42' customer_identifiers: x-scalar-ignore: true type: object description: Identifiers for the person in a response—`id`, `cio_id`, and `email`. Unset `id` or `email` values are `null`. We recommend this object over the less descriptive `customer_id`. This object doesn't include `phone`, even if your workspace uses phone numbers as an identifier; look for the person's `phone` attribute instead. required: - email - id - cio_id properties: email: type: - string - 'null' format: email description: A person's email address, if set. example: test@example.com id: type: - string - 'null' description: A person's unique ID, if set. This is the same as the `customer_id` if present. example: 2 cio_id: x-scalar-ignore: true type: string description: A unique identifier set by Customer.io, used to reference a person if you want to update their identifiers. example: a3000001 recipient: x-scalar-ignore: true description: The recipient address for an action. type: string example: '{{customer.email}}' subject: x-scalar-ignore: true type: string description: The subject line for an `email` action. example: Did you get that thing I sent you? metrics: x-scalar-ignore: true type: object description: Metrics for an individual instance of a message; each item in the object represents the timestamp when a message achieved a particular metric. This object only contains metrics that have been recorded. properties: bounced: type: integer description: The timestamp when the message `bounced`. clicked: type: integer description: The timestamp when the message was `clicked`. human_clicked: type: integer description: The number of `clicked` messages excluding machine clicks. This metric is reliable starting April 20, 2025. [Learn more](/journeys/metrics/analytics/#delivery-metrics). prefetch_clicked: type: integer description: The number of `clicked` messages attributed to machines. This metric is reliable starting April 20, 2025. converted: type: integer description: The timestamp when the message was `converted`. created: type: integer description: The timestamp when the message was `created`. delivered: type: integer description: The timestamp when the message was `delivered`. drafted: type: integer description: The timestamp when the message was `drafted`. dropped: type: integer description: The timestamp when the message was `dropped`. failed: type: integer description: The timestamp when the message `failed`. opened: type: integer description: The timestamp when the message was `opened`. human_opened: type: integer description: The number of `opened` messages excluding machine opens. This metric is reliable starting March 20, 2025. [Learn more](/journeys/metrics/analytics/#delivery-metrics). prefetch_opened: type: integer description: The number of `opened` messages attributed to machines. This metric is reliable starting March 20, 2025. sent: type: integer description: The timestamp when the message was `sent`. spammed: type: integer description: The timestamp when the message was marked as spam. undeliverable: type: integer description: The timestamp when the message became `undeliverable`. unsubscribed: type: integer description: The timestamp when a person unsubscribed based on this message. created: x-scalar-ignore: true type: integer format: unix timestamp description: The date time when the referenced ID was created. example: 1552341937 readOnly: true failure_message: type: - string - 'null' description: Explains why a message failed, if applicable. campaign_id: oneOf: - x-scalar-ignore: true description: The identifier for a campaign. type: integer example: 5 - type: 'null' action_id: x-scalar-ignore: true description: The identifier for an action. type: integer readOnly: true example: 96 parent_action_id: x-scalar-ignore: true type: integer description: The ID of the parent action, if the action occurred within a campaign and has a parent (like a randomized split, etc). example: 1 readOnly: true newsletter_id: oneOf: - x-scalar-ignore: true description: The identifier for a newsletter. type: integer example: 10 - type: 'null' content_id: x-scalar-ignore: true description: The identifier for a message in a newsletter. Newsletters can have multiple content IDs (for multi-language messages or A/B tests). type: integer readOnly: true example: 1 broadcast_id: oneOf: - x-scalar-ignore: true type: integer description: The identifier for a broadcast. example: 2 - type: 'null' trigger_event_id: x-scalar-ignore: true type: string description: The id of the event that triggered an event-triggered campaign (not an API-triggered broadcast). example: 21E4C3CT6YDC7Y4N7FE1GWWABC forgotten: type: boolean description: If true message contents are not retained by Customer.io. tracked_responses: type: object description: Tracked in-app survey responses, keyed by response option name. Present only when `get_tracked_responses` is `true`. additionalProperties: type: object properties: id: type: integer description: The identifier for the tracked response. ts: type: integer description: A unix timestamp representing when the response was tracked. example: cool-button: id: 1 ts: 1715769600 cool-button-2: id: 2 ts: 1715769600 - type: object properties: type: x-scalar-ignore: true description: Channel type for a newsletter or newsletter content variant. type: string enum: - email - webhook - twilio - push - in_app - inbox readOnly: true example: email examples: email: summary: An email message value: messages: - id: dgOq6QWq6QUDAAF22PaOyFVqVxHY3rI5fsg= deduplicate_id: dgOq6QWq6QUDAAF22PaOyFVqVxHY3rI5fsg=:1609957872 msg_template_id: 178 action_id: null customer_id: 1a55d8d1-b13d-4f1f-858f-a93ef21e3a7d recipient: person@email.com subject: Did you get that thing I sent you? metrics: delivered: 1609957872 sent: 1609957832 created: 1609957805 failure_message: null newsletter_id: 72 content_id: 178 campaign_id: null broadcast_id: null type: email forgotten: false in-app: summary: An in-app message with tracked survey responses value: messages: - id: bX2r7YZq8RCAAB5_DGopeWY8nGlEcSGv8fl= deduplicate_id: bX2r7YZq8RCAAB5_DGopeWY8nGlEcSGv8fl=:1715769500 msg_template_id: 87 action_id: null customer_id: user_2048 recipient: user_2048 subject: null metrics: opened: 1715769550 sent: 1715769500 created: 1715769500 failure_message: null newsletter_id: 72 content_id: null campaign_id: null broadcast_id: null type: in_app forgotten: false tracked_responses: how-satisfied: id: 1 ts: 1715769560 would-recommend: id: 2 ts: 1715769565 '404': description: The newsletter you requested does not exist. '429': description: Your request is over the 10-per-second limit. x-codeSamples: - lang: Shell + Curl source: "curl --request GET \\\n --header 'Authorization: Bearer REPLACE_BEARER_TOKEN' \\\n --url https://api.customer.io/v1/newsletters/{newsletter_id}/messages" - lang: Node + Native source: "const http = require(\"https\");\n\nconst options = {\n \"method\": \"GET\",\n \"hostname\": \"api.customer.io\",\n \"port\": null,\n \"path\": \"/v1/newsletters/%7Bnewsletter_id%7D/messages\",\n \"headers\": {}\n};\n\nconst req = http.request(options, function (res) {\n const chunks = [];\n\n res.on(\"data\", function (chunk) {\n chunks.push(chunk);\n });\n\n res.on(\"end\", function () {\n const body = Buffer.concat(chunks);\n console.log(body.toString());\n });\n});\n\nreq.end();" - lang: Ruby + Native source: 'require ''uri'' require ''net/http'' require ''openssl'' url = URI("https://api.customer.io/v1/newsletters/%7Bnewsletter_id%7D/messages") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE request = Net::HTTP::Get.new(url) response = http.request(request) puts response.read_body' - lang: Python + Python3 source: 'import http.client conn = http.client.HTTPSConnection("api.customer.io") conn.request("GET", "/v1/newsletters/%7Bnewsletter_id%7D/messages") res = conn.getresponse() data = res.read() print(data.decode("utf-8"))' - lang: Go + Native source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.customer.io/v1/newsletters/%7Bnewsletter_id%7D/messages\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}" /v1/newsletters/{newsletter_id}/contents/{content_id}/metrics: servers: - url: https://api.customer.io description: This API uses bearer authorization, requiring a [token that you generate in the UI](https://fly.customer.io/settings/api_credentials?keyType=app). parameters: - name: newsletter_id in: path required: true description: The identifier of a newsletter. schema: type: integer - name: content_id in: path required: true description: 'The identifier of a message in a newsletter. If your newsletter has an A/B test group or includes multiple languages, each variant has its own `content_id`, separate from the `newsletter_id`. Use [List variants of a newsletter](#tag/newsletter-variants/listNewsletterVariants) to find the `content_id` associated with the right variant. ' schema: type: integer - name: period in: query required: false description: The unit of time for your report. schema: type: string default: days enum: - hours - days - weeks - months - name: steps in: query required: false description: The number of periods you want to return. Defaults to the maximum available, or `12` if the period is in `months`. Maximums are 24 hours, 45 days, 12 weeks, or 121 months. Days start at 00:00 EST. Weeks start at 00:00 EST on Sunday. Months start at 00:00 EST on the 1st of the month. schema: type: integer - name: type in: query required: false description: The type of item you want to return metrics for. When empty, the response contains metrics for all possible types. schema: type: string enum: - email - webhook - twilio - push - in_app - inbox get: summary: Get metrics for a test or translation variant of a newsletter operationId: getVariantMetrics security: - Bearer-Auth: [] description: 'Returns a metrics for an individual newsletter variant—either an individual language in a multi-language newsletter or a message in an A/B test. This endpoint returns metrics both in total and in `steps` (days, weeks, etc) over a `period` of time. Stepped `series` metrics are arranged from oldest to newest (i.e. the 0-index for any result is the oldest period/step). You cannot request fewer than 2 steps of any period (2 hours, 2 days, 2 weeks, or 2 months). For instance, `?period=days&steps=1` means two days - the 48 hours before the API request was made. `?period=days&steps=0` returns the same as the maximum of the period - `?period=days&steps=45`. See the `steps` parameter below for the maximum count of each period. ' tags: - Newsletter Metrics responses: '200': description: Returns newsletter variant metrics by `series` (with increments are based on the `period` and `step` in your request) for the newsletter. content: application/json: schema: type: object properties: metric: type: object properties: type: x-scalar-ignore: true description: Channel type for a newsletter or newsletter content variant. type: string enum: - email - webhook - twilio - push - in_app - inbox readOnly: true example: email series: allOf: - x-scalar-ignore: true type: object description: Metrics grouped by the requested resolution. Each property is an array where each entry represents one period, such as one day. properties: 2xx: type: array items: type: integer description: 2xx responses by period, representative of webhook performance. 3xx: type: array items: type: integer description: 3xx responses by period, representative of webhook performance. 4xx: type: array items: type: integer description: 4xx responses by period, representative of webhook performance. 5xx: type: array items: type: integer description: 5xx responses by period, representative of webhook performance. - x-scalar-ignore: true description: Metrics grouped by the requested resolution. Each property is an array where each entry represents one period, such as one day. type: object properties: attempted: type: array items: type: integer description: The number of `attempted` messages. bounced: type: array items: type: integer description: The number of `bounced` messages. clicked: type: array items: type: integer description: The number of `clicked` messages. human_clicked: type: array items: type: integer description: The number of `clicked` emails excluding machine clicks. This metric is reliable starting April 20, 2025. [Learn more](/journeys/metrics/analytics/#delivery-metrics). prefetch_clicked: type: array items: type: integer description: The number of `clicked` emails attributed to machines. This metric is reliable starting April 20, 2025. converted: type: array items: type: integer description: The number of `converted` messages. created: type: array items: type: integer description: The number of `created` messages. deferred: type: array items: type: integer description: The number of `deferred` messages. delivered: type: array items: type: integer description: The number of `delivered` messages. drafted: type: array items: type: integer description: The number of `drafted` messages. failed: type: array items: type: integer description: The number of `failed` messages. opened: type: array items: type: integer description: The number of `opened` messages. human_opened: type: array items: type: integer description: The number of `opened` emails excluding machine opens. This metric is reliable starting March 20, 2025. [Learn more](/journeys/metrics/analytics/#delivery-metrics). prefetch_opened: type: array items: type: integer description: The number of `opened` emails attributed to machines. This metric is reliable starting March 20, 2025. sent: type: array items: type: integer description: The number of sent messages. spammed: type: array items: type: integer description: The number of spam complaints. suppressed: type: array items: type: integer description: The number of `suppressed` messages. undeliverable: type: array items: type: integer description: The number of `undeliverable` messages. topic_unsubscribed: type: array items: type: integer description: The number of topic unsubscribes in a given period. unsubscribed: type: array items: type: integer description: The number of unsubscribes attributed to the campaign or message. '404': description: The newsletter and/or variant does not exist. '429': description: Your request is over the 10-per-second limit. x-codeSamples: - lang: Shell + Curl source: "curl --request GET \\\n --header 'Authorization: Bearer REPLACE_BEARER_TOKEN' \\\n --url https://api.customer.io/v1/newsletters/{newsletter_id}/contents/{content_id}/metrics" - lang: Node + Native source: "const http = require(\"https\");\n\nconst options = {\n \"method\": \"GET\",\n \"hostname\": \"api.customer.io\",\n \"port\": null,\n \"path\": \"/v1/newsletters/%7Bnewsletter_id%7D/contents/%7Bcontent_id%7D/metrics\",\n \"headers\": {}\n};\n\nconst req = http.request(options, function (res) {\n const chunks = [];\n\n res.on(\"data\", function (chunk) {\n chunks.push(chunk);\n });\n\n res.on(\"end\", function () {\n const body = Buffer.concat(chunks);\n console.log(body.toString());\n });\n});\n\nreq.end();" - lang: Ruby + Native source: 'require ''uri'' require ''net/http'' require ''openssl'' url = URI("https://api.customer.io/v1/newsletters/%7Bnewsletter_id%7D/contents/%7Bcontent_id%7D/metrics") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE request = Net::HTTP::Get.new(url) response = http.request(request) puts response.read_body' - lang: Python + Python3 source: 'import http.client conn = http.client.HTTPSConnection("api.customer.io") conn.request("GET", "/v1/newsletters/%7Bnewsletter_id%7D/contents/%7Bcontent_id%7D/metrics") res = conn.getresponse() data = res.read() print(data.decode("utf-8"))' - lang: Go + Native source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.customer.io/v1/newsletters/%7Bnewsletter_id%7D/contents/%7Bcontent_id%7D/metrics\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}" /v1/newsletters/{newsletter_id}/contents/{content_id}/metrics/links: servers: - url: https://api.customer.io description: This API uses bearer authorization, requiring a [token that you generate in the UI](https://fly.customer.io/settings/api_credentials?keyType=app). parameters: - name: newsletter_id in: path required: true description: The identifier of a newsletter. schema: type: integer - name: content_id in: path required: true description: 'The identifier of a message in a newsletter. If your newsletter has an A/B test group or includes multiple languages, each variant has its own `content_id`, separate from the `newsletter_id`. Use [List variants of a newsletter](#tag/newsletter-variants/listNewsletterVariants) to find the `content_id` associated with the right variant. ' schema: type: integer - name: period in: query required: false description: The unit of time for your report. schema: type: string default: days enum: - hours - days - weeks - months - name: steps in: query required: false description: The number of periods you want to return. Defaults to the maximum available, or `12` if the period is in `months`. Maximums are 24 hours, 45 days, 12 weeks, or 121 months. Days start at 00:00 EST. Weeks start at 00:00 EST on Sunday. Months start at 00:00 EST on the 1st of the month. schema: type: integer - name: type in: query required: false description: The type of item you want to return metrics for. When empty, the response contains metrics for all possible types. schema: type: string enum: - email - webhook - twilio - push - in_app - inbox get: summary: Get click metrics for links in newsletter variants operationId: getVariantLinks security: - Bearer-Auth: [] description: 'Returns link click metrics for an individual newsletter variant—an individual language in a multi-language newsletter or a message in an A/B test. Unless you specify otherwise, the response contains data for the maximum period by days (45 days). You cannot request fewer than 2 steps of any period (2 hours, 2 days, 2 weeks, or 2 months). For instance, `?period=days&steps=1` means two days - the 48 hours before the API request was made. `?period=days&steps=0` returns the same as the maximum of the period - `?period=days&steps=45`. See the `steps` parameter below for the maximum count of each period. ' tags: - Newsletter Metrics responses: '200': description: Returns newsletter variant metrics by `series` (with increments are based on the `period` and `step` in your request) for the newsletter. content: application/json: schema: type: object properties: links: type: array description: Each object in the array represents a link in your newsletter variant. items: x-scalar-ignore: true type: object properties: link: type: object properties: id: type: integer description: The ID of the link. example: 1234 href: type: string description: The link destination—a URL, mailto, etc. example: https://docs.customer.io metric: type: object description: Contains metrics for the link. properties: series: type: object properties: clicked: type: array description: An array of results from oldest to newest, where each result indicates a period. items: type: integer example: - 1 - 3 - 5 - 7 human_clicked: type: array description: An array of human click counts (clicks excluding machine clicks) from oldest to newest, where each result indicates a period. This metric is reliable starting April 20, 2025. [Learn more](/journeys/metrics/analytics/#delivery-metrics). items: type: integer example: - 1 - 2 - 3 - 4 machine_clicked: type: array description: An array of machine click counts (clicks attributed to bots or prefetching) from oldest to newest, where each result indicates a period. This metric is reliable starting April 20, 2025. [Learn more](/journeys/metrics/analytics/#delivery-metrics). items: type: integer example: - 0 - 1 - 1 - 2 '404': description: The newsletter and/or variant does not exist. '429': description: Your request is over the 10-per-second limit. x-codeSamples: - lang: Shell + Curl source: "curl --request GET \\\n --header 'Authorization: Bearer REPLACE_BEARER_TOKEN' \\\n --url https://api.customer.io/v1/newsletters/{newsletter_id}/contents/{content_id}/metrics/links" - lang: Node + Native source: "const http = require(\"https\");\n\nconst options = {\n \"method\": \"GET\",\n \"hostname\": \"api.customer.io\",\n \"port\": null,\n \"path\": \"/v1/newsletters/%7Bnewsletter_id%7D/contents/%7Bcontent_id%7D/metrics/links\",\n \"headers\": {}\n};\n\nconst req = http.request(options, function (res) {\n const chunks = [];\n\n res.on(\"data\", function (chunk) {\n chunks.push(chunk);\n });\n\n res.on(\"end\", function () {\n const body = Buffer.concat(chunks);\n console.log(body.toString());\n });\n});\n\nreq.end();" - lang: Ruby + Native source: 'require ''uri'' require ''net/http'' require ''openssl'' url = URI("https://api.customer.io/v1/newsletters/%7Bnewsletter_id%7D/contents/%7Bcontent_id%7D/metrics/links") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE request = Net::HTTP::Get.new(url) response = http.request(request) puts response.read_body' - lang: Python + Python3 source: 'import http.client conn = http.client.HTTPSConnection("api.customer.io") conn.request("GET", "/v1/newsletters/%7Bnewsletter_id%7D/contents/%7Bcontent_id%7D/metrics/links") res = conn.getresponse() data = res.read() print(data.decode("utf-8"))' - lang: Go + Native source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://api.customer.io/v1/newsletters/%7Bnewsletter_id%7D/contents/%7Bcontent_id%7D/metrics/links\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}" components: securitySchemes: Bearer-Auth: type: http scheme: bearer description: 'The App API uses a bearer authentication scheme. You can generate a bearer token, known as an **App API Key**, with a defined scope in [your account settings](https://fly.customer.io/settings/api_credentials?keyType=app). [Learn more about bearer authorization in Customer.io](/accounts/settings/managing-credentials). ' ServiceAccount-Auth: x-scalar-ignore: true type: http scheme: bearer bearerFormat: sa_live_ description: 'Transactional send endpoints (`/v1/send/email`, `/v1/send/push`, `/v1/send/sms`, `/v1/send/in_app`, `/v1/send/inbox_message`) also accept a service-account bearer token, prefixed with `sa_live_`. Service-account tokens work across workspaces, so you must pass the target workspace as the `X-Workspace-Id` header on each request. Service-account tokens are intended for testing and one-off sends—for example, using the Customer.io CLI with an AI agent like Claude to verify that a transactional message renders correctly before wiring it into your production backend. **For the production integration that triggers the message from your application, use an App API Key instead**: it''s workspace-scoped, easier to rotate, and has a smaller blast radius. Service-account tokens are server-side credentials. Treat them like any API key—keep them in environment variables or a secret manager, and never embed them in client-side code, mobile apps, or other untrusted contexts. ' bearerAuth: type: http scheme: bearer description: API key passed as a Bearer token