> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chatsailer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Origin report

> Contacts, conversations, deals created, deals won and won value per origin, with the ratios between them. Use it to see which channels and campaigns produce revenue, not only volume.

`group_by` picks what a row is. `channel` → `source` → `campaign` → `adset` → `ad` is a drill-down: ask for the next level and name the parent rows' `key`s in `channel`, `source`, `campaign` and `adset`. `capture_source`, `entry_link`, `whatsapp_number` and `utm_campaign` are flat views.

**Attribution.** Each count uses the origin of the record itself: contacts by the contact's origin, conversations by the conversation's, deals by the deal's. The response states the rule in `attribution_rule`. Records without an origin are one `unknown` row, never dropped.

`date_basis` is `contact_created` (a cohort of contacts created in the window) or `deal_created` (each record by its own creation date). `captured_only` leaves out origins that were inferred, estimated or repaired.

Results are bounded: the top `limit` rows by `sort_by`, then one `other` row for the rest. Only records the token's holder may see are counted; a workspace token counts the whole workspace. The window defaults to the 30 days ending now and spans at most 366 days. Needs the `contacts:read`, `deals:read` and `conversations:read` scopes.



## OpenAPI

````yaml /api-reference/openapi.json get /v1/reports/origin
openapi: 3.1.0
info:
  description: >-
    The Sailer REST API for CRM, conversations, and campaigns.


    **Authentication.** Send a token as `Authorization: Bearer <token>`. A
    workspace

    token (`sk_live_...`) is created by a workspace administrator and its secret
    is

    shown once. An OAuth access token (`oat_live_...`) acts on behalf of the
    person

    who authorized it and sees only what they can see. Either way the workspace
    is

    implied by the token, so no tenant header is needed. Each token carries
    scopes;

    `GET /v1/me` lists them.


    **Pagination.** Collections return `{data, meta, links}`. Pass `links.next`
    back

    as `cursor` until it is `null`. Cursors are opaque and only valid for the
    query

    that produced them.


    **Errors.** Every non-2xx response is `{"error": {...}}` with a coarse
    `type`, a

    stable `code` and the offending `param`. Quote `error.request_id` (also the

    `X-Request-ID` header) when contacting support.


    **Retries.** Send an `Idempotency-Key` header on any write to make retrying
    it

    safe for 24 hours.


    **Rate limits.** Limits are per token. Every response carries

    `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`; a
    `429`

    also carries `Retry-After`.


    **Custom fields.** Workspace-defined fields appear under `custom_fields`,
    keyed

    by field key. `GET /v1/fields` lists every field on a resource, built-in and

    custom.
  title: Sailer API
  version: 1.0.0
servers:
  - url: https://api.chatsailer.com
security:
  - SailerApiToken: []
tags:
  - description: Token introspection and workspace capabilities.
    name: Meta
  - description: >-
      People you talk to. Called *leads* in older Sailer surfaces; `contact` is
      the current name.
    name: Contacts
  - description: Companies that contacts belong to (B2B accounts).
    name: Organizations
  - description: Opportunities moving through a pipeline.
    name: Deals
  - description: Pipelines and their stages.
    name: Pipelines
  - description: Field definitions, including custom fields.
    name: Fields
  - description: Free-text annotations on CRM records.
    name: Notes
  - description: Scheduled and logged CRM activities.
    name: Activities
  - description: Per-entity label catalogs.
    name: Tags
  - description: Members of the workspace, for assigning records.
    name: Users
  - description: Message threads with contacts, across every channel.
    name: Conversations
  - description: Outbound campaigns and their participants.
    name: Campaigns
  - description: Named contact lists campaigns draw from.
    name: Audiences
  - description: The connections you talk to contacts through.
    name: Channels
  - description: Pre-approved WhatsApp message templates.
    name: Templates
  - description: >-
      Aggregated numbers for your own BI, computed by the same code as the
      Sailer app's reports.
    name: Reports
  - description: >-
      Keeping another system in step with a workspace: the deletion feed.
      Changes are read from each resource's list with `updated_since`.
    name: Sync
  - description: >-
      Business outcomes your systems report — a sale closed, a meeting held —
      that your agreement may charge a success fee on. Append-only: a refund is
      a reversal, never an edit. Needs the `success_events:read` /
      `success_events:write` scopes.
    name: Success Events
paths:
  /v1/reports/origin:
    get:
      tags:
        - Reports
      summary: Origin report
      description: >-
        Contacts, conversations, deals created, deals won and won value per
        origin, with the ratios between them. Use it to see which channels and
        campaigns produce revenue, not only volume.


        `group_by` picks what a row is. `channel` → `source` → `campaign` →
        `adset` → `ad` is a drill-down: ask for the next level and name the
        parent rows' `key`s in `channel`, `source`, `campaign` and `adset`.
        `capture_source`, `entry_link`, `whatsapp_number` and `utm_campaign` are
        flat views.


        **Attribution.** Each count uses the origin of the record itself:
        contacts by the contact's origin, conversations by the conversation's,
        deals by the deal's. The response states the rule in `attribution_rule`.
        Records without an origin are one `unknown` row, never dropped.


        `date_basis` is `contact_created` (a cohort of contacts created in the
        window) or `deal_created` (each record by its own creation date).
        `captured_only` leaves out origins that were inferred, estimated or
        repaired.


        Results are bounded: the top `limit` rows by `sort_by`, then one `other`
        row for the rest. Only records the token's holder may see are counted; a
        workspace token counts the whole workspace. The window defaults to the
        30 days ending now and spans at most 366 days. Needs the
        `contacts:read`, `deals:read` and `conversations:read` scopes.
      operationId: get_origin_report
      parameters:
        - description: Start of the window, inclusive. Needs a timezone offset.
          in: query
          name: since
          required: false
          schema:
            anyOf:
              - format: date-time
                type: string
              - type: 'null'
            description: Start of the window, inclusive. Needs a timezone offset.
            title: Since
        - description: End of the window, exclusive. Defaults to now.
          in: query
          name: until
          required: false
          schema:
            anyOf:
              - format: date-time
                type: string
              - type: 'null'
            description: End of the window, exclusive. Defaults to now.
            title: Until
        - description: What a row stands for.
          in: query
          name: group_by
          required: false
          schema:
            $ref: '#/components/schemas/OriginGroupBy'
            default: channel
            description: What a row stands for.
        - description: Which creation date puts a record in the window.
          in: query
          name: date_basis
          required: false
          schema:
            $ref: '#/components/schemas/OriginDateBasis'
            default: contact_created
            description: Which creation date puts a record in the window.
        - description: >-
            Leave out inferred (backfill), estimated (lookback) and repaired
            origins. Records without an origin stay in their own row.
          in: query
          name: captured_only
          required: false
          schema:
            default: false
            description: >-
              Leave out inferred (backfill), estimated (lookback) and repaired
              origins. Records without an origin stay in their own row.
            title: Captured Only
            type: boolean
        - in: query
          name: sort_by
          required: false
          schema:
            $ref: '#/components/schemas/OriginSortBy'
            default: contacts
        - description: Top rows kept; the rest are summed into one `other` row.
          in: query
          name: limit
          required: false
          schema:
            default: 25
            description: Top rows kept; the rest are summed into one `other` row.
            maximum: 100
            minimum: 1
            title: Limit
            type: integer
        - description: 'Drill-down parent: a channel row''s `key`.'
          in: query
          name: channel
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: 'Drill-down parent: a channel row''s `key`.'
            title: Channel
        - description: 'Drill-down parent: a source row''s `key`.'
          in: query
          name: source
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: 'Drill-down parent: a source row''s `key`.'
            title: Source
        - description: 'Drill-down parent: a campaign row''s `key`.'
          in: query
          name: campaign
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: 'Drill-down parent: a campaign row''s `key`.'
            title: Campaign
        - description: 'Drill-down parent: an ad set row''s `key`.'
          in: query
          name: adset
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: 'Drill-down parent: an ad set row''s `key`.'
            title: Adset
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OriginReportResponse'
          description: Successful Response
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            X-Request-ID:
              $ref: '#/components/headers/X-Request-ID'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
      security:
        - SailerApiToken: []
components:
  schemas:
    OriginGroupBy:
      description: >-
        What a row of the report stands for.


        `channel`, `source`, `campaign`, `adset` and `ad` are the levels of a
        drill-down:

        ask for the next level with the parent rows' keys as filters. The other

        groupings are flat views of the same origins.
      enum:
        - channel
        - source
        - campaign
        - adset
        - ad
        - capture_source
        - entry_link
        - whatsapp_number
        - utm_campaign
      title: OriginGroupBy
      type: string
    OriginDateBasis:
      enum:
        - contact_created
        - deal_created
      title: OriginDateBasis
      type: string
    OriginSortBy:
      enum:
        - contacts
        - conversations
        - deals
        - won_deals
        - won_value
      title: OriginSortBy
      type: string
    OriginReportResponse:
      properties:
        attribution_rule:
          additionalProperties:
            type: string
          description: >-
            How each count is credited to an origin. Show it next to the
            numbers.
          title: Attribution Rule
          type: object
        captured_only:
          title: Captured Only
          type: boolean
        date_basis:
          $ref: '#/components/schemas/OriginDateBasis'
        date_basis_rule:
          additionalProperties:
            type: string
          title: Date Basis Rule
          type: object
        filters:
          additionalProperties:
            type: string
          description: The parent keys the rows are restricted to.
          title: Filters
          type: object
        group_by:
          $ref: '#/components/schemas/OriginGroupBy'
        limit:
          title: Limit
          type: integer
        next_group_by:
          anyOf:
            - $ref: '#/components/schemas/OriginGroupBy'
            - type: 'null'
          description: >-
            The next level of the drill-down hierarchy; null at the last level
            and for flat groupings.
        object:
          const: origin_report
          default: origin_report
          title: Object
          type: string
        period:
          $ref: '#/components/schemas/OriginPeriod'
        rows:
          description: >-
            At most `limit` origin rows ordered by `sort_by`, then the remainder
            row, then the unknown row.
          items:
            $ref: '#/components/schemas/OriginRow'
          title: Rows
          type: array
        sort_by:
          $ref: '#/components/schemas/OriginSortBy'
        totals:
          $ref: '#/components/schemas/OriginMetrics'
          description: Every row, the remainder and the unknown row summed.
        value_rule:
          additionalProperties:
            type: string
          description: What won value is and which currency it is in.
          title: Value Rule
          type: object
      required:
        - period
        - group_by
        - next_group_by
        - date_basis
        - captured_only
        - sort_by
        - limit
        - filters
        - attribution_rule
        - date_basis_rule
        - value_rule
        - totals
        - rows
      title: OriginReportResponse
      type: object
    OriginPeriod:
      properties:
        end:
          description: Exclusive.
          format: date-time
          title: End
          type: string
        start:
          description: Inclusive.
          format: date-time
          title: Start
          type: string
      required:
        - start
        - end
      title: OriginPeriod
      type: object
    OriginRow:
      properties:
        contacts:
          title: Contacts
          type: integer
        conversation_rate:
          anyOf:
            - type: number
            - type: 'null'
          description: Conversations per contact, in percent. Null without contacts.
          title: Conversation Rate
        conversations:
          title: Conversations
          type: integer
        deal_rate:
          anyOf:
            - type: number
            - type: 'null'
          description: Deals per contact, in percent. Null without contacts.
          title: Deal Rate
        deals:
          description: Deals created.
          title: Deals
          type: integer
        deals_value:
          description: Sum of the value of every deal created, open, won or lost.
          title: Deals Value
          type: number
        drillable:
          description: True when the next level of the hierarchy can be asked for.
          title: Drillable
          type: boolean
        groups:
          anyOf:
            - type: integer
            - type: 'null'
          description: 'On the remainder row: how many groups it sums.'
          title: Groups
        key:
          description: >-
            Stable key of the row. Send it back as the parent filter to drill
            down. `_none` is the bucket of origins that do not carry this
            dimension (an ad set on a campaign tracked only by UTM); `_unknown`
            is the record without a recorded origin; `_other` is the remainder
            row.
          title: Key
          type: string
        kind:
          $ref: '#/components/schemas/OriginRowKind'
        label:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            Display name when the ad catalog, the UTMs or an entry link carry
            one. Null for channel, source and capture source (the client
            translates the key) and for the `unknown` and `other` rows
            (translate by `kind`).
          title: Label
        win_rate:
          anyOf:
            - type: number
            - type: 'null'
          description: Won deals per deal created, in percent. Null without deals.
          title: Win Rate
        won_deals:
          title: Won Deals
          type: integer
        won_per_contact_rate:
          anyOf:
            - type: number
            - type: 'null'
          description: Won deals per contact, in percent. Null without contacts.
          title: Won Per Contact Rate
        won_value:
          description: Sum of the value of the won deals.
          title: Won Value
          type: number
      required:
        - contacts
        - conversations
        - deals
        - deals_value
        - won_deals
        - won_value
        - conversation_rate
        - deal_rate
        - win_rate
        - won_per_contact_rate
        - kind
        - key
        - label
        - drillable
      title: OriginRow
      type: object
    OriginMetrics:
      description: The funnel of one row. Each count uses the origin of the record itself.
      properties:
        contacts:
          title: Contacts
          type: integer
        conversation_rate:
          anyOf:
            - type: number
            - type: 'null'
          description: Conversations per contact, in percent. Null without contacts.
          title: Conversation Rate
        conversations:
          title: Conversations
          type: integer
        deal_rate:
          anyOf:
            - type: number
            - type: 'null'
          description: Deals per contact, in percent. Null without contacts.
          title: Deal Rate
        deals:
          description: Deals created.
          title: Deals
          type: integer
        deals_value:
          description: Sum of the value of every deal created, open, won or lost.
          title: Deals Value
          type: number
        win_rate:
          anyOf:
            - type: number
            - type: 'null'
          description: Won deals per deal created, in percent. Null without deals.
          title: Win Rate
        won_deals:
          title: Won Deals
          type: integer
        won_per_contact_rate:
          anyOf:
            - type: number
            - type: 'null'
          description: Won deals per contact, in percent. Null without contacts.
          title: Won Per Contact Rate
        won_value:
          description: Sum of the value of the won deals.
          title: Won Value
          type: number
      required:
        - contacts
        - conversations
        - deals
        - deals_value
        - won_deals
        - won_value
        - conversation_rate
        - deal_rate
        - win_rate
        - won_per_contact_rate
      title: OriginMetrics
      type: object
    ApiErrorResponse:
      description: Every non-2xx response on the public API has this body.
      properties:
        error:
          $ref: '#/components/schemas/ApiError'
      required:
        - error
      title: ApiErrorResponse
      type: object
    OriginRowKind:
      enum:
        - origin
        - other
        - unknown
      title: OriginRowKind
      type: string
    ApiError:
      properties:
        code:
          description: Stable, machine-readable identifier.
          title: Code
          type: string
        detail:
          anyOf:
            - items:
                $ref: '#/components/schemas/ErrorDetail'
              type: array
            - type: 'null'
          default: null
          description: Per-field errors for validation failures.
          title: Detail
        documentation_url:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          title: Documentation Url
        message:
          description: Human-readable explanation.
          title: Message
          type: string
        param:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          description: Request field the error refers to, if any.
          title: Param
        request_id:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          description: >-
            Echoes the X-Request-ID response header. Quote it in support
            requests.
          title: Request Id
        type:
          $ref: '#/components/schemas/ErrorType'
      required:
        - type
        - code
        - message
      title: ApiError
      type: object
    ErrorDetail:
      properties:
        code:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          title: Code
        field:
          description: Dotted path to the offending field.
          title: Field
          type: string
        message:
          title: Message
          type: string
      required:
        - field
        - message
      title: ErrorDetail
      type: object
    ErrorType:
      description: Coarse class of failure. Stable; new codes may appear under a type.
      enum:
        - invalid_request_error
        - authentication_error
        - permission_error
        - not_found_error
        - conflict_error
        - rate_limit_error
        - api_error
      title: ErrorType
      type: string
  headers:
    X-RateLimit-Limit:
      description: Requests allowed in the current window on this token.
      schema:
        type: integer
    X-RateLimit-Remaining:
      description: Requests left in the current window.
      schema:
        type: integer
    X-RateLimit-Reset:
      description: Seconds until the window resets and the allowance refills.
      schema:
        type: integer
    X-Request-ID:
      description: Unique id for this request. Quote it to support.
      schema:
        type: string
    Retry-After:
      description: Seconds to wait before retrying.
      schema:
        type: integer
  responses:
    BadRequest:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
      description: A query parameter, path segment or header is invalid.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/X-Request-ID'
    Unauthorized:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
      description: The token is missing, unknown, revoked or expired.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/X-Request-ID'
    Forbidden:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
      description: >-
        The token lacks a required scope, or the workspace's CRM write policy
        does not allow this change.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/X-Request-ID'
    RateLimited:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
      description: Too many requests on this token. Retry after the delay.
      headers:
        Retry-After:
          $ref: '#/components/headers/Retry-After'
        X-RateLimit-Limit:
          $ref: '#/components/headers/X-RateLimit-Limit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/X-RateLimit-Remaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/X-RateLimit-Reset'
        X-Request-ID:
          $ref: '#/components/headers/X-Request-ID'
    InternalError:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
      description: Something went wrong on our side. Quote `error.request_id` to support.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/X-Request-ID'
  securitySchemes:
    SailerApiToken:
      description: >-
        A workspace API token (`sk_live_...`) or an OAuth access token
        (`oat_live_...`). Create workspace tokens in Settings > API. Send either
        as `Authorization: Bearer <token>`.
      scheme: bearer
      type: http

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.