openapi: 3.0.0
x-stoplight:
    id: xl7ao4m61duhz
info:
    title: Instasent product API
    description: |
        ### Overview
        The Instasent Product API provides a comprehensive set of tools for managing **organizations** and **projects** within the Instasent platform. This API is designed to help businesses organize customer data, manage team permissions, and set up projects for targeted marketing and customer engagement.

        ### What is the Product API?
        The Product API is Instasent's primary management API for high-level entities and operations. It provides comprehensive tools for managing your organization's customer data platform, from initial setup to ongoing operations.

        > **NOTE**:
        > - To access the Product API you need a Product Token and your Project UID, you can get them in your project settings.

        **Core Purpose**: The Product API enables you to:
        - **Manage Organizations & Projects**: View organization information, create and configure projects, and manage project-level settings
        - **Configure Data Sources**: Create and manage API data sources that serve as entry points for customer data
        - **Access Unified Audience Data**: Query, search, and retrieve contacts from your project's unified audience, where data from multiple sources is automatically merged
        - **Track Customer Events**: Search and analyze customer interaction events to understand behavior patterns
        - **Manage Segments**: View and work with audience segments for targeted marketing
        - **Monitor Campaigns & Automations**: Access campaign and automation details, configurations, and status
        - **Send Direct Messages**: Create and send direct SMS messages to audience contacts, and manage SMS senders
        - **Ingest Data**: Push contacts and events to data sources via the Ingest API (integrated into the Product API)

        **Key Entities**:
        - **Organizations**: Represent companies using Instasent. Each organization has its own settings, billing information, user accounts, and API tokens. Organizations can contain multiple projects.
        - **Projects**: Isolated environments within an organization where customer data, contacts, events, segments, campaigns, and automations are managed. Projects allow you to organize different brands, customer segments, or marketing initiatives separately.
        - **Data Sources**: Collections of contacts and events that feed into a project. Multiple data sources can contribute to a single project's unified audience.
        - **Contacts**: Individual records representing customers, leads, or audience members. Contacts from different data sources are automatically merged into a unified `Audience Contact` when they share matching unique identifiers.
        - **Events**: Immutable records of customer interactions, such as purchases, page views, email opens, or any custom activity. Events enrich contact profiles and can trigger automations.

        ### Key Features and Benefits
        The Product API empowers organizations by providing comprehensive tools for customer data management and marketing automation:

        1. **Data Ingestion & Management**:
           - Create and manage API data sources for ingesting contacts and events
           - Push contacts and events to data sources via the Ingest API
           - View datasource statistics and stream specifications
           - Delete contacts from data sources when needed

        2. **Unified Audience Management**:
           - Access a project's unified audience where contacts from multiple data sources are automatically merged
           - Search and filter contacts using powerful QueryFilter syntax
           - Scroll through large contact lists with cursor-based pagination
           - Retrieve contacts by ID, user identifier, phone number, or email address
           - View contact events and interaction history

        3. **Event Tracking & Analysis**:
           - Search and filter audience events using the Event Filtering system
           - Scroll through event history with pagination support
           - Access event data to understand customer behavior and interactions

        4. **Segmentation & Targeting**:
           - View and manage audience segments (static and dynamic)
           - Scroll contacts within specific segments
           - Use segments for targeted marketing campaigns

        5. **Campaign & Automation Insights**:
           - View campaign details and status
           - Access automation configurations and settings
           - Track campaign and automation performance

        6. **Direct Messaging**:
           - Send direct SMS messages to audience contacts
           - Manage SMS senders for projects
           - Retrieve SMS message history by contact, campaign, automation, or send entity
           - Subscribe/unsubscribe contacts from SMS and email communications

        7. **Multi-Project Architecture**:
           - Set up multiple isolated projects within an organization
           - Each project maintains its own data sources, audience, and configurations
           - Ideal for managing different brands, customer segments, or marketing initiatives

        8. **Secure & Granular Access Control**:
           - Generate API tokens with specific scopes and permissions
           - Use datasource-specific tokens for write-only access
           - Control data visibility based on subscription plans and token scopes
           - Implement role-based access for different team members

        ### Components of an Organization
        - **Organization**: Represents a company and contains settings, billing, and permissions. Each organization has:
        - **Projects**: Independent areas where customer data and campaigns are managed.
        - **User Accounts**: Multiple users with varying permission levels can be set up to manage the organization.
        - **API Tokens**: Unique tokens for secure API access, specific to each organization and managed by privilege level.

        ### Components of a Project
        - **Data Sources**: Collections of contacts, events, and customer data. Multiple data sources, such as CSV files, third-party integrations, and APIs, can feed into a project. Each data source maintains its own collection of contacts, but these contacts are automatically merged into the project's unified audience based on matching unique identifiers.
        - **Contacts**: Individual records representing customers, leads, or other audience members. Contacts exist in two forms:
          - **Datasource Contacts**: Original contact records stored within each data source
          - **Audience Contacts / Project Audience**: Unified contact records created by merging datasource contacts that share matching merging attributes (e.g., `_user_id`, `_email`, `_phone_mobile`). Also known as "Audience". The consolidated collection of all audience contacts in a project. This is where contacts from multiple data sources are automatically merged based on their unique identifiers. The unified audience provides a single, comprehensive view of each customer across all data sources.
        - **Events**: Immutable records of customer interactions, such as purchases, views, or any other tracked activity. Events are associated with datasource contacts and are automatically aggregated into the unified audience contact when contacts are merged.
        - **Audience Segments**: Customizable groups of contacts based on attributes or behaviors, used for targeted marketing. Segments operate on the unified audience, allowing you to target customers regardless of which data source their data originated from.
        - **Campaigns and Automations**: Tools for sending messages to specific segments or triggering messages automatically based on events. Campaigns and automations work with the unified audience, ensuring consistent messaging across all customer touchpoints.

        ### Technical Details
        - **API Tokens**: Access to the Product API requires secure API tokens specific to each organization or project.
        - **Rate Limits**: To ensure performance, requests to the Product API are rate-limited based on the subscription plan [s, m, l, xl]

        ### Scopes (Permissions)
        Tokens can have specific scopes assigned to control access levels. The available scopes for the Product API are:

        *   **Datasource Management**:
            *   `PROJECT_DATASOURCE_READ`: Read-only access to datasources.
            *   `PROJECT_DATASOURCE_WRITE`: Create and modify datasources.

        *   **Organization Management**:
            *   `ACCOUNT_READ`: Read access to organization account details + funds.

        *   **Audience Management**:
            *   `PROJECT_AUDIENCE_READ`: Read access to audience contacts.
            *   `PROJECT_AUDIENCE_WRITE`: Write access to audience contacts.
            *   `PROJECT_AUDIENCE_LIST`: List audience contacts (*Requires specific subscription plan*).
            *   `PROJECT_AUDIENCE_DATA_BASIC`: Access to basic contact data (*Requires specific subscription plan*).
            *   `PROJECT_AUDIENCE_DATA_FULL`: Access to full contact data (*Requires to be manually granted by Instasent*).
            *   `PROJECT_AUDIENCE_DATA_EVENTS`: Access to audience events (*Requires specific subscription plan*).
            *   `PROJECT_AGGREGATIONS`: Access to audience and event aggregations (*Requires to be manually granted by Instasent*).

        *   **Campaign Management**:
            *   `PROJECT_CAMPAIGN_READ`: Read access to campaigns.

        *   **Automation Management**:
            *   `PROJECT_AUTOMATION_READ`: Read access to automations.

        *   **Messaging Access**:
            *   `PROJECT_DIRECT_READ`: Messaging read access
            *   `PROJECT_DIRECT_WRITE`: Messaging write access (required for creating direct SMS messages)

        ### Data Privacy & Scopes

        The amount of contact data returned by the API depends on the scopes assigned to the token and the organization's subscription plan.

        | Privilege Level | Required Scope | Data Returned |
        | :--- | :--- | :--- |
        | **Default** | `PROJECT_AUDIENCE_READ` | Full Name and User ID only. |
        | **Basic** | `PROJECT_AUDIENCE_DATA_BASIC` | Phone, Email, Country, Name and all boolean fields. |
        | **Full** | `PROJECT_AUDIENCE_DATA_FULL` | Full contact data (PII). |

        > **NOTE**:
        > - `PROJECT_AUDIENCE_LIST` is required for any endpoint that returns a list of contacts (scroll/search).
        > - `PROJECT_AUDIENCE_DATA_EVENTS` is required to access any event-related data.
        > - `PROJECT_AGGREGATIONS` is required for aggregation endpoints. It is manually granted by Instasent and is not generally available. It is reserved for trusted partners and requires approval.
        > - `PROJECT_AUDIENCE_DATA_FULL` is not generally available, it is reserved for trusted partners and requires approval by Instasent. It is not usually granted to customers.

        ### How Contacts Merge into the Project Unified Audience
        The unified audience is created by automatically combining contacts from all data sources within a project. When contacts from different data sources share matching values in their **merging attributes**, they are merged into a single audience contact:

        - **Primary Merging Attribute**: `_user_id` - If two contacts from different data sources have the same `_user_id`, they are automatically merged into one audience contact.
        - **Additional Merging Attributes**: Projects can configure additional attributes as merging attributes (e.g., `_email`, `_phone_mobile`). Contacts matching on any of these attributes will also be merged.
        - **Data Consolidation**: When contacts are merged, their attributes are combined. If multiple data sources provide values for the same attribute, the system uses priority rules to determine which value to keep.
        - **Event Aggregation**: All events from merged contacts are associated with the unified audience contact, providing a complete view of customer interactions across all data sources.
        - **Source Tracking**: The unified audience contact maintains references to all original data source contacts (`_datasources` and `_ds_contact_ids` attributes), allowing you to trace data back to its source.

        **Example**: If a contact with `_user_id: "12345"` exists in both your CRM data source and your e-commerce data source, they will be merged into a single audience contact. The resulting contact will contain attributes from both sources, and all events from both sources will be associated with this unified contact.

        ### Useful Links
        - **Instasent API Documentation**: [Full documentation](https://docs.instasent.com)
        - **Instasent APIs Overview Diagram**: [API diagram](https://lucid.app/publicSegments/view/e2941f34-b88e-4b96-a7f8-af65e1bf9dc8/image.png)
    version: 2.0.0
    contact:
        email: dev@instasent.com
        url: 'https://instasent.com'
        name: Instasent
    license:
        name: Instasent
        url: 'https://www.instasent.com/aviso-legal'
servers:
    - url: 'https://api.instasent.com/v1'
      description: Instasent product API
security:
    - BearerAuth: []
    - OAuth2: []
tags:
    - name: general
      description: General API endpoints for authentication and organization information
    - name: oauth
      description: |
        OAuth 2.1 endpoints — Dynamic Client Registration, the token endpoint, and the
        discovery metadata that lets a client find them. They exist so a third-party
        application (or an MCP client) can obtain a Product API token **in the user's
        name**, instead of asking them to create one in the dashboard and paste it. All
        of them are anonymous by design: a client has no credential until it completes
        the flow.

        These endpoints live at the **API host root**, outside the `/v1` product prefix
        (`https://api.instasent.com/oauth/token`, not
        `https://api.instasent.com/v1/oauth/token`), which is why each one declares its
        own server.

        The **authorization endpoint is part of this API too**, at
        `GET /oauth/authorize`. Consent still needs a logged-in user in front of a screen,
        which only the dashboard can provide, so that endpoint answers `302` and sends the
        browser to the dashboard's consent screen with the query string untouched. It is
        served here, on the issuer's own origin, so the whole authorization server is
        advertised on a single origin: clients that require the authorization endpoint to
        match the issuer work without special handling. Open it in a browser, then bring
        the resulting `code` back to `POST /oauth/token`.
    - name: project
      description: Project management and configuration endpoints
    - name: datasource
      description: Data source management endpoints for creating and managing data sources
    - name: audience
      description: Audience contact management, search, and filtering endpoints
    - name: stream
      description: Stream endpoints for ingesting contacts and events into data sources
    - name: segment
      description: Segment management endpoints for creating and managing audience segments
    - name: campaign
      description: Campaign management endpoints for viewing campaign details
    - name: automation
      description: Automation management endpoints for viewing automation details
    - name: flow
      description: |
        Flow discovery endpoints (read-only): list flows and their versions to find
        the ids the flow analytics endpoints take. Flows are built and managed in
        the dashboard; this surface exists so analytics consumers can enumerate them.
    - name: analytics
      description: |
        Curated Analytics & Reporting endpoints. A small, bounded, strongly-typed
        reporting surface over the same engines that power the dashboard. Reports
        come in two shapes — **values** (aggregate totals over a timeframe) and
        **series** (time-bucketed arrays) — plus **compare** convenience wrappers.
        Aggregate-only and PII-safe by construction. No raw aggregation DSL: a fixed
        catalog of named statistics, bounded enum filters and group-by dimensions,
        and project-driven conversion semantics. See the Analytics guide pages for
        the full conceptual model.
    - name: sms
      description: SMS message endpoints for sending and retrieving SMS messages
    - name: sms-sender
      description: SMS sender management endpoints
    - name: rcs-sender
      description: RCS sender (agent) management endpoints
    - name: third-party
      description: Other endpoints intendend for external services integration
    - name: readiness
      description: |
        Setup readiness endpoints — "what's left to configure" and "what needs
        attention" for a project or one of its channels, plus the real per-country
        send capability. Always recomputed from current data (no cached "done"
        flag); steps carry stable keys only (copy/i18n is the client's).
paths:
    /:
        get:
            summary: Get organization info and list projects
            description: |
                Retrieves information about the authenticated organization, usually used for authentication validation.
                This endpoint returns the organization token details, the projects that the token has access to,
                and organization summary information.

                **Use Cases**:
                - Validate authentication and token validity
                - Discover which projects your token can access
                - Check granted scopes and permissions

                **Response Details**:
                - `entity`: Contains the organization token and the projects that the token has access to
                - `metadata`: Organization summary. Will include more details if the token has the `ACCOUNT_READ` scope

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - general
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        type: object
                                        description: Organization token with accessible projects
                                        additionalProperties: true
                                    metadata:
                                        type: object
                                        properties:
                                            organization:
                                                type: object
                                                description: Organization summary
                                                additionalProperties: true
                                            dataMode:
                                                type: string
                                                description: |
                                                    The strictest data-access mode across every project this
                                                    credential can reach. `normal` is full access; under
                                                    `anonymized` personal identifiers come back masked; under
                                                    `blocked` no contact data is readable at all.
                                                enum:
                                                    - normal
                                                    - anonymized
                                                    - blocked
                                                example: normal
                                            dataPolicy:
                                                type: object
                                                description: |
                                                    What `dataMode` actually means for this credential, resolved
                                                    server-side so no client has to interpret the mode itself.
                                                    Read these two booleans rather than deriving access from the
                                                    mode name, and treat their absence as no access.
                                                properties:
                                                    mayReadContactData:
                                                        type: boolean
                                                        description: Whether contact records can be read at all.
                                                        example: true
                                                    mayReadPii:
                                                        type: boolean
                                                        description: |
                                                            Whether personal identifiers (phone, email) come back
                                                            readable rather than masked, and whether they can be
                                                            searched by.
                                                        example: true
                                                required:
                                                    - mayReadContactData
                                                    - mayReadPii
                                            scopes:
                                                type: array
                                                items:
                                                    type: string
                                                description: Array of granted scopes
                                        additionalProperties: true
                                required:
                                    - entity
                                    - metadata
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: vfqqt49vf5lqx
    '/project/{project}':
        get:
            summary: Get project info
            description: |
                Retrieves information about a specific project within your organization.

                **Required Scope**: Token must have access to the specified project

                **Response Details**:
                - `entity`: The project entity with project details. Important properties include:
                  - `id`: Project internal ID
                  - `uid`: Project UID (used in API paths)
                  - `name`: Project name
                  - `description`: Project description
                  - `projectType`: Type of project (e.g., `standard`)
                  - `projectStatus`: Project status (e.g., `active`)
                  - `locale`: Project locale (e.g., `es_ES`)
                  - `timezone`: Project timezone (e.g., `Europe/Madrid`)
                  - `defaultSmsSender`: ID of the default SMS sender
                  - `generalConfig`: General project configuration (auto-create settings, opt-in/out keywords)
                  - `attributionConfig`: Attribution tracking configuration (time windows for attribution)
                  - `createdAt`: When the project was created
                  - `updatedAt`: When the project was last updated
                - `metadata`: Contains:
                  - `uniqueAttributes`: Array of attribute UIDs that are marked as unique (used for contact merging)
                  - `organization`: Organization summary (includes plan, API tier, account funds/credits)
                  - `scopes`: Array of granted scopes for the current token

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - project
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/ResponseProjectItem'
                                    metadata:
                                        type: object
                                        properties:
                                            uniqueAttributes:
                                                type: array
                                                items:
                                                    type: string
                                                description: Array of attribute UIDs that are marked as unique (used for contact merging)
                                            organization:
                                                type: object
                                                description: Organization summary (includes plan, API tier, account funds/credits)
                                                additionalProperties: true
                                            scopes:
                                                type: array
                                                items:
                                                    type: string
                                                description: Array of granted scopes
                                        required:
                                            - uniqueAttributes
                                        additionalProperties: true
                                required:
                                    - entity
                                    - metadata
                            examples:
                                standard:
                                    summary: Standard project response
                                    value:
                                        entity:
                                            id: '67bdfa983114d0062d732655'
                                            uid: 'my-empty-project'
                                            name: 'My empty project'
                                            description: null
                                            projectType: 'standard'
                                            projectStatus: 'active'
                                            locale: 'es_ES'
                                            timezone: 'Europe/Madrid'
                                            defaultSmsSender: '68ad7ad14b8e760c1541f542'
                                            shortTrackingDomain: null
                                            unsubscribeTrackingDomain: null
                                            businessType: null
                                            businessContactsSize: 1013
                                            businessUrl: 'https://www.marin.org/ex-maxime-culpa-omnis-repudiandae-tempora-quasi'
                                            lockedUntil: null
                                            lockedReason: null
                                            testSmsNumbers: []
                                            conversionConfig: null
                                            attributionConfig:
                                                hoursSmsCampaignSent: 24
                                                hoursOtherCampaignSent: 24
                                                hoursCampaignCta: 120
                                                hoursCampaignOpen: 120
                                                hoursFindOutboundSms: 168
                                            generalConfig:
                                                channelDefaults:
                                                    defaultCompliancePolicy: basic
                                                    optInKeywords: null
                                                    optOutKeywords: null
                                                    autoCreateContactsOnInbounds: true
                                                    autoCreateContactsOnOutbounds: true
                                                    marketingOptOutScope: per_channel
                                                    marketingOptInScope: per_channel
                                                channelSms:
                                                    defaultCompliancePolicy: opt-out
                                                    optInKeywords: null
                                                    optOutKeywords: null
                                                    autoCreateContactsOnInbounds: true
                                                    autoCreateContactsOnOutbounds: true
                                                    marketingOptOutScope: per_channel
                                                    marketingOptInScope: per_channel
                                                channelEmail:
                                                    defaultCompliancePolicy: basic
                                                    optInKeywords: null
                                                    optOutKeywords: null
                                                    autoCreateContactsOnInbounds: true
                                                    autoCreateContactsOnOutbounds: true
                                                    marketingOptOutScope: per_channel
                                                    marketingOptInScope: per_channel
                                                channelRcs:
                                                    defaultCompliancePolicy: basic
                                                    optInKeywords: null
                                                    optOutKeywords: null
                                                    autoCreateContactsOnInbounds: true
                                                    autoCreateContactsOnOutbounds: true
                                                    marketingOptOutScope: per_channel
                                                    marketingOptInScope: per_channel
                                                channelWhatsapp:
                                                    defaultCompliancePolicy: basic
                                                    optInKeywords: null
                                                    optOutKeywords: null
                                                    autoCreateContactsOnInbounds: true
                                                    autoCreateContactsOnOutbounds: true
                                                    marketingOptOutScope: per_channel
                                                    marketingOptInScope: per_channel
                                            brand: null
                                            createdAt: '2025-02-25T18:13:46+01:00'
                                            updatedAt: '2025-09-09T17:27:23+02:00'
                                        metadata:
                                            uniqueAttributes:
                                                - '_user_id'
                                            organization:
                                                id: '60141bb26dccbf21a04a01d2'
                                                name: 'Instasent Frontend'
                                                plan:
                                                    key: 'premium_profile'
                                                    quality: 3
                                                    status: 'active'
                                                api:
                                                    tier: 5
                                                    extendedSupport:
                                                        - 'ROLE_ORGANIZATION_PROJECT_AUDIENCE_DATA_BASIC'
                                                        - 'ROLE_ORGANIZATION_PROJECT_AUDIENCE_DATA_EVENTS'
                                                        - 'ROLE_ORGANIZATION_PROJECT_AUDIENCE_LIST'
                                                        - 'ROLE_ORGANIZATION_PROJECT_AGGREGATIONS'
                                                account:
                                                    funds:
                                                        currency: 'EUR'
                                                        value: 2000.288
                                                    credit:
                                                        currency: 'EUR'
                                                        value: 28.527
                                                        resetAt: '2025-03-28T08:00:55+01:00'
                                                        resetTo: 30.99
                                            scopes:
                                                - 'PROJECT_DATASOURCE_READ'
                                                - 'PROJECT_AUDIENCE_READ'
                                                - 'PROJECT_AUDIENCE_DATA_BASIC'
                                                - 'PROJECT_DIRECT_WRITE'
                                                - 'PROJECT_AUDIENCE_LIST'
                                                - 'ACCOUNT_READ'
                                                - 'PROJECT_DIRECT_READ'
                                                - 'PROJECT_DATASOURCE_WRITE'
                                                - 'PROJECT_AUDIENCE_WRITE'
                                                - 'PROJECT_AUDIENCE_DATA_EVENTS'
                                                - 'PROJECT_CAMPAIGN_READ'
                                                - 'PROJECT_AUTOMATION_READ'
                                                - 'PROJECT_AGGREGATIONS'
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: 1lk93ptjalqxr
    '/project/{project}/specs/attributes':
        get:
            summary: Get project attributes specs
            description: |
                Retrieves the list of enabled project attributes (attribute specifications) for a project.
                These are the available fields that can be used for contacts in the project.

                **Use Cases**:
                - Discover which attributes are available for contacts in this project
                - Understand attribute types and constraints before creating contacts
                - Validate attribute names before sending data to the stream API
                - Identify unique attributes used for contact merging
                - Check which attributes are readonly, custom, or internal

                **Response Details**:
                - `entities`: Array of enabled project attribute specifications. Each attribute includes:
                  - `uid`: The attribute identifier (e.g., `_full_name`, `_user_id`)
                  - `label`: The attribute's human label, ready to show (prefer this)
                  - `displayLabel`: The raw stored label, which may carry the panel's `<XLT>` translation markers
                  - `dataType`: The data type (e.g., `string`, `number`, `boolean`, `date`)
                  - `visualType`: The visual representation type
                  - `enabled`: Whether the attribute is enabled for use
                  - `readonly`: Whether the attribute is read-only
                  - `unique`: Whether the attribute is unique (used for merging contacts)
                  - `custom`: Whether the attribute is custom (user-created)
                  - `internal`: Whether the attribute is internal-only
                  - `multivalue`: Maximum number of values allowed (1 = single value)
                  - `mappeable`: Whether the attribute can be mapped from datasources
                  - `eventBased`: Whether the attribute is derived from events
                  - `description`: Localized description of the attribute
                - `metadata`: Contains:
                  - `uniqueAttributes`: Array of attribute UIDs that are marked as unique (used for contact merging)
                  - `organization`: Organization summary
                  - `scopes`: Array of granted scopes

                **Rate Limit**: [s] restrictive - varies by subscription tier
            tags:
                - project
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/AttributeSpec'
                                        description: Array of enabled project attribute specifications
                                    metadata:
                                        type: object
                                        properties:
                                            uniqueAttributes:
                                                type: array
                                                items:
                                                    type: string
                                                description: Array of attribute UIDs that are marked as unique (used for contact merging)
                                            organization:
                                                type: object
                                                description: Organization summary
                                                additionalProperties: true
                                            scopes:
                                                type: array
                                                items:
                                                    type: string
                                                description: Array of granted scopes
                                        required:
                                            - uniqueAttributes
                                required:
                                    - entities
                                    - metadata
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: wt1q2uuyc2ni2
    '/project/{project}/specs/events':
        get:
            summary: Get project events specs
            description: |
                Retrieves the list of available event types and their specifications for a project.
                This endpoint uses a mock datasource to determine the available event types.

                **Use Cases**:
                - Discover which event types are available for this project
                - Understand event structure and properties before creating events
                - Validate event types before sending data to the stream API
                - Identify which events support attribution or automation triggers

                **Response Details**:
                - `entities`: Array of event type specifications. Each event includes:
                  - `uid`: The event type identifier (e.g., `appointment`, `ecommerce_order_create`)
                  - `name`: Human-readable name for the event type
                  - `description`: Description of what the event represents
                  - `category`: Event category (e.g., `contact_behaviour`, `ecommerce`)
                  - `attribution`: Whether the event supports attribution tracking
                  - `automation`: Whether the event can trigger automations
                  - `important`: Whether the event is marked as important
                  - `icon`: Icon identifier for the event type
                  - `emoji`: Emoji representation of the event type

                **Rate Limit**: [s] restrictive - varies by subscription tier
            tags:
                - project
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/EventSpec'
                                        description: Array of available event type specifications
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: vmuk4icujmbyr
    '/project/{project}/specs/events/{eventType}':
        get:
            summary: Get event parameters specs
            description: |
                Retrieves the parameter specifications for a specific event type.
                This allows you to understand what parameters are available when creating events of this type.

                **Use Cases**:
                - Discover required and optional parameters for a specific event type
                - Understand parameter types, constraints, and validation rules
                - Validate event data before sending to the stream API
                - Check maximum length constraints for string parameters
                - Identify which parameters support multiple values

                **Response Details**:
                - `entities`: Array of parameter specifications for the event type. Each parameter includes:
                  - `parameter`: The parameter identifier (e.g., `order-id`, `order-currency`)
                  - `title`: Human-readable title for the parameter
                  - `description`: Description of the parameter and its purpose
                  - `dataType`: The data type (e.g., `keyword`, `number`, `date`)
                  - `visualType`: The visual representation type
                  - `required`: Whether the parameter is required
                  - `multiValue`: Maximum number of values allowed (1 = single value)
                  - `maxLength`: Maximum length for string parameters (if applicable)
                  - `icon`: Icon identifier for the parameter type
                - `metadata`: Contains:
                  - `event`: The event type specification object (if found), matching the structure returned by `/specs/events`

                **Note**: Each event type has its own set of parameters. Use `/specs/events` to discover available event types first.

                **Rate Limit**: [s] restrictive - varies by subscription tier
            tags:
                - project
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: eventType
                  in: path
                  description: 'Event type UID (e.g., ''ecommerce_order_create'', ''appointment'')'
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/EventParameterSpec'
                                        description: Array of parameter specifications for the event type
                                    metadata:
                                        type: object
                                        properties:
                                            event:
                                                $ref: '#/components/schemas/EventSpec'
                                                description: Event type specification (if found)
                                        additionalProperties: true
                                required:
                                    - entities
                                    - metadata
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: bpwrh8qd8z8q6
    '/project/{project}/datasource':
        get:
            x-see-docs:
                - query-filter
            summary: List datasources
            description: |
                Lists all `dsapi` type datasources for a project. Supports Query String Filtering for filtering, sorting, and pagination.

                **Query Parameters**: Supports Query String Filtering parameters:
                - Filtering: Use `field_operator=value` format (e.g., `name_eq=MyDatasource`, `createdAt_gte=2024-01-01T00:00:00+00:00`)
                - Sorting: Use `_sort=field:direction` format (e.g., `_sort=name:asc`, `_sort=createdAt:desc`)
                - Pagination: Use `_start=0&_limit=50` format. **Important**: When using `_limit`, you must also provide `_start`
                - JSON QueryFilter: Use `_q` parameter with JSON structure
            tags:
                - datasource
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseDatasourceItem'
                                    metadata:
                                        $ref: '#/components/schemas/ResponseMetadata'
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: g90er8mmsklo3
        post:
            summary: Create data source
            description: |
                This endpoint allows you to create data sources for your project.

                Currently, only API data sources can be created using this endpoint. Other types of data sources must be manually created using the management dashboard.

                API data sources allow you to input contacts and events through our Ingest API. First, you create the contact, then send its past, present, or future events.

                As a response, you will receive the data source token. This token is specific to the data source and can only be used for sending contacts and events to it.
            tags:
                - datasource
            parameters:
                - name: project
                  in: path
                  description: Project identifier
                  required: true
                  schema:
                      type: string
            requestBody:
                $ref: '#/components/requestBodies/DatasourceItemBody'
            responses:
                '201':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/ResponseDatasourceItem'
                                required:
                                    - entity
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '413':
                    $ref: '#/components/responses/RequestTooLarge'
                '422':
                    $ref: '#/components/responses/ValidationError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: sapv4z60yco0a
    '/project/{project}/datasource/{id}':
        get:
            summary: View datasource
            description: |
                Retrieves details of a specific datasource.

                **Datasource Parameter**: `{id}` can be the id of a dsapi datasource or just `dsapi`.
                It will be automatically resolved to the first found `type === 'dsapi' + integration === null` of the project.
            tags:
                - datasource
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: id
                  in: path
                  description: Datasource ID or 'dsapi' to use the first dsapi datasource
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/ResponseDatasourceItem'
                                required:
                                    - entity
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: q1z5ysvpfm95z
    '/project/{project}/audience/overview':
        get:
            summary: Audience overview by channel and policy
            description: |
                How many people this project can actually reach, and what a different consent
                policy would change.

                Returns the total number of contacts and, for each channel the project can send
                on, the number reachable under the project's own consent policy plus the full
                ladder of what every other policy would reach. Aggregate numbers only: no contact
                rows, so it is not restricted by the project's data policy.

                Use it instead of composing a count query when the question is "what is my
                audience" or "what would I gain by asking for opt-in". Absent channels mean the
                project cannot send on them.

                **Required Scope**: `PROJECT_AUDIENCE_READ`

                **Freshness**: the counters are cached per project with a TTL that grows with the
                audience's size, from one minute for small audiences up to twelve hours for very
                large ones, and every response carries its age in `computedAgo`. The `reachable`
                figure and the policy name are resolved on each request, so a policy change shows
                immediately. `entity` is null when the audience index cannot be read.

                **Rate Limit**: [s] small - varies by subscription tier
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        allOf:
                                            - $ref: '#/components/schemas/AudienceOverview'
                                        nullable: true
                                required:
                                    - entity
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
    '/project/{project}/audience/user/{userId}':
        get:
            summary: Get audience contact by user_id
            description: |
                Retrieves contact information for a specific user within a project's audience.
                The system will search for the user in any of the unique attributes configured
                in the project (e.g., email, phone, _user_id).

                **Required Scope**: `PROJECT_AUDIENCE_READ`

                **Rate Limit**: [m] moderate - varies by subscription tier

                **Search Behavior**:
                - If the userId matches an audience contact ID format, it retrieves directly by ID
                - If it matches a datasource contact ID format, it searches for it
                - Otherwise, it searches across all unique attributes configured in the project

                **Data Privacy**: The amount of contact data returned depends on your token's
                scopes and subscription plan. See [Data Privacy & Scopes](#data-privacy--scopes)
                for details.

                Remember, an audience contact may contain data from multiple data sources if the
                contact is present in more than one. The **_datasources** attribute indicates
                which data sources are providing data to the contact.

                To update an audience contact you must feed the corresponding data source with
                the updated data. It will automatically update the data source contact and after
                a few seconds, the audience contact will be refreshed.

                **Search ID Resolution**:
                - In case it's an id with an audience id format it will retrieve directly by id
                - In case it's a datasource contact id format, it will retrieve by searching for it
                - Otherwise, it searches across all unique attributes configured in the project

                Only 1 user will be returned, the first that matches.
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: userId
                  in: path
                  description: 'User identifier to search for in any of the unique attributes (e.g., email, phone, _user_id)'
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/ResponseAudienceItem'
                                required:
                                    - entity
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: zk1yrato9qcsx
    '/project/{project}/audience/search/phone/{userPhone}':
        get:
            summary: Search audience contacts by phone
            description: |
                Searches for up to 10 audience contacts matching a specific phone number.
                The phone number is normalized automatically (non-numeric characters are removed
                and a `+` prefix is added).

                **Required Scope**: `PROJECT_AUDIENCE_READ`

                **Rate Limit**: [m] moderate - varies by subscription tier

                **Data Privacy**: The amount of contact data returned depends on your token's
                scopes and subscription plan. See [Data Privacy & Scopes](#data-privacy--scopes)
                for details.
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: userPhone
                  in: path
                  description: Phone number to search for (will be normalized automatically)
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseAudienceItem'
                                        maxItems: 10
                                        description: Array of up to 10 audience contact entities
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: 1kegihud8mka5
    '/project/{project}/audience/search/email/{userEmail}':
        get:
            summary: Search audience contacts by email
            description: |
                Searches for up to 10 audience contacts matching a specific email address.

                **Required Scope**: `PROJECT_AUDIENCE_READ`

                **Rate Limit**: [m] moderate - varies by subscription tier

                **Data Privacy**: The amount of contact data returned depends on your token's
                scopes and subscription plan. See [Data Privacy & Scopes](#data-privacy--scopes)
                for details.
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: userEmail
                  in: path
                  description: Email address to search for
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseAudienceItem'
                                        maxItems: 10
                                        description: Array of up to 10 audience contact entities
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: ioztdykh6ykee
    '/project/{project}/audience/{audienceId}':
        get:
            summary: Get audience contact by ID
            description: |
                Retrieves contact information for a specific audience contact by its internal ID.

                **Required Scope**: `PROJECT_AUDIENCE_READ`

                **Rate Limit**: [m] moderate - varies by subscription tier

                **Data Privacy**: The amount of contact data returned depends on your token's
                scopes and subscription plan. See [Data Privacy & Scopes](#data-privacy--scopes)
                for details.
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: audienceId
                  in: path
                  description: Internal audience contact ID
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/ResponseAudienceItem'
                                required:
                                    - entity
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: qoqbrhmorojew
    '/project/{project}/audience/{audienceId}/events':
        get:
            summary: Get audience contact events
            description: |
                Retrieves latest 5 events of each type associated with a specific audience contact.

                **Required Scope**: `PROJECT_AUDIENCE_READ` AND `PROJECT_AUDIENCE_DATA_EVENTS`

                **Rate Limit**: [m] moderate - varies by subscription tier

                **Note**: Extended API scopes required in subscription for event access.

                Returns latest 5 events of each type associated with the audience contact.
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: audienceId
                  in: path
                  description: Internal audience contact ID
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    events:
                                        type: array
                                        items:
                                            type: object
                                            description: Event entity
                                            additionalProperties: true
                                        description: List of events (latest 5 of each type)
                                required:
                                    - events
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: 199wrm1n5c8pd
    '/project/{project}/audience/search':
        post:
            x-see-docs:
                - audience-query-filter
            summary: Search audience contacts
            description: |
                Searches audience contacts using the powerful Audience Contact Filtering system.
                Supports offset/limit pagination for simple searches with restrictive rate limits.

                **Required Scope**: `PROJECT_AUDIENCE_READ`

                **Rate Limit**: [s] restrictive - varies by subscription tier

                **Important Notes**:
                - This endpoint uses offset/limit pagination, not cursor-based pagination like `/scroll`
                - Offset is always enforced to 0 - this endpoint is designed for simple searches starting from the beginning
                - Maximum limit is 50 contacts per request (lower than the scroll endpoint's 100 limit)
                - For large result sets or pagination beyond the first page, use the `/scroll` endpoint instead

                **Data Privacy**: The amount of contact data returned depends on your token's
                scopes and subscription plan. See [Data Privacy & Scopes](#data-privacy--scopes)
                for details.
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            requestBody:
                $ref: '#/components/requestBodies/AudienceSearchRequest'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseAudienceItem'
                                        description: List of audience contacts (obfuscated based on token permissions)
                                    metadata:
                                        type: object
                                        properties:
                                            totalHits:
                                                type: integer
                                                description: Total number of matching contacts
                                            limit:
                                                type: integer
                                                description: Contacts per page
                                            offset:
                                                type: integer
                                                description: Always 0 (offset is enforced to 0 for this endpoint)
                                        required:
                                            - totalHits
                                            - limit
                                            - offset
                                required:
                                    - entities
                                    - metadata
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '422':
                    $ref: '#/components/responses/ValidationError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: 0kfbgomot9k3c
    '/project/{project}/audience/count':
        post:
            x-see-docs:
                - audience-query-filter
            summary: Count audience contacts
            description: |
                Counts the audience contacts matching an Audience Query Filter - AQF — the same
                filter language used by `/audience/search` and by segment definitions —
                and returns ONLY the total. No contact data is returned.

                **Required Scope**: `PROJECT_AUDIENCE_READ`

                **Rate Limit**: [s] restrictive - varies by subscription tier

                **Important Notes**:
                - Returns an exact total (not capped), with zero contact rows
                - `limit`/`offset` in the request body are ignored
                - An empty body counts the whole audience
                - Because the response carries no contact data, this endpoint stays
                  available under the `anonymized` data policy mode, where
                  `/audience/search` is disabled — it is the supported way for
                  restricted credentials (e.g. AI agents) to answer "how many
                  contacts match this filter"
                - To count who can actually receive on a channel, pass `filterCompliance`
                  (`{"sms": "opt-out"}`): the same consent and reach rules as the campaign
                  estimate. Without it the total is the raw audience, not a sending figure.
                  The project's own policy per channel is `generalConfig.channel<Channel>.defaultCompliancePolicy`
                  in `GET /project/{project}`
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            requestBody:
                $ref: '#/components/requestBodies/AudienceSearchRequest'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    metadata:
                                        type: object
                                        properties:
                                            totalHits:
                                                type: integer
                                                description: Total number of matching contacts (exact)
                                        required:
                                            - totalHits
                                required:
                                    - metadata
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '422':
                    $ref: '#/components/responses/ValidationError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
    '/project/{project}/audience/coverage':
        post:
            x-see-docs:
                - audience-probes
            summary: Probe attribute coverage
            description: |
                What the audience actually CONTAINS, attribute by attribute: how many contacts have
                each one filled in, how many distinct values it holds, and its most frequent values
                when it holds few enough to be a vocabulary rather than an identifier.

                `GET /project/{project}/attribute` says which attributes EXIST. This says which of
                them are usable. An attribute declared in the schema and filled on 2% of contacts is
                absent in practice, and a segment built on it comes back empty with nothing to
                explain why.

                **Required Scope**: `PROJECT_AUDIENCE_READ`

                **Rate Limit**: [s] restrictive - varies by subscription tier

                **Important Notes**:
                - This is a PROBE, not a count. Above 10,000 contacts the figures are estimated from
                  a sample, so they say a value EXISTS and never how many people it reaches. For a
                  size use `/audience/count`, which is exact and live
                - `sampling` is always present, `applied: false` included, and every figure carries
                  whether it is approximate and whether it cleared the detection floor
                - The absence of `topValues` is never "this attribute has no values". When they are
                  omitted, `topValuesOmitted` says why: an identifier by shape, too many distinct
                  values, or a name that reads as an id, a timestamp or a quantity. That last one is
                  overridden by naming the attribute explicitly in `attributes`
                - `min` and `max` are omitted while sampling, because sample extremes are biased and
                  no threshold repairs that. `avg` survives
                - `datasource` means contacts this source CONTRIBUTED TO, never that it supplied the
                  value: the audience contact is a consolidated projection of every source that
                  touched it
                - A call carrying an AQF `root` is computed live and never cached
                - `refresh: true` is throttled to one every five minutes. A denial is not an error:
                  the cached answer comes back with `refreshDeclined` and `nextRefreshIn`
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            requestBody:
                content:
                    application/json:
                        schema:
                            type: object
                            properties:
                                root:
                                    type: object
                                    description: Optional AQF root, same grammar as `/audience/search`. Narrows the population being probed.
                                datasource:
                                    type: string
                                    description: Connector name, uid or Mongo id. Keeps contacts this datasource contributed to. Unknown values are refused with the list of what exists.
                                attributes:
                                    type: array
                                    description: Attribute uids to probe. Omit for the ones worth probing by default. Naming one explicitly also measures attributes the name rules would skip.
                                    items:
                                        type: string
                                refresh:
                                    type: boolean
                                    description: Force a recomputation, throttled to one every five minutes.
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    metadata:
                                        type: object
                                        properties:
                                            denominator:
                                                type: object
                                                description: The population the fill rates are over, and its definition.
                                            sampling:
                                                type: object
                                                description: 'How these figures were produced: applied, fraction, buckets, band, scanned, scaled, minReliableCount.'
                                            attributes:
                                                type: array
                                                description: One entry per attribute probed, each with its own summary sentence carrying the sampling qualifier.
                                                items:
                                                    type: object
                                            datasource:
                                                type: object
                                                description: Present when narrowed. Carries the resolved uids and what the narrowing MEANS.
                                            truncated:
                                                type: object
                                                description: Present when the attribute cap was hit, naming what was left out.
                                            cached:
                                                type: boolean
                                            computedAgo:
                                                type: string
                                                description: Age of the figures in words.
                                            summary:
                                                type: string
                                        required:
                                            - denominator
                                            - sampling
                                            - attributes
                                required:
                                    - metadata
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '422':
                    $ref: '#/components/responses/ValidationError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
    '/project/{project}/audience/aggregations':
        post:
            x-see-docs:
                - audience-query-filter
            summary: Aggregate audience contacts
            description: |
                Performs aggregations on audience contacts using the powerful Audience Contact Filtering system.
                Returns only aggregation results without individual contact data.

                **Required Scope**: `PROJECT_AGGREGATIONS` (Manually granted by Instasent - not generally available)

                **Rate Limit**: [xs] very restrictive - varies by subscription tier

                **Important Notes**:
                - This endpoint requires `PROJECT_AGGREGATIONS` scope which is manually granted by Instasent
                - No contact entities are returned - limit is enforced to 0
                - This endpoint is designed for analytics and reporting use cases
                - Aggregations are the primary purpose of this endpoint

                **Data Privacy**: The amount of contact data returned depends on your token's
                scopes and subscription plan. See [Data Privacy & Scopes](#data-privacy--scopes)
                for details.
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            requestBody:
                $ref: '#/components/requestBodies/AudienceAggregationsRequest'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: object
                                        description: Object containing aggregation results
                                        additionalProperties: true
                                    metadata:
                                        type: object
                                        properties:
                                            totalHits:
                                                type: integer
                                                description: Total number of matching contacts
                                        required:
                                            - totalHits
                                        additionalProperties: true
                                required:
                                    - entities
                                    - metadata
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '403':
                    description: Missing PROJECT_AGGREGATIONS scope or subscription support
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ResponseErrorDetail'
                '422':
                    $ref: '#/components/responses/ValidationError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: audience-aggregations-endpoint
    '/project/{project}/audience/scroll':
        post:
            x-see-docs:
                - audience-query-filter
            summary: Scroll audience contacts
            description: |
                Lists audience contacts using the powerful Audience Contact Filtering system.
                Supports cursor-based pagination for iterating through large result sets.

                **Required Scope**: `PROJECT_AUDIENCE_LIST` (Extended API scope required in subscription)

                **Rate Limit**: [s] restrictive - varies by subscription tier

                **Important Notes**:
                - The cursor is a base64 string representing the internal query state, and it expires after 1 minute of inactivity
                - Maximum limit is 100 contacts per request
                - Use the cursor from the previous response's metadata to get the next page

                **Data Privacy**: The amount of contact data returned depends on your token's
                scopes and subscription plan. See [Data Privacy & Scopes](#data-privacy--scopes)
                for details.
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            requestBody:
                $ref: '#/components/requestBodies/AudienceScrollRequest'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseAudienceItem'
                                        description: List of audience contacts (obfuscated based on token permissions)
                                    metadata:
                                        type: object
                                        properties:
                                            totalHits:
                                                type: integer
                                                nullable: true
                                                description: Total number of matching contacts. Only returned on the first page (when no cursor is provided). Subsequent pages return null since the client already has the total from the first response.
                                            limit:
                                                type: integer
                                                description: Contacts per page
                                            cursor:
                                                type: string
                                                nullable: true
                                                description: Cursor for the next page. Pass this back in the request body to get the next page. null if no more results
                                        required:
                                            - totalHits
                                            - limit
                                            - cursor
                                required:
                                    - entities
                                    - metadata
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '422':
                    $ref: '#/components/responses/ValidationError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: cmtqy1sy4vrra
    '/project/{project}/audience/segment/{uid}/scroll':
        post:
            x-see-docs:
                - audience-query-filter
            summary: Scroll audience contacts by segment
            description: |
                Lists audience contacts belonging to a specific segment using the Audience Contact Filtering system.
                The segment queryFilter is merged into the provided one (you can filter within the segment
                by providing a root node as body).

                **Required Scope**: `PROJECT_AUDIENCE_LIST` (Extended API scope required in subscription)

                **Rate Limit**: [s] restrictive - varies by subscription tier.

                **Important Notes**:
                - The cursor is a base64 string representing the internal query state, and it expires after 1 minute of inactivity
                - Maximum limit is 100 contacts per request
                - For dynamic segments, use the `parameter` query parameter

                **Data Privacy**: The amount of contact data returned depends on your token's
                scopes and subscription plan. See [Data Privacy & Scopes](#data-privacy--scopes)
                for details.

                The segment queryFilter is merged into the provided one (you can filter within the segment by providing a root node as body).
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: uid
                  in: path
                  description: Segment UID
                  required: true
                  schema:
                      type: string
                - name: parameter
                  in: query
                  description: 'For dynamic segments, the parameter value (e.g., ''val1|val2'')'
                  required: false
                  schema:
                      type: string
            requestBody:
                $ref: '#/components/requestBodies/AudienceScrollRequest'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseAudienceItem'
                                        description: List of audience contacts (obfuscated based on token permissions)
                                    metadata:
                                        type: object
                                        properties:
                                            totalHits:
                                                type: integer
                                                nullable: true
                                                description: Total number of matching contacts. Only returned on the first page (when no cursor is provided). Subsequent pages return null since the client already has the total from the first response.
                                            limit:
                                                type: integer
                                                description: Contacts per page
                                            cursor:
                                                type: string
                                                nullable: true
                                                description: Cursor for the next page. Pass this back in the request body to get the next page. null if no more results
                                        required:
                                            - totalHits
                                            - limit
                                            - cursor
                                required:
                                    - entities
                                    - metadata
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: 4ulttnljvgklc
    '/project/{project}/event/search':
        post:
            x-see-docs:
                - audience-event-query-filter
            summary: Search audience events
            description: |
                Searches audience events using the powerful Event Filtering system.
                Supports offset/limit pagination for simple searches with restrictive rate limits.

                **Required Scope**: `PROJECT_AUDIENCE_READ` AND `PROJECT_AUDIENCE_DATA_EVENTS`
                (Extended API scopes required in subscription)

                **Rate Limit**: [s] restrictive - varies by subscription tier

                **Important Notes**:
                - This endpoint uses offset/limit pagination, not cursor-based pagination like `/scroll`
                - Offset is always enforced to 0 - this endpoint is designed for simple searches starting from the beginning
                - Maximum limit is 50 events per request (lower than the scroll endpoint's 100 limit)
                - Events are automatically filtered to the last 180 days in paid plans. 30 days in free plans.
                - For large result sets or pagination beyond the first page, use the `/scroll` endpoint instead

                For complete documentation on Event Filtering, see [Event QueryFilter documentation](docs/dev/project-audience-event-query-filter.es.md).
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            requestBody:
                $ref: '#/components/requestBodies/EventSearchRequest'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseEventItem'
                                        description: List of audience events
                                    metadata:
                                        type: object
                                        properties:
                                            totalHits:
                                                type: integer
                                                description: Total number of matching events
                                            limit:
                                                type: integer
                                                description: Events per page
                                            offset:
                                                type: integer
                                                description: Always 0 (offset is enforced to 0 for this endpoint)
                                        required:
                                            - totalHits
                                            - limit
                                            - offset
                                required:
                                    - entities
                                    - metadata
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: 61cbdz34hnyrv
    '/project/{project}/event/aggregations':
        post:
            x-see-docs:
                - audience-event-query-filter
            summary: Aggregate audience events
            description: |
                Performs aggregations on audience events using the powerful Event Filtering system.
                Returns only aggregation results without individual event data.

                **Required Scope**: `PROJECT_AGGREGATIONS` AND `PROJECT_AUDIENCE_DATA_EVENTS`
                (Both scopes manually granted by Instasent - not generally available)

                **Rate Limit**: [xs] very restrictive - varies by subscription tier

                **Important Notes**:
                - This endpoint requires `PROJECT_AGGREGATIONS` and `PROJECT_AUDIENCE_DATA_EVENTS` scopes which are manually granted by Instasent
                - No event entities are returned - limit is enforced to 0
                - Events are automatically filtered to the last 180 days in paid plans. 30 days in free plans.
                - This endpoint is designed for analytics and reporting use cases
                - Aggregations are the primary purpose of this endpoint

                For complete documentation on Event Filtering, see [Event QueryFilter documentation](docs/dev/project-audience-event-query-filter.es.md).
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            requestBody:
                $ref: '#/components/requestBodies/EventAggregationsRequest'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: object
                                        description: Object containing aggregation results
                                        additionalProperties: true
                                    metadata:
                                        type: object
                                        properties:
                                            totalHits:
                                                type: integer
                                                description: Total number of matching events
                                        required:
                                            - totalHits
                                        additionalProperties: true
                                required:
                                    - entities
                                    - metadata
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '403':
                    description: Missing PROJECT_AGGREGATIONS or PROJECT_AUDIENCE_DATA_EVENTS scope or subscription support
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ResponseErrorDetail'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: event-aggregations-endpoint
    '/project/{project}/event/volumes':
        post:
            x-see-docs:
                - audience-probes
            summary: Probe event volumes and parameter values
            description: |
                Which event types this project actually RECEIVES, how many of each inside the
                window, and when the last one arrived. A type that is declared and never received
                comes back as an explicit zero, which the event catalog cannot tell you, and that
                difference is what decides whether an event is worth building on.

                Pass `event` to probe a level deeper: the answer then also carries the real VALUES
                that type's parameters hold, such as product id and name, category, tags and vendor,
                the campaign id and name behind an attribution, its source and medium, statuses and
                methods.

                **Required Scope**: `PROJECT_AUDIENCE_READ`

                **Rate Limit**: [s] restrictive - varies by subscription tier

                **Important Notes**:
                - Read the window from `window.days` in the RESPONSE, not from what you asked for.
                  It defaults wider on small audiences (90 days under 10,000 contacts, 30 up to
                  20,000, 15 above) and the subscription clamps whatever results, reporting the
                  clamped value
                - A null `lastSeenAt` means nothing arrived in THIS window. It does NOT mean the
                  type was never seen, and nothing here can tell the two apart. Widen `window`
                  before concluding a type is unused
                - `effectiveWindowDays` is smaller than the window when that event type is pruned
                  sooner than the window reaches
                - `datasource` here means events this source PRODUCED. On `/audience/coverage` the
                  same word means membership: they are different questions on different indexes
                - When `event` is passed, `other` above zero means those are the most frequent
                  values and not all of them
                - Where a thing has both an id and a name, both are listed and they are not
                  interchangeable. Filter on the id (`product-id`, `utm-id`): it is stable and
                  unique. Read the name (`product-name`, `utm-campaign`): it is neither, and a
                  condition written against it breaks the moment the customer renames it
                - Those values are read from the event index, which keeps everything. A SEGMENT's
                  event condition reads a different store, the recent events kept on each contact
                  and capped per type, so a value listed here can match fewer people than it looks,
                  or none. Use it to pick a value, then `/audience/count` for any number
                - Events are never sampled at the type level: the aggregation is over about thirty
                  distinct values and is cheap regardless of document count. The PARAMETER values
                  returned when `event` is passed are a different matter and ARE sampled above
                  10,000 contacts, at 75% up to 20,000, then 50%, 20% and 15% as the audience
                  grows. Read `event.sampling` for the answer you got, not the top-level `sampling`,
                  which only describes the type counts
                - While parameter values are sampled, each carries `observedCount` (what was seen)
                  next to the scaled `count`, and `reliable: false` on a value too rare for the
                  sample to have measured it dependably. A value absent from a sampled list has not
                  been shown not to occur
                - A call carrying an AQF `root` is computed live and never cached. `refresh: true`
                  is throttled to one every five minutes and a denial returns the cached answer
                  rather than an error
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            requestBody:
                content:
                    application/json:
                        schema:
                            type: object
                            properties:
                                root:
                                    type: object
                                    description: Optional event query filter root. Narrows the events being probed.
                                datasource:
                                    type: string
                                    description: Connector name, uid or Mongo id. Keeps events this datasource produced. Unknown values are refused with the list of what exists.
                                window:
                                    type: integer
                                    description: Days to look back. Omit for the default, which varies by audience size. The subscription clamps the result.
                                event:
                                    type: string
                                    description: One event type uid. Adds the values that type's parameters actually carry.
                                refresh:
                                    type: boolean
                                    description: Force a recomputation, throttled to one every five minutes.
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    metadata:
                                        type: object
                                        properties:
                                            window:
                                                type: object
                                                description: 'The window ACTUALLY queried: days, requestedDays, band, clamped, clampedBy, from, to, and the anchor.'
                                            sampling:
                                                type: object
                                                description: 'Covers the per-type counts ONLY, and always says applied false: that aggregation is over about thirty distinct values and is cheap at any size. It says nothing about parameter values, which have their own block under `event`.'
                                            eventTypes:
                                                type: array
                                                description: One entry per declared type, received or not, with its count, lastSeenAt and effective window.
                                                items:
                                                    type: object
                                            event:
                                                type: object
                                                description: Present only when `event` was passed. Carries that type's parameters, the values they hold, and its OWN `sampling` block, which is the one that answers whether those values were measured on a sample.
                                            datasource:
                                                type: object
                                            cached:
                                                type: boolean
                                            computedAgo:
                                                type: string
                                            summary:
                                                type: string
                                        required:
                                            - window
                                            - eventTypes
                                required:
                                    - metadata
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '422':
                    $ref: '#/components/responses/ValidationError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
    '/project/{project}/event/scroll':
        post:
            x-see-docs:
                - audience-event-query-filter
            summary: Scroll audience events
            description: |
                Lists audience events using the powerful Event Filtering system.
                Supports cursor-based pagination for iterating through large result sets.

                **Required Scope**: `PROJECT_AUDIENCE_LIST` AND `PROJECT_AUDIENCE_DATA_EVENTS`
                (Extended API scopes required in subscription)

                **Rate Limit**: [s] restrictive - varies by subscription tier

                **Important Notes**:
                - Events are automatically filtered to the last 180 days in paid plans. 30 days in free plans.
                - The cursor is a base64 string representing the internal query state, and it expires after 1 minute of inactivity
                - Maximum limit is 100 events per request
                - Use the cursor from the previous response's metadata to get the next page
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            requestBody:
                $ref: '#/components/requestBodies/EventScrollRequest'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseEventItem'
                                        description: List of audience events
                                    metadata:
                                        type: object
                                        properties:
                                            totalHits:
                                                type: integer
                                                nullable: true
                                                description: Total number of matching events. Only returned on the first page (when no cursor is provided). Subsequent pages return null since the client already has the total from the first response.
                                            limit:
                                                type: integer
                                                description: Events per page
                                            cursor:
                                                type: string
                                                nullable: true
                                                description: Cursor for the next page. Pass this back in the request body to get the next page. null if no more results
                                        required:
                                            - totalHits
                                            - limit
                                            - cursor
                                required:
                                    - entities
                                    - metadata
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: uf75ylj41isrq
    '/project/{project}/event/scroll/{utmType}/{utmId}':
        post:
            x-see-docs:
                - audience-event-query-filter
            summary: Scroll audience events by campaign attribution
            description: |
                Lists audience events attributed to a specific campaign, automation, or direct message using the powerful Event Filtering system.
                Supports cursor-based pagination for iterating through large result sets.

                **Required Scope**: `PROJECT_AUDIENCE_LIST` AND `PROJECT_AUDIENCE_DATA_EVENTS`
                (Extended API scopes required in subscription)

                **Rate Limit**: [s] restrictive - varies by subscription tier

                **Important Notes**:
                - Events are automatically filtered to the last 180 days in paid plans. 30 days in free plans.
                - The cursor is a base64 string representing the internal query state, and it expires after 1 minute of inactivity
                - Maximum limit is 100 events per request
                - Use the cursor from the previous response's metadata to get the next page
                - For `utmType = 'direct'`, the `utmId` must be the channel type (email, sms...)
                - The endpoint automatically filters events by `utm-type` and `utm-id` matching the provided parameters
                - To filter by a specific event type, use the `/scroll/{utmType}/{utmId}/{eventType}` variant endpoint

                **UTM Attribution Parameters**:
                - `utmType`: The type of attribution (`campaign`, `automation`, or `direct`)
                - `utmId`: The ID of the campaign or automation. For `direct` type, it must be the channel type (`sms`, `email`, ...)

                **Use Cases**:
                - Retrieve all events attributed to a specific marketing campaign
                - Track events triggered by a specific automation
                - Analyze events from direct messaging campaigns

                For complete documentation on Event Filtering, see [Event QueryFilter documentation](docs/dev/project-audience-event-query-filter.es.md).
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: utmType
                  in: path
                  description: 'The type of attribution (campaign, automation, or direct)'
                  required: true
                  schema:
                      type: string
                      enum:
                          - campaign
                          - automation
                          - direct
                - name: utmId
                  in: path
                  description: 'The ID of the campaign or automation. For `direct` type, it must be the channel type (`sms`, `email`, ...)'
                  required: true
                  schema:
                      type: string
            requestBody:
                $ref: '#/components/requestBodies/EventScrollRequest'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseEventItem'
                                        description: List of audience events matching the attribution criteria
                                    metadata:
                                        type: object
                                        properties:
                                            totalHits:
                                                type: integer
                                                nullable: true
                                                description: Total number of matching events. Only returned on the first page (when no cursor is provided). Subsequent pages return null since the client already has the total from the first response.
                                            limit:
                                                type: integer
                                                description: Events per page
                                            cursor:
                                                type: string
                                                nullable: true
                                                description: Cursor for the next page. Pass this back in the request body to get the next page. null if no more results
                                        required:
                                            - totalHits
                                            - limit
                                            - cursor
                                required:
                                    - entities
                                    - metadata
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: event-scroll-by-attribution
    '/project/{project}/event/scroll/{utmType}/{utmId}/{eventType}':
        post:
            x-see-docs:
                - audience-event-query-filter
            summary: Scroll audience events by campaign attribution and event type
            description: |
                Lists audience events attributed to a specific campaign, automation, or direct message, filtered by a specific event type.
                This is a variant of the attribution scroll endpoint that additionally filters events by event type.

                **Required Scope**: `PROJECT_AUDIENCE_LIST` AND `PROJECT_AUDIENCE_DATA_EVENTS`
                (Extended API scopes required in subscription)

                **Rate Limit**: [s] restrictive - varies by subscription tier

                **Important Notes**:
                - Events are automatically filtered to the last 180 days in paid plans. 30 days in free plans.
                - The cursor is a base64 string representing the internal query state, and it expires after 1 minute of inactivity
                - Maximum limit is 100 events per request
                - Use the cursor from the previous response's metadata to get the next page
                - For `utmType = 'direct'`, the `utmId` must be the channel type (email, sms...)
                - The endpoint automatically filters events by `utm-type`, `utm-id`, and `event-type` matching the provided parameters

                **UTM Attribution Parameters**:
                - `utmType`: The type of attribution (`campaign`, `automation`, or `direct`)
                - `utmId`: The ID of the campaign or automation. For `direct` type, it must be the channel type (`sms`, `email`, ...)
                - `eventType`: A specific event type to filter by (e.g., `ecommerce_order_create`, `campaign_cta`, `campaign_send`)

                **Use Cases**:
                - Retrieve only `campaign_send` events for a specific campaign
                - Track only `campaign_cta` (click-through) events for an automation
                - Analyze specific event types from direct messaging campaigns

                For complete documentation on Event Filtering, see [Event QueryFilter documentation](docs/dev/project-audience-event-query-filter.es.md).
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: utmType
                  in: path
                  description: 'The type of attribution (campaign, automation, or direct)'
                  required: true
                  schema:
                      type: string
                      enum:
                          - campaign
                          - automation
                          - direct
                - name: utmId
                  in: path
                  description: 'The ID of the campaign or automation. For `direct` type, it must be the channel type (`sms`, `email`, ...)'
                  required: true
                  schema:
                      type: string
                - name: eventType
                  in: path
                  description: 'A specific event type to filter by (e.g., ecommerce_order_create, campaign_cta, campaign_send)'
                  required: true
                  schema:
                      type: string
            requestBody:
                $ref: '#/components/requestBodies/EventScrollRequest'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseEventItem'
                                        description: List of audience events matching the attribution criteria and event type
                                    metadata:
                                        type: object
                                        properties:
                                            totalHits:
                                                type: integer
                                                nullable: true
                                                description: Total number of matching events. Only returned on the first page (when no cursor is provided). Subsequent pages return null since the client already has the total from the first response.
                                            limit:
                                                type: integer
                                                description: Events per page
                                            cursor:
                                                type: string
                                                nullable: true
                                                description: Cursor for the next page. Pass this back in the request body to get the next page. null if no more results
                                        required:
                                            - totalHits
                                            - limit
                                            - cursor
                                required:
                                    - entities
                                    - metadata
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: event-scroll-by-attribution-event-type
    '/project/{project}/datasource/{datasource}/stream':
        get:
            summary: View stream
            description: |
                Retrieves the active stream for a datasource.

                **Required Scope**: `PROJECT_DATASOURCE_READ` (for reading datasource related data)

                **Datasource Parameter**: `{datasource}` can be the id of a dsapi datasource or just `dsapi`.
                It will be automatically resolved to the first found in the project.
            tags:
                - stream
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: datasource
                  in: path
                  description: Datasource UID or 'dsapi' to use the first dsapi datasource
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    stream:
                                        type: object
                                        description: Stream details
                                        additionalProperties: true
                                    datasource:
                                        $ref: '#/components/schemas/ResponseDatasourceItem'
                                    project:
                                        type: object
                                        description: Project summary
                                        additionalProperties: true
                                    organization:
                                        type: object
                                        description: Organization summary
                                        additionalProperties: true
                                required:
                                    - stream
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: dtofz12d96tbw
    '/project/{project}/datasource/{datasource}/stream/specs/{spec}':
        get:
            summary: View stream specs
            description: |
                Retrieves specifications for the stream (attributes, events, etc.).

                **Required Scope**: `PROJECT_DATASOURCE_READ`

                **Datasource Parameter**: `{datasource}` can be the id of a dsapi datasource or just `dsapi`.
                It will be automatically resolved to the first one found.

                **Spec Types**: `attributes`, `events`, `event-parameters`
            tags:
                - stream
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: datasource
                  in: path
                  description: Datasource UID or 'dsapi' to use the first dsapi datasource
                  required: true
                  schema:
                      type: string
                - name: spec
                  in: path
                  description: 'Specification type (attributes, events, event-parameters)'
                  required: true
                  schema:
                      type: string
                      enum:
                          - attributes
                          - events
                          - event-parameters
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    specs:
                                        type: object
                                        description: The requested specifications
                                        additionalProperties: true
                                required:
                                    - specs
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: mkvgigwq0j08v
    '/project/{project}/datasource/{datasource}/stream/specs/{spec}/{type}':
        get:
            summary: View stream specs with type filter
            description: |
                Retrieves specifications for the stream filtered by a specific type.

                **Required Scope**: `PROJECT_DATASOURCE_READ`

                **Datasource Parameter**: `{datasource}` can be the id of a dsapi datasource or just `dsapi`.
                It will be automatically resolved to the first found.

                **Spec Types**: `attributes`, `events`, `event-parameters`
            tags:
                - stream
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: datasource
                  in: path
                  description: Datasource UID or 'dsapi' to use the first dsapi datasource
                  required: true
                  schema:
                      type: string
                - name: spec
                  in: path
                  description: 'Specification type (attributes, events, event-parameters)'
                  required: true
                  schema:
                      type: string
                      enum:
                          - attributes
                          - events
                          - event-parameters
                - name: type
                  in: path
                  description: Optional type filter
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    specs:
                                        type: object
                                        description: The requested specifications filtered by type
                                        additionalProperties: true
                                required:
                                    - specs
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: o5bq08dp2jlwg
    '/project/{project}/datasource/{datasource}/stats':
        get:
            summary: View datasource stats
            description: |
                Retrieves statistics for a datasource stream.

                **Required Scope**: `PROJECT_DATASOURCE_READ`

                **Datasource Parameter**: `{datasource}` can be the id of a dsapi datasource or just `dsapi`.
                It will be automatically resolved to the first found.

                **Response Details**:
                - `contacts`: Total and errored contact count
            tags:
                - stream
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: datasource
                  in: path
                  description: Datasource UID or 'dsapi' to use the first dsapi datasource
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    contacts:
                                        type: object
                                        properties:
                                            total:
                                                type: integer
                                                description: Total contact count
                                            errored:
                                                type: integer
                                                description: Errored contact count
                                        additionalProperties: true
                                    datasource:
                                        $ref: '#/components/schemas/ResponseDatasourceItem'
                                required:
                                    - contacts
                                    - datasource
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: 53hcv8togdvv8
    '/project/{project}/datasource/{datasource}/stream/contacts/{userId}':
        get:
            summary: Get stream contact
            description: |
                Retrieves a specific contact from the datasource stream.

                **Required Scope**: `PROJECT_DATASOURCE_READ`

                **Datasource Parameter**: `{datasource}` can be the id of a dsapi datasource or just `dsapi`.
                It will be automatically resolved to the first found.
            tags:
                - stream
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: datasource
                  in: path
                  description: Datasource UID or 'dsapi' to use the first dsapi datasource
                  required: true
                  schema:
                      type: string
                - name: userId
                  in: path
                  description: User ID
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    contact:
                                        type: object
                                        description: The contact entity
                                        additionalProperties: true
                                required:
                                    - contact
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: rgd0dkrkw8bkm
    '/project/{project}/datasource/{datasource}/stream/{action}':
        post:
            summary: Push data to stream
            description: |
                Sends data to the datasource stream. This is the primary endpoint for ingesting contacts and events.

                **Required Scope**: `PROJECT_DATASOURCE_READ` AND `PROJECT_AUDIENCE_WRITE`
                (for writing contacts/events)

                **Datasource Parameter**: `{datasource}` can be the id of a dsapi datasource or just `dsapi`.
                It will be automatically resolved to the first found.

                **Actions**: `contacts`, `events`, etc.
                - `_sync`: If present, performs synchronous processing (recommended for testing)

                **Audience Override Feature** (for `contacts` action):
                When pushing contacts to the stream, you can update an existing audience contact's data by using
                the audience contact ID as the `_user_id` parameter. This feature allows you to bypass automatic
                merging and directly target a specific audience contact.

                **How it works**:
                - When you provide an audience contact ID (format: `[a-zA-Z0-9]{28}-[0-9]{3}`) as the `_user_id` value,
                  the system will automatically detect it, force merge to that specific audience contact, mark the
                  `_user_id` field as untrusted, and add the `instasent-api-override` tag.

                **Response**: Processing results with success/failure counts, errors, and accepted items.

                **Authentication**: You can use the DATASOURCE WEBHOOK TOKEN or the PROJECT TOKEN given it has:
                - `PROJECT_DATASOURCE_READ`: For reading datasource related data
                - `PROJECT_AUDIENCE_WRITE`: For writing contacts/Events

                **Use Cases**:
                - Update audience contact data directly without relying on automatic merging
                - Merge data from external systems to specific audience contacts
                - Override contact attributes for existing audience contacts

                **Important Notes**:
                - The audience contact ID must exist in the project
                - The contact must be new (not already exist in the datasource) for the override to take effect
                - The `_user_id` field will be marked as untrusted to prevent it from overriding other `_user_id` values during merging

                **Automation trigger time window (for `events` action)**:
                Events are always ingested and stored regardless of their `_event_date`, but only events whose date
                falls within a symmetric time window around the moment of reception will trigger automations. For
                dsapi datasources the window is **±1 hour** (i.e. `abs(_event_date - now) <= 1h`). Events outside
                this window are accepted and persisted in the contact timeline, but will **not** fire automations.
                This protects projects from accidentally triggering large batches of automations when backfilling
                historical data or when a producer sends future-dated timestamps. Omit `_event_date` (or set it
                close to the current time) if you need automations to fire.
            tags:
                - stream
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: datasource
                  in: path
                  description: Datasource UID or 'dsapi' to use the first dsapi datasource
                  required: true
                  schema:
                      type: string
                - name: action
                  in: path
                  description: 'The action to perform (contacts, events, etc.)'
                  required: true
                  schema:
                      type: string
                - name: _sync
                  in: query
                  description: 'If present, performs synchronous processing'
                  required: false
                  schema:
                      type: boolean
            requestBody:
                $ref: '#/components/requestBodies/StreamPushRequest'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    success:
                                        type: boolean
                                        description: Whether the operation was successful
                                    status:
                                        type: integer
                                        description: HTTP status code
                                    errors:
                                        type: array
                                        items:
                                            type: object
                                            properties:
                                                position:
                                                    type: integer
                                                error:
                                                    type: string
                                                item:
                                                    type: object
                                                    additionalProperties: true
                                            additionalProperties: true
                                        description: Array of error objects (if any)
                                    accepted:
                                        type: array
                                        items:
                                            type: object
                                            properties:
                                                position:
                                                    type: integer
                                                item:
                                                    type: object
                                                    additionalProperties: true
                                            additionalProperties: true
                                        description: Array of accepted items
                                required:
                                    - success
                                    - status
                                    - errors
                                    - accepted
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '413':
                    $ref: '#/components/responses/RequestTooLarge'
                '422':
                    $ref: '#/components/responses/ValidationError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: y0hbwo1fycurq
    '/project/{project}/datasource/{datasource}/stream/{action}/{userId}':
        delete:
            summary: Delete from stream
            description: |
                Deletes a user from the stream.

                **Required Scope**: `PROJECT_DATASOURCE_READ` AND `PROJECT_AUDIENCE_WRITE`

                **Datasource Parameter**: `{datasource}` can be the id of a dsapi datasource or just `dsapi`.
                It will be automatically resolved to the first found.

                **Actions**: `contacts`, `events`, etc.
                - `PROJECT_DATASOURCE_READ`: For reading datasource related data
                - `PROJECT_AUDIENCE_WRITE`: For writing contacts/Events
            tags:
                - stream
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: datasource
                  in: path
                  description: Datasource UID or 'dsapi' to use the first dsapi datasource
                  required: true
                  schema:
                      type: string
                - name: action
                  in: path
                  description: 'The action (contacts, events, etc.)'
                  required: true
                  schema:
                      type: string
                - name: userId
                  in: path
                  description: User ID to delete
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                additionalProperties: true
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: 4ebhtawh9nnq2
    '/project/{project}/audience/stream/{action}':
        post:
            summary: Audience Contact stream actions
            description: |
                Performs direct actions on the Audience Contact. Its sole purpose is to allow the API to manage
                subscription state and delete contacts. These operations cannot be done through an Instasent API Data Source since
                these fields are stored within a special Data Source `instasent` that is protected and isolated from other Data Sources.

                **Important**: These subscription attributes are also modified automatically by the platform through
                unsubscription links, inbound keyword replies (STOP/START), delivery blocks, and admin actions.
                Use this endpoint only if you manage subscription statuses externally and need direct control.
                Otherwise, the platform handles consent automatically based on the `compliancePolicy` of each message
                (`opt-in`, `opt-out`, `basic`, `none`) which determines how contacts
                can opt-out and what compliance gates are enforced.

                **Required Scope**: `PROJECT_AUDIENCE_WRITE`

                **Rate Limit**: [xl] permissive - varies by subscription tier

                **Actions**: Must be one of:
                - `subscription-manage`: Direct, atomic subscription management at the audience contact level.
                  Provides independent control over both marketing consent and suppression without implicit escalation.

                  Instasent uses a **two-tier subscription model** per channel:
                  - **Suppression** (`_is_subscribed_{channel}`): hard gate that blocks ALL messages except `none`.
                  - **Marketing consent** (`_accepts_marketing_{channel}`): controls whether marketing messages are allowed.

                  **Body parameters**:
                  - `audienceId` (required): The audience contact ID.
                  - `channel` (required): `sms` or `email`.
                  - `operation` (required): One of the following:

                    | Operation | Attribute changed | Value | Effect |
                    |-----------|-------------------|-------|--------|
                    | `suppress` | `_is_subscribed_{channel}` | `false` | Hard block all messages except none |
                    | `reactivate` | `_is_subscribed_{channel}` | `true` | Remove hard block |
                    | `opt-out` | `_accepts_marketing_{channel}` | `false` | Block marketing messages |
                    | `opt-in` | `_accepts_marketing_{channel}` | `true` | Allow marketing messages |

                  - `reason` (optional): Free-text reason for the operation.
                  - `createdAt` (optional): ISO 8601 datetime.

                  Each operation sets exactly one attribute — no implicit escalation or side effects.
                  Operations are always per-channel — they never affect other channels.

                - `delete-contact`: Delete a contact from the stream

                **Query Parameters**:
                - `_sync`: (optional) If present, performs synchronous processing

                **Request Body**: JSON object with action-specific fields:
                - For `subscription-manage`: `audienceId` (required), `channel` (required: sms/email),
                  `operation` (required: suppress/reactivate/opt-out/opt-in), `reason` (optional), `createdAt` (optional)
                - For `delete-contact`: `audienceId` (required)

                **Note**: This endpoint automatically resolves to the Instasent datasource for the project.
                The endpoint uses the same processing logic as the standard stream endpoint but provides a simplified interface for common operations.
                All actions require the `PROJECT_AUDIENCE_WRITE` scope (or admin privileges) and a product token.
            tags:
                - audience
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: action
                  in: path
                  description: The action to perform
                  required: true
                  schema:
                      type: string
                      enum:
                          - subscription-manage
                          - delete-contact
                - name: _sync
                  in: query
                  description: 'If present, performs synchronous processing'
                  required: false
                  schema:
                      type: boolean
            requestBody:
                $ref: '#/components/requestBodies/AudienceStreamActionRequest'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    success:
                                        type: boolean
                                        description: Boolean indicating if the operation was successful
                                    status:
                                        type: integer
                                        description: 'HTTP status code (202 for success, 422 for failures)'
                                    errors:
                                        type: array
                                        items:
                                            type: object
                                            properties:
                                                position:
                                                    type: integer
                                                    description: Index of the item that failed
                                                error:
                                                    type: string
                                                    description: Error message
                                                item:
                                                    type: object
                                                    description: The item data that failed
                                                    additionalProperties: true
                                            additionalProperties: true
                                        description: Array of error objects (if any)
                                    accepted:
                                        type: array
                                        items:
                                            type: object
                                            properties:
                                                position:
                                                    type: integer
                                                    description: Index of the accepted item
                                                item:
                                                    type: object
                                                    description: The accepted item data
                                                    additionalProperties: true
                                            additionalProperties: true
                                        description: Array of accepted items
                                required:
                                    - success
                                    - status
                                    - errors
                                    - accepted
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '422':
                    $ref: '#/components/responses/ValidationError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: iq8ixzbswbaf6
    '/project/{project}/segment':
        get:
            x-see-docs:
                - query-filter
            summary: List segments
            description: |
                Lists all segments for a project. Supports Query String Filtering for filtering, sorting, and pagination.

                **Required Scope**: `PROJECT_AUDIENCE_READ`

                **Query Parameters**: Supports Query String Filtering parameters:
                - Filtering: Use `field_operator=value` format (e.g., `name_eq=MySegment`, `type_eq=static`, `createdAt_gte=2024-01-01T00:00:00+00:00`)
                - Sorting: Use `_sort=field:direction` format (e.g., `_sort=name:asc`, `_sort=createdAt:desc`)
                - Pagination: Use `_start=0&_limit=50` format. **Important**: When using `_limit`, you must also provide `_start`
                - JSON QueryFilter: Use `_q` parameter with JSON structure

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - segment
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseSegmentItem'
                                    metadata:
                                        $ref: '#/components/schemas/ResponseMetadata'
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: 3m3evqyhnqrwt
        post:
            x-see-docs:
                - audience-query-filter
            summary: Create segment
            description: |
                Saves an audience filter as a named segment the project keeps, so it can be targeted by a campaign, reused in another filter and counted later.

                The `queryFilter` takes the same Audience Query Filter grammar the audience count and search endpoints take, and is validated the same way: a root group whose children are attribute conditions, event groups or segment memberships. Compose and verify it against the count endpoint first, so you know how many contacts it selects before you save it.

                Consent and channel reach never go inside the filter. They are decided when something is sent, under the project's policy for the channel, so a segment stores who the contacts ARE and not who may be messaged.

                An invalid filter is refused with `422` naming the offending node, and nothing is saved.

                **Required Scope**: `PROJECT_SEGMENT_WRITE`

                **Rate Limit**: [s] small - varies by subscription tier
            tags:
                - segment
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            properties:
                                name:
                                    type: string
                                    description: What the segment is called in the dashboard. Say who it selects; the detail goes in description.
                                    maxLength: 50
                                description:
                                    type: string
                                    description: What the filter selects, and the use case if there is one. Listings show this text and not the filter, so it is what a reader has to judge the segment by.
                                    maxLength: 256
                                queryFilter:
                                    type: object
                                    description: The membership filter, in Audience Query Filter form.
                            required:
                                - name
                                - queryFilter
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/ResponseSegmentItem'
                                required:
                                    - entity
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '422':
                    $ref: '#/components/responses/ValidationError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
    '/project/{project}/segment/dynamic':
        get:
            summary: List dynamic segments
            description: |
                Lists dynamic segments that don't require parameters.

                **Required Scope**: `PROJECT_AUDIENCE_READ`

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - segment
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseSegmentItem'
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: 88c1uxq8s7jt3
    '/project/{project}/segment/usage':
        get:
            summary: Segment usage map
            description: |
                Where each segment is used: campaigns that have targeted it and automations that
                enroll from it.

                **Only segments with at least one reference are returned.** A segment that
                appears in the segment list but not here is an orphan, and that absence is the
                point of this endpoint: it makes an audit possible in one request instead of one
                per segment. Combined with the cached size on each list row you can find the
                orphaned, the empty and the stale in two calls.

                Flows carry no segment reference and are deliberately not counted here.

                **Required Scope**: `PROJECT_AUDIENCE_READ`

                **Freshness**: aggregated per project and cached ten minutes; `computedAt` says
                when.

                **Rate Limit**: [m] medium - varies by subscription tier
            tags:
                - segment
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/SegmentUsage'
                                required:
                                    - entity
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
    '/project/{project}/segment/{uid}':
        get:
            summary: View segment
            description: |
                Retrieves details of a specific segment. Supports both static and dynamic segments.

                **Required Scope**: `PROJECT_AUDIENCE_READ`

                **Query Parameters**:
                - `parameter`: (optional) For dynamic segments, the parameter value (e.g., 'val1|val2')

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - segment
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: uid
                  in: path
                  description: Segment UID
                  required: true
                  schema:
                      type: string
                - name: parameter
                  in: query
                  description: 'For dynamic segments, the parameter value (e.g., ''val1|val2'')'
                  required: false
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/ResponseSegmentItem'
                                required:
                                    - entity
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: n5aazeny2cnif
    '/project/{project}/campaign':
        get:
            x-see-docs:
                - query-filter
            summary: List campaigns
            description: |
                Lists all campaigns for a project. Supports Query String Filtering for filtering, sorting, and pagination.

                **Required Scope**: `PROJECT_CAMPAIGN_READ`

                **Query Parameters**: Supports Query String Filtering parameters:
                - Filtering: Use `field_operator=value` format (e.g., `status_eq=sent`, `purpose_eq=standard`, `campaignAt_gte=2024-01-01T00:00:00+00:00`)
                - Sorting: Use `_sort=field:direction` format (e.g., `_sort=campaignAt:asc`, `_sort=createdAt:desc`). Defaults to `campaignAt:desc` if no sort is specified
                - Pagination: Use `_start=0&_limit=50` format. **Important**: When using `_limit`, you must also provide `_start`
                - JSON QueryFilter: Use `_q` parameter with JSON structure

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - campaign
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseCampaignItem'
                                    metadata:
                                        $ref: '#/components/schemas/ResponseMetadata'
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: risxqws304594
        post:
            summary: Create campaign draft
            description: |
                Creates a campaign as a **draft**. It never arms, schedules, sends, or spends balance — sending stays in the dashboard, where a human confirms it.

                Drafts are the only thing this endpoint produces and it cannot modify one: to change a campaign after creating it, open it in the dashboard editor.

                **Required Scope**: `PROJECT_CAMPAIGN_WRITE`

                **Audience** takes segment UIDs and inline audience filters, mixed freely. A UID targets a segment by identity and keeps its name in the dashboard; an inline filter is a *custom audience*, written in the same Audience Query Filter - AQF DSL the segment endpoints return. `include` defaults to `["_all"]` (everyone), and anyone matching an `exclude` entry is removed afterwards. Inline filters are parsed before the draft is stored, so a filter that does not compile is refused up front instead of failing later at the first audience count.

                **Sender** is optional. Pass a sender id belonging to this project to choose one explicitly; omit it and the project's default for the channel applies. If neither exists the draft is still created and `metadata.warnings` contains `no-default-sender` — the campaign cannot be sent until a sender is chosen in the dashboard. Whichever sender lands on the campaign is inherited by every declared language that does not name its own.

                **Scheduling is never implied.** `date` (and the optional `time`) place the campaign on the dashboard calendar and nothing more: this endpoint does not arm, schedule or send, at any precision. `date` accepts a day, a day and time, or a full ISO 8601 datetime with an offset — send the last one from an integration, it is the only form that means the same instant on both ends. Anything without an offset is read in the project's timezone; with no time at all the anchor sits at 19:00.

                **Message content** is one block of copy in one language. The RCS body accepted here is a text bubble plus optional `url`/`dialer` suggestions; the richer RCS forms (cards, carousels, media) are not surfaced on this endpoint yet.

                **RCS fallback**: add `fallback` to reach the contacts RCS cannot, forming an `rcs -> sms` chain. Its copy is required — it is never derived from the RCS body — and its sender resolves to the SMS sender configured on the RCS sender before the project default.

                **Declaring other languages**: list them in `translateTo`, and pass `message.language` alongside so the language you already wrote is not declared as one of the missing ones. Each entry becomes a declared but empty language on the draft — a recorded intention to translate, not a translation. This endpoint never writes copy you did not supply. A campaign with a declared language that still has no content cannot be estimated, quoted or prepared until someone fills it in the dashboard. Multi-language campaigns are a paid feature; if the subscription does not include it the request is rejected with `409` and no draft is created.

                **Errors** carry a stable code in `fields.errorCode` alongside the message: `unknown-channel`, `channel-not-available`, `unknown-segment`, `invalid-audience-filter`, `unknown-sender`, `fallback-not-supported`, `invalid-message`, `invalid-date`.

                **Rate Limit**: [m] medium - varies by subscription tier
            tags:
                - campaign
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            requestBody:
                $ref: '#/components/requestBodies/CampaignDraftBody'
            responses:
                '201':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/ResponseCampaignItem'
                                    metadata:
                                        type: object
                                        properties:
                                            warnings:
                                                type: array
                                                description: |
                                                    Non-fatal findings about the created draft. `no-default-sender`: the project has no default sender for the channel, so the draft has none and cannot be sent until one is picked in the dashboard. `no-fallback-sender`: the same, for the SMS leg of an RCS fallback.
                                                items:
                                                    type: string
                                                    enum:
                                                        - no-default-sender
                                                        - no-fallback-sender
                                required:
                                    - entity
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '409':
                    $ref: '#/components/responses/PlanFeatureError'
                '422':
                    $ref: '#/components/responses/ValidationError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: c4mp41gndr4ft1
    '/project/{project}/campaign/summary':
        get:
            summary: Campaign summary
            description: |
                A digest of the project's campaign activity: counts by phase and by status, the
                last campaign sent with its headline figures, the next one scheduled, and how
                many went out in the last 30 and 90 days.

                Answers "what has this project been doing" in one request, without paging through
                the campaign list. Built from aggregates and two point reads, so it stays cheap on
                projects with thousands of campaigns.

                **Required Scope**: `PROJECT_CAMPAIGN_READ`

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - campaign
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/CampaignSummary'
                                required:
                                    - entity
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
    '/project/{project}/campaign/{id}':
        get:
            summary: View campaign
            description: |
                Retrieves details of a specific campaign.

                **Required Scope**: `PROJECT_CAMPAIGN_READ`

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - campaign
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: id
                  in: path
                  description: Campaign ID
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/ResponseCampaignItem'
                                required:
                                    - entity
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: nfat2pokc846n
        delete:
            summary: Delete campaign
            description: |
                Deletes a campaign. The deletion is **hard and there is no undo**: nothing is archived and no copy is kept.

                **Required Scope**: `PROJECT_CAMPAIGN_WRITE`

                **Which campaigns can be deleted.** A draft or preview, a campaign already estimated or quoted, one already prepared, and one still scheduled while its send time is more than five minutes away. A campaign that is sending, sent, aborted or unpaid is refused with a `409`, and so is a scheduled one inside that five minute edition window. Cancelling is the alternative for a campaign that is already on its way.

                **A campaign mid-calculation cannot be deleted either.** While a forecast or a quote is running the campaign sits in `estimating` or `quoting`, and neither is deletable, so a delete issued right after starting an estimate answers `409`. Wait for the calculation to land, about a minute, and delete then.

                **It is not always immediate.** A campaign that was `prepared` or `scheduled` is moved to `deleted` first, and a worker removes its prepared messages before the campaign itself goes, so it keeps appearing in the listings for a short while. Anything else is gone by the time this returns. The response has no body: to tell the two cases apart, read the campaign again and look for the `deleted` status.

                **Rate Limit**: [s] restrictive - varies by subscription tier
            tags:
                - campaign
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: id
                  in: path
                  description: Campaign ID
                  required: true
                  schema:
                      type: string
            responses:
                '204':
                    description: Deleted, or scheduled for deletion. No content.
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '409':
                    $ref: '#/components/responses/StateConflictError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: c4mp41gnd3l3t3
    '/project/{project}/campaign/{id}/estimate':
        patch:
            summary: Estimate campaign
            description: |
                Starts the estimation of a campaign: how many contacts it would reach, how many message parts that becomes, and what it would cost.

                **Required Scope**: `PROJECT_CAMPAIGN_WRITE`

                **This is asynchronous.** The call returns `202` immediately with the campaign in `estimating`, and a worker fills in the figures about a minute later. Read the campaign again to collect the result: `contactsCount`, `ignoredContactsCount`, `preparedUnits` and the price range. While it is still running the status stays `estimating`.

                **Estimating a prepared campaign changes it.** The states accepted are the same ones the dashboard accepts: `draft`, `preview`, `estimated`, `quoted` and `prepared`. On a `prepared` campaign, estimating discards the prepared messages and returns it to the estimation pipeline, which is a real change and not a read. A `scheduled` campaign is not estimable at all and answers `409`; unschedule it first from the dashboard.

                **A `409` is not only about state.** The campaign also has to be complete: every declared language needs its copy and a sender, so a draft missing either, or carrying a language declared and left empty, is refused the same way. On RCS there is a second gate that is easy to miss: when the campaign declares an SMS fallback and does not skip it, that fallback leg has to be complete too, so an RCS message that looks finished still refuses while its SMS leg has no sender. The dashboard editor is where those gaps get filled, and the `no-default-sender` and `no-fallback-sender` warnings returned when the draft was created are exactly the two predictors of this error.

                **Accuracy.** The estimate walks a sample of the audience and extrapolates, deliberately erring on the high side, so the real cost is never above it. `precision` sets the sample size and is optional; the default is derived from the audience size.

                **Rate Limit**: [s] restrictive - varies by subscription tier
            tags:
                - campaign
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: id
                  in: path
                  description: Campaign ID
                  required: true
                  schema:
                      type: string
            requestBody:
                required: false
                content:
                    application/json:
                        schema:
                            type: object
                            properties:
                                precision:
                                    type: integer
                                    description: How many contacts the estimate samples. Omit it to let the audience size decide.
                                    minimum: 100
                                    maximum: 20000
            responses:
                '202':
                    description: Estimation started
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/ResponseCampaignItem'
                                required:
                                    - entity
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '409':
                    $ref: '#/components/responses/StateConflictError'
                '422':
                    $ref: '#/components/responses/ValidationError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: c4mp41gn3st1m8
    '/project/{project}/automation':
        get:
            x-see-docs:
                - query-filter
            summary: List automations
            description: |
                Lists all automations for a project. Supports Query String Filtering for filtering, sorting, and pagination.

                **Required Scope**: `PROJECT_AUTOMATION_READ`

                **Query Parameters**: Supports Query String Filtering parameters:
                - Filtering: Use `field_operator=value` format (e.g., `status_eq=active`, `purpose_eq=automation`, `createdAt_gte=2024-01-01T00:00:00+00:00`)
                - Sorting: Use `_sort=field:direction` format (e.g., `_sort=createdAt:asc`, `_sort=updatedAt:desc`). Defaults to `createdAt:desc` if no sort is specified
                - Pagination: Use `_start=0&_limit=50` format. **Important**: When using `_limit`, you must also provide `_start`
                - JSON QueryFilter: Use `_q` parameter with JSON structure

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - automation
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseAutomationItem'
                                    metadata:
                                        $ref: '#/components/schemas/ResponseMetadata'
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: acnm1byjrfsig
    '/project/{project}/automation/{id}':
        get:
            summary: View automation
            description: |
                Retrieves details of a specific automation.

                **Required Scope**: `PROJECT_AUTOMATION_READ`

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - automation
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: id
                  in: path
                  description: Automation ID
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/ResponseAutomationItem'
                                required:
                                    - entity
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: zolmcc1cgpf6x
    '/project/{project}/flow':
        get:
            x-see-docs:
                - query-filter
            summary: List flows
            description: |
                Lists all flows for a project — the discovery surface for the flow
                analytics endpoints: the returned `id` is the value
                `/project/{project}/analytics/flow/{flow}/series` takes. Supports Query
                String Filtering for filtering, sorting, and pagination (e.g.
                `status_eq=enabled`). Defaults to `createdAt:desc` if no sort is
                specified.

                **Required Scope**: `PROJECT_AUTOMATION_READ`

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - flow
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseFlowItem'
                                    metadata:
                                        $ref: '#/components/schemas/ResponseMetadata'
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: xflowindex001
    '/project/{project}/flow/{id}':
        get:
            summary: View flow
            description: |
                Retrieves details of a specific flow.

                **Required Scope**: `PROJECT_AUTOMATION_READ`

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - flow
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: id
                  in: path
                  description: Flow ID
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/ResponseFlowItem'
                                required:
                                    - entity
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: xflowview0001
    '/project/{project}/flow/{id}/versions':
        get:
            x-see-docs:
                - query-filter
            summary: List flow versions
            description: |
                Lists a flow's versions (draft / test / live-test / live, archived
                included), newest first — where an analytics consumer finds the
                version ids `filter[version]` takes. A version's `status` role is
                permanent (promoting clones to a new id), so it also tells which
                cohort (live vs A/B canary) its traffic belongs to. Supports Query
                String Filtering for filtering, sorting, and pagination.

                **Required Scope**: `PROJECT_AUTOMATION_READ`

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - flow
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: id
                  in: path
                  description: Flow ID
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseFlowVersionItem'
                                    metadata:
                                        $ref: '#/components/schemas/ResponseMetadata'
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: xflowversions1
    '/project/{project}/channel/sms/sender':
        get:
            x-see-docs:
                - query-filter
            summary: List SMS senders
            description: |
                Lists all SMS senders for a project. Supports Query String Filtering for filtering, sorting, and pagination.

                **Required Scope**: `PROJECT_READ`

                **Query Parameters**: Supports Query String Filtering parameters:
                - Filtering: Use `field_operator=value` format (e.g., `status_eq=active`, `from_eq=MySender`, `createdAt_gte=2024-01-01T00:00:00+00:00`)
                - Sorting: Use `_sort=field:direction` format (e.g., `_sort=from:asc`, `_sort=createdAt:desc`)
                - Pagination: Use `_start=0&_limit=50` format. **Important**: When using `_limit`, you must also provide `_start`
                - JSON QueryFilter: Use `_q` parameter with JSON structure
            tags:
                - sms-sender
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseSenderItem'
                                    metadata:
                                        $ref: '#/components/schemas/ResponseMetadata'
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: pfqk8wbz18wt7
    '/project/{project}/channel/rcs/sender':
        get:
            x-see-docs:
                - query-filter
            summary: List RCS senders
            description: |
                Lists all RCS senders (agents) for a project, each with its
                per-country `registers` and the resolved `acceptedCountries` (the
                countries the agent can deliver to). Supports Query String Filtering
                for filtering, sorting, and pagination.

                **Required Scope**: `PROJECT_READ`

                **Query Parameters**: Supports Query String Filtering parameters:
                - Filtering: Use `field_operator=value` format (e.g., `status_eq=active`, `createdAt_gte=2024-01-01T00:00:00+00:00`)
                - Sorting: Use `_sort=field:direction` format (e.g., `_sort=createdAt:desc`)
                - Pagination: Use `_start=0&_limit=50` format. **Important**: When using `_limit`, you must also provide `_start`
                - JSON QueryFilter: Use `_q` parameter with JSON structure
            tags:
                - rcs-sender
            parameters:
                - $ref: '#/components/parameters/ProjectPath'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseRcsSenderItem'
                                    metadata:
                                        $ref: '#/components/schemas/ResponseMetadata'
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
    '/project/{project}/channel/sms/sms/{id}':
        get:
            summary: Get SMS by ID
            description: |
                Retrieves a specific SMS message by its ID.

                **Required Scope**: `PROJECT_DIRECT_READ`

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - sms
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: id
                  in: path
                  description: SMS message ID
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/ResponseSmsItem'
                                    metadata:
                                        type: object
                                        properties:
                                            audienceId:
                                                type: string
                                                description: The audience contact ID associated with the SMS
                                        additionalProperties: true
                                required:
                                    - entity
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: p7pafu3ft5d51
    '/project/{project}/channel/sms/sms/audience/{audienceId}':
        get:
            summary: List SMS messages by audience contact
            description: |
                Lists SMS messages for a specific audience contact. Supports using a phone number as audienceID.
                In that case, the audience contact will be searched.

                **Required Scope**: `PROJECT_DIRECT_READ`

                **Query Parameters**: Supports Query String Filtering parameters.

                **Rate Limit**: [l] large - varies by subscription tier

                **Note**: Results are automatically restricted to the last 12 months in paid plans (1 month in free plan).

                Supports using a phone number as audienceID. In that case, the audience contact will be searched.
            tags:
                - sms
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: audienceId
                  in: path
                  description: Audience contact ID or phone number (will be normalized)
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseSmsItem'
                                    metadata:
                                        type: object
                                        properties:
                                            audienceId:
                                                type: string
                                                description: The audience contact ID
                                        additionalProperties: true
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: dyywbm1jyb7v5
    '/project/{project}/channel/sms/sms/send/{send}':
        get:
            summary: List SMS messages by send
            description: |
                Lists SMS messages belonging to a specific Send entity.

                **Required Scope**: `PROJECT_DIRECT_READ`

                **Query Parameters**: Supports Query String Filtering parameters.

                **Rate Limit**: [l] large - varies by subscription tier

                **Note**: For automations or direct messages, results are automatically restricted to the last 3 months in paid plans (7 days in free plan). Draft SMS messages are excluded from results.
            tags:
                - sms
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: send
                  in: path
                  description: Send entity ID
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseSmsItem'
                                    metadata:
                                        type: object
                                        properties:
                                            send:
                                                type: object
                                                description: The Send entity
                                                additionalProperties: true
                                        additionalProperties: true
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: wdgdl8g4y8bgk
    '/project/{project}/channel/sms/sms/campaign/{campaign}':
        get:
            summary: List SMS messages by campaign
            description: |
                Lists SMS messages sent as part of a campaign. Supports filtering by campaign option (for A/B testing campaigns).
                This endpoint defaults to optionIdx=0 (first option).

                **Required Scope**: `PROJECT_DIRECT_READ`

                **Query Parameters**: Supports Query String Filtering parameters.

                **Rate Limit**: [l] large - varies by subscription tier

                **Important Notes**:
                - Draft SMS messages are excluded from results
                - Options are deprecated (legacy A/B testing feature)

                **URL Variants**: This endpoint also supports `/project/{project}/channel/sms/sms/campaign/{campaign}` (without optionIdx), which defaults to optionIdx=0.
            tags:
                - sms
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: campaign
                  in: path
                  description: Campaign ID
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseSmsItem'
                                    metadata:
                                        type: object
                                        properties:
                                            send:
                                                type: object
                                                description: The Send entity for the campaign option
                                                additionalProperties: true
                                            campaign:
                                                type: object
                                                description: The campaign entity
                                                additionalProperties: true
                                        additionalProperties: true
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: gg5ds1up2kzqt
    '/project/{project}/channel/sms/sms/campaign/{campaign}/{optionIdx}':
        get:
            summary: List SMS messages by campaign option
            description: |
                Lists SMS messages sent as part of a campaign for a specific campaign option (for A/B testing campaigns which are now deprecated).

                **Required Scope**: `PROJECT_DIRECT_READ`

                **Query Parameters**: Supports Query String Filtering parameters.

                **Rate Limit**: [l] large - varies by subscription tier

                **Important Notes**:
                - Draft SMS messages are excluded from results
            tags:
                - sms
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: campaign
                  in: path
                  description: Campaign ID
                  required: true
                  schema:
                      type: string
                - name: optionIdx
                  in: path
                  description: Campaign option index (0-indexed). Defaults to 0 (first option)
                  required: true
                  schema:
                      type: integer
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseSmsItem'
                                    metadata:
                                        type: object
                                        properties:
                                            send:
                                                type: object
                                                description: The Send entity for the campaign option
                                                additionalProperties: true
                                            campaign:
                                                type: object
                                                description: The campaign entity
                                                additionalProperties: true
                                        additionalProperties: true
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: 8vnzncxrocgb1
    '/project/{project}/channel/sms/sms/automation/{automation}':
        get:
            summary: List SMS messages by automation
            description: |
                Lists SMS messages sent as part of an automation. Each message in an automation has its own Send entity.
                This endpoint defaults to messageIdx=0 (first message).

                **Required Scope**: `PROJECT_DIRECT_READ`

                **Query Parameters**: Supports Query String Filtering parameters.

                **Rate Limit**: [l] large - varies by subscription tier

                **Important Notes**:
                - Each message in an automation has its own Send entity
                - Each message has only 1 option (no optionIdx needed per message)
                - Results are automatically restricted to the last 3 months in paid plans. 7 days in free plan.
                - Draft SMS messages are excluded from results

                **URL Variants**: This endpoint also supports `/project/{project}/channel/sms/sms/automation/{automation}` (without messageIdx), which defaults to messageIdx=0.
            tags:
                - sms
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: automation
                  in: path
                  description: Automation ID
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseSmsItem'
                                    metadata:
                                        type: object
                                        properties:
                                            send:
                                                type: object
                                                description: The Send entity for the automation message
                                                additionalProperties: true
                                            automation:
                                                type: object
                                                description: The automation entity
                                                additionalProperties: true
                                        additionalProperties: true
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: nu02qqvcjvvqo
    '/project/{project}/channel/sms/sms/automation/{automation}/{messageIdx}':
        get:
            summary: List SMS messages by automation message
            description: |
                Lists SMS messages sent as part of an automation for a specific message. Each message in an automation
                has its own Send entity.

                **Required Scope**: `PROJECT_DIRECT_READ`

                **Query Parameters**: Supports Query String Filtering parameters.

                **Rate Limit**: [l] large - varies by subscription tier

                **Important Notes**:
                - Each message in an automation has its own Send entity
                - Each message has only 1 option (no optionIdx needed per message)
                - Results are automatically restricted to the last 3 months in paid plans. 7 days in free plan.
                - Draft SMS messages are excluded from results
            tags:
                - sms
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: automation
                  in: path
                  description: Automation ID
                  required: true
                  schema:
                      type: string
                - name: messageIdx
                  in: path
                  description: Automation message index (0-indexed). Defaults to 0 (first message)
                  required: true
                  schema:
                      type: integer
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseSmsItem'
                                    metadata:
                                        type: object
                                        properties:
                                            send:
                                                type: object
                                                description: The Send entity for the automation message
                                                additionalProperties: true
                                            automation:
                                                type: object
                                                description: The automation entity
                                                additionalProperties: true
                                            messageIdx:
                                                type: integer
                                                description: The message index used
                                        additionalProperties: true
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: wzs9ow2f4y6p8
    '/project/{project}/channel/sms/sms/direct':
        get:
            summary: List direct SMS messages
            description: |
                Lists SMS messages sent as direct messages (not part of campaigns or automations).

                **Required Scope**: `PROJECT_DIRECT_READ`

                **Query Parameters**: Supports Query String Filtering parameters.

                **Rate Limit**: [l] large - varies by subscription tier

                **Note**: Results are automatically restricted to the last 3 months in paid plans. 7 days in free plan.

                **Restrict to custom Tracking Term / Utm Term**: You can add an optional utm term to the path to only retrieve messages sent with the tracking term provided. Use the endpoint `/project/{project}/channel/sms/sms/direct/{utmTerm}`
            tags:
                - sms
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entities:
                                        type: array
                                        items:
                                            $ref: '#/components/schemas/ResponseSmsItem'
                                    metadata:
                                        type: object
                                        properties:
                                            send:
                                                type: object
                                                description: The Send entity for direct messages
                                                additionalProperties: true
                                        additionalProperties: true
                                required:
                                    - entities
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: cjlj58sgxaio4
    '/project/{project}/channel/sms/sms/direct/{senderId}/{audienceId}':
        post:
            summary: Send SMS message (Direct message)
            description: |
                Creates and sends a direct SMS message immediatelly to an audience contact. The audience contact will be automatically
                created from the phone number if it doesn't exist and auto-creation is enabled for the project.

                **Required Scope**: `PROJECT_DIRECT_WRITE`

                **Rate Limit**: [m] moderate - varies by subscription tier

                **Sender ID**: The sender ID or `"default"` to use the project's default sender. If empty string is provided,
                it will be treated as `"default"`. The default sender ID is cached for 1 minute to reduce database queries.

                **Audience Contact Resolution**:
                - If `audienceId` is an audience contact ID format, it will be retrieved directly by ID
                - If `audienceId` is a phone number:
                  - The phone number will be normalized and validated as a mobile phone number
                  - If valid, the system will search for an existing contact with that phone number
                  - If not found and auto-creation is enabled, a new contact will be created with the normalized phone number
                    and tags: `auto-created`, `auto-created-outbound`
                  - If the phone number is not a valid mobile number, the request will fail with a descriptive error

                **Auto-creation**: Requires the project to have `auto creation` enabled for outbound messages. It will fail if it's not enabled and the phone does not exist.

                **Tracking Term / Utm Term** (Optional): You can set an additional tracking term (up to 40 chars). It will be added into all events related with this message: Delivery, Clicks, Unsubscribes, Sales... That way, you could easily differentiate the events origin / source. Imagine you send repurchase reminders, you can provide `repurchase` as utm-term to tag all events. Use the endpoint `/project/{project}/channel/sms/sms/direct/{senderId}/{audienceId}/{utmTerm}` to set a custom tracking term.

                **Error Handling**:
                - `404 Not Found`: Audience contact not found and auto-creation is disabled or phone number is invalid
                - `400 Bad Request`: Validation errors (missing phone number, invalid sender, etc.)
                - `402 Payment Required`: Organization has insufficient funds
                - `403 Forbidden`: Project does not allow direct messages or organization service is disabled

                **Note**: This endpoint is rate-limited to prevent abuse. It's intended for individual message sending, not bulk campaigns.

                **Request Body Fields**:
                - `text`: (required) The SMS message text. Supports template variables like `{{short:url}}` for link shortening and `{{unsubscribe}}` for unsubscribe links
                - `allowUnicode`: (optional) Whether to allow Unicode characters (e.g., emojis). Defaults to false
                - `compliancePolicy`: (optional) Consent policy enforced for this message. Defaults to `basic`, which skips only contacts suppressed on the channel. `none` bypasses suppression too and reaches a contact who replied STOP

                **Response Details**:
                - `entity`: The SMS message entity. Important properties include:
                  - `id`: SMS message ID
                  - `status`: Message status (e.g., `enqueued`, `sent`, `delivered`)
                  - `from`: Sender ID or phone number
                  - `to`: Recipient phone number
                  - `normalizedTo`: Normalized recipient phone number
                  - `text`: Original message text (with template variables)
                  - `deliveredText`: Final delivered text (with template variables resolved)
                  - `charsCount`: Character count of the message
                  - `messagesCount`: Number of SMS parts (1 for single SMS, 2+ for concatenated)
                  - `encoding`: Message encoding (e.g., `GSM_7BIT`, `UCS2`)
                  - `unicode`: Whether the message contains Unicode characters
                  - `charged`: Whether the message was charged
                  - `pricePerSms`: Price per SMS part
                  - `priceUser`: Total price charged to user
                  - `sentAt`: When the message was sent (null if not yet sent)
                  - `deliveredAt`: When the message was delivered (null if not yet delivered)
                  - `audienceContact`: Audience contact information
                  - `metadata`: Additional metadata about the message (organization, token, send entity)
                - `metadata`: Contains:
                  - `audienceId`: The audience contact ID associated with the SMS
                  - `autoCreated`: Boolean indicating if the audience contact was auto-created during this request
            tags:
                - sms
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: senderId
                  in: path
                  description: Sender ID or 'default' to use the project's default sender. Empty string is treated as 'default'
                  required: true
                  schema:
                      type: string
                - name: audienceId
                  in: path
                  description: Audience contact ID or phone number (will be normalized and searched)
                  required: true
                  schema:
                      type: string
            requestBody:
                $ref: '#/components/requestBodies/DirectSmsRequest'
            responses:
                '201':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/ResponseSmsItem'
                                    metadata:
                                        type: object
                                        properties:
                                            audienceId:
                                                type: string
                                                description: The audience contact ID associated with the SMS
                                            autoCreated:
                                                type: boolean
                                                description: Boolean indicating if the audience contact was auto-created during this request
                                        required:
                                            - audienceId
                                            - autoCreated
                                required:
                                    - entity
                                    - metadata
                            examples:
                                standard:
                                    summary: Standard SMS response
                                    value:
                                        entity:
                                            id: '6954234341ef67caa32a14c2'
                                            inbound: false
                                            clientId: null
                                            status: 'enqueued'
                                            statusCode: null
                                            from: 'info'
                                            country: 'ES'
                                            to: '+34670215553'
                                            normalizedTo: '+34670215553'
                                            charsCount: 77
                                            text: 'This is a test message: {{short:http://google.com}} {{unsubscribe}}'
                                            deliveredText: 'This is a test message: 0003 https://inst.dev/6xQ6nMq STOP nosms.dev/2qrgdG2'
                                            messagesCount: 1
                                            encoding: 'GSM_7BIT'
                                            unicode: false
                                            allowUnicode: false
                                            charged: true
                                            pricePerSms: 0.04
                                            priceUser: 0.04
                                            scheduledAt: null
                                            sentAt: null
                                            deliveredAt: null
                                            audienceContact:
                                                id: 'uQTuHNBKLdwTxzGldW5pocUNqzyz-066'
                                                project: '67bdfa983114d0062d73262f'
                                            relatedSms: null
                                            metadata:
                                                organizationName: 'Instasent Frontend'
                                                organizationId: '60141bb26dccbf21a04a01d2'
                                                tokenName: 'frontend product token Token Token Token'
                                                tokenType: 'product'
                                                tokenId: '67bdfa983114d0062d734497'
                                                sendName: 'Direct SMS'
                                                sendId: '67bdfa9e3114d0062d7344b3'
                                        metadata:
                                            audienceId: 'uQTuHNBKLdwTxzGldW5pocUNqzyz-066'
                                            autoCreated: false
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '402':
                    $ref: '#/components/responses/NoFundsError'
                '403':
                    description: Project does not allow direct messages or organization service is disabled
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ResponseErrorDetail'
                '404':
                    $ref: '#/components/responses/NotFound'
                '422':
                    $ref: '#/components/responses/ValidationError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: 71qsh4btgyhwx
    '/project/{project}/hybrid-auth':
        get:
            summary: Hybrid auth view
            description: |
                Handles hybrid authentication for external services (e.g., Zapier). It is used just to verify authentication.

                **Query Parameters**:
                - `datasourceSecretToken`: Token for datasource access
                - `projectSecretToken`: Token for project access
                - `datasourceId`: ID of the datasource (required if `datasourceSecretToken` is used)

                **Response Details**:
                - `entity`: Project or Datasource entity
                - `metadata`: Connection name, privileges, and scopes

                **Use Case**: Handles hybrid authentication for external services (e.g., Zapier). It is used just to verify authentication.
            tags:
                - third-party
            parameters:
                - name: project
                  in: path
                  description: 'Project UID (e.g., ''my-project-123'')'
                  required: true
                  schema:
                      type: string
                - name: datasourceSecretToken
                  in: query
                  description: Token for datasource access
                  required: false
                  schema:
                      type: string
                - name: projectSecretToken
                  in: query
                  description: Token for project access
                  required: false
                  schema:
                      type: string
                - name: datasourceId
                  in: query
                  description: ID of the datasource (required if datasourceSecretToken is used)
                  required: false
                  schema:
                      type: string
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        type: object
                                        description: Project or Datasource entity
                                        additionalProperties: true
                                    metadata:
                                        type: object
                                        properties:
                                            connection:
                                                type: string
                                                description: Connection name
                                            privileges:
                                                type: array
                                                items:
                                                    type: string
                                                description: Privileges
                                            scopes:
                                                type: array
                                                items:
                                                    type: string
                                                description: Scopes
                                        additionalProperties: true
                                required:
                                    - entity
                                    - metadata
                '400':
                    $ref: '#/components/responses/WrongRequestError'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
            x-stoplight:
                id: fy10felnfwbil
    '/project/{project}/analytics/overview':
        get:
            summary: Account overview (values)
            description: |
                Aggregate account performance over a timeframe — the dashboard
                "Performance summary", "Revenue" and "Channels" cards as a single
                bounded report. Computed on demand by the analytics engine
                (terminal-state delivery, engagement, conversion, cost, suppression)
                across the traffic Instasent sent for you, cached for 1 hour.

                Reads terminal-state delivery only — it never reports `sent` or
                in-transit traffic (the most recent ~48 h under-report until delivery
                receipts land). Also supports `compare_to=previous_period`.

                **Supported on this report:** `group_by` = `channel`, `country`,
                `language`, `gender`, `segment`, `communication_type`; `filter` =
                `country` (scopes the whole report, or a `group_by`, to one country).

                **Required Scope**: the subset of `PROJECT_CAMPAIGN_READ` /
                `PROJECT_AUTOMATION_READ` / `PROJECT_DIRECT_READ` the token holds
                (the report covers the families it can read).

                **Default timeframe**: `last_3_months`.

                **Rate Limit**: [m] moderate - varies by subscription tier
            tags:
                - analytics
            parameters:
                - $ref: '#/components/parameters/ProjectPath'
                - $ref: '#/components/parameters/AnalyticsStatisticsParam'
                - $ref: '#/components/parameters/AnalyticsTimeframeParam'
                - $ref: '#/components/parameters/AnalyticsStartParam'
                - $ref: '#/components/parameters/AnalyticsEndParam'
                - $ref: '#/components/parameters/AnalyticsTimezoneParam'
                - $ref: '#/components/parameters/AnalyticsConversionScopeParam'
                - $ref: '#/components/parameters/AnalyticsCurrencyParam'
                - $ref: '#/components/parameters/AnalyticsCompareToParam'
                - $ref: '#/components/parameters/AnalyticsFilterParam'
                - $ref: '#/components/parameters/AnalyticsGroupByParam'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AnalyticsValuesResponse'
                            examples:
                                flat:
                                    summary: Account totals over the last 3 months
                                    value:
                                        resource:
                                            type: overview
                                        timeframe:
                                            key: last_3_months
                                            start: '2026-03-01T00:00:00+01:00'
                                            end: '2026-05-29T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        conversion_scope: sales
                                        currency: EUR
                                        source_currency: EUR
                                        statistics:
                                            delivered: 48210
                                            failed: 1203
                                            delivery_rate: 0.9757
                                            clicks_first: 5120
                                            click_rate: 0.1062
                                            conversions: 612
                                            conversion_value: 38420.55
                                            cost_approx: 2410.80
                                            roas_approx: 15.94
                                            unsubscribes: 88
                                            unsubscribe_rate: 0.0018
                                        freshness:
                                            computed_at: '2026-05-29T10:00:00Z'
                                            source: cache
                                            stale: false
                                            ttl_seconds_remaining: 1842
                                        warnings: []
                                grouped_by_channel:
                                    summary: Same report split by channel (group_by=channel)
                                    value:
                                        resource:
                                            type: overview
                                        timeframe:
                                            key: last_3_months
                                            start: '2026-03-01T00:00:00+01:00'
                                            end: '2026-05-29T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        currency: EUR
                                        source_currency: EUR
                                        statistics:
                                            sms:
                                                delivered: 31200
                                                conversion_value: 21010.00
                                            rcs:
                                                delivered: 17010
                                                conversion_value: 17410.55
                                        totals_per_group:
                                            delivered: 48210
                                            conversion_value: 38420.55
                                        group_order:
                                            - sms
                                            - rcs
                                        group_by:
                                            - channel
                                        freshness:
                                            computed_at: '2026-05-29T10:00:00Z'
                                            source: cache
                                            stale: false
                                            ttl_seconds_remaining: 1842
                                        warnings: []
                                grouped_by_country:
                                    summary: Same report split by country (group_by=country)
                                    value:
                                        resource:
                                            type: overview
                                        timeframe:
                                            key: last_3_months
                                            start: '2026-03-01T00:00:00+01:00'
                                            end: '2026-05-29T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        currency: EUR
                                        source_currency: EUR
                                        statistics:
                                            ES:
                                                delivered: 30120
                                                conversion_value: 24800.00
                                            FR:
                                                delivered: 16880
                                                conversion_value: 13110.55
                                            unknown:
                                                delivered: 1210
                                                conversion_value: 510.00
                                        totals_per_group:
                                            delivered: 48210
                                            conversion_value: 38420.55
                                        group_order:
                                            - ES
                                            - FR
                                            - unknown
                                        group_by:
                                            - country
                                        freshness:
                                            computed_at: '2026-05-29T10:00:00Z'
                                            source: cache
                                            stale: false
                                            ttl_seconds_remaining: 1842
                                        warnings: []
                '400':
                    $ref: '#/components/responses/AnalyticsBadRequest'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '403':
                    $ref: '#/components/responses/AnalyticsForbidden'
                '429':
                    $ref: '#/components/responses/AnalyticsRateLimit'
                '500':
                    $ref: '#/components/responses/AnalyticsServerError'
            x-stoplight:
                id: xanalyticsov01
    '/project/{project}/analytics/overview/series':
        get:
            summary: Account overview (series)
            description: |
                Time-bucketed account performance. Returns a continuous, zero-filled
                `date_times` axis with each statistic as an index-aligned array.
                Adds `interval` (the response becomes the grouped shape under
                `group_by`).

                **Supported on this report:** `group_by` = `channel`, `country`,
                `language`, `gender`, `segment`, `communication_type`; `filter` =
                `country`.

                **Required Scope**: as the overview values report.

                **Default timeframe**: `last_3_months`. **Default interval**: `day`.

                **Rate Limit**: [m] moderate - varies by subscription tier
            tags:
                - analytics
            parameters:
                - $ref: '#/components/parameters/ProjectPath'
                - $ref: '#/components/parameters/AnalyticsStatisticsParam'
                - $ref: '#/components/parameters/AnalyticsTimeframeParam'
                - $ref: '#/components/parameters/AnalyticsStartParam'
                - $ref: '#/components/parameters/AnalyticsEndParam'
                - $ref: '#/components/parameters/AnalyticsIntervalParam'
                - $ref: '#/components/parameters/AnalyticsTimezoneParam'
                - $ref: '#/components/parameters/AnalyticsConversionScopeParam'
                - $ref: '#/components/parameters/AnalyticsCurrencyParam'
                - $ref: '#/components/parameters/AnalyticsFilterParam'
                - $ref: '#/components/parameters/AnalyticsGroupByParam'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        oneOf:
                                            - $ref: '#/components/schemas/AnalyticsSeriesReport'
                                            - $ref: '#/components/schemas/AnalyticsSeriesGroupedReport'
                                required: [entity]
                            examples:
                                grouped_by_channel:
                                    summary: Daily delivered + clicks split by channel
                                    value:
                                        timeframe:
                                            key: last_30_days
                                            start: '2026-05-01T00:00:00+02:00'
                                            end: '2026-05-30T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        currency: EUR
                                        source_currency: EUR
                                        interval: day
                                        date_times:
                                            - '2026-05-01'
                                            - '2026-05-02'
                                            - '2026-05-03'
                                        group_by:
                                            - channel
                                        totals_per_group:
                                            delivered:
                                                - 0
                                                - 1200
                                                - 1800
                                            clicks_first:
                                                - 0
                                                - 210
                                                - 350
                                        series:
                                            sms:
                                                delivered:
                                                    - 0
                                                    - 800
                                                    - 1200
                                                clicks_first:
                                                    - 0
                                                    - 120
                                                    - 200
                                            rcs:
                                                delivered:
                                                    - 0
                                                    - 400
                                                    - 600
                                                clicks_first:
                                                    - 0
                                                    - 90
                                                    - 150
                                        group_order:
                                            - sms
                                            - rcs
                                        freshness:
                                            computed_at: '2026-05-29T10:15:02Z'
                                            source: live
                                            stale: false
                                            ttl_seconds_remaining: null
                                        warnings: []
                '400':
                    $ref: '#/components/responses/AnalyticsBadRequest'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '403':
                    $ref: '#/components/responses/AnalyticsForbidden'
                '429':
                    $ref: '#/components/responses/AnalyticsRateLimit'
                '500':
                    $ref: '#/components/responses/AnalyticsServerError'
            x-stoplight:
                id: xanalyticsov02
    '/project/{project}/analytics/campaign/{campaign}':
        get:
            summary: Campaign report (values)
            description: |
                Aggregate statistics for a single campaign. The default report is the
                campaign **lifetime**, served from the campaign's cached statistics —
                including the `group_by` and `filter` cross-cuts below; a custom
                timeframe is computed live. `group_by=channel` produces the per-leg
                RCS+SMS fallback breakdown (reconciled against the cached combined
                figure, with a drift warning if they diverge >1%).

                **Supported on this report:** `group_by` = `channel`, `country`;
                `filter` = `channel`, `country`. Pair a single `group_by` with a
                `filter` on the other dimension to cross-cut (e.g.
                `filter[channel]=rcs` + `group_by=country`).

                Also serves **direct / transactional** sends (`purpose=direct`):
                those have no cached statistics, default to `last_30_days` with
                `previous_period`, and are always computed live from transactional
                send/click activity.

                **Required Scope**: `PROJECT_CAMPAIGN_READ`.

                **Default timeframe**: campaign lifetime (standard) / `last_30_days`
                + `previous_period` (direct). `compare_to=previous_period` is
                rejected for standard campaigns. When you name no timeframe on a
                standard campaign the response declares that entity's real window
                as `timeframe.key = "lifetime"` with its true `start` and `end`,
                rather than echoing a period nobody asked for, and no
                `timeframe_clamped_to_entity_lifetime` warning is emitted, because
                nothing was clamped. That warning fires only when you did ask for a
                window and the cached lifetime aggregate is served regardless.

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - analytics
            parameters:
                - $ref: '#/components/parameters/ProjectPath'
                - name: campaign
                  in: path
                  description: Campaign ID
                  required: true
                  schema:
                      type: string
                - $ref: '#/components/parameters/AnalyticsStatisticsParam'
                - $ref: '#/components/parameters/AnalyticsTimeframeParam'
                - $ref: '#/components/parameters/AnalyticsStartParam'
                - $ref: '#/components/parameters/AnalyticsEndParam'
                - $ref: '#/components/parameters/AnalyticsTimezoneParam'
                - $ref: '#/components/parameters/AnalyticsConversionScopeParam'
                - $ref: '#/components/parameters/AnalyticsCurrencyParam'
                - $ref: '#/components/parameters/AnalyticsCompareToParam'
                - $ref: '#/components/parameters/AnalyticsGroupByParam'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AnalyticsValuesResponse'
                            examples:
                                lifetime:
                                    summary: Campaign lifetime values with resource metadata
                                    value:
                                        resource:
                                            type: campaign
                                            id: 6627f1a2b3c4d5e6f7a8b9c0
                                            name: Spring sale
                                            status: sent
                                            purpose: standard
                                            channel_type: sms
                                            channel_chain:
                                                - sms
                                            options_count: 1
                                            audience_contacts_count: 9200
                                            confirmed_at: '2026-04-12T10:00:00Z'
                                        timeframe:
                                            key: lifetime
                                            start: '2026-04-12T00:00:00+02:00'
                                            end: '2026-05-29T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        conversion_scope: sales
                                        currency: EUR
                                        source_currency: EUR
                                        statistics:
                                            recipients: 9
                                            delivered: 8
                                            delivery_rate: 0.8889
                                            click_rate: 0.3750
                                            conversions: 3
                                            conversion_value: 386.80
                                            average_order_value: 128.93
                                            revenue_per_recipient: 42.98
                                            roas: 1175.69
                                            unsubscribe_rate: 0.1250
                                        freshness:
                                            computed_at: '2026-05-28T10:14:33Z'
                                            source: entity_stats
                                            stale: false
                                            ttl_seconds_remaining: null
                                        warnings: []
                                grouped_by_channel:
                                    summary: Same lifetime values split by channel (group_by=channel)
                                    value:
                                        resource:
                                            type: campaign
                                            id: 6627f1a2b3c4d5e6f7a8b9c0
                                            name: Spring sale
                                            status: sent
                                            purpose: standard
                                            channel_type: sms
                                            channel_chain:
                                                - rcs
                                                - sms
                                        timeframe:
                                            key: lifetime
                                            start: '2026-04-12T00:00:00+02:00'
                                            end: '2026-05-29T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        conversion_scope: sales
                                        currency: EUR
                                        source_currency: EUR
                                        statistics:
                                            rcs:
                                                delivered: 3
                                                conversions: 1
                                                conversion_value: 150.00
                                            sms:
                                                delivered: 5
                                                conversions: 2
                                                conversion_value: 236.80
                                        totals_per_group:
                                            delivered: 8
                                            conversions: 3
                                            conversion_value: 386.80
                                        group_order:
                                            - sms
                                            - rcs
                                        group_by:
                                            - channel
                                        freshness:
                                            computed_at: '2026-05-28T10:14:33Z'
                                            source: entity_stats
                                            stale: false
                                            ttl_seconds_remaining: null
                                        warnings: []
                '400':
                    $ref: '#/components/responses/AnalyticsBadRequest'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '403':
                    $ref: '#/components/responses/AnalyticsForbidden'
                '404':
                    $ref: '#/components/responses/AnalyticsNotFound'
                '429':
                    $ref: '#/components/responses/AnalyticsRateLimit'
                '500':
                    $ref: '#/components/responses/AnalyticsServerError'
            x-stoplight:
                id: xanalyticscm01
    '/project/{project}/analytics/campaign/{campaign}/series':
        get:
            summary: Campaign report (series)
            description: |
                Time-bucketed statistics for a single campaign, scoped to the
                campaign and clamped to its lifetime. `group_by=channel` produces
                the per-leg fallback series. `contacts_clicked_approx` is available
                per bucket but is **not summable across buckets** (a warning flags
                this). Cost is not part of the per-entity series — the precise
                per-entity cost figure cannot be time-bucketed cheaply.

                **Supported on this report:** `group_by` = `channel`, `country`;
                `filter` = `channel`, `country`. Pair a single `group_by` with a
                `filter` on the other dimension to cross-cut over time (e.g.
                `filter[country]=ES` + `group_by=channel`).

                **Required Scope**: `PROJECT_CAMPAIGN_READ`.

                **Default interval**: `day`.

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - analytics
            parameters:
                - $ref: '#/components/parameters/ProjectPath'
                - name: campaign
                  in: path
                  description: Campaign ID
                  required: true
                  schema:
                      type: string
                - $ref: '#/components/parameters/AnalyticsStatisticsParam'
                - $ref: '#/components/parameters/AnalyticsTimeframeParam'
                - $ref: '#/components/parameters/AnalyticsStartParam'
                - $ref: '#/components/parameters/AnalyticsEndParam'
                - $ref: '#/components/parameters/AnalyticsIntervalParam'
                - $ref: '#/components/parameters/AnalyticsTimezoneParam'
                - $ref: '#/components/parameters/AnalyticsConversionScopeParam'
                - $ref: '#/components/parameters/AnalyticsCurrencyParam'
                - $ref: '#/components/parameters/AnalyticsGroupByParam'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        oneOf:
                                            - $ref: '#/components/schemas/AnalyticsSeriesReport'
                                            - $ref: '#/components/schemas/AnalyticsSeriesGroupedReport'
                                required: [entity]
                            examples:
                                flat:
                                    summary: Daily delivered / clicks / conversions / revenue
                                    value:
                                        resource:
                                            type: campaign
                                            id: 6627f1a2b3c4d5e6f7a8b9c0
                                            name: Spring sale
                                        timeframe:
                                            key: last_30_days
                                            start: '2026-05-01T00:00:00+02:00'
                                            end: '2026-05-30T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        currency: EUR
                                        source_currency: EUR
                                        interval: day
                                        date_times:
                                            - '2026-05-01'
                                            - '2026-05-02'
                                            - '2026-05-03'
                                        series:
                                            delivered:
                                                - 0
                                                - 8
                                                - 0
                                            clicks_first:
                                                - 0
                                                - 3
                                                - 0
                                            conversions:
                                                - 0
                                                - 3
                                                - 0
                                            conversion_value:
                                                - 0
                                                - 386.80
                                                - 0
                                        freshness:
                                            computed_at: '2026-05-28T10:15:02Z'
                                            source: live
                                            stale: false
                                            ttl_seconds_remaining: null
                                        warnings: []
                                grouped_by_channel:
                                    summary: Same series split by channel (group_by=channel)
                                    value:
                                        resource:
                                            type: campaign
                                            id: 6627f1a2b3c4d5e6f7a8b9c0
                                            name: Spring sale
                                        timeframe:
                                            key: last_30_days
                                            start: '2026-05-01T00:00:00+02:00'
                                            end: '2026-05-30T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        currency: EUR
                                        source_currency: EUR
                                        interval: day
                                        date_times:
                                            - '2026-05-01'
                                            - '2026-05-02'
                                            - '2026-05-03'
                                        series:
                                            sms:
                                                delivered:
                                                    - 0
                                                    - 5
                                                    - 0
                                                conversions:
                                                    - 0
                                                    - 2
                                                    - 0
                                            rcs:
                                                delivered:
                                                    - 0
                                                    - 3
                                                    - 0
                                                conversions:
                                                    - 0
                                                    - 1
                                                    - 0
                                        totals_per_group:
                                            delivered:
                                                - 0
                                                - 8
                                                - 0
                                            conversions:
                                                - 0
                                                - 3
                                                - 0
                                        group_order:
                                            - sms
                                            - rcs
                                        group_by:
                                            - channel
                                        freshness:
                                            computed_at: '2026-05-28T10:15:02Z'
                                            source: live
                                            stale: false
                                            ttl_seconds_remaining: null
                                        warnings: []
                '400':
                    $ref: '#/components/responses/AnalyticsBadRequest'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '403':
                    $ref: '#/components/responses/AnalyticsForbidden'
                '404':
                    $ref: '#/components/responses/AnalyticsNotFound'
                '429':
                    $ref: '#/components/responses/AnalyticsRateLimit'
                '500':
                    $ref: '#/components/responses/AnalyticsServerError'
            x-stoplight:
                id: xanalyticscm02
    '/project/{project}/analytics/campaign/compare':
        post:
            summary: Compare campaigns
            description: |
                Compare a set of campaigns over a single timeframe in one call.
                `reports[]` is ordered descending by the first requested statistic;
                `summary.best` / `summary.worst` map each statistic to the
                winning / losing campaign id. The set size is plan-scaled
                (3 / 10 / 25).

                **Required Scope**: `PROJECT_CAMPAIGN_READ`.

                **Rate Limit**: [s] restrictive - varies by subscription tier
            tags:
                - analytics
            parameters:
                - $ref: '#/components/parameters/ProjectPath'
            requestBody:
                $ref: '#/components/requestBodies/AnalyticsCampaignCompareRequest'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AnalyticsCompareResponse'
                            examples:
                                basic:
                                    summary: Three campaigns ranked by delivery rate
                                    value:
                                        timeframe:
                                            key: last_90_days
                                            start: '2026-03-01T00:00:00+01:00'
                                            end: '2026-05-29T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        conversion_scope: sales
                                        currency: EUR
                                        source_currency: EUR
                                        reports:
                                            - resource:
                                                  type: campaign
                                                  id: 6627f1a2b3c4d5e6f7a8b9c1
                                                  name: Black Friday
                                                  status: sent
                                                  channel_type: sms
                                                  channel_chain:
                                                      - sms
                                                  options_count: 2
                                                  audience_contacts_count: 14500
                                              statistics:
                                                  delivery_rate: 0.9120
                                                  click_rate: 0.4123
                                                  conversion_value: 542.15
                                                  roas: 980.45
                                            - resource:
                                                  type: campaign
                                                  id: 6627f1a2b3c4d5e6f7a8b9c0
                                                  name: Spring sale
                                                  status: sent
                                                  channel_type: sms
                                                  channel_chain:
                                                      - sms
                                                  options_count: 1
                                                  audience_contacts_count: 9200
                                              statistics:
                                                  delivery_rate: 0.8889
                                                  click_rate: 0.3750
                                                  conversion_value: 386.80
                                                  roas: 1175.69
                                        summary:
                                            best:
                                                delivery_rate: 6627f1a2b3c4d5e6f7a8b9c1
                                                click_rate: 6627f1a2b3c4d5e6f7a8b9c1
                                                conversion_value: 6627f1a2b3c4d5e6f7a8b9c1
                                                roas: 6627f1a2b3c4d5e6f7a8b9c0
                                            worst:
                                                delivery_rate: 6627f1a2b3c4d5e6f7a8b9c0
                                                click_rate: 6627f1a2b3c4d5e6f7a8b9c0
                                                conversion_value: 6627f1a2b3c4d5e6f7a8b9c0
                                                roas: 6627f1a2b3c4d5e6f7a8b9c1
                                        freshness:
                                            computed_at: '2026-05-29T11:02:14Z'
                                            source: live
                                            stale: false
                                            ttl_seconds_remaining: null
                                        warnings: []
                '400':
                    $ref: '#/components/responses/AnalyticsBadRequest'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '403':
                    $ref: '#/components/responses/AnalyticsForbidden'
                '404':
                    $ref: '#/components/responses/AnalyticsNotFound'
                '429':
                    $ref: '#/components/responses/AnalyticsRateLimit'
                '500':
                    $ref: '#/components/responses/AnalyticsServerError'
            x-stoplight:
                id: xanalyticscm03
    '/project/{project}/analytics/automation/{automation}':
        get:
            summary: Automation report (values)
            description: |
                Aggregate statistics for a single automation. The default report is
                `last_30_days` with `previous_period`, both halves served from the
                automation's cached statistics (which keep the current + previous
                30-day windows) — including the `group_by` and `filter` cross-cuts
                below. Cost reads from the cached send spend.

                **Supported on this report:** `group_by` = `channel`, `country`;
                `filter` = `channel`, `country`. Pair a single `group_by` with a
                `filter` on the other dimension to cross-cut (e.g.
                `filter[country]=ES` + `group_by=channel`).

                **Required Scope**: `PROJECT_AUTOMATION_READ`.

                **Default timeframe**: `last_30_days` + `previous_period`.

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - analytics
            parameters:
                - $ref: '#/components/parameters/ProjectPath'
                - name: automation
                  in: path
                  description: Automation ID
                  required: true
                  schema:
                      type: string
                - $ref: '#/components/parameters/AnalyticsStatisticsParam'
                - $ref: '#/components/parameters/AnalyticsTimeframeParam'
                - $ref: '#/components/parameters/AnalyticsStartParam'
                - $ref: '#/components/parameters/AnalyticsEndParam'
                - $ref: '#/components/parameters/AnalyticsTimezoneParam'
                - $ref: '#/components/parameters/AnalyticsConversionScopeParam'
                - $ref: '#/components/parameters/AnalyticsCurrencyParam'
                - $ref: '#/components/parameters/AnalyticsCompareToParam'
                - $ref: '#/components/parameters/AnalyticsGroupByParam'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AnalyticsValuesResponse'
                            examples:
                                with_comparison:
                                    summary: Last 30 days vs previous period
                                    value:
                                        resource:
                                            type: automation
                                            id: 6627f1a2b3c4d5e6f7a8b9d0
                                            name: Welcome series
                                            status: active
                                            channel_type: sms
                                            channel_chain:
                                                - sms
                                            options_count: 3
                                            audience_contacts_count: 21000
                                            activated_at: '2026-01-10T08:00:00Z'
                                        timeframe:
                                            key: last_30_days
                                            start: '2026-04-30T00:00:00+02:00'
                                            end: '2026-05-29T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        conversion_scope: sales
                                        currency: EUR
                                        source_currency: EUR
                                        statistics:
                                            triggers: 1840
                                            delivered: 1790
                                            click_rate: 0.2212
                                            conversions: 142
                                            conversion_value: 8910.30
                                        comparison:
                                            timeframe:
                                                start: '2026-03-31T00:00:00+02:00'
                                                end: '2026-04-29T23:59:59+02:00'
                                            statistics:
                                                conversion_value: 7420.10
                                            delta:
                                                conversion_value: 1490.20
                                                conversion_value_pct: 0.2008
                                        freshness:
                                            computed_at: '2026-05-29T09:30:00Z'
                                            source: entity_stats
                                            stale: false
                                            ttl_seconds_remaining: null
                                        warnings: []
                                grouped_by_country:
                                    summary: Same report split by country (group_by=country)
                                    value:
                                        resource:
                                            type: automation
                                            id: 6627f1a2b3c4d5e6f7a8b9d0
                                            name: Welcome series
                                            status: active
                                            channel_type: sms
                                            channel_chain:
                                                - sms
                                        timeframe:
                                            key: last_30_days
                                            start: '2026-04-30T00:00:00+02:00'
                                            end: '2026-05-29T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        conversion_scope: sales
                                        currency: EUR
                                        source_currency: EUR
                                        statistics:
                                            ES:
                                                delivered: 1180
                                                conversions: 95
                                                conversion_value: 6010.10
                                            FR:
                                                delivered: 610
                                                conversions: 47
                                                conversion_value: 2900.20
                                        totals_per_group:
                                            delivered: 1790
                                            conversions: 142
                                            conversion_value: 8910.30
                                        group_order:
                                            - ES
                                            - FR
                                        group_by:
                                            - country
                                        freshness:
                                            computed_at: '2026-05-29T09:30:00Z'
                                            source: entity_stats
                                            stale: false
                                            ttl_seconds_remaining: null
                                        warnings: []
                '400':
                    $ref: '#/components/responses/AnalyticsBadRequest'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '403':
                    $ref: '#/components/responses/AnalyticsForbidden'
                '404':
                    $ref: '#/components/responses/AnalyticsNotFound'
                '429':
                    $ref: '#/components/responses/AnalyticsRateLimit'
                '500':
                    $ref: '#/components/responses/AnalyticsServerError'
            x-stoplight:
                id: xanalyticsau01
    '/project/{project}/analytics/automation/{automation}/series':
        get:
            summary: Automation report (series)
            description: |
                Time-bucketed statistics for a single automation, scoped to the
                automation and clamped to its lifetime (from activation to
                archival). `group_by=channel` produces the per-leg series.
                `contacts_clicked_approx` per bucket is not summable across buckets
                (a warning flags this).

                **Supported on this report:** `group_by` = `channel`, `country`;
                `filter` = `channel`, `country`. Pair a single `group_by` with a
                `filter` on the other dimension to cross-cut over time (e.g.
                `filter[country]=ES` + `group_by=channel`).

                **Required Scope**: `PROJECT_AUTOMATION_READ`.

                **Default interval**: `day`.

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - analytics
            parameters:
                - $ref: '#/components/parameters/ProjectPath'
                - name: automation
                  in: path
                  description: Automation ID
                  required: true
                  schema:
                      type: string
                - $ref: '#/components/parameters/AnalyticsStatisticsParam'
                - $ref: '#/components/parameters/AnalyticsTimeframeParam'
                - $ref: '#/components/parameters/AnalyticsStartParam'
                - $ref: '#/components/parameters/AnalyticsEndParam'
                - $ref: '#/components/parameters/AnalyticsIntervalParam'
                - $ref: '#/components/parameters/AnalyticsTimezoneParam'
                - $ref: '#/components/parameters/AnalyticsConversionScopeParam'
                - $ref: '#/components/parameters/AnalyticsCurrencyParam'
                - $ref: '#/components/parameters/AnalyticsGroupByParam'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        oneOf:
                                            - $ref: '#/components/schemas/AnalyticsSeriesReport'
                                            - $ref: '#/components/schemas/AnalyticsSeriesGroupedReport'
                                required: [entity]
                            examples:
                                flat:
                                    summary: Daily delivered / conversions
                                    value:
                                        resource:
                                            type: automation
                                            id: 6627f1a2b3c4d5e6f7a8b9d0
                                            name: Welcome series
                                        timeframe:
                                            key: last_30_days
                                            start: '2026-05-01T00:00:00+02:00'
                                            end: '2026-05-30T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        currency: EUR
                                        source_currency: EUR
                                        interval: day
                                        date_times:
                                            - '2026-05-01'
                                            - '2026-05-02'
                                            - '2026-05-03'
                                        series:
                                            delivered:
                                                - 60
                                                - 58
                                                - 64
                                            conversions:
                                                - 4
                                                - 5
                                                - 6
                                        freshness:
                                            computed_at: '2026-05-30T10:00:00Z'
                                            source: live
                                            stale: false
                                            ttl_seconds_remaining: null
                                        warnings: []
                                grouped_by_channel:
                                    summary: Same series split by channel (group_by=channel)
                                    value:
                                        resource:
                                            type: automation
                                            id: 6627f1a2b3c4d5e6f7a8b9d0
                                            name: Welcome series
                                        timeframe:
                                            key: last_30_days
                                            start: '2026-05-01T00:00:00+02:00'
                                            end: '2026-05-30T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        currency: EUR
                                        source_currency: EUR
                                        interval: day
                                        date_times:
                                            - '2026-05-01'
                                            - '2026-05-02'
                                            - '2026-05-03'
                                        series:
                                            sms:
                                                delivered:
                                                    - 40
                                                    - 38
                                                    - 44
                                                conversions:
                                                    - 3
                                                    - 3
                                                    - 4
                                            rcs:
                                                delivered:
                                                    - 20
                                                    - 20
                                                    - 20
                                                conversions:
                                                    - 1
                                                    - 2
                                                    - 2
                                        totals_per_group:
                                            delivered:
                                                - 60
                                                - 58
                                                - 64
                                            conversions:
                                                - 4
                                                - 5
                                                - 6
                                        group_order:
                                            - sms
                                            - rcs
                                        group_by:
                                            - channel
                                        freshness:
                                            computed_at: '2026-05-30T10:00:00Z'
                                            source: live
                                            stale: false
                                            ttl_seconds_remaining: null
                                        warnings: []
                '400':
                    $ref: '#/components/responses/AnalyticsBadRequest'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '403':
                    $ref: '#/components/responses/AnalyticsForbidden'
                '404':
                    $ref: '#/components/responses/AnalyticsNotFound'
                '429':
                    $ref: '#/components/responses/AnalyticsRateLimit'
                '500':
                    $ref: '#/components/responses/AnalyticsServerError'
            x-stoplight:
                id: xanalyticsau02
    '/project/{project}/analytics/flow/{flow}/series':
        get:
            summary: Flow report (series)
            description: |
                Time-bucketed statistics for a single flow, scoped to the flow and
                clamped to its lifetime (from creation to archival; active flows
                are served as requested). `group_by=channel` produces the per-leg
                series. `contacts_clicked_approx` per bucket is not summable across
                buckets (a warning flags this). `delivered` counts messages whose
                terminal status is delivered.

                Conversion/revenue statistics are computed over the project
                conversion scope, credited to the flow by the attribution engine.

                `goals` counts fulfilled flow objectives — at most one per flow
                execution, and only when the flow had reached the contact first.
                It carries no monetary value; revenue stays in the conversion
                statistics.

                `replies` counts inbound messages linked back to a message this
                flow sent (conversation matching). Tapping a suggested reply
                counts — it arrives as a real inbound message; action taps
                (dial, calendar, map, copy) and URL taps do not (URL taps are
                clicks).

                Without `filter[version]` the report is the flow TOTAL across all
                versions (live + test cohorts — the honest business number). Pass
                `filter[version]=<flowVersionId>` to scope every statistic,
                conversions and revenue included, to a single version (e.g. to
                judge a test canary before promoting it).

                **Supported on this report:** `group_by` = `channel`, `country`;
                `filter` = `channel`, `country`, `version`. Pair a single
                `group_by` with a `filter` on another dimension to cross-cut over
                time (e.g. `filter[country]=ES` + `group_by=channel`).

                **Required Scope**: `PROJECT_AUTOMATION_READ`.

                **Default interval**: `day`.

                **Rate Limit**: [l] large - varies by subscription tier
            tags:
                - analytics
            parameters:
                - $ref: '#/components/parameters/ProjectPath'
                - name: flow
                  in: path
                  description: Flow ID
                  required: true
                  schema:
                      type: string
                - $ref: '#/components/parameters/AnalyticsStatisticsParam'
                - $ref: '#/components/parameters/AnalyticsTimeframeParam'
                - $ref: '#/components/parameters/AnalyticsStartParam'
                - $ref: '#/components/parameters/AnalyticsEndParam'
                - $ref: '#/components/parameters/AnalyticsIntervalParam'
                - $ref: '#/components/parameters/AnalyticsTimezoneParam'
                - $ref: '#/components/parameters/AnalyticsConversionScopeParam'
                - $ref: '#/components/parameters/AnalyticsCurrencyParam'
                - $ref: '#/components/parameters/AnalyticsGroupByParam'
                - $ref: '#/components/parameters/AnalyticsFilterParam'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        oneOf:
                                            - $ref: '#/components/schemas/AnalyticsSeriesReport'
                                            - $ref: '#/components/schemas/AnalyticsSeriesGroupedReport'
                                required: [entity]
                            examples:
                                flat:
                                    summary: Daily delivered / conversions
                                    value:
                                        resource:
                                            type: flow
                                            id: 6627f1a2b3c4d5e6f7a8b9e1
                                            name: Abandoned cart recovery
                                        timeframe:
                                            key: last_30_days
                                            start: '2026-05-01T00:00:00+02:00'
                                            end: '2026-05-30T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        currency: EUR
                                        source_currency: EUR
                                        interval: day
                                        date_times:
                                            - '2026-05-01'
                                            - '2026-05-02'
                                            - '2026-05-03'
                                        series:
                                            delivered:
                                                - 60
                                                - 58
                                                - 64
                                            conversions:
                                                - 4
                                                - 5
                                                - 6
                                        freshness:
                                            computed_at: '2026-05-30T10:00:00Z'
                                            source: live
                                            stale: false
                                            ttl_seconds_remaining: null
                                        warnings: []
                '400':
                    $ref: '#/components/responses/AnalyticsBadRequest'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '403':
                    $ref: '#/components/responses/AnalyticsForbidden'
                '404':
                    $ref: '#/components/responses/AnalyticsNotFound'
                '429':
                    $ref: '#/components/responses/AnalyticsRateLimit'
                '500':
                    $ref: '#/components/responses/AnalyticsServerError'
            x-stoplight:
                id: xanalyticsfl01
    '/project/{project}/analytics/automation/compare':
        post:
            summary: Compare automations
            description: |
                Compare a set of automations over a single timeframe. Same shape as
                campaign compare — `reports[]` ordered descending by the first
                statistic, with a best/worst `summary`.

                **Required Scope**: `PROJECT_AUTOMATION_READ`.

                **Rate Limit**: [s] restrictive - varies by subscription tier
            tags:
                - analytics
            parameters:
                - $ref: '#/components/parameters/ProjectPath'
            requestBody:
                $ref: '#/components/requestBodies/AnalyticsAutomationCompareRequest'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AnalyticsCompareResponse'
                '400':
                    $ref: '#/components/responses/AnalyticsBadRequest'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '403':
                    $ref: '#/components/responses/AnalyticsForbidden'
                '404':
                    $ref: '#/components/responses/AnalyticsNotFound'
                '429':
                    $ref: '#/components/responses/AnalyticsRateLimit'
                '500':
                    $ref: '#/components/responses/AnalyticsServerError'
            x-stoplight:
                id: xanalyticsau03
    '/project/{project}/analytics/direct':
        get:
            summary: Direct / transactional rollup (values)
            description: |
                Project-level rollup of all `purpose=direct` (transactional / API)
                traffic over a timeframe — the "how is my transactional traffic
                performing?" view. Always computed live from transactional activity
                (direct sends have no cached statistics). Always-on, so it accepts
                `compare_to=previous_period`.

                **Supported on this report:** `group_by` = `channel`, `country`;
                `filter` = `channel`, `country`.

                **Required Scope**: `PROJECT_DIRECT_READ`.

                **Default timeframe**: `last_30_days` + `previous_period`.

                **Rate Limit**: [m] moderate - varies by subscription tier
            tags:
                - analytics
            parameters:
                - $ref: '#/components/parameters/ProjectPath'
                - $ref: '#/components/parameters/AnalyticsStatisticsParam'
                - $ref: '#/components/parameters/AnalyticsTimeframeParam'
                - $ref: '#/components/parameters/AnalyticsStartParam'
                - $ref: '#/components/parameters/AnalyticsEndParam'
                - $ref: '#/components/parameters/AnalyticsTimezoneParam'
                - $ref: '#/components/parameters/AnalyticsConversionScopeParam'
                - $ref: '#/components/parameters/AnalyticsCurrencyParam'
                - $ref: '#/components/parameters/AnalyticsCompareToParam'
                - $ref: '#/components/parameters/AnalyticsFilterParam'
                - $ref: '#/components/parameters/AnalyticsGroupByParam'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AnalyticsValuesResponse'
                            examples:
                                flat:
                                    summary: Transactional rollup over the last 30 days
                                    value:
                                        resource:
                                            type: direct
                                        timeframe:
                                            key: last_30_days
                                            start: '2026-04-30T00:00:00+02:00'
                                            end: '2026-05-29T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        conversion_scope: sales
                                        currency: EUR
                                        source_currency: EUR
                                        statistics:
                                            delivered: 12450
                                            delivery_rate: 0.9810
                                            clicks_first: 980
                                            click_rate: 0.0787
                                            conversions: 210
                                            conversion_value: 14210.40
                                        freshness:
                                            computed_at: '2026-05-29T10:05:00Z'
                                            source: live
                                            stale: false
                                            ttl_seconds_remaining: null
                                        warnings: []
                                grouped_by_channel:
                                    summary: Same rollup split by channel (group_by=channel)
                                    value:
                                        resource:
                                            type: direct
                                        timeframe:
                                            key: last_30_days
                                            start: '2026-04-30T00:00:00+02:00'
                                            end: '2026-05-29T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        conversion_scope: sales
                                        currency: EUR
                                        source_currency: EUR
                                        statistics:
                                            sms:
                                                delivered: 8200
                                                conversions: 140
                                                conversion_value: 9010.40
                                            rcs:
                                                delivered: 4250
                                                conversions: 70
                                                conversion_value: 5200.00
                                        totals_per_group:
                                            delivered: 12450
                                            conversions: 210
                                            conversion_value: 14210.40
                                        group_order:
                                            - sms
                                            - rcs
                                        group_by:
                                            - channel
                                        freshness:
                                            computed_at: '2026-05-29T10:05:00Z'
                                            source: live
                                            stale: false
                                            ttl_seconds_remaining: null
                                        warnings: []
                '400':
                    $ref: '#/components/responses/AnalyticsBadRequest'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '403':
                    $ref: '#/components/responses/AnalyticsForbidden'
                '429':
                    $ref: '#/components/responses/AnalyticsRateLimit'
                '500':
                    $ref: '#/components/responses/AnalyticsServerError'
            x-stoplight:
                id: xanalyticsdr01
    '/project/{project}/analytics/direct/series':
        get:
            summary: Direct / transactional rollup (series)
            description: |
                Time-bucketed transactional rollup over direct send/click activity.

                **Supported on this report:** `group_by` = `channel`, `country`;
                `filter` = `channel`, `country`.

                **Required Scope**: `PROJECT_DIRECT_READ`.

                **Default interval**: `day`.

                **Rate Limit**: [m] moderate - varies by subscription tier
            tags:
                - analytics
            parameters:
                - $ref: '#/components/parameters/ProjectPath'
                - $ref: '#/components/parameters/AnalyticsStatisticsParam'
                - $ref: '#/components/parameters/AnalyticsTimeframeParam'
                - $ref: '#/components/parameters/AnalyticsStartParam'
                - $ref: '#/components/parameters/AnalyticsEndParam'
                - $ref: '#/components/parameters/AnalyticsIntervalParam'
                - $ref: '#/components/parameters/AnalyticsTimezoneParam'
                - $ref: '#/components/parameters/AnalyticsConversionScopeParam'
                - $ref: '#/components/parameters/AnalyticsCurrencyParam'
                - $ref: '#/components/parameters/AnalyticsFilterParam'
                - $ref: '#/components/parameters/AnalyticsGroupByParam'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        oneOf:
                                            - $ref: '#/components/schemas/AnalyticsSeriesReport'
                                            - $ref: '#/components/schemas/AnalyticsSeriesGroupedReport'
                                required: [entity]
                            examples:
                                flat:
                                    summary: Daily transactional delivered / conversions
                                    value:
                                        resource:
                                            type: direct
                                        timeframe:
                                            key: last_30_days
                                            start: '2026-05-01T00:00:00+02:00'
                                            end: '2026-05-30T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        currency: EUR
                                        source_currency: EUR
                                        interval: day
                                        date_times:
                                            - '2026-05-01'
                                            - '2026-05-02'
                                            - '2026-05-03'
                                        series:
                                            delivered:
                                                - 410
                                                - 395
                                                - 430
                                            conversions:
                                                - 7
                                                - 6
                                                - 8
                                        freshness:
                                            computed_at: '2026-05-30T10:05:00Z'
                                            source: live
                                            stale: false
                                            ttl_seconds_remaining: null
                                        warnings: []
                                grouped_by_channel:
                                    summary: Same series split by channel (group_by=channel)
                                    value:
                                        resource:
                                            type: direct
                                        timeframe:
                                            key: last_30_days
                                            start: '2026-05-01T00:00:00+02:00'
                                            end: '2026-05-30T23:59:59+02:00'
                                            timezone: Europe/Madrid
                                        currency: EUR
                                        source_currency: EUR
                                        interval: day
                                        date_times:
                                            - '2026-05-01'
                                            - '2026-05-02'
                                            - '2026-05-03'
                                        series:
                                            sms:
                                                delivered:
                                                    - 270
                                                    - 255
                                                    - 280
                                                conversions:
                                                    - 5
                                                    - 4
                                                    - 5
                                            rcs:
                                                delivered:
                                                    - 140
                                                    - 140
                                                    - 150
                                                conversions:
                                                    - 2
                                                    - 2
                                                    - 3
                                        totals_per_group:
                                            delivered:
                                                - 410
                                                - 395
                                                - 430
                                            conversions:
                                                - 7
                                                - 6
                                                - 8
                                        group_order:
                                            - sms
                                            - rcs
                                        group_by:
                                            - channel
                                        freshness:
                                            computed_at: '2026-05-30T10:05:00Z'
                                            source: live
                                            stale: false
                                            ttl_seconds_remaining: null
                                        warnings: []
                '400':
                    $ref: '#/components/responses/AnalyticsBadRequest'
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '403':
                    $ref: '#/components/responses/AnalyticsForbidden'
                '429':
                    $ref: '#/components/responses/AnalyticsRateLimit'
                '500':
                    $ref: '#/components/responses/AnalyticsServerError'
            x-stoplight:
                id: xanalyticsdr02
    '/project/{project}/readiness':
        get:
            summary: Project setup readiness
            description: |
                "What's left to configure" for a project home, as a live report of
                onboarding steps. Always recomputed from current data — a completed
                step re-opens if its condition regresses; there is no cached "done"
                flag. The step set depends on the project type (`standard` vs
                `api_sms`). Steps carry only stable keys (no copy); blockers are the
                steps that gate operation, and the home scope never returns warnings.

                **Rate Limit**: [m] moderate - varies by subscription tier
            tags:
                - readiness
            parameters:
                - $ref: '#/components/parameters/ProjectPath'
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/ReadinessReport'
                                required:
                                    - entity
                            examples:
                                standard:
                                    summary: Standard project, partially configured
                                    value:
                                        entity:
                                            scope: project
                                            projectType: standard
                                            channel: null
                                            percent: 29
                                            operational: false
                                            steps:
                                                - { key: import, completed: true, isBlocker: true, disabled: false }
                                                - { key: channels, completed: true, isBlocker: true, disabled: false }
                                                - { key: legal-profile, completed: false, isBlocker: true, disabled: false }
                                                - { key: funds, completed: false, isBlocker: true, disabled: false }
                                                - { key: campaign, completed: false, isBlocker: false, disabled: false }
                                                - { key: automation, completed: false, isBlocker: false, disabled: false }
                                                - { key: brand, completed: false, isBlocker: false, disabled: false }
                                            warnings: []
                                            reach: null
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
    '/project/{project}/readiness/channel/{channel}':
        get:
            summary: Channel setup readiness
            description: |
                "What's left" + "what needs attention" for one channel of a project.
                Returns onboarding `steps` plus needs-attention `warnings`:
                `pending-review` (registers awaiting carrier/regulator) and
                `rejected-registration` (registers a carrier or regulator rejected).
                Warnings only appear once the channel has an active sender. SMS has
                no per-country onboarding step (auto-accepted); RCS keeps
                `register-country`. The report also carries `reach` — the countries
                you can send to right now on this channel (union plus a per-sender
                breakdown); it is null only on the project home scope.

                **Rate Limit**: [m] moderate - varies by subscription tier
            tags:
                - readiness
            parameters:
                - $ref: '#/components/parameters/ProjectPath'
                - name: channel
                  in: path
                  required: true
                  description: Channel to report on.
                  schema:
                      type: string
                      enum: [sms, rcs]
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    entity:
                                        $ref: '#/components/schemas/ReadinessReport'
                                required:
                                    - entity
                            examples:
                                sms:
                                    summary: SMS channel with a sender and registers in review / rejected
                                    value:
                                        entity:
                                            scope: channel
                                            projectType: standard
                                            channel: sms
                                            percent: 50
                                            operational: true
                                            steps:
                                                - { key: add-sender, completed: true, isBlocker: true, disabled: false }
                                                - { key: send-first-sms-campaign, completed: false, isBlocker: false, disabled: false }
                                            warnings:
                                                - key: pending-review
                                                  severity: warning
                                                  count: 1
                                                  rows:
                                                      - { channel: sms, senderId: '5f3a9b2c4d1e0f00112233ab', senderName: MYBRAND, country: ES, effectiveStatus: pending_carrier }
                                                - key: rejected-registration
                                                  severity: error
                                                  count: 1
                                                  rows:
                                                      - { channel: sms, senderId: '5f3a9b2c4d1e0f00112233ab', senderName: MYBRAND, country: FR, effectiveStatus: rejected_carrier }
                                            reach:
                                                reachableCountries: ['PT']
                                                senders:
                                                    MYBRAND: ['PT']
                '401':
                    $ref: '#/components/responses/UnauthorizedError'
                '404':
                    $ref: '#/components/responses/NotFound'
                '429':
                    $ref: '#/components/responses/RateLimitError'
    /oauth/register:
        servers:
            - url: 'https://api.instasent.com'
              description: 'Instasent API host root — OAuth endpoints are not under /v1'
        post:
            summary: Register an OAuth client
            description: |
                Dynamic Client Registration (RFC 7591): self-service creation of a public
                OAuth client, returning the `client_id` used to drive the Authorization
                Code + PKCE flow. There is no `client_secret` — every client registered
                here is a public client and proves itself with PKCE alone
                (`token_endpoint_auth_method: none`).

                A client registered this way is **unverified**: it works, but the consent
                screen presents it as an unknown application. Verified clients are curated
                by Instasent and are not created through this endpoint.

                **Client name rules**: the name is what the user reads on the consent
                screen, so it is validated tightly — 2 to 128 characters; letters, digits,
                marks and the punctuation real product names use (`. , - _ ' " & ( ) / + :
                ! ?`); single spaces only, none leading or trailing; and no invisible,
                control or formatting characters. Names reserved for known brands are
                rejected.

                **Redirect URIs**: exact absolute URLs, at least one. Wildcards are
                rejected, duplicates are collapsed, and the code exchange later requires
                the same `redirect_uri` verbatim.

                **Rejection reasons** (`400`): rejections are returned in the RFC 7591
                error shape — a JSON body `{"error": ..., "error_description": ...}`, the
                same shape `POST /oauth/token` uses, not the platform's generic error
                envelope. The codes are `invalid_client_metadata` (body is not a JSON
                object, missing or invalid `client_name`, malformed `logo_uri`/`client_uri`,
                unknown `client_kind`), `invalid_redirect_uri` (missing, empty, wildcarded
                or not an absolute URL) and `client_name_not_allowed` (the name is
                reserved).

                Responses are returned with `Cache-Control: no-store`.

                **Authentication**: none. This endpoint is anonymous.

                **Rate Limit**: `[xs]` very restrictive. Registration is meant to happen
                once: register once and reuse the `client_id`, rather than registering on
                every run, from every machine, or from a test suite. If a registration is
                rejected unexpectedly, that is the likeliest cause — wait and retry before
                concluding the request itself is wrong.
            tags:
                - oauth
            security: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/OAuthClientRegistrationRequest'
                        example:
                            client_name: Acme Assistant
                            redirect_uris:
                                - 'https://assistant.acme.com/oauth/callback'
                            client_uri: 'https://assistant.acme.com'
                            logo_uri: 'https://assistant.acme.com/logo.png'
                            client_kind: api
            responses:
                '201':
                    description: Client registered
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/OAuthClientRegistrationResponse'
                            example:
                                client_id: 5f3a9b2c4d1e0f00112233ab
                                client_id_issued_at: 1756425600
                                client_name: Acme Assistant
                                redirect_uris:
                                    - 'https://assistant.acme.com/oauth/callback'
                                token_endpoint_auth_method: none
                                grant_types:
                                    - authorization_code
                                    - refresh_token
                                response_types:
                                    - code
                '400':
                    description: |
                        The registration was rejected — invalid client metadata, an
                        unacceptable `redirect_uris` list, or a reserved client name.
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/OAuthError'
                            examples:
                                invalid_client_metadata:
                                    summary: Missing or invalid client metadata
                                    value:
                                        error: invalid_client_metadata
                                        error_description: client_name is missing or not an acceptable client name.
                                invalid_redirect_uri:
                                    summary: Redirect URI list not usable
                                    value:
                                        error: invalid_redirect_uri
                                        error_description: redirect_uris must be a non-empty array of absolute URLs.
                                client_name_not_allowed:
                                    summary: Name reserved for a known brand
                                    value:
                                        error: client_name_not_allowed
                                        error_description: This client name is reserved.
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
    /oauth/authorize:
        servers:
            - url: 'https://api.instasent.com'
              description: 'Instasent API host root — OAuth endpoints are not under /v1'
        get:
            summary: Start the authorization flow (redirects to the consent screen)
            description: |
                The OAuth 2.1 authorization endpoint. Open it in the user's **browser** with
                the PKCE parameters; it answers `302` and sends the browser on to the
                Instasent dashboard, which authenticates the user and renders the consent
                screen. Nothing is validated or minted here.

                The endpoint lives on this API host so that the whole authorization server
                is advertised on **one origin**: everything in
                `/.well-known/oauth-authorization-server` (`issuer`,
                `authorization_endpoint`, `token_endpoint`, `registration_endpoint`) shares
                the API host. Consent itself is still shown by the dashboard, reached
                through the redirect. Clients that require the authorization endpoint to
                match the issuer's origin therefore work without special handling.

                The query string is forwarded to the consent screen untouched, so send the
                parameters you want the flow to carry, including `resource` (RFC 8707) when
                the client is an MCP client: that indicator is what limits the grant to the
                curated MCP permission set.

                This is a browser redirect, not an API call: it returns no body, and it must
                not be followed by a machine client that cannot present a logged-in user.
                After the user approves, the dashboard redirects back to your `redirect_uri`
                with a single-use `code`, which you exchange at `POST /oauth/token`.

                **Authentication**: none on this endpoint. The user authenticates on the
                dashboard, after the redirect.
            tags:
                - oauth
            security: []
            parameters:
                - name: client_id
                  in: query
                  required: true
                  description: The client identifier, from `POST /oauth/register` or issued by Instasent.
                  schema:
                      type: string
                - name: redirect_uri
                  in: query
                  required: true
                  description: Where to send the user back with the authorization code. Must be one of the client's registered redirect URIs.
                  schema:
                      type: string
                      format: uri
                - name: response_type
                  in: query
                  required: true
                  description: Only `code` is supported.
                  schema:
                      type: string
                      enum:
                          - code
                - name: code_challenge
                  in: query
                  required: true
                  description: PKCE challenge, the base64url-encoded SHA-256 of your verifier.
                  schema:
                      type: string
                - name: code_challenge_method
                  in: query
                  required: true
                  description: PKCE method. Only `S256` is accepted; `plain` is refused.
                  schema:
                      type: string
                      enum:
                          - S256
                - name: state
                  in: query
                  required: false
                  description: Opaque value returned unchanged with the code. Use it to bind the response to your session.
                  schema:
                      type: string
                - name: scope
                  in: query
                  required: false
                  description: 'Space-separated product permissions to request. Optional: omitted, the consent screen offers everything the client''s lane allows and the user chooses.'
                  schema:
                      type: string
                - name: resource
                  in: query
                  required: false
                  description: 'RFC 8707 resource indicator naming the MCP server this token is for. Send it from an MCP client: it is what curates the offered permissions down to the agent-safe subset.'
                  schema:
                      type: string
                      format: uri
            responses:
                '302':
                    description: |
                        Redirect to the dashboard's consent screen, carrying the same query
                        string. No response body.
                    headers:
                        Location:
                            description: The dashboard consent URL.
                            schema:
                                type: string
                                format: uri
                                example: 'https://dashboard.instasent.com/oauth/authorize?client_id=abc&redirect_uri=https%3A%2F%2Fclient.example%2Fcb&response_type=code&code_challenge=E9M...&code_challenge_method=S256&state=xyz'
                '500':
                    $ref: '#/components/responses/InternalError'
    /oauth/token:
        servers:
            - url: 'https://api.instasent.com'
              description: 'Instasent API host root — OAuth endpoints are not under /v1'
        post:
            summary: Exchange an authorization code or rotate a refresh token
            description: |
                The OAuth 2.1 token endpoint. It serves both grants:

                - `grant_type=authorization_code` — exchanges the single-use `code` the
                  dashboard sent to your `redirect_uri`, together with the PKCE
                  `code_verifier`, for an access token and a refresh token. The `client_id`
                  and `redirect_uri` must match the ones the code was issued for, and the
                  verifier must satisfy the `S256` challenge sent to the authorization
                  endpoint.
                - `grant_type=refresh_token` — rotates the pair: the presented refresh
                  token is consumed and a **new** access token and refresh token come back.

                The access token is an ordinary Product API token (it carries the `isoa_`
                prefix) and is used exactly like any other one: `Authorization: Bearer
                <access_token>`. It is valid for 24 hours (`expires_in: 86400`); refresh
                before it lapses.

                **Refresh tokens are single-use.** A successful refresh invalidates the one
                you presented, so store the new one. Replaying a rotated, revoked or forged
                refresh token returns `invalid_grant`.

                **Connection lifetime**: a connection has no fixed expiry — it renews
                indefinitely while it is used — but one with neither an API call nor a
                refresh for 365 days is treated as abandoned: the next refresh is rejected
                with `invalid_grant` and the user has to authorize again.

                **Granted permissions on refresh**: for an MCP client the granted
                permissions are re-resolved on every refresh against the current
                auto-grantable MCP package (still intersected with the user's roles and the
                organization's plan), so newly added safe read permissions reach connected
                clients without a reconnect — and a permission that is not in that package
                is dropped on the next refresh even if it was consented to. For an API
                client the user's chosen subset is frozen: a refresh never widens it.

                The request body is `application/x-www-form-urlencoded`. Responses are
                returned with `Cache-Control: no-store`.

                **Authentication**: none. The client is proven by PKCE and by owning the
                authorization code or the refresh token, never by a client secret.

                **Rate Limit**: `[s]` restrictive.
            tags:
                - oauth
            security: []
            requestBody:
                required: true
                content:
                    application/x-www-form-urlencoded:
                        schema:
                            $ref: '#/components/schemas/OAuthTokenRequest'
                        examples:
                            authorization_code:
                                summary: Exchange an authorization code
                                value:
                                    grant_type: authorization_code
                                    client_id: 5f3a9b2c4d1e0f00112233ab
                                    code: 9f8e7d6c5b4a39281706f5e4d3c2b1a0
                                    code_verifier: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
                                    redirect_uri: 'https://assistant.acme.com/oauth/callback'
                            refresh_token:
                                summary: Rotate the token pair
                                value:
                                    grant_type: refresh_token
                                    client_id: 5f3a9b2c4d1e0f00112233ab
                                    refresh_token: 4c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d
            responses:
                '200':
                    description: Access and refresh token pair
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/OAuthTokenResponse'
                            example:
                                access_token: isoa_QeF3nZs8kXbW2pR7vTgY1mL4dC6hJ0aU
                                token_type: Bearer
                                expires_in: 86400
                                refresh_token: 4c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d
                                scope: 'ACCOUNT_READ PROJECT_AUDIENCE_READ PROJECT_CAMPAIGN_READ'
                '400':
                    $ref: '#/components/responses/OAuthGrantError'
                '429':
                    $ref: '#/components/responses/RateLimitError'
                '500':
                    $ref: '#/components/responses/InternalError'
    /.well-known/oauth-protected-resource:
        servers:
            - url: 'https://api.instasent.com'
              description: 'Instasent API host root — OAuth endpoints are not under /v1'
        get:
            summary: Protected resource metadata
            description: |
                Protected Resource Metadata (RFC 9728): tells a client which authorization
                server protects this API, which permissions exist, and how the access token
                is presented. It is the first document an MCP client fetches when it meets
                the API without a credential.

                `resource` and `authorization_servers` are built from the host the request
                arrived on, so the document is correct on every environment without
                configuration.

                **Authentication**: none. This endpoint is anonymous.
            tags:
                - oauth
            security: []
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/OAuthProtectedResourceMetadata'
                            example:
                                resource: 'https://api.instasent.com'
                                authorization_servers:
                                    - 'https://api.instasent.com'
                                scopes_supported:
                                    - ACCOUNT_READ
                                    - PROJECT_DATASOURCE_READ
                                    - PROJECT_AUDIENCE_READ
                                    - PROJECT_CAMPAIGN_READ
                                bearer_methods_supported:
                                    - header
                '500':
                    $ref: '#/components/responses/InternalError'
    /.well-known/oauth-authorization-server:
        servers:
            - url: 'https://api.instasent.com'
              description: 'Instasent API host root — OAuth endpoints are not under /v1'
        get:
            summary: Authorization server metadata
            description: |
                Authorization Server Metadata (RFC 8414): the endpoint map a client uses to
                drive the Authorization Code + PKCE flow.

                Every endpoint in the document is on **this API host**, the same origin as
                `issuer`: `authorization_endpoint`, `token_endpoint` and
                `registration_endpoint` alike. `authorization_endpoint` is a browser
                redirect that hands the user to the dashboard's consent screen, since
                consent requires a logged-in user and only the panel can provide one.
                Always drive the flow from this document rather than assembling URLs by
                hand.

                Everything the flow supports is stated here and is deliberately narrow:
                `code` responses only, `authorization_code` and `refresh_token` grants only,
                PKCE `S256` only (never `plain`), and `none` for client authentication —
                every client is public.

                The same document is served at
                `/.well-known/openid-configuration`; the two paths are aliases.

                **Authentication**: none. This endpoint is anonymous.
            tags:
                - oauth
            security: []
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/OAuthAuthorizationServerMetadata'
                            example:
                                issuer: 'https://api.instasent.com'
                                authorization_endpoint: 'https://api.instasent.com/oauth/authorize'
                                token_endpoint: 'https://api.instasent.com/oauth/token'
                                registration_endpoint: 'https://api.instasent.com/oauth/register'
                                scopes_supported:
                                    - ACCOUNT_READ
                                    - PROJECT_DATASOURCE_READ
                                    - PROJECT_AUDIENCE_READ
                                    - PROJECT_CAMPAIGN_READ
                                response_types_supported:
                                    - code
                                grant_types_supported:
                                    - authorization_code
                                    - refresh_token
                                code_challenge_methods_supported:
                                    - S256
                                token_endpoint_auth_methods_supported:
                                    - none
                '500':
                    $ref: '#/components/responses/InternalError'
    /.well-known/openid-configuration:
        servers:
            - url: 'https://api.instasent.com'
              description: 'Instasent API host root — OAuth endpoints are not under /v1'
        get:
            summary: Authorization server metadata (OpenID Connect path)
            description: |
                The exact same Authorization Server Metadata document as
                `/.well-known/oauth-authorization-server`, served at the path many MCP and
                OAuth clients probe first. The payload is identical, built from the request
                host and not from the path, so a client may use whichever of the two it
                looks for.

                This is not an OpenID Connect provider: there is no ID token, no UserInfo
                endpoint and no `openid` scope. Only the discovery path is shared.

                **Authentication**: none. This endpoint is anonymous.
            tags:
                - oauth
            security: []
            responses:
                '200':
                    description: Success
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/OAuthAuthorizationServerMetadata'
                '500':
                    $ref: '#/components/responses/InternalError'
components:
    securitySchemes:
        BearerAuth:
            description: Token with permissions to access the endpoint
            type: http
            scheme: bearer
        OAuth2:
            description: |
                OAuth 2.1 Authorization Code flow with PKCE, for applications that act in a
                user's name instead of using a token the user pasted. The resulting access
                token is an ordinary Product API bearer token (`isoa_` prefix) with a
                24-hour lifetime, refreshed through `POST /oauth/token`.

                Register the client first with `POST /oauth/register`, or use a `client_id`
                issued by Instasent. PKCE is mandatory and only `S256` is accepted; there is
                no client secret. Discover these URLs at runtime from
                `/.well-known/oauth-authorization-server` rather than hard-coding them. All
                three are on this API host; the authorization URL is a browser redirect that
                hands the user to the dashboard's consent screen.

                The scopes below are the same product permissions the API returns in
                `GET /` and the dashboard shows when creating a token. Which of them a
                client may be granted depends on its lane: MCP clients are limited to a
                curated, agent-safe subset, while API clients may be offered the full set.
                The final grant is always intersected with the authorizing user's roles and
                the organization's plan.
            type: oauth2
            flows:
                authorizationCode:
                    authorizationUrl: 'https://api.instasent.com/oauth/authorize'
                    tokenUrl: 'https://api.instasent.com/oauth/token'
                    refreshUrl: 'https://api.instasent.com/oauth/token'
                    scopes:
                        ACCOUNT_READ: Read access to organization account details and funds
                        PROJECT_DATASOURCE_READ: Read-only access to datasources
                        PROJECT_DATASOURCE_WRITE: Create and modify datasources
                        PROJECT_AUDIENCE_READ: Read access to audience contacts
                        PROJECT_AUDIENCE_WRITE: Write access to audience contacts
                        PROJECT_AUDIENCE_LIST: List and scroll audience contacts
                        PROJECT_AUDIENCE_DATA_BASIC: Access to basic contact data
                        PROJECT_AUDIENCE_DATA_FULL: Access to full contact data
                        PROJECT_AUDIENCE_DATA_EVENTS: Access to audience events
                        PROJECT_AGGREGATIONS: Access to audience and event aggregations
                        PROJECT_SEGMENT_WRITE: Save an audience filter as a segment
                        PROJECT_CAMPAIGN_READ: Read access to campaigns
                        PROJECT_CAMPAIGN_WRITE: Create a campaign draft
                        PROJECT_AUTOMATION_READ: Read access to automations
                        PROJECT_DIRECT_READ: Messaging read access
                        PROJECT_DIRECT_WRITE: Messaging write access
    parameters:
        ProjectPath:
            name: project
            in: path
            description: 'Project UID (e.g., ''my-project-123'')'
            required: true
            schema:
                type: string
        AnalyticsStatisticsParam:
            name: statistics
            in: query
            required: true
            description: |
                Comma-separated list of statistics to compute, drawn from the
                report family's catalog. Unknown names (or names not valid for the
                family) return `400 unknown_statistic_for_family`. The **first**
                statistic also drives ordering for grouped/compare reports.
            style: form
            explode: false
            schema:
                type: array
                minItems: 1
                items:
                    $ref: '#/components/schemas/AnalyticsStatistic'
            example:
                - delivered
                - delivery_rate
                - click_rate
                - conversions
                - conversion_value
                - roas
        AnalyticsTimeframeParam:
            name: timeframe
            in: query
            required: false
            description: |
                Predefined timeframe key. Optional; the default is **family-specific**
                so the default report matches the cached window (campaign-standard →
                lifetime, automation / direct → `last_30_days`, overview →
                `last_3_months`). For a custom range use `start` + `end` instead,
                which overrides this key. All timeframes are snapped to hour
                boundaries in the resolved timezone (a `timeframe_snapped_to_hour`
                warning is emitted when snapping changes the requested bounds).
            schema:
                $ref: '#/components/schemas/AnalyticsTimeframeKey'
            example: last_30_days
        AnalyticsStartParam:
            name: start
            in: query
            required: false
            description: |
                Custom range start (ISO 8601). When `start` and `end` are both
                provided they define the timeframe and override `timeframe`. Snapped
                down to `HH:00:00.000` in the resolved timezone.
            schema:
                type: string
                format: date-time
            example: '2026-05-01T00:00:00+02:00'
        AnalyticsEndParam:
            name: end
            in: query
            required: false
            description: |
                Custom range end (ISO 8601). Snapped up to `HH:59:59.999` in the
                resolved timezone. Use `now` semantics by passing the current time.
            schema:
                type: string
                format: date-time
            example: '2026-05-30T23:59:59+02:00'
        AnalyticsIntervalParam:
            name: interval
            in: query
            required: false
            description: |
                Series bucket grain. **Default `day`** regardless of timeframe
                length. The server auto-coarsens to the next coarser interval when
                the request would exceed the per-interval bucket cap (minute=300,
                hour=300, day=400, week=60, month=24) and reports it via an
                `interval_coarsened` warning; if even `month` does not fit the
                request is rejected with `histogram_bucket_limit_exceeded`. The
                **effective** interval is echoed in the response.
            schema:
                $ref: '#/components/schemas/AnalyticsInterval'
            example: day
        AnalyticsTimezoneParam:
            name: timezone
            in: query
            required: false
            description: |
                IANA timezone used for bucketing and day/week/month boundaries.
                **Default: the project timezone** (`Project.timezone`) — never the
                caller's local time and never UTC. Part of the cache key.
            schema:
                type: string
            example: Europe/Madrid
        AnalyticsConversionScopeParam:
            name: conversion_scope
            in: query
            required: false
            description: |
                Which conversion scope drives the revenue statistics. The
                conversion event, value field and contributing datasources are
                **project configuration** — never chosen by the caller. Projects get
                a default conversion configuration automatically that covers **both**
                `sales` and `profit`, so revenue is tracked out of the box. A scope's
                statistics come back `null` with a `conversion_scope_unconfigured`
                warning (not an error) **only** when the project's configuration does
                not include the requested scope (for example a custom configuration
                that omits `profit`). That is distinct from a configured scope
                returning `0` because there were no conversions in the window —
                `null` means "not configured", `0` means "no conversions". `lost` is
                deliberately not exposed.
            schema:
                $ref: '#/components/schemas/AnalyticsConversionScope'
            example: sales
        AnalyticsCurrencyParam:
            name: currency
            in: query
            required: false
            description: |
                Output currency (ISO 4217) for every monetary statistic. Monetary
                data is stored in EUR; any other value triggers a single conversion
                pass at serialisation. `roas`/`epm` stay dimensionless. Echoed back
                in `currency`; the stored currency is echoed in `source_currency`.
            schema:
                type: string
                minLength: 3
                maxLength: 3
            example: EUR
        AnalyticsCompareToParam:
            name: compare_to
            in: query
            required: false
            description: |
                Values reports only. `previous_period` adds a calendar-aware
                `comparison` block (the natural "before" of the primary window).
                **Rejected for standard campaigns** (one-shot — compare similar
                campaigns via the compare endpoint instead); accepted for direct
                campaigns, automations and the overview. Default is family-specific.
            schema:
                $ref: '#/components/schemas/AnalyticsCompareTo'
            example: previous_period
        AnalyticsFilterParam:
            name: filter
            in: query
            required: false
            description: |
                Bounded enum filters (no raw DSL). Use bracket syntax, e.g.
                `filter[channel]=sms&filter[country]=ES`. The only filter keys are
                `channel` and `country` (the cross-cut filters) — the remaining
                breakdown dimensions are `group_by`-only and are **not** accepted as
                filters. **Which of the two a given report honors varies — each
                analytics endpoint lists its supported filters under "Supported on
                this report".** An unsupported key, or a value outside the closed enum,
                returns `400`. Country values are ISO 3166-1 alpha-2 and
                case-insensitive (`ES` or `es`).
            style: deepObject
            explode: true
            schema:
                $ref: '#/components/schemas/AnalyticsFilter'
        AnalyticsGroupByParam:
            name: group_by
            in: query
            required: false
            description: |
                A single breakdown dimension. `group_by` is **one-dimensional** —
                multi-dimensional / nested `group_by` is not supported, and a request
                with two or more dimensions is rejected. The dimension vocabulary is
                `channel`, `country`, `language`, `gender`, `segment`,
                `communication_type`; **which dimensions a given report supports
                varies — each analytics endpoint lists them under "Supported on this
                report".** Open-cardinality dimensions (`country`, `language`,
                `segment`) return the top 20 buckets ordered by the first statistic (no
                overflow `other` bucket); closed enums (`channel`, `gender`,
                `communication_type`) return all buckets. Country breakdown keys are
                the uppercase ISO 3166-1 alpha-2 code (`ES`), with `unknown` for
                recipients whose country could not be resolved.
                `group_by=campaign_variant` is reserved and currently returns
                `dimension_not_yet_available`.

                To **cross-cut**, pair a single `group_by` with a `filter` on the
                other dimension (where the report supports both) — e.g.
                `filter[channel]=rcs` + `group_by=country` ("countries within RCS") or
                `filter[country]=ES` + `group_by=channel` ("channels within Spain").
                Filtering and grouping on the same dimension is redundant and rejected.
            style: form
            explode: false
            schema:
                type: array
                maxItems: 1
                items:
                    $ref: '#/components/schemas/AnalyticsGroupByDimension'
            example:
                - channel
    requestBodies:
        AudienceSearchRequest:
            description: Request body for searching or counting audience contacts
            required: true
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/AudienceSearchRequestBody'
                    examples:
                        attributes:
                            summary: Contacts with an email, by conditions
                            value:
                                root:
                                    type: group
                                    join: and
                                    children:
                                        - type: attribute_condition
                                          key: _email
                                          operator: exists
                                          values: []
                        consent:
                            summary: How many can receive an SMS campaign under the opt-out policy
                            value:
                                filterCompliance:
                                    sms: opt-out
                        segmentAndConsent:
                            summary: A segment, counted under the project's policy for RCS, in two countries
                            value:
                                root:
                                    type: group
                                    join: and
                                    children:
                                        - type: segment_condition
                                          key: vip-customers
                                          operator: in-segment
                                filterCompliance:
                                    rcs: basic
                                filterCountryCodes:
                                    - ES
                                    - FR
        AudienceScrollRequest:
            description: Request body for scrolling audience contacts
            required: true
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/AudienceScrollRequestBody'
        AudienceAggregationsRequest:
            description: Request body for aggregating audience contacts
            required: true
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/AudienceAggregationsRequestBody'
        EventSearchRequest:
            description: Request body for searching audience events
            required: true
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/EventSearchRequestBody'
        EventScrollRequest:
            description: Request body for scrolling audience events
            required: true
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/EventScrollRequestBody'
        EventAggregationsRequest:
            description: Request body for aggregating audience events
            required: true
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/EventAggregationsRequestBody'
        StreamPushRequest:
            description: Request body for pushing data to stream (contacts or events)
            required: true
            content:
                application/json:
                    schema:
                        type: array
                        items:
                            type: object
                            description: Contact or event data
                            additionalProperties: true
        AudienceStreamActionRequest:
            description: Request body for direct Instasent stream actions
            required: true
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/AudienceStreamActionRequestBody'
        DirectSmsRequest:
            description: Request body for creating direct SMS message
            required: true
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/DirectSmsRequestBody'
        CampaignDraftBody:
            description: A campaign draft to create
            required: true
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/RequestCampaignDraft'
                    examples:
                        sms:
                            summary: SMS campaign to one segment
                            value:
                                channel: sms
                                title: Summer sale announcement
                                emoji: ☀️
                                date: '2026-06-15'
                                compliance: opt-out
                                audience:
                                    include:
                                        - vip-customers
                                    exclude:
                                        - recently-messaged
                                message:
                                    text: |-
                                        Summer sale starts today: 30% off everything.
                                        Shop now: {{short:https://example.com/sale}}
                                    allowUnicode: false
                        smsWholeAudience:
                            summary: SMS campaign to the whole audience
                            value:
                                channel: sms
                                title: Service notice
                                message:
                                    text: 'We are performing maintenance this Sunday. Service may be briefly unavailable.'
                        smsWithLanguageIntent:
                            summary: SMS campaign in English, with French and German declared for later
                            value:
                                channel: sms
                                title: Product launch
                                audience:
                                    include:
                                        - newsletter-subscribers
                                message:
                                    language: en
                                    text: 'Our new collection is live. Take a look: {{short:https://example.com/new}}'
                                translateTo:
                                    - fr
                                    - de
                        customAudience:
                            summary: Custom audience, explicit sender and a time of day
                            value:
                                channel: sms
                                title: Flash sale for Spanish contacts
                                date: '2026-06-15T09:30:00+02:00'
                                sender: 67bdfa983114d0062d733795
                                audience:
                                    include:
                                        - loyalty-members
                                        - root:
                                              type: group
                                              children:
                                                  - type: attribute_condition
                                                    key: _country_code
                                                    operator: matches-string
                                                    values:
                                                        - ES
                                                  - type: attribute_condition
                                                    key: _phone_mobile
                                                    operator: exists
                                    exclude:
                                        - recently-messaged
                                message:
                                    text: 'Solo hoy: 30% de descuento en toda la tienda.'
                                    allowUnicode: true
                        rcs:
                            summary: RCS campaign with tappable buttons
                            value:
                                channel: rcs
                                title: Order pickup reminder
                                audience:
                                    include:
                                        - pending-pickups
                                message:
                                    text: 'Your order is ready for pickup at our downtown store.'
                                    suggestions:
                                        - type: url
                                          displayText: View order
                                          url: 'https://example.com/orders'
                                        - type: dialer
                                          displayText: Call the store
                                          phoneNumber: '+34600000000'
                        rcsWithSmsFallback:
                            summary: RCS campaign with an SMS fallback for unreachable contacts
                            value:
                                channel: rcs
                                title: Order pickup reminder
                                audience:
                                    include:
                                        - pending-pickups
                                message:
                                    text: 'Your order is ready for pickup at our downtown store.'
                                    suggestions:
                                        - type: url
                                          displayText: View order
                                          url: 'https://example.com/orders'
                                fallback:
                                    text: 'Your order is ready for pickup at our downtown store: {{short:https://example.com/orders}}'
        DatasourceItemBody:
            description: A single Datasource item
            required: true
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/RequestDatasourceItem'
                    examples:
                        basic:
                            summary: Standard API data source
                            value:
                                name: My API data source
                                description: This is a test data source
                                audienceTags:
                                    - auto-tag-for-all-imported-contacts
                                defaultCountry: ES
                                locale: en_US
                                timezone: America/Los_Angeles
                        ecommerce:
                            summary: API data source with support for E-commerce events
                            value:
                                name: My API data source
                                description: This is a test data source
                                audienceTags:
                                    - auto-tag-for-all-imported-contacts
                                tags:
                                    - ecommerce
                                defaultCountry: ES
                                locale: en_US
                                timezone: America/Los_Angeles
        AnalyticsCampaignCompareRequest:
            description: Compare a set of campaigns over a single timeframe.
            required: true
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/AnalyticsCampaignCompareBody'
                    examples:
                        basic:
                            summary: Compare three campaigns by delivery, CTR, revenue and ROAS
                            value:
                                campaigns:
                                    - 6627f1a2b3c4d5e6f7a8b9c0
                                    - 6627f1a2b3c4d5e6f7a8b9c1
                                    - 6627f1a2b3c4d5e6f7a8b9c2
                                statistics:
                                    - delivery_rate
                                    - click_rate
                                    - conversion_value
                                    - roas
                                timeframe: last_90_days
                                conversion_scope: sales
        AnalyticsAutomationCompareRequest:
            description: Compare a set of automations over a single timeframe.
            required: true
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/AnalyticsAutomationCompareBody'
                    examples:
                        basic:
                            summary: Compare two automations by triggers, CTR and revenue
                            value:
                                automations:
                                    - 6627f1a2b3c4d5e6f7a8b9d0
                                    - 6627f1a2b3c4d5e6f7a8b9d1
                                statistics:
                                    - triggers
                                    - delivered
                                    - click_rate
                                    - conversion_value
                                timeframe: last_30_days
                                conversion_scope: sales
    schemas:
        AnalyticsStatistic:
            type: string
            title: Analytics statistic
            description: |
                A named statistic from the curated catalog. A report family exposes
                the subset that makes sense for it; requesting one outside the
                family's catalog returns `400 unknown_statistic_for_family`.

                Statistics whose name ends in `_approx` are **not exact**: either an
                approximate distinct-count estimate (~1–2 % error) or events-based
                figures that under-report the most recent ~48 h until delivery
                receipts land. The suffix is the contract and propagates to anything
                derived from them.

                | Group | Statistics |
                |---|---|
                | Delivery | `recipients`, `sent`, `delivered`, `failed`, `expired`, `delivery_rate`, `failed_rate` |
                | Engagement | `clicks`, `clicks_first`, `clicks_returning`, `contacts_clicked_approx`, `click_rate`, `ctr`, `contacts_click_rate_approx`, `opens`, `open_rate`, `click_to_open_rate` |
                | Conversion / revenue | `conversions`, `conversion_value`, `conversion_rate`, `average_order_value`, `revenue_per_recipient`, `profit` |
                | Cost & ROI | `cost`, `cost_approx`, `cost_per_message`, `cost_per_message_approx`, `roas`, `roas_approx`, `epm`, `epm_approx` |
                | Suppression | `unsubscribes`, `unsubscribe_rate` |
                | Automation only | `triggers` |
                | Flow only | `goals`, `replies` |

                Notes that matter:
                - `sent` and in-transit traffic are **not available in the overview**
                  family — it reads terminal-state delivery events only (delivered /
                  failed / expired). Use a per-entity report for full dispatch volume.
                - `clicks_first` is "first click of a link by a contact" and can
                  over-count distinct clickers on RCS multi-link messages; prefer
                  `contacts_clicked_approx` there.
                - `opens` (message displays / read receipts) and the derived
                  `open_rate` / `click_to_open_rate` are available on **campaign and
                  automation** reports and are **channel-specific**: today only RCS
                  reports them. On a channel with no open signal (SMS) these
                  statistics are **omitted from the response, not returned as `0`** —
                  absence means "not measurable on this channel", not "nobody opened".
                  On a multi-channel report `opens` reflects the RCS leg only; read
                  the per-channel `open_rate` via `group_by=channel` rather than a
                  blended rate.
                - `recipients`, per-entity `sent` and per-entity `cost` are exact;
                  the overview's `cost_approx` is the same monetary concept computed
                  from events (DLR-lagged).
            enum:
                - recipients
                - sent
                - delivered
                - failed
                - expired
                - delivery_rate
                - failed_rate
                - clicks
                - clicks_first
                - clicks_returning
                - contacts_clicked_approx
                - click_rate
                - ctr
                - contacts_click_rate_approx
                - opens
                - open_rate
                - click_to_open_rate
                - conversions
                - conversion_value
                - conversion_rate
                - average_order_value
                - revenue_per_recipient
                - profit
                - cost
                - cost_approx
                - cost_per_message
                - cost_per_message_approx
                - roas
                - roas_approx
                - epm
                - epm_approx
                - unsubscribes
                - unsubscribe_rate
                - triggers
                - goals
                - replies
        AnalyticsTimeframeKey:
            type: string
            title: Predefined timeframe
            description: |
                Predefined timeframe vocabulary (same keys as the Klaviyo Reporting
                API). For an arbitrary window use the `start`/`end` parameters
                instead. The maximum span is plan-scaled (90 days on Free/Lite, 12
                months on higher plans, 18 months hard ceiling); over-long requests
                are clamped with a `bounds_clamped` warning rather than rejected.
            enum:
                - today
                - yesterday
                - this_week
                - last_week
                - last_7_days
                - this_month
                - last_month
                - last_30_days
                - last_90_days
                - last_3_months
                - last_12_months
                - this_year
                - last_year
        AnalyticsInterval:
            type: string
            title: Series interval
            description: Time-bucket grain for series reports. Default `day`.
            enum:
                - hour
                - day
                - week
                - month
        AnalyticsConversionScope:
            type: string
            title: Conversion scope
            description: |
                Which conversion scope drives revenue statistics. Projects get a
                default conversion configuration automatically that covers both
                scopes: `sales` is the revenue conversion and `profit` the margin
                conversion (profit amounts depend on your conversion events carrying
                a margin value — without it they are `0`, not `null`). A scope is
                `null` with a `conversion_scope_unconfigured` warning only when the
                project's configuration omits it. `lost` (abandoned) exists in
                project configuration but is deliberately not exposed here.
            enum:
                - sales
                - profit
            default: sales
        AnalyticsCompareTo:
            type: string
            title: Comparison mode
            enum:
                - none
                - previous_period
        AnalyticsChannel:
            type: string
            title: Channel
            enum:
                - sms
                - rcs
                - whatsapp
                - email
        AnalyticsCommunicationType:
            type: string
            title: Communication type
            description: The communication type the activity is attributed to.
            enum:
                - campaign
                - automation
                - direct
        AnalyticsGroupByDimension:
            type: string
            title: Group-by dimension
            description: |
                A bounded breakdown dimension. `channel` / `communication_type` /
                `gender` are closed enums (all buckets returned). `country` /
                `language` / `segment` are open-cardinality (top-20 by the first
                statistic, no overflow `other` bucket). `campaign_option` applies to
                per-entity reports. `sender` (direct rollup) and `campaign_variant`
                are **reserved** and currently return `dimension_not_yet_available`.
            enum:
                - channel
                - country
                - language
                - gender
                - communication_type
                - segment
                - sender
                - campaign_option
                - campaign_variant
        AnalyticsFilter:
            type: object
            title: Bounded filter
            description: |
                Bounded enum filters. No raw DSL — only the keys below, each
                validated against its closed enum or a code/id list. Unknown keys or
                out-of-enum values return `400`.
            additionalProperties: false
            properties:
                channel:
                    $ref: '#/components/schemas/AnalyticsChannel'
                country:
                    type: string
                    description: Country code (phone-mobile country, falling back to contact country).
                    example: ES
                language:
                    type: string
                    description: ISO language code.
                    example: es
                gender:
                    type: string
                    description: Normalized gender token.
                communication_type:
                    $ref: '#/components/schemas/AnalyticsCommunicationType'
                segment:
                    type: string
                    description: Segment id (dynamic membership stamped on the event at send time).
                sender:
                    type: string
                    description: Parent sender identity id. Direct rollup only. **Reserved** — currently returns `dimension_not_yet_available`.
        AnalyticsTimeframeResolved:
            type: object
            title: Resolved timeframe
            description: The resolved, hour-snapped window actually queried, echoed on every response.
            properties:
                key:
                    type: string
                    nullable: true
                    description: |
                        The predefined key when one was named; `null` for custom ranges.

                        One value is produced by the server and can never be requested:
                        **`lifetime`**, emitted on a per-entity campaign report when the caller
                        named no timeframe. It means the window is that campaign's own life, from
                        its confirmation to the last statistics refresh, which is the window the
                        cached figures actually cover. Read `start` and `end` for the real bounds
                        and narrate those; do not present `lifetime` as if a period had been
                        requested, and do not compare one entity's `lifetime` against another's as
                        though they were the same window.
                    example: last_30_days
                start:
                    type: string
                    format: date-time
                    description: Hour-snapped window start (`HH:00:00.000`) in the resolved timezone.
                end:
                    type: string
                    format: date-time
                    description: Hour-snapped window end (`HH:59:59.999`) in the resolved timezone.
                timezone:
                    type: string
                    description: The resolved IANA timezone (project default unless overridden).
                    example: Europe/Madrid
            required:
                - start
                - end
                - timezone
        AnalyticsFreshness:
            type: object
            title: Freshness
            description: |
                Provenance and age of the returned numbers. **Always present.**
                Consumers use `computed_at` to render "data as of …" and decide
                whether to wait for a fresher value.
            properties:
                computed_at:
                    type: string
                    format: date-time
                    description: When the numbers were produced (cache-write time for a cache hit, not the request time).
                source:
                    type: string
                    enum:
                        - cache
                        - live
                        - entity_stats
                    description: '`cache` = overview cache; `live` = freshly computed on demand; `entity_stats` = served from cached per-entity statistics.'
                stale:
                    type: boolean
                    description: True when `computed_at` is older than the family's freshness expectation.
                ttl_seconds_remaining:
                    type: integer
                    nullable: true
                    description: Seconds until the cached value expires; `null` for non-cached sources.
            required:
                - computed_at
                - source
                - stale
                - ttl_seconds_remaining
        AnalyticsWarning:
            type: object
            title: Warning
            description: |
                Benign metadata only — never a swallowed failure. Analytics is
                fail-closed (§ error model): any real failure aborts the whole call.
            properties:
                code:
                    type: string
                    description: Stable warning code.
                    enum:
                        - bounds_clamped
                        - interval_coarsened
                        - timeframe_snapped_to_hour
                        - timeframe_clamped_to_entity_lifetime
                        - conversion_scope_unconfigured
                        - retention_truncated
                        - cache_drift
                        - series_hll_not_summable
                        - partial_retention_window
                message:
                    type: string
                    description: Human-readable explanation safe to relay to an end user.
                details:
                    type: object
                    additionalProperties: true
                    description: 'Optional structured context (e.g. requested vs applied bounds, drift percentage).'
            required:
                - code
                - message
        AnalyticsStatisticValues:
            type: object
            title: Statistic values
            description: |
                Map of statistic name to value. Counts are integers; rates are
                floats with 4 decimals; currency amounts floats with 2 decimals;
                ratios (`roas`, `epm`) floats with 2 decimals. Empty windows return
                explicit zeros, never omitted keys. The only legitimate `null` is a
                statistic whose backing conversion scope is not configured.
            additionalProperties:
                type: number
                nullable: true
            example:
                recipients: 9
                delivered: 8
                delivery_rate: 0.8889
                click_rate: 0.3750
                conversions: 3
                conversion_value: 386.80
                average_order_value: 128.93
                revenue_per_recipient: 42.98
                roas: 1175.69
                unsubscribe_rate: 0.1250
        AnalyticsResourceMeta:
            type: object
            title: Resource metadata
            description: |
                Context about the reported entity, rich enough for an agent to write
                a sensible answer without a follow-up `get_campaign` / `get_automation`
                call. Carries the entity's public metadata fields; cached raw
                statistics are deliberately omitted (the curated `statistics` block
                is authoritative). For rollup / overview reports this degenerates to
                `{ type }` plus the resolved scope.
            properties:
                type:
                    type: string
                    enum:
                        - campaign
                        - automation
                        - direct
                        - segment
                        - overview
                id:
                    type: string
                name:
                    type: string
                status:
                    type: string
                purpose:
                    type: string
                    enum:
                        - standard
                        - direct
                        - api_sms
                        - legacy
                channel_type:
                    $ref: '#/components/schemas/AnalyticsChannel'
                channel_chain:
                    type: array
                    description: Fallback chain, e.g. `["rcs", "sms"]`; tells the consumer whether to expect per-leg fallback statistics.
                    items:
                        $ref: '#/components/schemas/AnalyticsChannel'
                options_count:
                    type: integer
                    description: A/B option count. `1` means the consumer should drop A/B vocabulary.
                variants_count:
                    type: integer
                    description: Distinct variant (language) count.
                sender:
                    type: object
                    nullable: true
                    properties:
                        id:
                            type: string
                        label:
                            type: string
                audience_contacts_count:
                    type: integer
                confirmed_at:
                    type: string
                    format: date-time
                    nullable: true
                activated_at:
                    type: string
                    format: date-time
                    nullable: true
                archived_at:
                    type: string
                    format: date-time
                    nullable: true
                canceled_at:
                    type: string
                    format: date-time
                    nullable: true
                scheduled_at:
                    type: string
                    format: date-time
                    nullable: true
            required:
                - type
        AnalyticsComparison:
            type: object
            title: Comparison block
            description: Present only when `compare_to=previous_period`. Carries the resolved previous window, the same statistics, and per-statistic deltas (absolute + percentage).
            properties:
                timeframe:
                    $ref: '#/components/schemas/AnalyticsTimeframeResolved'
                statistics:
                    $ref: '#/components/schemas/AnalyticsStatisticValues'
                delta:
                    type: object
                    description: 'Per-statistic change. `{stat}` carries the absolute change; `{stat}_pct` the fractional change (`null` when the previous value is zero).'
                    additionalProperties:
                        type: number
                        nullable: true
            required:
                - timeframe
                - statistics
                - delta
        AnalyticsValuesResponse:
            type: object
            properties:
                entity:
                    oneOf:
                        - $ref: '#/components/schemas/AnalyticsValuesReport'
                        - $ref: '#/components/schemas/AnalyticsValuesGroupedReport'
            required: [entity]
        AnalyticsCompareResponse:
            type: object
            properties:
                entity:
                    $ref: '#/components/schemas/AnalyticsCompareReport'
            required: [entity]
        AnalyticsValuesReport:
            type: object
            title: Values report
            description: Aggregate totals over the resolved timeframe.
            properties:
                resource:
                    $ref: '#/components/schemas/AnalyticsResourceMeta'
                timeframe:
                    $ref: '#/components/schemas/AnalyticsTimeframeResolved'
                conversion_scope:
                    allOf:
                        - $ref: '#/components/schemas/AnalyticsConversionScope'
                    description: Present when monetary statistics were requested.
                currency:
                    type: string
                    description: Output currency (ISO 4217). Always present.
                    example: EUR
                source_currency:
                    type: string
                    description: Currency the monetary data is stored in before serialisation (constant `EUR` today).
                    example: EUR
                statistics:
                    $ref: '#/components/schemas/AnalyticsStatisticValues'
                comparison:
                    $ref: '#/components/schemas/AnalyticsComparison'
                freshness:
                    $ref: '#/components/schemas/AnalyticsFreshness'
                warnings:
                    type: array
                    items:
                        $ref: '#/components/schemas/AnalyticsWarning'
            required:
                - resource
                - timeframe
                - currency
                - source_currency
                - statistics
                - freshness
                - warnings
        AnalyticsValuesGroupedReport:
            type: object
            title: Values report (grouped)
            description: |
                Aggregate totals with a single `group_by` dimension. `statistics` is
                keyed by the dimension's values (each value a full statistics object),
                `totals_per_group` carries the cross-group roll-up (the same totals the
                flat report would return), and `group_order` makes the ranking explicit.
                Open-cardinality dimensions return the top 20 by the first statistic (no
                overflow `other` bucket). `group_by` is single-dimension —
                multi-dimensional / nested grouping is not supported (two or more
                dimensions are rejected).
            properties:
                resource:
                    $ref: '#/components/schemas/AnalyticsResourceMeta'
                timeframe:
                    $ref: '#/components/schemas/AnalyticsTimeframeResolved'
                conversion_scope:
                    allOf:
                        - $ref: '#/components/schemas/AnalyticsConversionScope'
                    description: Present when monetary statistics were requested.
                currency:
                    type: string
                    example: EUR
                source_currency:
                    type: string
                    example: EUR
                statistics:
                    type: object
                    description: 'Keyed by dimension value: `{ group: { stat: number } }`.'
                    additionalProperties:
                        $ref: '#/components/schemas/AnalyticsStatisticValues'
                totals_per_group:
                    allOf:
                        - $ref: '#/components/schemas/AnalyticsStatisticValues'
                    description: Cross-group roll-up (the same totals the flat report would return).
                group_order:
                    type: array
                    description: Group keys in rank order.
                    items:
                        type: string
                group_by:
                    type: array
                    maxItems: 1
                    items:
                        $ref: '#/components/schemas/AnalyticsGroupByDimension'
                freshness:
                    $ref: '#/components/schemas/AnalyticsFreshness'
                warnings:
                    type: array
                    items:
                        $ref: '#/components/schemas/AnalyticsWarning'
            required:
                - resource
                - timeframe
                - currency
                - source_currency
                - statistics
                - totals_per_group
                - group_order
                - group_by
                - freshness
                - warnings
        AnalyticsSeriesReport:
            type: object
            title: Series report
            description: |
                Time-bucketed arrays sharing a single continuous, zero-filled
                `date_times` axis. Every statistic array is the same length as
                `date_times` and index-aligned, with no gaps to interpolate.
            properties:
                resource:
                    $ref: '#/components/schemas/AnalyticsResourceMeta'
                timeframe:
                    $ref: '#/components/schemas/AnalyticsTimeframeResolved'
                conversion_scope:
                    allOf:
                        - $ref: '#/components/schemas/AnalyticsConversionScope'
                    description: Present when monetary statistics were requested.
                currency:
                    type: string
                    example: EUR
                source_currency:
                    type: string
                    example: EUR
                interval:
                    allOf:
                        - $ref: '#/components/schemas/AnalyticsInterval'
                    description: The **effective** interval after any auto-coarsening (an `interval_coarsened` warning records the change).
                date_times:
                    type: array
                    description: The continuous bucket axis (ISO 8601). Shared by every statistic array.
                    items:
                        type: string
                series:
                    type: object
                    description: Map of statistic name to a value array index-aligned with `date_times`.
                    additionalProperties:
                        type: array
                        items:
                            type: number
                            nullable: true
                freshness:
                    $ref: '#/components/schemas/AnalyticsFreshness'
                warnings:
                    type: array
                    items:
                        $ref: '#/components/schemas/AnalyticsWarning'
            required:
                - timeframe
                - currency
                - source_currency
                - interval
                - date_times
                - series
                - freshness
                - warnings
        AnalyticsSeriesGroupedReport:
            type: object
            title: Series report (grouped)
            description: |
                Series with a single `group_by` dimension. The `series` is keyed by
                the dimension's values, `totals_per_group` rolls them up, and
                `group_order` makes the ranking explicit. Open-cardinality dimensions
                return the top 20 by the first statistic (no overflow `other` bucket).
                `group_by` is single-dimension — multi-dimensional / nested grouping is
                not supported (two or more dimensions are rejected).
            properties:
                resource:
                    $ref: '#/components/schemas/AnalyticsResourceMeta'
                timeframe:
                    $ref: '#/components/schemas/AnalyticsTimeframeResolved'
                conversion_scope:
                    allOf:
                        - $ref: '#/components/schemas/AnalyticsConversionScope'
                    description: Present when monetary statistics were requested.
                currency:
                    type: string
                    example: EUR
                source_currency:
                    type: string
                    example: EUR
                interval:
                    $ref: '#/components/schemas/AnalyticsInterval'
                date_times:
                    type: array
                    items:
                        type: string
                group_by:
                    type: array
                    maxItems: 1
                    items:
                        $ref: '#/components/schemas/AnalyticsGroupByDimension'
                totals_per_group:
                    type: object
                    description: Across-groups roll-up (the same arrays the flat report would return).
                    additionalProperties:
                        type: array
                        items:
                            type: number
                            nullable: true
                series:
                    type: object
                    description: 'Keyed by dimension value: `{ group: { stat: number[] } }`.'
                    additionalProperties: true
                group_order:
                    type: array
                    description: Group keys in rank order.
                    items:
                        type: string
                freshness:
                    $ref: '#/components/schemas/AnalyticsFreshness'
                warnings:
                    type: array
                    items:
                        $ref: '#/components/schemas/AnalyticsWarning'
            required:
                - timeframe
                - currency
                - source_currency
                - interval
                - date_times
                - group_by
                - series
                - totals_per_group
                - group_order
                - freshness
                - warnings
        AnalyticsCompareReport:
            type: object
            title: Compare report
            description: |
                Aligned values reports for N entities (or one entity over N periods),
                ordered descending by the first requested statistic, plus a
                `summary` mapping each statistic to the best / worst entity id.
            properties:
                timeframe:
                    $ref: '#/components/schemas/AnalyticsTimeframeResolved'
                conversion_scope:
                    $ref: '#/components/schemas/AnalyticsConversionScope'
                currency:
                    type: string
                    example: EUR
                source_currency:
                    type: string
                    example: EUR
                reports:
                    type: array
                    description: Per-entity reports, ordered descending by the first requested statistic.
                    items:
                        type: object
                        properties:
                            resource:
                                $ref: '#/components/schemas/AnalyticsResourceMeta'
                            statistics:
                                $ref: '#/components/schemas/AnalyticsStatisticValues'
                        required:
                            - resource
                            - statistics
                summary:
                    type: object
                    properties:
                        best:
                            type: object
                            description: Statistic name → winning entity id.
                            additionalProperties:
                                type: string
                        worst:
                            type: object
                            description: Statistic name → losing entity id.
                            additionalProperties:
                                type: string
                    required:
                        - best
                        - worst
                freshness:
                    $ref: '#/components/schemas/AnalyticsFreshness'
                warnings:
                    type: array
                    items:
                        $ref: '#/components/schemas/AnalyticsWarning'
            required:
                - timeframe
                - currency
                - source_currency
                - reports
                - summary
                - freshness
                - warnings
        AnalyticsError:
            type: object
            title: Analytics error
            description: |
                Fail-closed error envelope. Verbose enough for an agent to explain
                the user-visible cause and how to fix the request, but never leaks
                internals (no DB engine, index names, DSL, hostnames or stack
                traces).
            properties:
                code:
                    type: string
                    description: Stable, machine-readable error code.
                    enum:
                        - unknown_statistic_for_family
                        - bound_exceeded
                        - histogram_bucket_limit_exceeded
                        - invalid_currency
                        - invalid_timezone
                        - dimension_not_yet_available
                        - too_many_group_by_dimensions
                        - compare_to_not_supported
                        - entity_not_found
                        - forbidden_resource
                        - rate_limit_exceeded
                        - analytics_timeout
                message:
                    type: string
                    description: Human-readable, user-safe description of the cause.
                hint:
                    type: string
                    description: How to fix the request (which parameter, allowed values, missing config).
                resource_id:
                    type: string
                    description: The specific entity or compare-set entry that triggered the error, when applicable.
                plan:
                    type: string
                    description: Current plan (on `rate_limit_exceeded`).
                tier:
                    type: string
                    description: Current API speed tier (on `rate_limit_exceeded`).
                retry_after:
                    type: integer
                    description: Seconds to wait before retrying (on `rate_limit_exceeded`).
                panel_url:
                    type: string
                    description: Upgrade-panel link the agent can surface (on `rate_limit_exceeded`).
            required:
                - code
                - message
        AnalyticsCampaignCompareBody:
            type: object
            title: Campaign compare request
            properties:
                campaigns:
                    type: array
                    description: Campaign ids to compare. Bounded by plan (3 / 10 / 25).
                    minItems: 2
                    items:
                        type: string
                statistics:
                    type: array
                    minItems: 1
                    items:
                        $ref: '#/components/schemas/AnalyticsStatistic'
                timeframe:
                    $ref: '#/components/schemas/AnalyticsTimeframeKey'
                conversion_scope:
                    $ref: '#/components/schemas/AnalyticsConversionScope'
                currency:
                    type: string
                timezone:
                    type: string
            required:
                - campaigns
                - statistics
        AnalyticsAutomationCompareBody:
            type: object
            title: Automation compare request
            properties:
                automations:
                    type: array
                    minItems: 2
                    items:
                        type: string
                statistics:
                    type: array
                    minItems: 1
                    items:
                        $ref: '#/components/schemas/AnalyticsStatistic'
                timeframe:
                    $ref: '#/components/schemas/AnalyticsTimeframeKey'
                conversion_scope:
                    $ref: '#/components/schemas/AnalyticsConversionScope'
                currency:
                    type: string
                timezone:
                    type: string
            required:
                - automations
                - statistics
        ChannelConfig:
            type: object
            description: Per-channel configuration. Values cascade from channel → channelDefaults → hardcoded constants.
            properties:
                defaultCompliancePolicy:
                    type: string
                    enum: [opt-in, opt-out, basic]
                    description: |
                        The consent policy campaigns on this channel take when they do not name one. Always resolved in responses (channel, then `channelDefaults`, then `basic`), never null. Pass it as `filterCompliance` to `/audience/count` to know how many contacts a campaign under the project's policy reaches.
                optInKeywords:
                    type: array
                    nullable: true
                    items:
                        type: string
                    description: Keywords that trigger opt-in
                optOutKeywords:
                    type: array
                    nullable: true
                    items:
                        type: string
                    description: Keywords that trigger opt-out
                autoMarketingOptInOnInbound:
                    type: boolean
                    nullable: true
                    description: Whether to auto-set accepts_marketing=true on inbound messages
                autoCreateContactsOnInbounds:
                    type: boolean
                    nullable: true
                    description: Whether to automatically create contacts on inbound messages
                autoCreateContactsOnOutbounds:
                    type: boolean
                    nullable: true
                    description: Whether to automatically create contacts on outbound messages
                marketingOptOutScope:
                    type: string
                    nullable: true
                    enum: [per_channel, global]
                    description: Scope of marketing opt-out (per_channel or global across all channels)
                marketingOptInScope:
                    type: string
                    nullable: true
                    enum: [per_channel, global]
                    description: Scope of marketing opt-in (per_channel or global across all channels)
        ResponseMetadata:
            type: object
            properties:
                count:
                    type: integer
                start:
                    type: integer
                limit:
                    type: integer
            x-stoplight:
                id: 2uyqyhidqntar
        AttributeSpec:
            type: object
            description: Project attribute specification
            properties:
                uid:
                    type: string
                    description: The attribute identifier (e.g., '_full_name', '_user_id')
                    example: '_full_name'
                adhocUid:
                    type: string
                    nullable: true
                    description: Ad-hoc attribute identifier (if applicable)
                label:
                    type: string
                    nullable: true
                    description: |
                        The attribute's human label, ready to read and to show. **Use this one.**
                        Falls back to the attribute's uid when no label is set.
                    example: Full name
                displayLabel:
                    type: string
                    description: |
                        The raw stored label, which for built-in attributes carries the panel's
                        translation markers (`<XLT>...</XLT>`). It exists for the panel, which
                        owns the translation dictionary. Every other reader wants `label`:
                        showing or narrating this value leaks markup to the end user.
                    example: '<XLT>Full name</XLT>'
                description:
                    type: string
                    description: Localized description of the attribute
                    example: '<XLT>User full name. Will be automatically generated from first name + last name if not mapped.</XLT>'
                dataType:
                    type: string
                    description: The data type (e.g., 'string', 'number', 'boolean', 'date')
                    example: 'string'
                visualType:
                    type: string
                    description: The visual representation type
                    example: 'string'
                enabled:
                    type: boolean
                    description: Whether the attribute is enabled for use
                readonly:
                    type: boolean
                    description: Whether the attribute is read-only
                visible:
                    type: boolean
                    description: Whether the attribute is visible in the UI
                unique:
                    type: boolean
                    description: Whether the attribute is unique (used for merging contacts)
                custom:
                    type: boolean
                    description: Whether the attribute is custom (user-created)
                internal:
                    type: boolean
                    description: Whether the attribute is internal-only
                multivalue:
                    type: integer
                    description: Maximum number of values allowed (1 = single value)
                    example: 1
                mappeable:
                    type: boolean
                    description: Whether the attribute can be mapped from datasources
                eventBased:
                    type: boolean
                    description: Whether the attribute is derived from events
                important:
                    type: boolean
                    description: Whether the attribute is marked as important
                createdByDatasourceType:
                    type: string
                    nullable: true
                    description: Type of datasource that created this attribute (if applicable)
                createdByDatasourceUid:
                    type: string
                    nullable: true
                    description: UID of datasource that created this attribute (if applicable)
                createdAt:
                    type: string
                    format: date-time
                    description: When the attribute was created
                    example: '2025-02-25T18:13:48+01:00'
                updatedAt:
                    type: string
                    format: date-time
                    description: When the attribute was last updated
                    example: '2025-02-25T18:13:48+01:00'
            required:
                - uid
                - displayLabel
                - dataType
                - visualType
                - enabled
                - readonly
                - visible
                - unique
                - custom
                - internal
                - multivalue
                - mappeable
                - eventBased
            x-stoplight:
                id: attribute-spec
        EventSpec:
            type: object
            description: Event type specification
            properties:
                uid:
                    type: string
                    description: The event type identifier (e.g., 'appointment', 'ecommerce_order_create')
                    example: 'appointment'
                name:
                    type: string
                    description: Human-readable name for the event type
                    example: 'Appointment'
                description:
                    type: string
                    description: Description of what the event represents
                    example: 'Event for appointments and reservations'
                category:
                    type: string
                    description: Event category (e.g., 'contact_behaviour', 'ecommerce')
                    example: 'contact_behaviour'
                attribution:
                    type: boolean
                    description: Whether the event supports attribution tracking
                automation:
                    type: boolean
                    description: Whether the event can trigger automations
                important:
                    type: boolean
                    description: Whether the event is marked as important
                icon:
                    type: string
                    description: Icon identifier for the event type
                    example: 'appointment'
                emoji:
                    type: string
                    description: Emoji representation of the event type
                    example: '🏁'
            required:
                - uid
                - name
                - category
                - attribution
                - automation
                - important
            x-stoplight:
                id: event-spec
        EventParameterSpec:
            type: object
            description: Event parameter specification
            properties:
                parameter:
                    type: string
                    description: The parameter identifier (e.g., 'order-id', 'order-currency')
                    example: 'order-id'
                title:
                    type: string
                    description: Human-readable title for the parameter
                    example: 'Order Id'
                description:
                    type: string
                    description: Description of the parameter and its purpose
                    example: 'Currency used for all amounts on the order. ISO4217 3 Uppercase letters (i.e. EUR, USD)'
                dataType:
                    type: string
                    description: The data type (e.g., 'keyword', 'number', 'date')
                    example: 'keyword'
                visualType:
                    type: string
                    description: The visual representation type
                    example: 'keyword'
                required:
                    type: boolean
                    description: Whether the parameter is required
                multiValue:
                    type: integer
                    description: Maximum number of values allowed (1 = single value)
                    example: 1
                maxLength:
                    type: integer
                    nullable: true
                    description: Maximum length for string parameters (if applicable)
                    example: 128
                icon:
                    type: string
                    description: Icon identifier for the parameter type
                    example: 'keyword'
            required:
                - parameter
                - title
                - dataType
                - visualType
                - required
                - multiValue
            x-stoplight:
                id: event-parameter-spec
        AudienceSearchRequestBody:
            type: object
            description: |
                An Audience Query Filter body. `root` holds the conditions on attributes and events (the same expression that defines a segment); the `filter*` keys are top-level scopes applied on top of `root`, so consent, reach, country and datasource never need to be expressed as conditions. All keys are optional; an empty body means the whole audience.
            properties:
                version:
                    type: string
                    description: Filter language version. Optional; `0.0.1` is the only value and the default
                root:
                    type: object
                    description: The QueryFilter root node with conditions. See Audience QueryFilter documentation for details
                    additionalProperties: true
                filterCompliance:
                    type: object
                    description: |
                        Consent policy to apply, per channel (`{"sms": "opt-out"}`). Expands to the channel's consent and reach requirements with the same rules the campaign estimate and the send gate use, so the count equals the dashboard estimate for a campaign with that policy:

                        | Policy | `_is_subscribed_<channel>` | `_accepts_marketing_<channel>` |
                        |---|---|---|
                        | `basic` | must not be `false` | ignored |
                        | `opt-out` | must not be `false` | must not be `false` (no stated preference still counts) |
                        | `opt-in` | must not be `false` | must be `true` |

                        Every policy also requires a valid `_phone_mobile`; `rcs` and `whatsapp` additionally exclude contacts known not to support the channel. `rcs` shares the SMS consent attributes. Several channels are AND-ed. Omit the key to count with no consent applied (the raw audience). A project's own default policy per channel is `generalConfig.channel<Channel>.defaultCompliancePolicy` in the project resource (always resolved, never null). An unknown channel or policy is rejected with `422`.
                    additionalProperties:
                        type: string
                        enum:
                            - basic
                            - opt-out
                            - opt-in
                    example:
                        sms: opt-out
                filterMandatoryAttributes:
                    type: array
                    description: Attribute uids that must have a value, e.g. `["_phone_mobile"]` for contacts with a valid mobile.
                    items:
                        type: string
                filterCountryCodes:
                    type: array
                    description: ISO 3166-1 alpha-2 codes. Keeps contacts whose resolved country (the phone's country first, then `_country_code`) is one of them.
                    items:
                        type: string
                        minLength: 2
                        maxLength: 2
                filterByChannelSupport:
                    type: object
                    description: |
                        Reach by device support, per channel, for channels with verification-based reachability (`rcs`, `whatsapp`). The value lists the accepted states of the channel's support attribute: `[true]` keeps only verified contacts, `[true, null]` also keeps contacts not verified yet. Already implied by `filterCompliance` for those channels; set it explicitly only to be stricter.
                    additionalProperties:
                        type: array
                        items:
                            type: boolean
                            nullable: true
                    example:
                        rcs:
                            - true
                filterDatasourceIds:
                    type: array
                    description: Keeps contacts that came from any of these datasource ids.
                    items:
                        type: string
                sortField:
                    type: string
                    description: Attribute uid to sort by (search only)
                sortAsc:
                    type: boolean
                    description: Sort ascending (search only). Default false
                limit:
                    type: integer
                    description: Number of contacts per page. Max 50. Default 50
                    minimum: 1
                    maximum: 50
                offset:
                    type: integer
                    description: Ignored - always enforced to 0 for this endpoint
            additionalProperties: false
            x-stoplight:
                id: bmnxaw1w40obi
        AudienceScrollRequestBody:
            type: object
            properties:
                root:
                    type: object
                    description: The QueryFilter root node with conditions. See Audience QueryFilter documentation for details
                    additionalProperties: true
                limit:
                    type: integer
                    description: Number of contacts per page. Max 100. Default 100
                    minimum: 1
                    maximum: 100
                cursor:
                    type: string
                    nullable: true
                    description: 'For pagination, pass the cursor returned in the previous response''s metadata'
            additionalProperties: false
            x-stoplight:
                id: kv81l0npr7nod
        AudienceAggregationsRequestBody:
            type: object
            properties:
                root:
                    type: object
                    description: The QueryFilter root node with conditions. See Audience QueryFilter documentation for details
                    additionalProperties: true
                aggregations:
                    type: object
                    description: Object defining the aggregations to perform. Each key is an aggregation name, and the value defines the aggregation type and parameters
                    additionalProperties: true
                    example:
                        countries:
                            type: terms
                            params:
                                field: '@_country_code'
                                size: 5
                                order:
                                    _count: desc
            required:
                - aggregations
            additionalProperties: false
            x-stoplight:
                id: audience-aggregations-request-body
        EventSearchRequestBody:
            type: object
            properties:
                root:
                    type: object
                    description: The QueryFilter root node with conditions. See Event QueryFilter documentation for details
                    additionalProperties: true
                limit:
                    type: integer
                    description: Number of events per page. Max 50. Default 50
                    minimum: 1
                    maximum: 50
                offset:
                    type: integer
                    description: Ignored - always enforced to 0 for this endpoint
            additionalProperties: false
            x-stoplight:
                id: 7yei3dlb1z6i8
        EventScrollRequestBody:
            type: object
            properties:
                root:
                    type: object
                    description: The QueryFilter root node with conditions. See Event QueryFilter documentation for details
                    additionalProperties: true
                limit:
                    type: integer
                    description: Number of events per page. Max 100. Default 100
                    minimum: 1
                    maximum: 100
                cursor:
                    type: string
                    nullable: true
                    description: 'For pagination, pass the cursor returned in the previous response''s metadata'
            additionalProperties: false
            x-stoplight:
                id: 3ap1cwbippxwe
        EventAggregationsRequestBody:
            type: object
            properties:
                root:
                    type: object
                    description: The QueryFilter root node with conditions. See Event QueryFilter documentation for details
                    additionalProperties: true
                aggregations:
                    type: object
                    description: Object defining the aggregations to perform. Each key is an aggregation name, and the value defines the aggregation type and parameters
                    additionalProperties: true
                    example:
                        eventsByDay:
                            type: date_histogram
                            params:
                                field: '@created-at'
                                calendar_interval: 1d
                                format: yyyy-MM-dd
                                time_zone: UTC
            required:
                - aggregations
            additionalProperties: false
            x-stoplight:
                id: event-aggregations-request-body
        AudienceStreamActionRequestBody:
            type: object
            properties:
                audienceId:
                    type: string
                    description: The audience contact ID (required for all actions)
                reason:
                    type: string
                    description: 'Reason for unsubscription (optional, for unsubscribe actions)'
                utm-source:
                    type: string
                    description: 'UTM source parameter for tracking (optional, for unsubscribe actions)'
                utm-medium:
                    type: string
                    description: 'UTM medium parameter for tracking (optional, for unsubscribe actions)'
                utm-campaign:
                    type: string
                    description: 'UTM campaign parameter for tracking (optional, for unsubscribe actions)'
            required:
                - audienceId
            additionalProperties: true
            x-stoplight:
                id: gt6sn00x78j6i
        DirectSmsRequestBody:
            type: object
            properties:
                text:
                    type: string
                    description: |
                        The SMS message text (required). Supports template variables:
                        - `{{short:url}}`: Shortens the URL and replaces it with a short link
                        - `{{unsubscribe}}`: Adds an unsubscribe link
                    example: 'This is a test message {{short:http://google.com}} {{unsubscribe}}'
                allowUnicode:
                    type: boolean
                    description: Whether to allow Unicode characters (e.g., emojis). Defaults to false
                    default: false
                compliancePolicy:
                    type: string
                    enum:
                        - basic
                        - opt-out
                        - opt-in
                        - none
                    default: basic
                    description: |
                        Consent policy enforced for this message. Omitted, a direct send runs under `basic`: the contact must not be suppressed on the channel, and their marketing preference is ignored. `opt-out` additionally excludes contacts who explicitly refused marketing; `opt-in` requires explicit marketing consent.

                        `none` sends unconditionally and is the only value that also bypasses suppression: a contact who replied STOP still receives the message. It exists for messages you are obliged to deliver regardless of consent, and that judgement is yours to make.
            required:
                - text
            additionalProperties: true
            x-stoplight:
                id: 7qstfdgjrbhmg
        CampaignAudienceEntry:
            description: |
                One audience term: a segment UID, or an inline Audience Query Filter - AQF (a *custom audience*).
            oneOf:
                - type: string
                  description: 'A segment UID (`_all` is the whole audience).'
                - type: object
                  description: |
                      A serialized Audience Query Filter - AQF. Same DSL the segment endpoints return, so the quickest way to build one is to read a segment back and adapt it.

                      A root `group` holds `children`, ANDed together; each child is typically an `attribute_condition` with a `key`, an `operator`, and `values` as an **array** — note the plural, there is no singular `value`. Operators are named, not symbolic: `matches-string`, `exists`, `contains`, `startswith`, `matches-number`, `range-date`, `in-segment` and their `-not` variants, among others. **There is no `eq`.**

                      Both the operator and the attribute key are validated before the draft is stored and the reason comes back in the error, so a wrong one is refused up front instead of failing later at the first audience count. The root group must hold at least one condition.

                      Any `metadata.segment` sent along is discarded: a filter supplied here is a custom audience by definition, and the campaign labels it as such.
                  properties:
                      version:
                          type: string
                          description: 'Audience Query Filter - AQF DSL'
                      root:
                          type: object
                          description: Root group of the filter, holding at least one condition in `children`.
                  required:
                      - root
            x-stoplight:
                id: c4mp41gnaud13nc
        RequestCampaignDraft:
            type: object
            description: |
                A campaign draft. The shape is intentionally flat: `channel` decides which of the channel-specific message fields apply, and fields belonging to the other channel are ignored.
            properties:
                channel:
                    type: string
                    description: |
                        Delivery channel. Immutable once the draft exists. `rcs` requires the project to have RCS enabled.
                    enum:
                        - sms
                        - rcs
                title:
                    type: string
                    description: The campaign name, shown in the dashboard. Internal — recipients never see it.
                    minLength: 3
                    maxLength: 64
                emoji:
                    type: string
                    description: 'Optional emoji for the campaign card in the dashboard (e.g. ''🎉'').'
                    minLength: 1
                    maxLength: 2
                description:
                    type: string
                    description: Optional internal note about the campaign.
                    maxLength: 1000
                date:
                    type: string
                    description: |
                        When the campaign is pencilled in for. A planning anchor that positions it in the dashboard calendar — it is **not** a send time and nothing is scheduled by setting it, at any precision. Scheduling happens in the dashboard. Must be less than a year from now.

                        Three accepted forms, and **the input decides which timezone applies**:

                        | Form | Example | Interpreted in |
                        |---|---|---|
                        | ISO 8601 with offset | `2026-06-15T09:30:00+02:00`, `...Z`, `...+0200` | the offset you sent — the project timezone is not consulted |
                        | Date and time, no offset | `2026-06-15T09:30`, `2026-06-15 09:30` | the project's timezone |
                        | Day only | `2026-06-15` | the project's timezone, at 19:00 |

                        **Integrations should send the first form.** It is the only one that means the same instant regardless of where either side runs.

                        Parsing is strict against those forms: overflowing values (`25:99`, `2026-13-45`) are rejected rather than rolled over, and ambiguous or free-form input (`15/06/2026`, `tomorrow`) is refused rather than guessed at.
                time:
                    type: string
                    description: |
                        Time of day for the calendar anchor, `HH:MM`, read in the **project's timezone** (falling back to the organization's) — so `09:30` means half past nine where the audience is, not on the server.

                        A convenience for callers holding the day and the time apart. Sending it alongside a `date` that already carries a time is rejected rather than resolved: two answers to the same question is how an integration ends up anchored at the wrong hour and never finds out why.
                    pattern: '^\d{2}:\d{2}$'
                sender:
                    type: string
                    description: |
                        Id of the sender to use, which must belong to this project — an unknown id is rejected rather than accepted blindly. Omit it and the project's default sender for the channel is used; when the project has no default, the draft is still created with none and `metadata.warnings` says so. Whatever sender ends up on the campaign is also inherited by every declared language that does not name its own.
                compliance:
                    type: string
                    description: |
                        Consent policy applied when the audience is resolved. `basic` messages every contact not suppressed on the channel, `opt-out` additionally excludes contacts who explicitly refused marketing (no stated preference still receives), `opt-in` messages only contacts with explicit marketing consent. Omitted, the draft takes the project's own policy for the channel (`generalConfig.channel<Channel>.defaultCompliancePolicy` in `GET /project/{project}`), and the resolved policy is stored on the campaign. To know how many contacts the draft will reach before creating it, `POST /audience/count` with the same audience and `filterCompliance: {"<channel>": "<policy>"}`.
                    enum:
                        - basic
                        - opt-out
                        - opt-in
                audience:
                    type: object
                    description: |
                        Who the campaign targets. Omit entirely to target everyone.

                        Each entry is **either** a segment UID (a string) **or** an inline Audience Query Filter - AQF (an object) — what the dashboard calls a *custom audience*. The two mix freely in the same list. A segment keeps its identity and shows in the dashboard under its own name; an inline filter is stored as a custom audience named after its position.

                        An inline filter must contain at least one condition. To target everyone, use the `_all` segment rather than an empty filter.
                    properties:
                        include:
                            type: array
                            description: |
                                Segments and/or filters to target. A contact matching any of them is included. Defaults to `["_all"]`, the whole audience.
                            items:
                                $ref: '#/components/schemas/CampaignAudienceEntry'
                        exclude:
                            type: array
                            description: Segments and/or filters to remove from the target, applied after `include`.
                            items:
                                $ref: '#/components/schemas/CampaignAudienceEntry'
                message:
                    type: object
                    description: |
                        The campaign copy, in one language. May be omitted to create an empty draft to fill in the dashboard.
                    properties:
                        language:
                            type: string
                            description: |
                                Two-letter lowercase language code of this copy (e.g. `es`). Not a locale: `en`, never `en-US`.

                                **It is not stored.** A campaign holds a message, a sender and its declared languages, but no language of its own for the principal copy, so there is nothing to save it into. Its only job is to keep `translateTo` from declaring the language you already wrote as a missing translation — which is why it is optional on its own and **required whenever `translateTo` is used**.
                            pattern: '^[a-z]{2}$'
                        text:
                            type: string
                            description: |
                                The message body. For RCS it is capped at 3072 characters. Supports the same template variables as the rest of the platform:
                                - `{{short:url}}`: replaces the URL with a short link, which costs only 10-15 characters and makes the clicks measurable. Worth using for every link in an SMS, where length is billed
                                - `{{unsubscribe}}`: adds an unsubscribe link
                            minLength: 1
                        allowUnicode:
                            type: boolean
                            description: |
                                **SMS only.** Allow non-GSM characters (accents, emoji). Unicode SMS hold fewer characters per part, so enabling it can raise the cost per message. Ignored for RCS.
                        suggestions:
                            type: array
                            description: |
                                **RCS only.** Tappable buttons shown under the message, in order, up to 4. Entries that are incomplete or of an unsupported type are dropped rather than rejected. Ignored for SMS.
                            maxItems: 4
                            items:
                                type: object
                                properties:
                                    type:
                                        type: string
                                        description: '`url` opens a link; `dialer` starts a phone call.'
                                        enum:
                                            - url
                                            - dialer
                                    displayText:
                                        type: string
                                        description: The button label the recipient sees. Kept short by the channel.
                                        minLength: 1
                                        maxLength: 25
                                    url:
                                        type: string
                                        format: uri
                                        description: |
                                            Required when `type` is `url`. The destination link, as a plain absolute URL. Do **not** wrap it in `{{short:...}}` here: unlike message text, suggestion URLs are shortened and tracked by the platform on their own when the message is prepared.
                                    phoneNumber:
                                        type: string
                                        description: 'Required when `type` is `dialer`. The number to call, in E.164 format.'
                                required:
                                    - type
                                    - displayText
                    required:
                        - text
                fallback:
                    type: object
                    description: |
                        **RCS only.** An SMS to deliver to the contacts RCS cannot reach, forming an `rcs -> sms` chain. Omit it and the campaign is RCS-only. Sending it on an `sms` campaign is an error: SMS reaches everyone, so there is nothing to fall back to.

                        Its copy is required and is never derived from the RCS body — partly because this endpoint does not write copy nobody supplied, and partly because an RCS body may run to 3072 characters and pushing that into SMS would bill a long multi-part message nobody asked for.
                    properties:
                        text:
                            type: string
                            description: |
                                The SMS body. Required whenever `fallback` is present. Takes the same template variables as `message.text`, and `{{short:url}}` matters more here: this leg is billed by length.
                            minLength: 1
                        sender:
                            type: string
                            description: |
                                Id of the SMS sender for this leg, which must belong to the project. Omit it and the most specific answer available is used: the SMS sender configured on the RCS sender itself, then the project default. When none exists the draft is still created and `metadata.warnings` contains `no-fallback-sender`.
                        allowUnicode:
                            type: boolean
                            description: Allow non-GSM characters in the fallback SMS. Unicode SMS fit fewer characters per part, so enabling it can raise the cost per message.
                    required:
                        - text
                translateTo:
                    type: array
                    description: |
                        Other languages this campaign should eventually go out in, as two-letter lowercase codes. Each is recorded on the draft as a declared but **empty** language — an intention, not a translation. No copy is generated: a human fills them in the dashboard, and until they do, the campaign cannot be estimated, quoted or prepared. The `message` language is ignored if repeated here. Requires a subscription that includes multi-language campaigns.
                    items:
                        type: string
                        pattern: '^[a-z]{2}$'
            required:
                - channel
                - title
            x-stoplight:
                id: c4mp41gndr4ftr
        RequestDatasourceItem:
            type: object
            properties:
                name:
                    type: string
                description:
                    type: string
                audienceTags:
                    type: array
                    items:
                        type: string
                tags:
                    type: array
                    items:
                        type: string
                defaultCountry:
                    type: string
                locale:
                    type: string
                timezone:
                    type: string
            required:
                - name
                - audienceTags
            x-stoplight:
                id: se7aijsen5gaf
        ResponseDatasourceItem:
            type: object
            properties:
                id:
                    type: string
                uid:
                    type: string
                name:
                    type: string
                description:
                    type: string
                    nullable: true
                token:
                    type: object
                    properties:
                        id:
                            type: string
                        token:
                            type: string
                        title:
                            type: string
                        createdAt:
                            type: string
                type:
                    type: string
                integration:
                    type: string
                    nullable: true
                deleted:
                    type: boolean
                priority:
                    type: string
                locale:
                    type: string
                timezone:
                    type: string
                defaultCountry:
                    type: string
                    nullable: true
                createdAt:
                    type: string
                updatedAt:
                    type: string
                uniqueDsfield:
                    type: string
                    nullable: true
                    description: The unique identifier field for this datasource (e.g., `_user_id`).
                sequenceDate:
                    type: string
                    format: date-time
                    nullable: true
                    description: When the current data sequence started (used for CSV batch imports).
                audienceTags:
                    type: array
                    items:
                        type: string
                    nullable: true
                projectAttributeMappings:
                    type: array
                    description: List of field mappings that define how datasource fields map to project attributes.
                    items:
                        $ref: '#/components/schemas/ProjectAttributeMapping'
                    nullable: true
            required:
                - id
                - uid
                - name
                - type
                - integration
                - locale
                - timezone
                - defaultCountry
                - createdAt
                - audienceTags
            x-stoplight:
                id: 6u3xd6qilofef
        ProjectAttributeMapping:
            type: object
            description: Defines how a datasource field maps to a project attribute.
            properties:
                dsfield:
                    type: string
                    description: The datasource field name (e.g., `email`, `first_name`, `_user_id`).
                projectAttributeUid:
                    type: string
                    description: UID of the project attribute this field maps to.
                label:
                    type: string
                    description: Human-readable label for the mapping.
                priority:
                    type: integer
                    nullable: true
                    description: Optional custom priority for this field's value during merging. When null, the datasource default priority is used.
                autoSystemMapping:
                    type: boolean
                    description: Whether this mapping was auto-generated by the system.
            required:
                - dsfield
                - projectAttributeUid
                - label
                - autoSystemMapping
        ResponseAudienceItem:
            type: object
            properties:
                id:
                    type: string
                createdAt:
                    type: string
                updatedAt:
                    type: string
                indexedAt:
                    type: string
                deletedAt:
                    type: string
                    nullable: true
                attributesData:
                    type: object
                    properties:
                        _user_id:
                            type: string
                        _first_name:
                            type: string
                            nullable: true
                        _last_name:
                            type: string
                            nullable: true
                        _full_name:
                            type: string
                            nullable: true
                        _phone_mobile:
                            type: string
                            nullable: true
                        _phone_other:
                            type: string
                            nullable: true
                        _email:
                            type: string
                            nullable: true
                        _date_imported:
                            type: string
                        _date_updated:
                            type: string
                        _date_registered:
                            type: string
                        _client_tags:
                            oneOf:
                                - type: string
                                - type: array
                                  items:
                                      type: string
                        _country_code:
                            type: string
                            nullable: true
                        _gender:
                            type: string
                            nullable: true
                        _ip:
                            type: string
                            nullable: true
                        _username:
                            type: string
                            nullable: true
                        _language_code:
                            type: string
                            nullable: true
                        _state:
                            type: string
                            nullable: true
                        _region:
                            type: string
                            nullable: true
                        _city:
                            type: string
                            nullable: true
                        _zipcode:
                            type: string
                            nullable: true
                        _address:
                            type: string
                            nullable: true
                        _user_vat:
                            type: string
                            nullable: true
                        _user_ssn:
                            type: string
                            nullable: true
                        _company:
                            type: string
                            nullable: true
                        _branch:
                            type: string
                            nullable: true
                        _company_vat:
                            type: string
                            nullable: true
                        _url:
                            type: string
                            nullable: true
                        _currency_code:
                            type: string
                            nullable: true
                        _is_subscribed_email:
                            type: boolean
                        _accepts_marketing_email:
                            type: boolean
                        _is_subscribed_sms:
                            type: boolean
                        _accepts_marketing_sms:
                            type: boolean
                        _date_birthday:
                            type: string
                            nullable: true
                        _geopoint:
                            type: string
                            nullable: true
                        _row:
                            type: string
                            nullable: true
                        _audience_ids:
                            type: array
                            items:
                                type: string
                        _datasources:
                            type: array
                            items:
                                type: string
                        _ds_contact_ids:
                            type: array
                            items:
                                type: string
            required:
                - id
                - createdAt
                - updatedAt
                - indexedAt
                - deletedAt
                - attributesData
            x-stoplight:
                id: a500g0vorgcqx
        ResponseProjectItem:
            type: object
            description: Project entity
            properties:
                id:
                    type: string
                    description: Project internal ID
                    example: '67bdfa983114d0062d732655'
                uid:
                    type: string
                    description: Project UID (used in API paths)
                    example: 'my-empty-project'
                name:
                    type: string
                    description: Project name
                    example: 'My empty project'
                description:
                    type: string
                    nullable: true
                    description: Project description
                projectType:
                    type: string
                    description: Type of project (e.g., 'standard')
                    example: 'standard'
                projectStatus:
                    type: string
                    description: Project status (e.g., 'active')
                    example: 'active'
                locale:
                    type: string
                    description: Project locale (e.g., 'es_ES')
                    example: 'es_ES'
                timezone:
                    type: string
                    description: Project timezone (e.g., 'Europe/Madrid')
                    example: 'Europe/Madrid'
                defaultSmsSender:
                    type: string
                    nullable: true
                    description: ID of the default SMS sender
                    example: '68ad7ad14b8e760c1541f542'
                shortTrackingDomain:
                    type: string
                    nullable: true
                    description: >-
                        ID of the project's own short-link tracking domain. null is the normal state and does not
                        indicate a problem: the project then shortens links on a shared Instasent domain, and clicks
                        are tracked identically. Configuring a domain of your own changes the branding of the link,
                        not whether shortening or click tracking work.
                    example: null
                unsubscribeTrackingDomain:
                    type: string
                    nullable: true
                    description: >-
                        ID of the project's own unsubscribe domain. null is the normal state and does not indicate a
                        problem: the unsubscribe link is then served on a shared Instasent domain and opt-out works
                        identically. Configuring a domain of your own changes the branding of the link only.
                    example: null
                generalConfig:
                    type: object
                    description: General project configuration with per-channel settings and cascade defaults
                    additionalProperties: true
                    properties:
                        channelDefaults:
                            $ref: '#/components/schemas/ChannelConfig'
                            description: Default channel configuration (cascade terminal for all channels)
                        channelSms:
                            $ref: '#/components/schemas/ChannelConfig'
                            nullable: true
                            description: SMS-specific channel configuration (overrides channelDefaults)
                        channelEmail:
                            $ref: '#/components/schemas/ChannelConfig'
                            nullable: true
                            description: Email-specific channel configuration (overrides channelDefaults)
                        channelRcs:
                            $ref: '#/components/schemas/ChannelConfig'
                            description: RCS-specific channel configuration (overrides channelDefaults). RCS shares the SMS consent attributes but keeps its own default policy
                        channelWhatsapp:
                            $ref: '#/components/schemas/ChannelConfig'
                            description: WhatsApp-specific channel configuration (overrides channelDefaults)
                attributionConfig:
                    type: object
                    nullable: true
                    description: Attribution tracking configuration (time windows for attribution)
                    additionalProperties: true
                    properties:
                        hoursSmsCampaignSent:
                            type: integer
                            description: Hours window for SMS campaign attribution
                        hoursOtherCampaignSent:
                            type: integer
                            description: Hours window for other campaign attribution
                        hoursCampaignCta:
                            type: integer
                            description: Hours window for campaign CTA attribution
                        hoursCampaignOpen:
                            type: integer
                            description: Hours window for campaign open attribution
                        hoursFindOutboundSms:
                            type: integer
                            description: Hours window for outbound SMS attribution
                createdAt:
                    type: string
                    format: date-time
                    description: When the project was created
                    example: '2025-02-25T18:13:46+01:00'
                updatedAt:
                    type: string
                    format: date-time
                    description: When the project was last updated
                    example: '2025-09-09T17:27:23+02:00'
            required:
                - id
                - uid
                - name
                - projectType
                - projectStatus
                - locale
                - timezone
                - createdAt
                - updatedAt
            additionalProperties: true
            x-stoplight:
                id: m5p46cm804ius
        AudienceOverview:
            type: object
            description: |
                The audience of a project by channel and consent policy. `contacts` is every
                contact in the project; each channel block says how many of them can actually
                receive a message under the project's own policy, and what every other policy
                would reach. Only channels the project can send on appear.
            properties:
                contacts:
                    type: integer
                    description: Total contacts in the project's audience.
                    example: 12480
                computedAgo:
                    type: string
                    description: |
                        Age of the counters in words. The counters are cached per project with a
                        TTL that grows with the audience's size, from one minute for small
                        audiences up to twelve hours for very large ones; narrate the figure with
                        this age rather than as a live total.
                    example: 22 minutes ago
                sms:
                    $ref: '#/components/schemas/AudienceChannelReach'
                rcs:
                    $ref: '#/components/schemas/AudienceChannelReach'
            required:
                - contacts
                - computedAgo
            additionalProperties: true
        AudienceChannelReach:
            type: object
            description: How many contacts one channel can reach, under the project's policy and under every other.
            properties:
                reachable:
                    type: integer
                    description: |
                        Contacts that would receive on this channel under the project's own
                        policy: the number that answers "how many can I reach". Absent when the
                        policy has no entry in the ladder.
                    example: 4180
                policy:
                    type: string
                    description: The consent policy this project applies on this channel, which is the one `reachable` counts under.
                    enum:
                        - basic
                        - opt-out
                        - opt-in
                    example: opt-out
                byPolicy:
                    type: object
                    description: |
                        The full ladder: what each policy would reach on this channel. Read it as
                        "under opt-in I would reach N", never as a bare number, and use it to show
                        what tightening or loosening the policy would cost or gain.
                    additionalProperties:
                        type: integer
                    example:
                        basic: 9310
                        opt-out: 4180
                        opt-in: 1120
            required:
                - policy
                - byPolicy
        SegmentUsage:
            type: object
            description: |
                Where each segment of the project is actually used. **Only segments with at least
                one reference appear**: a segment that is listed but missing here is an orphan,
                which is what this endpoint exists to make visible. Flows carry no segment
                reference and are deliberately not counted.
            properties:
                segments:
                    type: object
                    description: Keyed by segment uid.
                    additionalProperties:
                        type: object
                        properties:
                            campaigns:
                                type: integer
                                description: Campaigns that have targeted this segment.
                                example: 3
                            lastTargetedAt:
                                type: string
                                format: date-time
                                nullable: true
                                description: When a campaign last targeted it, ISO 8601.
                                example: '2026-07-30T10:00:00+00:00'
                            automations:
                                type: integer
                                description: Automations that enroll from this segment.
                                example: 2
                            activeAutomations:
                                type: integer
                                description: |
                                    How many of those automations are active. A segment alive only
                                    through inactive automations is effectively unused.
                                example: 1
                computedAt:
                    type: string
                    format: date-time
                    description: When the map was aggregated, ISO 8601. Cached ten minutes.
                    example: '2026-08-24T09:12:44+00:00'
            required:
                - segments
                - computedAt
        CampaignSummary:
            type: object
            description: |
                What this project has been doing with campaigns: how many are in each phase, the
                last one sent with its headline figures, the next one scheduled, and the sending
                cadence. Built from aggregates, so it answers "what is going on here" without
                paging through the campaign list.
            properties:
                total:
                    type: integer
                    example: 47
                byPhase:
                    type: object
                    description: |
                        Campaign counts grouped into the six phases a person recognizes. The many
                        internal statuses map onto these; `byStatus` keeps the raw breakdown.
                    properties:
                        draft:
                            type: integer
                        preparing:
                            type: integer
                        scheduled:
                            type: integer
                        sending:
                            type: integer
                        sent:
                            type: integer
                        stopped:
                            type: integer
                byStatus:
                    type: object
                    description: Campaign counts by raw status.
                    additionalProperties:
                        type: integer
                lastSent:
                    type: object
                    nullable: true
                    description: The most recently sent campaign with its headline figures, or null if none was ever sent.
                    properties:
                        id:
                            type: string
                        title:
                            type: string
                        channel:
                            type: string
                            example: sms
                        sentAt:
                            type: string
                            format: date-time
                            nullable: true
                        sentAgo:
                            type: string
                            nullable: true
                            description: How long ago it was sent, in words.
                            example: 6 days ago
                        recipients:
                            type: integer
                            description: Contacts the campaign was sent to.
                        sent:
                            type: integer
                        delivered:
                            type: integer
                        deliveryRate:
                            type: number
                            format: float
                            description: Delivered over sent, precomputed as a fraction between 0 and 1.
                            example: 0.9812
                        clicks:
                            type: integer
                        cost:
                            type: number
                            format: float
                            description: What the campaign cost, in the organization's billing currency.
                        statsAt:
                            type: string
                            format: date-time
                            nullable: true
                            description: When the campaign's statistics were last consolidated.
                nextScheduled:
                    type: object
                    nullable: true
                    description: The next campaign due to go out, or null if none is scheduled.
                    properties:
                        id:
                            type: string
                        title:
                            type: string
                        channel:
                            type: string
                        scheduledAt:
                            type: string
                            format: date-time
                            nullable: true
                        inHours:
                            type: number
                            format: float
                            nullable: true
                            description: Hours from now until it sends.
                            example: 18.5
                        recipients:
                            type: integer
                            description: Contacts currently resolved for it.
                cadence:
                    type: object
                    description: How many campaigns were sent in each recent window.
                    properties:
                        sentLast30Days:
                            type: integer
                        sentLast90Days:
                            type: integer
            required:
                - total
                - byPhase
                - byStatus
                - cadence
        SegmentContacts:
            type: object
            description: |
                A segment's size as served from the segment-size cache, with its age. Never a
                live count: the number is computed when someone looks at the segment and then
                cached, with a TTL that grows with the segment's size, so always narrate it with
                its age rather than as a current total.
            properties:
                count:
                    type: integer
                    description: Number of contacts in the segment when it was computed.
                    example: 4180
                computedAt:
                    type: string
                    format: date-time
                    description: When the number was computed, ISO 8601.
                    example: '2026-08-24T09:12:44+00:00'
                computedAgo:
                    type: string
                    description: The same instant in words, ready to show or say.
                    example: 12 minutes ago
                approximate:
                    type: boolean
                    description: |
                        True when the count was sampled instead of counted exactly (very large
                        or event-heavy segments), in which case it is rounded to three
                        significant figures and `sampleFraction` and `marginPct` are present.
                    example: false
                sampleFraction:
                    type: number
                    format: float
                    description: Fraction of the segment actually counted. Only when `approximate` is true.
                    example: 0.1
                marginPct:
                    type: number
                    format: float
                    description: Margin of error of the sampled count, in per cent. Only when `approximate` is true.
                    example: 1.5
            required:
                - count
                - computedAt
                - computedAgo
                - approximate
        ResponseSegmentItem:
            type: object
            description: Segment entity
            properties:
                contacts:
                    allOf:
                        - $ref: '#/components/schemas/SegmentContacts'
                    nullable: true
                    description: |
                        The segment's cached size. Present on every segment view and on the list
                        endpoints, which serve what the cache already holds and never compute.
                        **Null means the size is not known yet**, not that the segment is empty:
                        a listing returns null for any segment whose size has never been
                        computed or has expired, and the single-segment view computes it. Always
                        null for parameterized dynamic segments.
                totalContacts:
                    type: integer
                    nullable: true
                    description: |
                        The same number as `contacts.count`, kept for existing clients. Prefer
                        `contacts`, which carries the age of the figure; a bare total invites
                        reading a cached number as a live one.
                    example: 1247
            additionalProperties: true
            x-stoplight:
                id: 1td95mbeye75x
        ResponseCampaignItem:
            type: object
            description: Campaign entity
            additionalProperties: true
            x-stoplight:
                id: gmopxu1q0oq40
        ResponseAutomationItem:
            type: object
            description: Automation entity
            additionalProperties: true
            x-stoplight:
                id: pzzjyg73nrsz3
        ResponseFlowItem:
            type: object
            description: Flow entity
            properties:
                id:
                    type: string
                    description: Flow id — the value the flow analytics endpoints take.
                    example: 66b1f2a4e5a6b7c8d9e0f1a2
                name:
                    type: string
                    nullable: true
                    example: Welcome series
                status:
                    type: string
                    description: Flow status.
                    enum: [enabled, disabled, archived]
                archivedAt:
                    type: string
                    format: date-time
                    nullable: true
                stats:
                    type: object
                    nullable: true
                    description: >-
                        Persisted performance pair over the last 30 days (live + A/B test
                        cohorts; manual test runs excluded). Refreshed by the platform on
                        its own cadence — NOT recomputed by this read; `statsAt` tells you
                        how fresh it is. Null until the flow first goes live. For
                        time-bucketed or custom-window reporting use the flow analytics
                        series endpoint instead.
                    additionalProperties: true
                statsPrev:
                    type: object
                    nullable: true
                    description: Same statistics over the immediately preceding 30-day window, for deltas.
                    additionalProperties: true
                statsAt:
                    type: string
                    format: date-time
                    nullable: true
                    description: When the persisted stats pair was last computed.
                statsScheduledAt:
                    type: string
                    format: date-time
                    nullable: true
                    description: >-
                        When the next background refresh of the persisted pair is
                        scheduled; null when frozen.
            additionalProperties: true
            x-stoplight:
                id: xflowitem0001
        ResponseFlowVersionItem:
            type: object
            description: Flow version entity
            properties:
                id:
                    type: string
                    description: >-
                        FlowVersion id — the value `filter[version]` takes on the flow
                        analytics series endpoint.
                    example: 66b1f2a4e5a6b7c8d9e0f1b3
                name:
                    type: string
                    nullable: true
                    example: v3
                sequence:
                    type: integer
                    description: >-
                        Permanent version number (v1, v2, …), assigned when the version
                        first goes live; 0 = unnumbered (draft / manual test preview).
                    example: 3
                triggerType:
                    type: string
                    nullable: true
                    description: The trigger family of this version's graph.
                valid:
                    type: boolean
                    description: Whether the version's graph passed validation.
                status:
                    type: string
                    description: >-
                        Version role, permanent for the version's life: `live` (full
                        audience), `live-test` (the A/B canary cohort — REAL traffic,
                        counted in flow totals), `draft`, `test` (manual preview runs,
                        excluded from stats).
                    enum: [draft, test, live-test, live]
                liveAt:
                    type: string
                    format: date-time
                    nullable: true
                archivedAt:
                    type: string
                    format: date-time
                    nullable: true
            additionalProperties: true
            x-stoplight:
                id: xflowversionit1
        ResponseSenderItem:
            type: object
            description: Sender entity
            properties:
                acceptedCountries:
                    type: array
                    description: |
                        ISO 3166-1 alpha-2 country codes this sender can deliver to,
                        default-allow over the operator catalog: a country is
                        reachable unless it requires a registration you do not yet
                        have, an enforced regulation gates it, or your register for
                        it was rejected/revoked.
                    items:
                        type: string
                    example: ['ES', 'PT', 'FR']
            additionalProperties: true
            x-stoplight:
                id: 3ftgxdbzs3r9j
        ResponseRcsSenderItem:
            type: object
            description: RCS sender (agent) entity
            properties:
                acceptedCountries:
                    type: array
                    description: |
                        ISO 3166-1 alpha-2 country codes this agent can deliver to:
                        the countries with an accepted register.
                    items:
                        type: string
                    example: ['ES']
            additionalProperties: true
        ResponseSmsItem:
            type: object
            description: SMS message entity
            properties:
                id:
                    type: string
                    description: SMS message ID
                    example: '6954234341ef67caa32a14c2'
                status:
                    type: string
                    description: Message status (e.g., 'enqueued', 'sent', 'delivered', 'failed')
                    example: 'enqueued'
                statusCode:
                    type: string
                    nullable: true
                    description: Status code from the SMS provider (if available)
                from:
                    type: string
                    description: Sender ID or phone number
                    example: 'info'
                to:
                    type: string
                    description: Recipient phone number
                    example: '+34670215553'
                normalizedTo:
                    type: string
                    description: Normalized recipient phone number
                    example: '+34670215553'
                text:
                    type: string
                    description: Original message text (with template variables like {{short:url}} and {{unsubscribe}})
                    example: 'This is a test message: {{short:http://google.com}} {{unsubscribe}}'
                deliveredText:
                    type: string
                    nullable: true
                    description: Final delivered text (with template variables resolved)
                    example: 'This is a test message: 0003 https://inst.dev/6xQ6nMq STOP nosms.dev/2qrgdG2'
                charsCount:
                    type: integer
                    description: Character count of the message
                    example: 77
                messagesCount:
                    type: integer
                    description: Number of SMS parts (1 for single SMS, 2+ for concatenated messages)
                    example: 1
                encoding:
                    type: string
                    description: Message encoding (e.g., 'GSM_7BIT', 'UCS2')
                    example: 'GSM_7BIT'
                unicode:
                    type: boolean
                    description: Whether the message contains Unicode characters
                allowUnicode:
                    type: boolean
                    description: Whether Unicode characters were allowed for this message
                charged:
                    type: boolean
                    description: Whether the message was charged
                pricePerSms:
                    type: number
                    nullable: true
                    description: Price per SMS part
                    example: 0.04
                priceUser:
                    type: number
                    nullable: true
                    description: Total price charged to user
                    example: 0.04
                sentAt:
                    type: string
                    format: date-time
                    nullable: true
                    description: When the message was sent (null if not yet sent)
                deliveredAt:
                    type: string
                    format: date-time
                    nullable: true
                    description: When the message was delivered (null if not yet delivered)
                scheduledAt:
                    type: string
                    format: date-time
                    nullable: true
                    description: When the message is scheduled to be sent (null for immediate sending)
                inbound:
                    type: boolean
                    description: Whether this is an inbound message (false for outbound)
                country:
                    type: string
                    nullable: true
                    description: Country code of the recipient
                    example: 'ES'
                audienceContact:
                    type: object
                    nullable: true
                    description: Audience contact information
                    additionalProperties: true
                    properties:
                        id:
                            type: string
                            description: Audience contact ID
                        project:
                            type: string
                            description: Project ID
                metadata:
                    type: object
                    nullable: true
                    description: Additional metadata about the message (organization, token, send entity)
                    additionalProperties: true
            required:
                - id
                - status
                - from
                - to
                - normalizedTo
                - text
                - charsCount
                - messagesCount
                - encoding
                - unicode
                - allowUnicode
                - charged
                - inbound
            additionalProperties: true
            x-stoplight:
                id: htlexm05z2jyf
        ResponseEventItem:
            type: object
            description: Event entity
            additionalProperties: true
            x-stoplight:
                id: 1v800st0ivodb
        ResponseErrorDetail:
            type: object
            properties:
                title:
                    type: string
                status:
                    type: integer
                detail:
                    type: string
            required:
                - title
                - status
                - detail
            x-stoplight:
                id: v3njemo17ya0e
        ResponseErrorMessage:
            type: object
            properties:
                message:
                    type: string
                code:
                    type: integer
            required:
                - message
            x-stoplight:
                id: nzjbrnkttcnxo
        ResponseErrorValidation:
            type: object
            description: |
                A 422 always names the field that is wrong under `errors.fields`, keyed by field
                name, with one or more human-readable reasons. Read the reasons: they say what to
                change. `queryFilter` is the key used when an audience query is malformed (an
                unknown operator, an attribute that does not exist, a node nested too deep).
            properties:
                errors:
                    type: object
                    properties:
                        fields:
                            type: object
                            additionalProperties:
                                type: array
                                items:
                                    type: string
                            properties:
                                to:
                                    type: array
                                    items:
                                        type: string
                                queryFilter:
                                    type: array
                                    description: Why the audience query was rejected, one entry per reason.
                                    items:
                                        type: string
                                    example:
                                        - 'Unknown attribute "purchase_totl" in condition 2.'
                    required:
                        - fields
            required:
                - errors
            x-stoplight:
                id: 9sf9ijkeqwxhk
        ReadinessStep:
            type: object
            description: A single onboarding milestone ("what's left").
            properties:
                key:
                    type: string
                    description: Stable identifier of the step (no copy; the client owns i18n).
                    example: add-sender
                completed:
                    type: boolean
                isBlocker:
                    type: boolean
                    description: When true, the project/channel cannot operate until this step is completed.
                disabled:
                    type: boolean
                    description: When true, a prerequisite is not yet met, so the user cannot act on it yet.
            required:
                - key
                - completed
                - isBlocker
                - disabled
            additionalProperties: false
        ReadinessWarningRow:
            type: object
            description: A per-register entry of a needs-attention warning.
            properties:
                channel:
                    type: string
                    enum: [sms, rcs]
                senderId:
                    type: string
                senderName:
                    type: string
                country:
                    type: string
                    description: ISO 3166-1 alpha-2 country code.
                effectiveStatus:
                    type: string
                    description: The register's fused carrier + regulatory status.
            required:
                - channel
                - senderId
                - senderName
                - country
                - effectiveStatus
            additionalProperties: false
        ReadinessWarning:
            type: object
            description: An emergent "needs attention" signal the user cannot complete.
            properties:
                key:
                    type: string
                    enum: [pending-review, rejected-registration]
                severity:
                    type: string
                    enum: [error, warning]
                count:
                    type: integer
                    description: Total affected registers.
                rows:
                    type: array
                    items:
                        $ref: '#/components/schemas/ReadinessWarningRow'
            required:
                - key
                - severity
                - count
                - rows
            additionalProperties: false
        ReadinessReach:
            type: object
            description: |
                The countries the channel can send to right now ("countries you
                can send to"): the union plus the per-sender breakdown. Regulation-
                aware (register effective status): a country is reachable unless a
                registration is required/pending, an enforced regulation gates it,
                or the register is rejected/revoked. SMS is default-allow over the
                operator catalog (same set as the SMS sender `acceptedCountries`);
                RCS is register-driven and may be stricter than the RCS sender
                `acceptedCountries`, which is carrier-only.
            properties:
                reachableCountries:
                    type: array
                    description: |
                        Union of reachable countries across the channel's active
                        senders (ISO 3166-1 alpha-2), deduplicated and sorted.
                    items:
                        type: string
                    example: ['ES', 'FR', 'PT']
                senders:
                    type: object
                    description: |
                        Per-sender breakdown keyed by sender alias (SMS `from`,
                        RCS agent name). Senders that reach no country are omitted.
                    additionalProperties:
                        type: array
                        items:
                            type: string
                    example:
                        ACME: ['ES', 'PT']
                        INFO: ['FR']
            required:
                - reachableCountries
                - senders
            additionalProperties: false
        ReadinessReport:
            type: object
            description: |
                Live readiness report for a project (home) or a channel. Always
                recomputed; never cached as "done".
            properties:
                scope:
                    type: string
                    enum: [project, channel]
                projectType:
                    type: string
                    enum: [standard, api_sms]
                channel:
                    type: string
                    nullable: true
                    description: The channel for a channel-scoped report; null for the project home.
                percent:
                    type: integer
                    description: Completion percentage (0-100); 100 when there are no steps.
                operational:
                    type: boolean
                    description: True when no incomplete blocker steps remain.
                steps:
                    type: array
                    items:
                        $ref: '#/components/schemas/ReadinessStep'
                warnings:
                    type: array
                    description: Always empty for the project (home) scope.
                    items:
                        $ref: '#/components/schemas/ReadinessWarning'
                reach:
                    nullable: true
                    description: |
                        Channel send reach — the countries you can send to right
                        now, with a per-sender breakdown. Present on channel-scoped
                        reports; null for the project (home) scope.
                    allOf:
                        - $ref: '#/components/schemas/ReadinessReach'
            required:
                - scope
                - projectType
                - channel
                - percent
                - operational
                - steps
                - warnings
                - reach
            additionalProperties: false
        OAuthClientRegistrationRequest:
            type: object
            title: OAuth client registration request
            description: |
                RFC 7591 client metadata. Only the fields below are read; any other member
                of the body is ignored.
            properties:
                client_name:
                    type: string
                    minLength: 2
                    maxLength: 128
                    description: |
                        Display name shown to the user on the consent screen. Letters,
                        digits, marks and a small punctuation set only; single spaces, none
                        leading or trailing; no invisible or control characters. Reserved
                        brand names are rejected.
                    example: Acme Assistant
                redirect_uris:
                    type: array
                    minItems: 1
                    description: |
                        Exact absolute callback URLs. Wildcards are rejected and duplicates
                        are collapsed. The authorization code exchange requires one of these
                        verbatim.
                    items:
                        type: string
                        format: uri
                    example:
                        - 'https://assistant.acme.com/oauth/callback'
                logo_uri:
                    type: string
                    format: uri
                    description: Optional absolute URL of the client logo, shown on the consent screen.
                    example: 'https://assistant.acme.com/logo.png'
                client_uri:
                    type: string
                    format: uri
                    description: Optional absolute URL of the client's home page.
                    example: 'https://assistant.acme.com'
                client_kind:
                    type: string
                    description: |
                        Which lane the client belongs to. Self-declared and never a
                        credential: it selects the permission catalog consent may offer —
                        `mcp` a curated, agent-safe subset, `api` the full product
                        permission set. Defaults to `api` when omitted; an unknown value is
                        rejected.
                    enum:
                        - mcp
                        - api
                    default: api
                    example: mcp
            required:
                - client_name
                - redirect_uris
        OAuthClientRegistrationResponse:
            type: object
            title: OAuth client registration response
            description: |
                The registered client. There is no `client_secret`: registered clients are
                public and authenticate with PKCE only.
            properties:
                client_id:
                    type: string
                    description: Identifier to send to the authorization and token endpoints.
                    example: 5f3a9b2c4d1e0f00112233ab
                client_id_issued_at:
                    type: integer
                    description: Issue time of the client id, as a Unix timestamp in seconds.
                    example: 1756425600
                client_name:
                    type: string
                    description: The registered display name.
                    example: Acme Assistant
                redirect_uris:
                    type: array
                    description: The accepted callback URLs, after de-duplication.
                    items:
                        type: string
                        format: uri
                token_endpoint_auth_method:
                    type: string
                    description: Always `none` — the client is public and proves itself with PKCE.
                    enum:
                        - none
                    example: none
                grant_types:
                    type: array
                    description: Grants this client may use.
                    items:
                        type: string
                        enum:
                            - authorization_code
                            - refresh_token
                response_types:
                    type: array
                    description: Authorization response types this client may request.
                    items:
                        type: string
                        enum:
                            - code
            required:
                - client_id
                - client_id_issued_at
                - client_name
                - redirect_uris
                - token_endpoint_auth_method
                - grant_types
                - response_types
        OAuthTokenRequest:
            type: object
            title: OAuth token request
            description: |
                Form-encoded token request. Which fields are required depends on
                `grant_type`: `authorization_code` needs `code`, `code_verifier` and
                `redirect_uri`; `refresh_token` needs `refresh_token`. `client_id` is
                required for both.
            properties:
                grant_type:
                    type: string
                    description: The grant being used. Any other value returns `unsupported_grant_type`.
                    enum:
                        - authorization_code
                        - refresh_token
                    example: authorization_code
                client_id:
                    type: string
                    description: |
                        The registered client id. It must match the client the code or
                        refresh token was issued to.
                    example: 5f3a9b2c4d1e0f00112233ab
                code:
                    type: string
                    description: |
                        `authorization_code` only. The single-use code the dashboard
                        returned to the callback. Consumed on first use.
                code_verifier:
                    type: string
                    description: |
                        `authorization_code` only. The PKCE verifier whose `S256` hash was
                        sent as `code_challenge` to the authorization endpoint.
                redirect_uri:
                    type: string
                    format: uri
                    description: |
                        `authorization_code` only. Must be byte-identical to the
                        `redirect_uri` the code was issued for.
                refresh_token:
                    type: string
                    description: |
                        `refresh_token` only. The current refresh token, which this call
                        consumes and replaces.
            required:
                - grant_type
                - client_id
        OAuthTokenResponse:
            type: object
            title: OAuth token response
            description: |
                A freshly issued access and refresh token pair. Both grants return the same
                shape. Store the new `refresh_token`: the one you presented is no longer
                valid.
            properties:
                access_token:
                    type: string
                    description: |
                        Product API bearer token (`isoa_` prefix). Send it as
                        `Authorization: Bearer <access_token>`.
                    example: isoa_QeF3nZs8kXbW2pR7vTgY1mL4dC6hJ0aU
                token_type:
                    type: string
                    description: Always `Bearer`.
                    enum:
                        - Bearer
                    example: Bearer
                expires_in:
                    type: integer
                    description: Access token lifetime in seconds (24 hours).
                    example: 86400
                refresh_token:
                    type: string
                    description: |
                        Single-use refresh token replacing the previous one. Use it against
                        `POST /oauth/token` with `grant_type=refresh_token`.
                scope:
                    type: string
                    description: |
                        Space-delimited permissions granted by the user. Present on the
                        authorization code exchange when the consent carried permissions;
                        **omitted on refresh**, where the granted set is unchanged for an API
                        client and re-resolved server-side for an MCP one. Read the
                        authoritative granted set from `GET /` at any time.
                    example: 'ACCOUNT_READ PROJECT_AUDIENCE_READ PROJECT_CAMPAIGN_READ'
            required:
                - access_token
                - token_type
                - expires_in
                - refresh_token
        OAuthError:
            type: object
            title: OAuth error
            description: |
                The OAuth error response shape, returned by the token endpoint (RFC 6749)
                and by the registration endpoint (RFC 7591). `error` is the stable
                machine-readable code; `error_description` is human-readable and may change.
                Which codes an endpoint can return is documented on that endpoint.
            properties:
                error:
                    type: string
                    description: |
                        From `POST /oauth/token`: `invalid_grant` — the code or refresh
                        token is invalid, expired, already used, does not match this client
                        or `redirect_uri`, failed PKCE verification, or the connection
                        lapsed through inactivity. `invalid_client` — the `client_id` is
                        unknown. `unsupported_grant_type` — the `grant_type` is not one
                        this endpoint serves.

                        From `POST /oauth/register`: `invalid_client_metadata` — the body
                        is not a JSON object, `client_name` is missing or invalid,
                        `logo_uri`/`client_uri` is not an absolute URL, or `client_kind` is
                        unknown. `invalid_redirect_uri` — `redirect_uris` is missing, empty,
                        wildcarded or not an exact absolute URL. `client_name_not_allowed` —
                        the name is reserved for a known brand.
                    enum:
                        - invalid_grant
                        - invalid_client
                        - unsupported_grant_type
                        - invalid_client_metadata
                        - invalid_redirect_uri
                        - client_name_not_allowed
                    example: invalid_grant
                error_description:
                    type: string
                    description: Human-readable explanation, for logs and developers.
                    example: Authorization code is invalid or expired.
            required:
                - error
        OAuthProtectedResourceMetadata:
            type: object
            title: Protected resource metadata
            description: RFC 9728 metadata describing this API as an OAuth protected resource.
            properties:
                resource:
                    type: string
                    format: uri
                    description: Canonical identifier of this API, derived from the request host.
                    example: 'https://api.instasent.com'
                authorization_servers:
                    type: array
                    description: Authorization servers that can issue tokens for this resource.
                    items:
                        type: string
                        format: uri
                scopes_supported:
                    type: array
                    description: Every product permission that can appear in a grant.
                    items:
                        type: string
                bearer_methods_supported:
                    type: array
                    description: How the access token may be presented. Only the `Authorization` header is accepted.
                    items:
                        type: string
                        enum:
                            - header
            required:
                - resource
                - authorization_servers
                - scopes_supported
                - bearer_methods_supported
        OAuthAuthorizationServerMetadata:
            type: object
            title: Authorization server metadata
            description: |
                RFC 8414 metadata. Served identically at
                `/.well-known/oauth-authorization-server` and
                `/.well-known/openid-configuration`.
            properties:
                issuer:
                    type: string
                    format: uri
                    description: Issuer identifier, derived from the request host.
                    example: 'https://api.instasent.com'
                authorization_endpoint:
                    type: string
                    format: uri
                    description: |
                        Where to send the user's browser to start the flow. It is on this API
                        host, the same origin as `issuer`, and redirects to the dashboard's
                        consent screen, which is where the user actually authenticates.
                    example: 'https://api.instasent.com/oauth/authorize'
                token_endpoint:
                    type: string
                    format: uri
                    description: Where to exchange the code and rotate refresh tokens.
                    example: 'https://api.instasent.com/oauth/token'
                registration_endpoint:
                    type: string
                    format: uri
                    description: Dynamic Client Registration endpoint.
                    example: 'https://api.instasent.com/oauth/register'
                scopes_supported:
                    type: array
                    description: Every product permission that can appear in a grant.
                    items:
                        type: string
                response_types_supported:
                    type: array
                    description: Only the authorization code response type is supported.
                    items:
                        type: string
                        enum:
                            - code
                grant_types_supported:
                    type: array
                    items:
                        type: string
                        enum:
                            - authorization_code
                            - refresh_token
                code_challenge_methods_supported:
                    type: array
                    description: PKCE is mandatory and only `S256` is accepted.
                    items:
                        type: string
                        enum:
                            - S256
                token_endpoint_auth_methods_supported:
                    type: array
                    description: Clients are public; there is no client secret.
                    items:
                        type: string
                        enum:
                            - none
            required:
                - issuer
                - authorization_endpoint
                - token_endpoint
                - registration_endpoint
                - scopes_supported
                - response_types_supported
                - grant_types_supported
                - code_challenge_methods_supported
                - token_endpoint_auth_methods_supported
    responses:
        OAuthGrantError:
            description: |
                The token request was rejected — an invalid, expired or already used code or
                refresh token, a PKCE or client mismatch, or an unsupported grant type.
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/OAuthError'
                    examples:
                        invalid_grant:
                            summary: Code or refresh token no longer usable
                            value:
                                error: invalid_grant
                                error_description: Refresh token is invalid or has been rotated.
                        unsupported_grant_type:
                            summary: Grant type this endpoint does not serve
                            value:
                                error: unsupported_grant_type
                                error_description: unsupported_grant_type
        WrongRequestError:
            description: You request body is malformed or something went wrong with your request
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ResponseErrorDetail'
        UnauthorizedError:
            description: You are missing or using bad credentials
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ResponseErrorMessage'
        NoFundsError:
            description: You're out of money
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ResponseErrorDetail'
        NotFound:
            description: Resource can't be found
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ResponseErrorDetail'
        RequestTooLarge:
            description: You are sending a large body in your request
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ResponseErrorDetail'
        ValidationError:
            description: Some field of your request body is not valid
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ResponseErrorValidation'
        PlanFeatureError:
            description: The request needs a feature the organization's subscription does not include
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ResponseErrorDetail'
        StateConflictError:
            description: The resource is in a state that does not allow this operation
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ResponseErrorDetail'
        RateLimitError:
            description: You reached your endpoint rate limit
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ResponseErrorMessage'
        InternalError:
            description: Internal server error
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ResponseErrorMessage'
        AnalyticsBadRequest:
            description: |
                The request is malformed against the curated contract — an unknown
                statistic for the family, an out-of-enum filter/group_by value, a
                breached bound, a reserved dimension, or a rejected `compare_to`.
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/AnalyticsError'
                    examples:
                        unknown_statistic:
                            summary: Statistic not in the family catalog
                            value:
                                code: unknown_statistic_for_family
                                message: '"opens" is not a valid statistic for a direct/transactional report.'
                                hint: 'Transactional sends have no open event. Remove "opens" or use a campaign/automation report.'
                        bucket_limit:
                            summary: Histogram bucket cap breached even at the coarsest interval
                            value:
                                code: histogram_bucket_limit_exceeded
                                message: The requested series would produce too many buckets.
                                hint: 'Narrow the timeframe or use a coarser interval. minute=300, hour=300, day=400, week=60, month=24.'
                        compare_rejected:
                            summary: previous_period rejected for a standard campaign
                            value:
                                code: compare_to_not_supported
                                message: Standard campaigns are one-shot and have no previous period.
                                hint: Compare similar campaigns via POST .../analytics/campaign/compare instead.
                        dimension_reserved:
                            summary: group_by dimension reserved until preconditions land
                            value:
                                code: dimension_not_yet_available
                                message: group_by=campaign_variant is not yet available.
                                hint: Use group_by=campaign_option meanwhile.
        AnalyticsForbidden:
            description: The token cannot read this resource, or the project is in `blocked` mode.
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/AnalyticsError'
                    example:
                        code: forbidden_resource
                        message: This token cannot read campaign analytics for this project.
        AnalyticsNotFound:
            description: The requested entity does not exist in this project.
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/AnalyticsError'
                    example:
                        code: entity_not_found
                        message: Campaign 6627f1a2b3c4d5e6f7a8b9c0 not found in this project.
                        resource_id: 6627f1a2b3c4d5e6f7a8b9c0
        AnalyticsRateLimit:
            description: The endpoint rate limit for the current plan/tier was reached.
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/AnalyticsError'
                    example:
                        code: rate_limit_exceeded
                        message: You have reached the analytics rate limit for your plan.
                        plan: lite
                        tier: LITE
                        retry_after: 42
                        panel_url: 'https://app.instasent.com/account/subscription'
        AnalyticsServerError:
            description: The analytics query failed or exceeded its time budget. Fail-closed — no partial results are returned.
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/AnalyticsError'
                    example:
                        code: analytics_timeout
                        message: Query exceeded the analytics time budget. Narrow the timeframe or reduce the compare set and retry.
