Inflection.io Stats API

Send and engagement metrics for journeys and emails, with a journey rollup.

Operations 4

GET /v2/journeys/stats Journey stats rollup #
GET /v2/journeys/{id}/stats Get a journey's stats #
GET /v2/emails/stats Email stats rollup #
GET /v2/emails/{id}/stats Get an email's stats #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/inflectionio:inflectionio-stats-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

inflectionio-stats-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Inflection Developer Stats API
  version: 1.0.0
  description: 'The Inflection Developer API (`/v2`). Authenticate with a Personal Access

    Token (`Authorization: Bearer inf_pat_...`).


    **`/v2` is `/v1` plus more.** Every `/v1` endpoint is served here unchanged,

    so a client can pin to one version and reach the whole API. On top of those

    sit reads `/v1` never had: journeys, journey and email stats with a journey

    rollup, and the email and list collections. `/v1` remains published.


    **Response envelope.** Every response is wrapped:

    `{ "data": …, "pagination": …, "errors": [...], "meta": { "status": "SUCCESS|FAILURE", "timestamp": "…" } }`.

    `data` carries the payload, `pagination` appears only on paged lists, and on

    failures `errors` is populated with `meta.status: "FAILURE"`.


    **Query parameters are camelCase**: `pageNumber`, `pageSize`, `startTime`,

    `folderId`. The journeys, emails, lists and stats reads additionally accept

    the snake_case spelling of every scalar parameter (`page_number`,

    `folder_id`, …); the contact, activity and list-member endpoints do not.

    Repeatable parameters also accept a `[]` suffix (`status[]`, `tag[]`).


    **Asynchronous contact writes.** `POST/PATCH /v2/contacts*` are processed

    asynchronously: they return **200** with a `PENDING` transaction

    acknowledgement (not the created/updated contact). Poll

    `GET /v2/contacts/transactions/{transactionId}` for the per-contact result.


    **Status-code quirks** (documented per-operation): a missing **contact** is

    reported as **400** (not 404), whereas a missing **list**, **email** or

    **journey** is **404**. For journeys and emails that also covers a

    soft-deleted record and a malformed id. Nothing returns 201 or 204.


    **Empty results are not errors.** On the paged reads, a `tag`, `folderId` or

    `search` that matches nothing returns an empty page, not a 404.


    **Errors.** Failures use the envelope above

    (`errors[].{errorCode,message,detail}`). Authentication and authorization

    failures (401/403) are returned with an empty body. The journey stats rollup

    adds `RESULT_SET_TOO_LARGE` (a **400**) when a filter matches more journeys

    than one request may cover; narrow it rather than paging further.


    **Rate limiting.** Requests are metered per tenant. Exceeding the budget

    yields **429** (empty body) on any endpoint; the `Retry-After` header gives

    the seconds to wait and `X-RateLimit-Limit` the sustained per-second budget.'
servers:
- url: https://api.inflection.io
  description: Production
security:
- PatAuth: []
tags:
- name: Stats
  description: Send and engagement metrics for journeys and emails, with a journey rollup.
paths:
  /v2/journeys/stats:
    get:
      tags:
      - Stats
      operationId: getJourneysStatsRollup
      summary: Journey stats rollup
      description: 'Counters for many journeys in one call, so reporting on 50 journeys is

        one request rather than 50.


        `data.aggregate` is a single ungrouped query over the **whole** matching

        set, so it is **not** the sum of `data.records`. Those cover only the

        current page. `pagination.totalElements` counts the whole matching set.


        At most **one** of `ids`, `tag`, `folderId` may be given; more than one

        is a 400. Explicit `ids` return a full row of zeros for a journey with no

        activity, whereas a `tag`/`folderId`/unfiltered request returns only

        journeys that have activity. A filter matching more than 1000 journeys is

        a 400 (`RESULT_SET_TOO_LARGE`). Nothing is silently trimmed. That guard

        cannot trigger when `ids` is used, since `ids` is itself capped at 50.'
      parameters:
      - $ref: '#/components/parameters/RollupIds'
      - $ref: '#/components/parameters/TagName'
      - $ref: '#/components/parameters/FolderId'
      - $ref: '#/components/parameters/Since'
      - $ref: '#/components/parameters/Until'
      - $ref: '#/components/parameters/PageNumber'
      - $ref: '#/components/parameters/PageSize'
      responses:
        '200':
          description: The aggregate plus one row per journey on the page.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatsRollupResponse'
        '400':
          description: '`VALIDATION_ERROR` for a malformed date, more than one narrowing

            filter, or more than 50 `ids`; or `RESULT_SET_TOO_LARGE` when the

            filter matches more than 1000 journeys.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v2/journeys/{id}/stats:
    get:
      tags:
      - Stats
      operationId: getJourneyStats
      summary: Get a journey's stats
      description: 'The thirteen counters for one journey. `since`/`until` bound the **send**

        time; omitting both covers all time.'
      parameters:
      - $ref: '#/components/parameters/JourneyId'
      - $ref: '#/components/parameters/Since'
      - $ref: '#/components/parameters/Until'
      responses:
        '200':
          description: The journey's stats. Not paged.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatsResponse'
        '400':
          description: A malformed `since` or `until`; `detail` names the field.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '404':
          description: Unknown, soft-deleted, or malformed journey id.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v2/emails/stats:
    get:
      tags:
      - Stats
      operationId: getEmailsStatsRollup
      summary: Email stats rollup
      description: 'Counters for many emails in one call. An email reused across journeys is

        reported once here, on its own totals. Per-journey stats cannot answer

        "how is this email performing" when the caller does not know which

        journeys send it.


        `data.aggregate` is a single ungrouped query over the **whole** matching

        set, so it is **not** the sum of `data.records`. Those cover only the

        current page.


        It is also **not** the sum of every page. A send that carries two

        different templates counts once in `data.aggregate` but once per template

        in `data.records`, so summing all pages can exceed the aggregate by a

        small margin (1 in 27,613 on our staging data). The aggregate is the

        de-duplicated figure; prefer it over adding the rows up.


        Emails are matched on **send** activity. An email with opens or clicks

        but no send record is not reported, and its engagement is not in the

        aggregate either.


        At most **one** of `ids`, `folderId` may be given; both is a 400.

        Explicit `ids` return a full row of zeros for an email with no activity,

        whereas a `folderId`/unfiltered request returns only emails that have

        activity. A filter matching more than 1000 emails is a 400

        (`RESULT_SET_TOO_LARGE`). Nothing is silently trimmed. That guard cannot

        trigger when `ids` is used, since `ids` is itself capped at 50.


        An email whose template record no longer exists is still reported when it

        has send activity. The sends happened, and the tracking is the record of

        them. Those ids are therefore reported here but return 404 from

        `GET /v2/emails/{id}/stats`. This is deliberately unlike the journey

        rollup, which reports only live journeys.'
      parameters:
      - $ref: '#/components/parameters/EmailRollupIds'
      - $ref: '#/components/parameters/FolderId'
      - $ref: '#/components/parameters/Since'
      - $ref: '#/components/parameters/Until'
      - $ref: '#/components/parameters/PageNumber'
      - $ref: '#/components/parameters/PageSize'
      responses:
        '200':
          description: The aggregate plus one row per email on the page.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmailStatsRollupResponse'
        '400':
          description: '`VALIDATION_ERROR` for a malformed date, both narrowing filters at

            once, or more than 50 `ids`; or `RESULT_SET_TOO_LARGE` when the

            filter matches more than 1000 emails.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v2/emails/{id}/stats:
    get:
      tags:
      - Stats
      operationId: getEmailStats
      summary: Get an email's stats
      description: 'The same thirteen counters as journey stats, aggregated across **every**

        journey that sends this email.'
      parameters:
      - $ref: '#/components/parameters/EmailId'
      - $ref: '#/components/parameters/Since'
      - $ref: '#/components/parameters/Until'
      responses:
        '200':
          description: The email's stats. Not paged.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatsResponse'
        '400':
          description: A malformed `since` or `until`; `detail` names the field.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '404':
          description: Unknown or malformed email id.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
components:
  responses:
    Unauthorized:
      description: Missing or invalid Personal Access Token. Empty body.
    TooManyRequests:
      description: 'Per-tenant rate limit exceeded. Empty body. Retry after the number of

        seconds given in the `Retry-After` header.

        '
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
        X-RateLimit-Limit:
          description: Sustained request budget, in requests per second, per tenant.
          schema:
            type: integer
    Forbidden:
      description: The PAT lacks the required scope (READ for reads, WRITE for writes). Empty body.
  schemas:
    StatsRollupResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/StatsRollup'
        pagination:
          $ref: '#/components/schemas/Pagination'
        meta:
          $ref: '#/components/schemas/Meta'
    ErrorItem:
      type: object
      properties:
        errorCode:
          type: string
          examples:
          - NOT_FOUND
        message:
          type: string
        detail:
          type: string
      required:
      - errorCode
      - message
    StatsRollup:
      type: object
      properties:
        aggregate:
          allOf:
          - $ref: '#/components/schemas/Stats'
          description: 'One ungrouped query over the **whole** matching set, deliberately

            not the sum of `records`, which covers only the current page. All

            zeros when nothing matched.

            '
        records:
          type: array
          items:
            $ref: '#/components/schemas/JourneyStatsRow'
    Meta:
      type: object
      description: Envelope metadata present on every response.
      properties:
        status:
          type: string
          enum:
          - SUCCESS
          - FAILURE
        timestamp:
          type: string
          format: date-time
      required:
      - status
    StatsResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/Stats'
        meta:
          $ref: '#/components/schemas/Meta'
    EmailStatsRollup:
      type: object
      properties:
        aggregate:
          allOf:
          - $ref: '#/components/schemas/Stats'
          description: 'Totals for every matching email, across all pages, deliberately not

            the sum of `records`, which covers only the current page. All zeros

            when nothing matched.

            '
        records:
          type: array
          items:
            $ref: '#/components/schemas/EmailStatsRow'
    EmailStatsRow:
      description: One email's counters within a rollup page.
      allOf:
      - type: object
        properties:
          templateId:
            type: string
      - $ref: '#/components/schemas/Stats'
    Stats:
      type: object
      description: 'The thirteen send and engagement counters. Every field is always present

        and defaults to `0`. None are null or omitted.

        '
      properties:
        totalCount:
          type: integer
          description: Emails sent.
        notSentCount:
          type: integer
        processedCount:
          type: integer
        deliveredCount:
          type: integer
        droppedCount:
          type: integer
        bounceCount:
          type: integer
        spamreportCount:
          type: integer
        totalOpenCount:
          type: integer
        uniqueOpenCount:
          type: integer
        totalClickCount:
          type: integer
        uniqueClickCountByUrl:
          type: integer
        uniqueClickCountByEmail:
          type: integer
        unsubCount:
          type: integer
    Pagination:
      type: object
      description: Present only on paged list responses.
      properties:
        pageNumber:
          type: integer
        pageSize:
          type: integer
        totalElements:
          type: integer
        totalPages:
          type: integer
    JourneyStatsRow:
      description: One journey's counters within a rollup page.
      allOf:
      - type: object
        properties:
          journeyId:
            type: string
      - $ref: '#/components/schemas/Stats'
    ErrorEnvelope:
      type: object
      description: Standard failure envelope.
      properties:
        errors:
          type: array
          items:
            $ref: '#/components/schemas/ErrorItem'
        meta:
          $ref: '#/components/schemas/Meta'
    EmailStatsRollupResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/EmailStatsRollup'
        pagination:
          $ref: '#/components/schemas/Pagination'
        meta:
          $ref: '#/components/schemas/Meta'
  parameters:
    JourneyId:
      name: id
      in: path
      required: true
      description: Journey (campaign) id.
      schema:
        type: string
    PageSize:
      name: pageSize
      in: query
      required: false
      description: Items per page.
      schema:
        type: integer
        minimum: 1
        maximum: 200
        default: 20
    FolderId:
      name: folderId
      in: query
      required: false
      description: 'Restrict to one folder. An id matching no folder yields an empty page

        rather than an error.

        '
      schema:
        type: string
    Since:
      name: since
      in: query
      required: false
      description: 'ISO-8601 lower bound on the **send** time, inclusive. A date

        (`2026-01-01`), a `Z` offset and a `±HH:MM` offset are all accepted and

        normalised to UTC; a value carrying no offset is read in the server''s

        zone. Omit for all time.

        '
      schema:
        type: string
    TagName:
      name: tag
      in: query
      required: false
      description: 'Repeatable. Matches the tag **name**, not its id. A name matching no tag

        yields an empty page.

        '
      schema:
        type: array
        items:
          type: string
    EmailRollupIds:
      name: ids
      in: query
      required: false
      description: 'Email ids to report on, at most **50** (a URL-length limit). Mutually

        exclusive with `folderId`.

        '
      schema:
        type: array
        maxItems: 50
        items:
          type: string
    PageNumber:
      name: pageNumber
      in: query
      required: false
      description: 1-based page number.
      schema:
        type: integer
        minimum: 1
        maximum: 2147483647
        default: 1
    RollupIds:
      name: ids
      in: query
      required: false
      description: 'Journey ids to report on, at most **50** (a URL-length limit). Mutually

        exclusive with `tag` and `folderId`.

        '
      schema:
        type: array
        maxItems: 50
        items:
          type: string
    Until:
      name: until
      in: query
      required: false
      description: 'ISO-8601 upper bound on the send time, inclusive. Same accepted forms as

        `since`.

        '
      schema:
        type: string
    EmailId:
      name: id
      in: path
      required: true
      description: Email id.
      schema:
        type: string
  securitySchemes:
    PatAuth:
      type: http
      scheme: bearer
      bearerFormat: inf_pat
      description: 'Personal Access Token or OAuth 2.1 access token, sent as a bearer

        credential (`Authorization: Bearer inf_pat_...`). Read operations

        require a token with the `READ` scope; write operations

        (POST/PATCH/DELETE) require `WRITE`.

        '
x-provenance:
  method: harvested
  publisher: Inflection.io
  source: https://docs.inflection.io/api-reference/openapi.yaml
  note: split by refine-openapis from the provider's own published document, verified by fetch (openapi/_original/*.fetch.json);
    stamped by stamp-openapi-provenance.py (roadmap#793)