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

# Send demo-attended

> Reports that a referred user attended a demo call. Carries the common
subject block and nothing else, on the same reasoning as
`signup-created`.

Part of the [v2 events contract](/api-reference/generic-events/send-event). See also: [signup created](/api-reference/generic-events/signup-created-v2), [purchase created](/api-reference/generic-events/purchase-created-v2), [purchase refunded](/api-reference/generic-events/purchase-refunded-v2).


## OpenAPI

````yaml api-reference/openapi-v2.json POST /v2/events/demo-attended
openapi: 3.1.0
info:
  title: Cello Events API
  version: 2.0.0
  contact:
    name: Cello Support
    url: https://docs.cello.so
  license:
    name: UNLICENSED
    identifier: LicenseRef-Proprietary
  summary: Ingestion of referral lifecycle events from customer products.
  description: |-
    Reports referral lifecycle events to Cello: a referred user signing up,
    attending a demo, paying an invoice, or having a charge refunded.

    This document is the typed v2 contract. Each business event has its own
    endpoint under `/v2/events/`, each with a closed schema, and requests are
    validated strictly: undeclared fields are refused, enums are closed,
    timestamps require an explicit UTC offset, and monetary amounts are
    integers in the currency's minor unit. Every violation in a request is
    reported at once, each with a JSON Pointer to the offending field, so a
    first integration takes one round trip rather than five.

    `POST /events` remains a live frozen alias served by a separate
    implementation. It is not part of this document. New integrations should
    use `v2`.
servers: []
security:
  - BearerAuth: []
tags:
  - name: Events v2
    description: The typed v2 event endpoints. One endpoint per business event.
paths:
  /v2/events/demo-attended:
    post:
      tags:
        - Events v2
      summary: Report an attended demo
      description: |-
        Reports that a referred user attended a demo call. Carries the common
        subject block and nothing else, on the same reasoning as
        `signup-created`.
      operationId: createDemoAttendedEvent
      parameters:
        - $ref: '#/components/parameters/Parameters.IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DemoAttendedRequest'
            examples:
              Only the required fields:
                summary: Only the required fields
                value:
                  ucc: ucc_7f3d9a
                  occurredAt: '2026-08-05T14:14:34Z'
                  newUser:
                    id: usr_1042
      responses:
        '202':
          description: >-
            The event was validated and durably written to the event stream.


            This says the event was accepted, not that attribution or reward
            accrual

            has run. Those happen asynchronously afterwards.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AcceptedResponse'
        '422':
          description: >-
            The body parsed but broke the contract. Every violation is listed,
            each

            with a pointer to the field, so one round trip is enough to fix it.
            No

            event is written.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
components:
  parameters:
    Parameters.IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: |-
        An opaque key identifying this submission attempt, so that a retry can
        be recognised as the same event.

        A repeat of the same request, for the same tenant and event type, within
        72 hours returns the original `202` — the same `eventId`, `receivedAt`,
        and `warnings` — and does not create a second event. A repeat that uses
        the same key with a different body is refused with `409` and
        `IDEMPOTENCY_CONFLICT`. After 72 hours the same key is a new submission.

        The same key may be sent in the `idempotencyKey` body field instead. If
        both are sent they must be equal after trim, otherwise the request is
        refused with `IDEMPOTENCY_KEY_CONFLICT` — there is no precedence rule,
        because guessing which one the caller meant is worse than saying so.

        Leading and trailing whitespace is trimmed on both forms before the key
        is stored or compared. The 1–255 character limit applies to that trimmed
        value; a key that is empty after trim is refused.
      schema:
        type: string
        minLength: 1
        maxLength: 255
  schemas:
    DemoAttendedRequest:
      type: object
      required:
        - ucc
        - occurredAt
        - newUser
      properties:
        ucc:
          type: string
          minLength: 1
          maxLength: 255
          description: The unique Cello code that attributes this event to a referral.
          examples:
            - ucc_7f3d9a
        occurredAt:
          $ref: '#/components/schemas/Timestamp'
        newUser:
          $ref: '#/components/schemas/NewUser'
        idempotencyKey:
          type: string
          minLength: 1
          maxLength: 255
          description: >-
            The body form of the `Idempotency-Key` header. See that header for
            the

            full description. Leading and trailing whitespace is trimmed; the
            1–255

            character limit applies after trim.
          examples:
            - purchase-inv_456
      unevaluatedProperties: false
      title: Demo attended request
    AcceptedResponse:
      type: object
      required:
        - eventId
        - receivedAt
      properties:
        eventId:
          type: string
          format: uuid
          description: >-
            A UUIDv7 assigned by Cello. It is the identifier of the stored event

            (`id` on the stream record). Quote it in support conversations.
            Never

            accepted from a request body.
          examples:
            - 019205ff-8e00-7cc0-9f2e-2b4c1d5a6e70
        receivedAt:
          type: string
          format: date-time
          description: |-
            When Cello received the event, from our clock. Compare it with your
            `occurredAt` to see the skew we saw. Never accepted from a request
            body.
          examples:
            - '2026-08-05T14:14:35.123Z'
        warnings:
          type: array
          items:
            $ref: '#/components/schemas/Warning'
          minItems: 1
          description: |-
            Non-fatal observations about an accepted event. Absent entirely when
            there is nothing to report, rather than present and empty.

            A warning never changes the status code and never changes how the
            event is processed: an event carrying warnings is stored, attributed
            and rewarded exactly like one without.

            New codes may appear at any time without a version bump, so treat
            an unrecognised code as informational rather than failing on it.
      unevaluatedProperties: false
      title: Accepted event
    Problem:
      type: object
      required:
        - type
        - title
        - status
        - detail
        - code
      properties:
        type:
          type: string
          format: uri
          description: |-
            A stable identifier for the class of problem. It doubles as a
            documentation URL, but treat it as an identifier: compare it, do not
            fetch it.
          examples:
            - https://docs.cello.so/api-reference/errors/validation-error
        title:
          type: string
          description: A short, unchanging summary of the problem class.
          examples:
            - Validation error
        status:
          type: integer
          description: The HTTP status code, repeated here so a logged body stands alone.
          examples:
            - 422
        detail:
          type: string
          description: What went wrong with this specific request.
        instance:
          type: string
          description: |-
            The request identifier. Quote it in a support ticket and we can find
            the exact request in our logs.
          examples:
            - Rk8xMjM0NTY3ODkw
        code:
          type: string
          enum:
            - MALFORMED_JSON
            - UNAUTHORIZED
            - UNKNOWN_EVENT_TYPE
            - PAYLOAD_TOO_LARGE
            - VALIDATION_ERROR
            - IDEMPOTENCY_CONFLICT
            - IDEMPOTENCY_KEY_CONFLICT
            - INTERNAL_ERROR
            - SERVICE_UNAVAILABLE
          description: >-
            The machine-readable Cello error code. Branch on this rather than on

            `title` or `detail`, which are written for people and may be
            reworded.
          examples:
            - VALIDATION_ERROR
        errors:
          type: array
          items:
            $ref: '#/components/schemas/Violation'
          description: |-
            Every violation in the request, not just the first, so one round
            trip is enough to fix an integration. Capped at 20 entries, with
            `detail` saying so when the list was truncated.
      unevaluatedProperties: false
      description: |-
        An RFC 9457 problem document, served as `application/problem+json`. Read
        `code` to branch in software and `detail` to show a human what to fix.
      title: Problem
    Timestamp:
      type: string
      format: rfc3339-offset
      description: |-
        When the event happened in your system, as an RFC 3339 date-time with an
        explicit UTC offset: either `Z` or `±HH:MM`.

        The offset is mandatory and the `T` separator is mandatory. A local time
        with no offset is ambiguous by exactly the amount that makes referral
        attribution wrong, and `2022-10-05 14:14:34` is refused even though v1
        tolerated it.

        A syntactically valid timestamp is never refused for being implausible,
        however far it is from our clock. If it looks wrong we accept the event
        and tell you so in `warnings`, because dropping a real purchase over a
        misconfigured clock is the worse failure.
      title: RFC 3339 timestamp with an explicit offset
      examples:
        - '2026-08-05T16:14:34+02:00'
        - '2026-08-05T14:14:34Z'
    NewUser:
      type: object
      required:
        - id
      properties:
        id:
          type: string
          minLength: 1
          maxLength: 255
          description: |-
            Your own identifier for the user. Stable across events, because it
            is how a later purchase is tied to an earlier signup.
          examples:
            - usr_1042
        email:
          type: string
          minLength: 1
          maxLength: 320
          format: email
          description: |-
            Optional. Omit the key entirely if you do not have it or cannot
            share it; do not send an empty string or `null`.
          examples:
            - ada@example.com
        name:
          type: string
          minLength: 1
          maxLength: 255
          examples:
            - Ada Lovelace
        organizationId:
          type: string
          minLength: 1
          maxLength: 255
          description: Your identifier for the organisation the user belongs to.
          examples:
            - org_88
      unevaluatedProperties: false
      description: |-
        The referred user this event is about. Only `id` is required; the rest
        improves what Cello can show in the portal.
      title: Referred user
    Warning:
      type: object
      required:
        - code
        - detail
      properties:
        code:
          type: string
          description: |-
            `OCCURRED_AT_IN_FUTURE` when `occurredAt` is more than five minutes
            ahead of our clock, `OCCURRED_AT_BACKDATED` when it is older than
            the backdating window, and `FIELD_NOT_YET_HONOURED` for a field that
            is part of the contract but not yet processed. The idempotency key
            is honoured and does not produce that warning.
          examples:
            - OCCURRED_AT_BACKDATED
        detail:
          type: string
          description: A human-readable sentence explaining the warning.
        pointer:
          type: string
          description: |-
            A JSON Pointer to the field the warning concerns, when it concerns
            one.
          examples:
            - '#/occurredAt'
      unevaluatedProperties: false
      title: Warning
    Violation:
      type: object
      required:
        - pointer
        - code
        - detail
      properties:
        pointer:
          type: string
          description: A JSON Pointer to the offending field, relative to the request body.
          examples:
            - '#/currency'
        code:
          type: string
          enum:
            - REQUIRED
            - INVALID_TYPE
            - INVALID_FORMAT
            - INVALID_ENUM
            - OUT_OF_RANGE
            - UNKNOWN_FIELD
          examples:
            - INVALID_FORMAT
        detail:
          type: string
          description: |-
            A sentence naming what was wrong and what would be right. For an
            unknown field that looks like a v1 field name, it names the v2 field
            you probably meant.
          examples:
            - currency must be an uppercase ISO 4217 code, for example EUR.
      unevaluatedProperties: false
      title: Violation
  securitySchemes:
    BearerAuth:
      type: http
      scheme: Bearer

````