openapi: 3.0.1
info:
  title: AttentPublicAPI
  version: '2024-05-17T21:09:13Z'
servers:
  - url: https://api.attent.app/v1
paths:
  /leads/create:
    post:
      description: 'Creates a new lead'
      requestBody:
        description: 'Lead to create. Phone number must be unique.'
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/leadItem'
        required: true
      responses:
        200:
          description: 'A lead has been successfully created.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: 'Returns `"Lead created successfully."` if the lead was created successfully.'
                  id:
                    type: string
                    description: 'The ID of the lead that was created.'
                example:
                  message: 'Lead created successfully.'
        400:
          description: 'Bad request.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  id:
                    type: string
                    description: 'When the phone number matches an existing lead, the ID of that lead.'
                example:
                  message: 'Lead already exists.'
                  id: 'existing-lead-id'
        403:
          $ref: '#/components/responses/403'
        429:
          description: 'The maximum number of leads has been reached for this month.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Max leads reached for this month.'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []
  /leads/{leadId}:
    get:
      description: 'Fetches details of a specific lead. Returns 404 if the lead has been deleted; use `GET /leads` to enumerate deleted leads (which surface with `deleted: true`) for incremental sync.'
      parameters:
        - name: leadId
          in: path
          required: true
          schema:
            type: string
          description: 'The ID of the lead to fetch'
      responses:
        200:
          description: 'Lead details fetched successfully.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    description: 'Stable unique identifier for the lead.'
                  firstName:
                    type: string
                    description: 'The first name of the lead.'
                  lastName:
                    type: string
                    description: 'The last name of the lead.'
                  phone:
                    type: string
                    description: 'The phone number of the lead. This will be in the format `+15555555555`.'
                  email:
                    type: string
                    description: 'The email address of the lead, if available.'
                  customerProfile:
                    type: string
                    description: 'The name of the customer profile to which this lead belongs.'
                  createdAt:
                    type: string
                    format: date-time
                    description: 'The date and time when the lead was created.'
                  updatedAt:
                    type: string
                    format: date-time
                    description: 'The date and time when the lead was last modified. Useful for cache freshness checks.'
                  additionalInfo:
                    type: object
                    description: 'An optional key-value object containing additional information stored with the lead. This includes any context from when the lead was created, along with any variables that Apten has stored for the lead.'
                  hasReplied:
                    type: boolean
                    description: 'Indicates whether the lead has replied to any messages from Apten.'
                  hasRepliedEmail:
                    type: boolean
                    description: 'Indicates whether the lead has replied to any emails from Apten.'
                  hasAiCall:
                    type: boolean
                    description: 'Indicates whether the lead has spoken with Apten on a phone call.'
                  isHot:
                    type: boolean
                    description: 'Indicates whether the lead is considered "hot" by Apten.'
                  isQualified:
                    type: boolean
                    description: 'Indicates whether the lead is considered "qualified" by Apten.'
                  hasTransferCall:
                    type: boolean
                    description: 'Indicates whether Apten classified the lead as having had a successful transfer call.'
                  isUnsubscribed:
                    type: boolean
                    description: 'Indicates whether the lead has unsubscribed from Apten.'
                  isAiOn:
                    type: boolean
                    description: 'Indicates whether Apten is turned on for the lead.'
                  tags:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          description: 'The ID of the tag.'
                        name:
                          type: string
                          description: 'The name of the tag.'
                        condition:
                          type: string
                          description: 'The condition defined for the tag.'
                    description: 'An array of tags associated with the lead.'
                  summary:
                    type: string
                    description: 'A summary of the conversation with the lead.'
                  externalLeadId:
                    type: string
                    description: 'The ID of the lead in an external CRM (Salesforce, HubSpot, Dynamics, or Close), if the lead is linked to one.'
                example:
                  id: 'room_abc'
                  firstName: 'John'
                  lastName: 'Doe'
                  phone: '+15555555555'
                  email: 'john.doe@example.com'
                  customerProfile: 'My Customer Profile'
                  additionalInfo:
                    key1: 'value1'
                    key2: 'value2'
                  createdAt: '2024-05-17T21:09:13Z'
                  updatedAt: '2024-05-18T10:30:00Z'
                  hasReplied: true
                  hasRepliedEmail: false
                  hasAiCall: true
                  isHot: true
                  isQualified: false
                  hasTransferCall: true
                  isUnsubscribed: false
                  isAiOn: true
                  tags:
                    - id: 'tag1'
                      name: 'Tag 1'
                      condition: 'Condition 1'
                    - id: 'tag2'
                      name: 'Tag 2'
                      condition: 'Condition 2'
                  summary: 'Summary of the conversation with the lead.'
                  externalLeadId: '00Q5g00000ABCDEAA1'
        404:
          description: 'Lead not found.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Lead not found.'
        403:
          $ref: '#/components/responses/403'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []
    delete:
      description: 'Deletes a specific lead'
      parameters:
        - name: leadId
          in: path
          required: true
          schema:
            type: string
          description: 'The ID of the lead to delete'
      responses:
        200:
          description: 'Lead deleted successfully.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: 'Returns `"Lead deletion initiated successfully"` when the lead deletion process begins.'
                  leadId:
                    type: string
                    description: 'The ID of the lead that was deleted.'
                example:
                  message: 'Lead deletion initiated successfully'
                  leadId: '771c3bc8-853c-4955-b4c5-2120ff6aa488'
        404:
          description: 'Lead not found.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Resource not found: Lead'
        403:
          $ref: '#/components/responses/403'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []
    patch:
      description: 'Updates properties of a specific lead'
      parameters:
        - name: leadId
          in: path
          required: true
          schema:
            type: string
          description: 'The ID of the lead to update'
      requestBody:
        description: 'Fields to update on the lead. All fields are optional.'
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  description: 'The email address of the lead. Must be a valid email format.'
                timeZone:
                  type: string
                  description: 'The timezone of the lead in tz database format (e.g. America/New_York).'
                state:
                  type: string
                  description: 'The US state or Canadian province for the lead. Accepts the full name or two-letter abbreviation and is stored as the canonical two-letter code. Unrecognized values are ignored.'
                  example: 'CA'
                additionalInfo:
                  type: object
                  nullable: true
                  description: 'Key-value object to merge with existing additionalInfo. Set a value to null to remove that key.'
                  additionalProperties: true
                followUpConfig:
                  type: object
                  description: 'Follow-up schedule configuration. Keys are step numbers (1, 2, 3, etc.). Each step must include `channel` and either `delayValue` + `delayUnit` (preferred) or legacy `hours` — not both.'
                  additionalProperties:
                    type: object
                    properties:
                      delayValue:
                        type: number
                        description: 'Delay until this follow-up step. Must be greater than 0. For delayUnit "minutes", must be a positive integer between 1 and 60.'
                      delayUnit:
                        type: string
                        enum:
                          - hours
                          - minutes
                        description: 'Unit for delayValue. Value must be either "hours" or "minutes". Use with delayValue instead of legacy hours.'
                      hours:
                        type: number
                        description: 'Hours until this follow-up step. Must be greater than 0. Deprecated — use delayValue with delayUnit instead. Cannot be sent alongside delayValue or delayUnit.'
                      channel:
                        type: string
                        description: 'Channel for this follow-up. Each channel requires specific features to be enabled on the customer profile.'
                        enum:
                          - text
                          - call
                          - email
                      voicemailEnabled:
                        type: boolean
                        description: 'Whether to leave a voicemail if the call is not answered. Only valid for channel "call".'
                      strategyIds:
                        description: 'Strategy IDs to use for this follow-up step. Use "auto" for automatic selection, or provide an array of specific strategy IDs.'
                        oneOf:
                          - type: string
                            enum:
                              - auto
                          - type: array
                            items:
                              type: string
                    required:
                      - channel
                followUpStep:
                  type: integer
                  description: 'The follow-up step to schedule. Use positive integers (1, 2, 3, ...) for specific steps, -1 for STOPPED, or -2 for LONG_TERM.'
                useStrategiesForFollowUps:
                  type: boolean
                  description: 'Enable strategy-based follow-ups for this lead. Must be true for strategyIds in followUpConfig steps to take effect.'
              example:
                email: 'newemail@example.com'
                timeZone: 'America/Los_Angeles'
                state: 'CA'
                additionalInfo:
                  newField: 'newValue'
                  existingField: 'updatedValue'
                  fieldToRemove: null
                followUpConfig:
                  '1':
                    delayValue: 24
                    delayUnit: 'hours'
                    channel: 'text'
                    strategyIds: 'auto'
                  '2':
                    delayValue: 30
                    delayUnit: 'minutes'
                    channel: 'email'
                  '3':
                    delayValue: 168
                    delayUnit: 'hours'
                    channel: 'call'
                    voicemailEnabled: true
                    strategyIds:
                      - '550e8400-e29b-41d4-a716-446655440000'
                followUpStep: 1
                useStrategiesForFollowUps: true
      responses:
        200:
          description: 'Lead updated successfully.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: 'Returns "Lead updated successfully." when the update completes.'
                example:
                  message: 'Lead updated successfully.'
        400:
          description: 'Bad request - validation error.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Invalid format for field: email must be a valid email address'
        403:
          $ref: '#/components/responses/403'
        404:
          description: 'Lead not found.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Resource not found: Lead'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []
  /leads/{leadId}/messages:
    get:
      description: 'Fetches messages sent to and received from a lead'
      parameters:
        - name: leadId
          in: path
          required: true
          schema:
            type: string
          description: 'The ID of the lead to fetch messages for.'
      responses:
        200:
          description: 'Messages fetched successfully.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  messages:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          description: 'The unique message record ID. For SMS messages, it matches `data.messageId` in the corresponding message webhook.'
                        content:
                          type: object
                          properties:
                            text:
                              type: string
                              description: 'The message content.'
                        createdAt:
                          type: string
                          format: date-time
                          description: 'The date and time when the message was sent or received.'
                        owner:
                          type: string
                          description: 'The owner of the message. This can be either `"lead"` or `"user"`.'
                        isAiGenerated:
                          type: boolean
                          description: '`true` for messages drafted by Apten AI; `false` for human-sent or inbound messages.'
                      required:
                        - id
                      description: 'An array of messages sent to and received from the lead.'
                example:
                  messages:
                    - id: '8f3a2c4d-1b6e-4a91-9c72-2e0f5d8b7a44'
                      content:
                        text: 'Hello, how can I help you?'
                      createdAt: '2024-05-17T21:09:13Z'
                      owner: 'user'
                      isAiGenerated: true
                    - id: '2c99773d-96b1-466c-9638-63dc81cdba7a'
                      content:
                        text: 'I would like to schedule a meeting.'
                      createdAt: '2024-05-17T21:09:13Z'
                      owner: 'lead'
                      isAiGenerated: false
      security:
        - api_key: []
  /leads/{leadId}/turnOff:
    post:
      description: 'Turns Apten off for a lead'
      parameters:
        - name: leadId
          in: path
          required: true
          schema:
            type: string
          description: 'The ID of the lead to turn off Apten for.'
      responses:
        200:
          description: 'Apten has successfully been turned off for the lead.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: 'Returns `"Lead turned off successfully."` if Apten was turned off for the lead.'
                example:
                  message: 'Lead turned off successfully.'
        400:
          description: 'Bad request.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Missing field: leadId'
        404:
          description: 'Lead with given id not found.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Resource not found: Lead with id abcd'
        403:
          $ref: '#/components/responses/403'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []
  /leads/{leadId}/turnOn:
    post:
      description: 'Turns Apten on for a lead'
      parameters:
        - name: leadId
          in: path
          required: true
          schema:
            type: string
          description: 'The ID of the lead to turn on Apten for.'
      responses:
        200:
          description: 'Apten has successfully been turned on for the lead.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: 'Returns `"Lead turned on successfully."` if Apten was turned on for the lead.'
                example:
                  message: 'Lead turned on successfully.'
        400:
          description: 'Bad request.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Missing field: leadId'
        404:
          description: 'Lead with given id not found.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Resource not found: Lead with id abcd'
        403:
          $ref: '#/components/responses/403'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []
  /leads/{leadId}/addContext:
    post:
      description: "Adds context or a timeline event to a lead. Use `event` for things that happened at a point in time (a status change, a completed step, an update from your system) — Apten places it in the conversation timeline so the AI knows when it occurred relative to the lead's messages and can react to it. Use `context` for standing facts and instructions that should always apply regardless of when they were added (preferences, rules, background). At least one of `context`, `event`, `additionalInfo`, or `command` is required; they can be combined in one call."
      parameters:
        - name: leadId
          in: path
          required: true
          schema:
            type: string
          description: 'The ID of the lead to add context to.'
      requestBody:
        description: "The lead's context and/or timeline event, and an optional command."
        content:
          application/json:
            schema:
              type: object
              properties:
                context:
                  type: string
                  description: 'A standing fact or instruction about the lead that should always apply, regardless of when it was added. For something that happened at a point in time, use `event` instead.'
                event:
                  type: string
                  description: "Something that just happened, stated as a short factual sentence (what changed, and the next step if there is one). Apten records it in the conversation timeline with a timestamp: the AI knows when it occurred relative to the lead's messages, treats it as information the lead has not seen, and can bring it up naturally or act on it in the flow-chart conversation. Example: 'Application submitted. Next step: upload proof of residence.'"
                additionalInfo:
                  type: object
                  description: 'An key-value object containing structured data to store with the lead. These will be stored as variables that can be accessed in the flow-chart conversation prompts.'
                command:
                  type: string
                  description: 'An optional command for Apten regarding the lead. Currently allowed commands are scheduling a follow up and sending a message.'
              example:
                context: 'The lead prefers to be contacted in the evenings.'
                event: 'The lead scheduled a meeting with our team for Monday at 2pm.'
                additionalInfo:
                  favoriteColor: 'blue'
                  favoriteFood: 'pizza'
                command: 'Schedule a follow up for next week.'
      responses:
        200:
          description: 'Context has successfully been added to the lead.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: 'Returns `"Success"` if the context was added successfully.'
                example:
                  message: 'Success'
        400:
          description: 'Bad request.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        403:
          $ref: '#/components/responses/403'
        # TODO: Add 404 response for no lead/room found (currently is a 500)
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []
  /leads/lookup:
    get:
      description: 'Looks up a lead by phone number'
      parameters:
        - name: phone
          in: query
          required: true
          schema:
            $ref: '#/components/schemas/phoneProperty'
          description: 'The phone number of the lead to look up'
        - name: outboundPhone
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/phoneProperty'
          description: 'The configured outbound phone number associated with the lead.'
      responses:
        200:
          description: 'Lead found successfully.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    description: 'The ID of the lead associated with the provided phone number.'
                required:
                  - id
              example:
                id: '1234'
        400:
          description: 'Bad request. Invalid phone number format.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Invalid phone number format.'
        404:
          description: 'Lead not found.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Lead not found.'
        409:
          description: 'Multiple leads match the provided phone number. Provide outboundPhone to identify the lead.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
              example:
                message: 'Multiple leads match this phone number; provide outboundPhone.'
        403:
          $ref: '#/components/responses/403'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []
  /leads/{leadId}/switchCustomerProfile:
    post:
      description: >-
        Switches the customer profile of a lead. The lead's variables and
        conversation history are kept unless `clearMemory` is `true`, and
        the flowchart restarts at the new profile's starting node.
        What happens to the lead's scheduled messages depends on
        `followUps`. With `followUps: "restart"` (the default), pending
        scheduled follow-ups (automated follow-up steps, dynamic
        AI-scheduled follow-ups, etc.) are canceled and the follow-up
        lead resets to step 1 of the new profile's cadence (nothing is
        scheduled by the reset itself). Manually scheduled
        messages (created via "Schedule Message" in the dashboard) are
        preserved. If `sendMessage` is `true`, a new first message for the
        new profile is scheduled in their place (see `sendMessage` below);
        if `sendMessage` is `false` (the default), nothing is scheduled,
        so the lead has no pending follow-up until its next interaction.
        With `followUps: "continue"`, callbacks, reminders, manual
        messages, and user-scheduled follow-ups are kept, and a lead not
        contacted yet gets its first message under the new profile; see
        `followUps` below.
      parameters:
        - name: leadId
          in: path
          required: true
          schema:
            type: string
          description: 'The ID of the lead to switch the customer profile for.'
      requestBody:
        description: 'The new customer profile to switch the lead to.'
        content:
          application/json:
            schema:
              type: object
              properties:
                profile:
                  type: string
                  description: 'The name of the new customer profile to switch the lead to.'
                sendMessage:
                  type: boolean
                  description: >-
                    Whether to send the new profile's first message to the
                    lead after switching. Defaults to `false`. When
                    `false`, no first message is scheduled for the new
                    profile, so the switch does not automatically
                    initiate contact. With `followUps: "restart"`, the
                    existing pending follow-up (if any) is also canceled.
                    Must be `false` or omitted when `followUps` is
                    `continue`. When `true`, the first message is
                    scheduled after the delay in `messageDelayHours` /
                    `messageDelayMins` below; if both are omitted (or
                    `0`), the message is sent immediately, subject only
                    to outreach-hours/holiday adjustment.
                messageDelayHours:
                  type: integer
                  description: 'The number of hours to wait before sending the message. Takes precedence over messageDelayMins.'
                messageDelayMins:
                  type: integer
                  description: 'The number of minutes to wait before sending the message. Used as an alternative to messageDelayHours.'
                clearMemory:
                  type: boolean
                  description: "Whether to clear the AI's memory of the existing conversation with the lead. Defaults to `false`."
                followUps:
                  type: string
                  enum: [restart, continue]
                  default: restart
                  description: >-
                    What happens to the lead's scheduled messages and
                    follow-up cadence when the profile changes. `restart`
                    (default) deletes the first-message schedule, cancels
                    all pending scheduled messages except manual ones, and
                    resets the lead to step 1 of the new profile's cadence
                    (follow-ups stop if the new profile has follow-ups
                    disabled). The reset schedules nothing by itself: with
                    `sendMessage` false the lead has no pending outreach
                    until its next interaction, and with `sendMessage`
                    true the new profile's first message goes out after the
                    delay and the cadence continues from there. `continue`
                    keeps AI-scheduled messages (callbacks, call reminders,
                    event reminders), manual messages, follow-ups a user
                    scheduled for a date, and the first-message schedule,
                    and replaces the pending automated follow-up with step
                    1 of the new profile's cadence, starting now. If one of
                    the kept messages is pending, no automated follow-up
                    is added; the cadence continues from step 1 after it
                    sends. A lead not contacted yet gets its first message
                    under the new profile. Stopped leads and leads in
                    long-term follow-up are left as they are, and only the
                    profile changes. If the new profile has follow-ups
                    disabled, automated follow-ups are canceled and
                    follow-ups stop, even for long-term leads. `continue`
                    can't be combined with `sendMessage: true` (returns
                    400), and an unknown
                    value also returns 400. Use `continue` to switch a lead
                    after a call without losing a callback the lead booked.
              required:
                - profile
      responses:
        200:
          description: 'Customer profile of lead switched successfully.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  sendMessage:
                    type: boolean
                  messageDelayHours:
                    type: integer
                  messageDelayMins:
                    type: integer
                  followUps:
                    type: string
                    enum: [restart, continue]
                    description: 'The follow-ups mode that was applied.'
                example:
                  message: 'Switched profile successfully.'
                  sendMessage: true
                  messageDelayHours: 0
                  messageDelayMins: 30
                  followUps: restart
      security:
        - api_key: []
  /leads/{leadId}/sendMessage:
    post:
      description: 'Sends a message to a lead'
      parameters:
        - name: leadId
          in: path
          required: true
          schema:
            type: string
          description: 'The ID of the lead to send a message to.'
      requestBody:
        description: 'The message to send to the lead.'
        content:
          application/json:
            schema:
              type: object
              properties:
                message:
                  type: string
                  description: 'The content of the message to send to the lead.'
                scheduledTime:
                  type: string
                  description: 'The date and time to schedule the message for. This should be in ISO 8601 format (e.g. 2024-05-17T21:09:13Z). If not provided, or if the scheduled time is in the past, the message will be sent immediately.'
                sendWhenOff:
                  type: boolean
                  description: 'Whether to send the message even if Apten is turned off for the lead. Defaults to `false`.'
                scheduleFollowUps:
                  type: boolean
                  description: 'Whether to schedule follow-up messages for this lead. Defaults to `false`.'
                overrideBusinessHours:
                  type: boolean
                  description: 'Whether to send the message immediately regardless of business hours. If not provided or `false`, and no scheduledTime is specified, the message will be scheduled for the next business hours. Defaults to `false`.'
              required:
                - message
      responses:
        200:
          description: 'Message sent successfully.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: 'Returns "Message sent successfully." if the message was sent.'
                example:
                  message: 'Message sent successfully.'
        400:
          description: 'Bad request.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Missing required field: message'
        404:
          description: 'Lead not found.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Lead not found.'
        403:
          $ref: '#/components/responses/403'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []
  /leads/{leadId}/unsubscribe:
    post:
      description: "Unsubscribes a lead from one or more channels (SMS, voice, email). Does not change whether Apten is on for the lead or add the lead's phone number to the Do Not Contact (DNC) list."
      x-notes: 'Channels are optional. When omitted, Apten unsubscribes every channel the lead can receive (SMS and voice if they have a phone number, email if they have an email). The 200 response channels array is the set that was applied. An explicit channel the lead cannot receive is ignored if another requested channel is available; if none of the requested channels are available, the request returns 400 (for example, "Lead has no email"). SMS and voice opt-outs may be coupled according to the organization''s settings. Repeated requests do not change channels that are already unsubscribed, and an opt-out webhook event is emitted only for channels that transition to unsubscribed. To undo an SMS opt-out, the lead can text "START" or the integration can use the resubscribe endpoint.'
      parameters:
        - name: leadId
          in: path
          required: true
          schema:
            type: string
          description: 'The ID of the lead to unsubscribe.'
      requestBody:
        description: 'Channels to unsubscribe. Optional; when omitted, defaults to every channel the lead can receive.'
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                channels:
                  type: array
                  minItems: 1
                  items:
                    type: string
                    enum: ['sms', 'voice', 'email']
                  description: 'The channels to unsubscribe the lead from. When omitted, Apten unsubscribes every channel the lead can receive (phone → sms and voice, email → email).'
            example:
              channels: ['sms']
      responses:
        200:
          description: 'Lead unsubscribed successfully.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: 'Returns "Lead unsubscribed successfully." if the lead was unsubscribed.'
                  channels:
                    type: array
                    items:
                      type: string
                      enum: ['sms', 'voice', 'email']
                    description: 'The channels that were applied. Channels the lead cannot receive (no phone or no email) are omitted.'
              example:
                message: 'Lead unsubscribed successfully.'
                channels: ['sms']
        400:
          description: 'Bad request. Returned when no valid channels are provided, the request body is invalid, or none of the requested channels are available on the lead (for example, an explicit email unsubscribe when the lead has no email).'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
              examples:
                invalidChannels:
                  value:
                    message: 'channels must include at least one of: sms, voice, email'
                noEmail:
                  value:
                    message: 'Lead has no email'
        403:
          $ref: '#/components/responses/403'
        404:
          description: 'Lead not found.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Resource not found: Lead with id 12345'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []
  /leads/{leadId}/resubscribe:
    post:
      description: 'Resubscribes a lead to one or more channels (SMS, voice, email) they previously unsubscribed from. Also clears the lead''s do-not-contact status, and removes the phone number from the Do Not Contact (DNC) list when SMS is resubscribed. Does not change whether Apten is on for the lead.'
      x-notes: 'When channels are omitted, Apten resubscribes every channel the lead can receive (SMS and voice if they have a phone number, email if they have an email). The 200 response channels array is the set that was applied. An explicit channel the lead cannot receive is ignored if another requested channel is available; if none of the requested channels are available, the request returns 400 (for example, "Lead has no email"). An opt-in webhook event is emitted for each channel that transitions from unsubscribed to subscribed.'
      parameters:
        - name: leadId
          in: path
          required: true
          schema:
            type: string
          description: 'The ID of the lead to resubscribe.'
      requestBody:
        description: 'Channels to resubscribe. Optional; when omitted, defaults to every channel the lead can receive.'
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                channels:
                  type: array
                  minItems: 1
                  items:
                    type: string
                    enum: ['sms', 'voice', 'email']
                  description: 'The channels to resubscribe the lead to. When omitted, Apten resubscribes every channel the lead can receive (phone → sms and voice, email → email).'
              example:
                channels: ['sms']
      responses:
        200:
          description: 'Lead resubscribed successfully, potentially with partial success.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: 'Returns "Lead resubscribed successfully." when all requested channels are resubscribed, or "Lead resubscribed with partial success." when one or more channels could not be resubscribed.'
                  channels:
                    type: array
                    items:
                      type: string
                      enum: ['sms', 'voice', 'email']
                    description: 'The channels that were applied. Channels the lead cannot receive (no phone or no email) are omitted.'
                  failedChannels:
                    type: array
                    description: 'Channels that could not be resubscribed. Present only when the response is a partial success.'
                    items:
                      type: object
                      properties:
                        channel:
                          type: string
                          enum: ['sms']
                        code:
                          type: string
                          enum: ['TWILIO_CONSENT_UNAVAILABLE']
                        retryable:
                          type: boolean
                      required: [channel, code, retryable]
              examples:
                success:
                  value:
                    message: 'Lead resubscribed successfully.'
                    channels: ['sms']
                partialSuccess:
                  value:
                    message: 'Lead resubscribed with partial success.'
                    channels: ['voice', 'email']
                    failedChannels:
                      - channel: 'sms'
                        code: 'TWILIO_CONSENT_UNAVAILABLE'
                        retryable: true
        400:
          description: 'Bad request. Returned when no valid channels are provided or none of the requested channels are available on the lead (for example, an explicit email resubscribe when the lead has no email).'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
              examples:
                invalidChannels:
                  value:
                    message: 'channels must include at least one of: sms, voice, email'
                noEmail:
                  value:
                    message: 'Lead has no email'
        502:
          description: 'Bad Gateway. The consent provider rejected the request or returned an invalid response.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
              examples:
                rejected:
                  value:
                    message: 'Consent provider rejected the re-opt-in'
                requestFailed:
                  value:
                    message: 'Consent provider request failed'
                invalidResponse:
                  value:
                    message: 'Consent provider returned an invalid response'
        503:
          description: 'Service Unavailable. The consent provider was unavailable or throttled after retries.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
              examples:
                unavailable:
                  value:
                    message: 'Consent provider is unavailable'
                requestFailed:
                  value:
                    message: 'Consent provider request failed'
        404:
          description: 'Lead not found.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Resource not found: Lead with id 12345'
      security:
        - api_key: []
  /dnc:
    get:
      description: "Lists the phone numbers on your organization's Do Not Contact (DNC) list, one page at a time, ordered by phone number. Returns 400 if `limit` is not a positive integer or `nextToken` is invalid."
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 1000
            default: 100
          description: 'Maximum number of phone numbers to return per page. Defaults to 100; values above 1000 return 1000.'
        - name: nextToken
          in: query
          required: false
          schema:
            type: string
          description: 'Opaque cursor returned by a prior response. Pass it back to fetch the next page.'
      responses:
        200:
          description: 'A page of phone numbers on the DNC list'
          content:
            application/json:
              schema:
                type: object
                properties:
                  numbers:
                    type: array
                    items:
                      type: object
                      properties:
                        phone:
                          type: string
                          description: 'The phone number on the DNC list'
                        createdAt:
                          type: string
                          format: date-time
                          description: 'The date and time when the phone number was added to the DNC list'
                  nextToken:
                    type: string
                    nullable: true
                    description: 'Cursor for the next page. `null` if there are no more results.'
              example:
                numbers:
                  - phone: '+15555555555'
                    createdAt: '2026-09-10T18:22:41Z'
                  - phone: '+15555555556'
                    createdAt: '2026-09-12T14:03:09Z'
                nextToken: null
        400:
          $ref: '#/components/responses/400'
        401:
          $ref: '#/components/responses/401'
        403:
          $ref: '#/components/responses/403'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []

  /dnc/register:
    post:
      description: 'Adds a phone number to the Do Not Contact (DNC) list to prevent it from being imported.'
      requestBody:
        description: 'Phone number to add to DNC list'
        content:
          application/json:
            schema:
              type: object
              properties:
                phone:
                  $ref: '#/components/schemas/phoneProperty'
              required:
                - phone
              example:
                phone: '+15555555555'
        required: true
      responses:
        200:
          description: 'Phone number successfully added to DNC list'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: 'Success message'
                example:
                  message: 'Phone number successfully added to DNC list'
        400:
          $ref: '#/components/responses/400'
        403:
          $ref: '#/components/responses/403'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []

  /dnc/{phone_number}:
    get:
      description: 'Checks if a phone number is in the DNC list'
      parameters:
        - name: phone_number
          in: path
          required: true
          schema:
            $ref: '#/components/schemas/phoneProperty'
          description: 'The phone number to check'
      responses:
        200:
          description: 'Phone number found in DNC list'
          content:
            application/json:
              schema:
                type: object
                properties:
                  phone:
                    type: string
                    description: 'The phone number in the DNC list'
                  createdAt:
                    type: string
                    format: date-time
                    description: 'The date and time when the phone number was added to the DNC list'
                example:
                  phone: '+15555555555'
                  createdAt: '2024-05-17T21:09:13Z'
        404:
          description: 'Phone number not found in DNC list'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Phone number not found in DNC list'
        403:
          $ref: '#/components/responses/403'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []

    delete:
      description: 'Removes a phone number from the DNC list'
      parameters:
        - name: phone_number
          in: path
          required: true
          schema:
            $ref: '#/components/schemas/phoneProperty'
          description: 'The phone number to remove'
      responses:
        200:
          description: 'Phone number successfully removed from DNC list'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: 'Success message'
                example:
                  message: 'Phone number successfully removed from DNC list'
        403:
          $ref: '#/components/responses/403'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []

  /leads/{leadId}/tags:
    put:
      description: 'Replace all tags on a lead'
      parameters:
        - name: leadId
          in: path
          required: true
          schema:
            type: string
          description: 'The ID of the lead to update tags for'
      requestBody:
        description: 'Array of tag IDs to set on the lead'
        content:
          application/json:
            schema:
              type: object
              required:
                - tagIds
              properties:
                tagIds:
                  type: array
                  items:
                    type: string
                  description: 'Array of tag IDs to replace existing tags with'
              example:
                tagIds: ['tag1', 'tag2', 'tag3']
        required: true
      responses:
        200:
          description: 'Tags successfully replaced.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  tags:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          description: 'The ID of the tag'
                        name:
                          type: string
                          description: 'The name of the tag'
                    description: 'Array of tags now assigned to the lead'
                example:
                  tags:
                    - id: 'tag1'
                      name: 'VIP Customer'
                    - id: 'tag2'
                      name: 'Interested'
                    - id: 'tag3'
                      name: 'Follow Up Required'
        400:
          description: 'Bad request - invalid tag IDs provided'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Invalid tag IDs: invalid-tag-1, invalid-tag-2'
        403:
          $ref: '#/components/responses/403'
        404:
          description: 'Lead not found'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Lead not found'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []
    post:
      description: 'Add tags to a lead (will not remove existing tags)'
      parameters:
        - name: leadId
          in: path
          required: true
          schema:
            type: string
          description: 'The ID of the lead to add tags to'
      requestBody:
        description: 'Array of tag IDs to add to the lead'
        content:
          application/json:
            schema:
              type: object
              required:
                - tagIds
              properties:
                tagIds:
                  type: array
                  items:
                    type: string
                  description: 'Array of tag IDs to add to the lead'
              example:
                tagIds: ['tag4', 'tag5']
        required: true
      responses:
        200:
          description: 'Tags successfully added.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  tags:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          description: 'The ID of the tag'
                        name:
                          type: string
                          description: 'The name of the tag'
                    description: 'Array of all tags now assigned to the lead'
                example:
                  tags:
                    - id: 'tag1'
                      name: 'VIP Customer'
                    - id: 'tag2'
                      name: 'Interested'
                    - id: 'tag4'
                      name: 'New Tag'
                    - id: 'tag5'
                      name: 'Another Tag'
        400:
          description: 'Bad request - invalid tag IDs provided'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Invalid tag IDs: invalid-tag-1'
        403:
          $ref: '#/components/responses/403'
        404:
          description: 'Lead not found'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Lead not found'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []

  /leads/{leadId}/tags/remove:
    post:
      description: 'Remove specific tags from a lead'
      parameters:
        - name: leadId
          in: path
          required: true
          schema:
            type: string
          description: 'The ID of the lead to remove tags from'
      requestBody:
        description: 'Array of tag IDs to remove from the lead'
        content:
          application/json:
            schema:
              type: object
              required:
                - tagIds
              properties:
                tagIds:
                  type: array
                  items:
                    type: string
                  description: 'Array of tag IDs to remove from the lead'
              example:
                tagIds: ['tag1', 'tag3']
        required: true
      responses:
        200:
          description: 'Tags successfully removed.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  tags:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          description: 'The ID of the tag'
                        name:
                          type: string
                          description: 'The name of the tag'
                    description: 'Array of remaining tags assigned to the lead'
                example:
                  tags:
                    - id: 'tag2'
                      name: 'Interested'
                    - id: 'tag4'
                      name: 'Follow Up Required'
        403:
          $ref: '#/components/responses/403'
        404:
          description: 'Lead not found'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Lead not found'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []

  /tags:
    get:
      description: 'Retrieve all custom tags defined for the organization'
      operationId: getOrganizationTags
      tags:
        - Tags
      responses:
        200:
          description: 'Successfully retrieved organization tags'
          content:
            application/json:
              schema:
                type: object
                properties:
                  tags:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          description: 'Unique identifier for the tag'
                        name:
                          type: string
                          description: 'Display name of the tag'
                        color:
                          type: string
                          description: 'Hex color code for the tag'
                        condition:
                          type: string
                          description: 'Optional natural language condition for automatic tagging'
                        isManual:
                          type: boolean
                          description: 'Whether this tag is manually applied or automatic'
                        profiles:
                          type: array
                          items:
                            type: string
                          description: 'Customer profile names that scope AI tag evaluation (matched against the lead''s `profile` field). When absent or empty, the tag is evaluated against all profiles. Only applies to AI tags (`isManual: false`); manual tags are never scoped by profile.'
                    description: 'Array of all organization tags'
                example:
                  tags:
                    - id: 'tag-uuid-1'
                      name: 'High Priority'
                      color: '#FF0000'
                      condition: 'When the customer mentions an urgent issue'
                      isManual: true
                    - id: 'tag-uuid-2'
                      name: 'VIP Customer'
                      color: '#00FF00'
                      condition: 'If the customer has made purchases over $10,000'
                      isManual: false
                      profiles:
                        - 'Profile 1'
                        - 'Profile 2'
                    - id: 'tag-uuid-3'
                      name: 'Follow Up'
                      color: '#0000FF'
                      isManual: true
        401:
          $ref: '#/components/responses/401'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []

  /usage:
    get:
      description: 'Retrieve the current usage limit status for the organization. Today the only tracked usage dimension is the per-organization lead limit; additional dimensions may be added in the future under sibling keys.'
      operationId: getUsageLimitStatus
      tags:
        - Usage
      responses:
        200:
          description: 'Successfully retrieved usage limit status'
          content:
            application/json:
              schema:
                type: object
                properties:
                  leads:
                    type: object
                    description: 'Lead usage for the organization.'
                    properties:
                      current:
                        type: integer
                        description: 'The number of leads currently counted against the limit.'
                      limit:
                        type: integer
                        description: 'The maximum number of leads allowed for the organization.'
                example:
                  leads:
                    current: 850
                    limit: 1000
        401:
          $ref: '#/components/responses/401'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []

  /leads:
    get:
      description: 'Incrementally list leads. Requires `updatedSince` and/or `createdSince`. Soft-deleted leads are included (`deleted: true`); treat as removals. Use `lastActivityAt` to decide whether to fetch events.'
      operationId: listLeads
      tags:
        - Leads
      parameters:
        - name: updatedSince
          in: query
          required: false
          schema:
            type: string
            format: date-time
          description: 'ISO 8601 timestamp. Returns leads whose `updatedAt` is greater than or equal to this value.'
        - name: createdSince
          in: query
          required: false
          schema:
            type: string
            format: date-time
          description: 'ISO 8601 timestamp. Returns leads whose `createdAt` is greater than or equal to this value.'
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 1000
            default: 100
          description: 'Maximum number of leads to return per page. Defaults to 100, max 1000.'
        - name: nextToken
          in: query
          required: false
          schema:
            type: string
          description: 'Opaque cursor returned by a prior response. Pass it back to fetch the next page.'
      responses:
        200:
          description: 'A page of leads ordered by the chosen timestamp ascending.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  leads:
                    type: array
                    items:
                      $ref: '#/components/schemas/incrementalLead'
                  nextToken:
                    type: string
                    nullable: true
                    description: 'Cursor for the next page. `null` if there are no more results.'
              example:
                leads:
                  - id: 'room_abc'
                    firstName: 'John'
                    lastName: 'Doe'
                    phone: '+15555555555'
                    email: 'john.doe@example.com'
                    customerProfile: 'My Customer Profile'
                    createdAt: '2026-05-01T12:00:00.000Z'
                    updatedAt: '2026-05-14T15:30:00.000Z'
                    lastActivityAt: '2026-05-14T15:30:00.000Z'
                    additionalInfo: { key1: 'value1' }
                    hasReplied: true
                    hasRepliedEmail: false
                    hasAiCall: true
                    isHot: true
                    isQualified: false
                    hasTransferCall: true
                    isUnsubscribed: false
                    isAiOn: true
                    tags:
                      - id: 'tag1'
                        name: 'Tag 1'
                        condition: 'Condition 1'
                    summary: 'Summary of the conversation.'
                    externalLeadId: '00Q5g00000ABCDEAA1'
                    deleted: false
                    deletedAt: null
                nextToken: 'eyJzZWFyY2hBZnRlciI6WyIyMDI2LTA1LTE0VDE1OjMwOjAwLjAwMFoiLCJyb29tX2FiYyJdfQ=='
        400:
          $ref: '#/components/responses/400'
        401:
          $ref: '#/components/responses/401'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []

  /leads/{leadId}/events:
    get:
      description: |
        List interaction events for a lead — messages, calls, emails, tool calls, custom events, compliance blocks, and other types documented in this reference.

        Apten may add new `type` values over time without a version bump or prior notice. Clients should treat unknown types as forward-compatible (skip or store opaquely) unless they filter with the `types` query parameter.
      operationId: listLeadEvents
      tags:
        - Events
      parameters:
        - name: leadId
          in: path
          required: true
          schema:
            type: string
          description: 'The lead (room) ID to fetch events for.'
        - name: since
          in: query
          required: true
          schema:
            type: string
            format: date-time
          description: 'ISO 8601 timestamp. Returns events whose `updatedAt` is greater than or equal to this value.'
        - name: types
          in: query
          required: false
          schema:
            type: string
            example: 'MESSAGE,COMPLIANCE_BLOCKED'
          description: |
            Comma-separated list of event types to include. Documented values include `MESSAGE`, `CALL`, `EMAIL`, `TOOL_CALL`, `CUSTOM`, and `COMPLIANCE_BLOCKED`. Use this filter when your integration only handles a known set of types. Defaults to all types currently stored for the lead (including types not listed here). When supplied, a page may return fewer than `limit` items even if more matching events exist — always paginate using `nextToken`.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 1000
            default: 100
          description: 'Maximum number of events to return per page. Defaults to 100, max 1000.'
        - name: nextToken
          in: query
          required: false
          schema:
            type: string
          description: 'Opaque cursor from a prior response. Pass it back to fetch the next page. `null` in the response means you have reached the end; a non-null value means more pages exist (true regardless of how many items the current page returned).'
      responses:
        200:
          description: 'A page of events ordered by `updatedAt` ascending.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  events:
                    type: array
                    items:
                      $ref: '#/components/schemas/interactionEvent'
                  nextToken:
                    type: string
                    nullable: true
                    description: 'Cursor for the next page. `null` if there are no more results.'
              example:
                events:
                  - id: 'msg_abc'
                    roomId: 'room_abc'
                    orgId: 'org_123'
                    type: 'MESSAGE'
                    timestamp: '2026-05-14T15:00:00.000Z'
                    updatedAt: '2026-05-14T15:00:00.000Z'
                    data:
                      direction: 'INBOUND'
                      content:
                        text: 'Hi, I am interested'
                      isAiGenerated: false
                      deliveryChannel: 'SMS'
                  - id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
                    roomId: 'room_abc'
                    orgId: 'org_123'
                    type: 'COMPLIANCE_BLOCKED'
                    timestamp: '2026-05-20T05:50:12.000Z'
                    updatedAt: '2026-05-20T05:50:12.000Z'
                    data:
                      channel: 'sms'
                      outreachType: 'follow_up'
                      reason: 'STATE_DNC'
                      source: 'http'
                      triggeredBy: 'llm'
                nextToken: null
        400:
          $ref: '#/components/responses/400'
        401:
          $ref: '#/components/responses/401'
        403:
          $ref: '#/components/responses/403'
        404:
          description: 'Lead not found, or the lead has been deleted. Deleted leads surface in `GET /leads` with `deleted: true` — drop all events for them in your sync target.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Lead not found.'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []

  /calls/{callId}:
    get:
      description: 'Fetch full details for a call by ID — transcript, AI summary, and transfer metadata.'
      operationId: getCall
      tags:
        - Calls
      parameters:
        - name: callId
          in: path
          required: true
          schema:
            type: string
          description: 'Call ID from `data.callId` (webhooks) or the event `id` (CALL events).'
      responses:
        200:
          description: 'Call details fetched successfully.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/callDetail'
              example:
                id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
                leadId: 'c4f23a1b-d8e5-4f67-90c1-2a5b8d3e9f7c'
                direction: 'OUTBOUND'
                callStatus: 'COMPLETE'
                durationSeconds: 145
                createdAt: '2026-06-15T14:30:00.000Z'
                updatedAt: '2026-06-15T14:32:25.000Z'
                summary: 'Customer inquired about pricing and requested a follow-up meeting.'
                transcript:
                  - role: 'assistant'
                    content: 'Hi, this is Apten calling about your inquiry.'
                    startTime: '2026-06-15T14:30:01.000Z'
                    endTime: '2026-06-15T14:30:05.000Z'
                  - role: 'user'
                    content: 'Yes, I had some questions about pricing.'
                    startTime: '2026-06-15T14:30:06.000Z'
                    endTime: '2026-06-15T14:30:10.000Z'
                isTransfer: false
        400:
          $ref: '#/components/responses/400'
        401:
          $ref: '#/components/responses/401'
        403:
          $ref: '#/components/responses/403'
        404:
          description: 'Call not found, or the call belongs to another organization.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Resource not found: Call'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []

  /calls/{callId}/recording:
    get:
      description: 'Create a temporary download URL for a completed call recording.'
      operationId: getCallRecording
      tags:
        - Calls
      parameters:
        - name: callId
          in: path
          required: true
          schema:
            type: string
          description: 'Call ID from `data.callId` in a call webhook or the event `id` in a CALL event.'
      responses:
        200:
          description: 'A temporary recording download URL was created.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/recordingDownload'
              example:
                url: 'https://recording-download.example.com/temporary-url'
                expiresAt: '2026-06-15T14:45:00.000Z'
                contentType: 'audio/mpeg'
        400:
          $ref: '#/components/responses/400'
        401:
          $ref: '#/components/responses/401'
        404:
          description: 'Call not found, or the call belongs to another organization.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Resource not found: Call'
        409:
          description: 'The recording is not available yet. Retry this request later.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                example:
                  message: 'Recording is not ready yet.'
        500:
          $ref: '#/components/responses/500'
      security:
        - api_key: []

components:
  responses:
    400:
      description: 'Bad Request'
      content:
        application/json:
          schema:
            type: object
            properties:
              message:
                type: string
            example:
              message: 'Invalid format for field: phone'
    401:
      description: 'Unauthorized - Invalid or missing API key'
      content:
        application/json:
          schema:
            type: object
            properties:
              message:
                type: string
            example:
              message: 'Unauthorized'
    403:
      description: 'Forbidden API key'
      content:
        application/json:
          schema:
            type: object
            properties:
              message:
                type: string
            example:
              message: 'Forbidden'
    500:
      description: 'Internal Server Error'
      content:
        application/json:
          schema:
            type: object
            properties:
              message:
                type: string
            example:
              message: 'Internal Server Error'
  schemas:
    phoneProperty:
      type: string
      description: >
        The phone number must begin with an area code (+) and
        can contain digits, spaces, parentheses, and hyphens
        (e.g. +1 (555) 555-5555)
    leadItem:
      type: object
      properties:
        firstName:
          type: string
          description: 'The first name of the lead.'
        lastName:
          type: string
          description: 'The last name of the lead.'
        phone:
          $ref: '#/components/schemas/phoneProperty'
        customerProfile:
          type: string
          description: 'The name of the customer profile to which this lead should belong to.'
        timeZone:
          type: string
          description: 'The timezone of the lead in tz database format (e.g. America/New_York, Europe/London). If omitted, Apten automatically infers the timezone when possible using `zipCode`, then `state`, then the phone number area code. An explicitly provided `timeZone` always takes precedence over inferred values.'
        state:
          type: string
          description: 'Optional US state or Canadian province for the lead. Accepts the full name or two-letter abbreviation. States or provinces spanning multiple timezones use the majority timezone for automatic timezone inference.'
          example: 'CA'
        zipCode:
          type: string
          description: 'Optional US ZIP code for the lead. ZIP+4 values are accepted. Used before `state` and phone area code for automatic timezone inference when `timeZone` is omitted.'
          example: '94105'
        email:
          type: string
          description: 'The email address of the lead. Must be a valid email format (e.g. user@domain.com).'
        outboundPhone:
          type: string
          description: 'The phone number to use for outbound texts and calls to this lead. Must be a phone number configured for your organization. If not specified, the organization default phone number will be used.'
        additionalInfo:
          type: object
          description: 'An optional key-value object containing additional information to store with the lead.'
        scheduledImportAt:
          type: string
          format: date-time
          description: |
            Optional. If set, the lead is imported now but the first outbound message is deferred until this time.
            Must be an ISO 8601 timestamp with seconds and an explicit timezone offset (e.g. `2027-12-15T18:30:00Z` or `2027-12-15T13:30:00-05:00`), must be strictly in the future, and must be no more than 365 days from now.
            When this is provided, it overrides your organization's business-hours, allowed-days, and holiday adjustments.
          example: '2027-12-15T18:30:00Z'
        lowPriorityInitialCall:
          type: boolean
          description: |
            Optional. For customer profiles whose first contact is a call, set to `true` to mark this lead's initial call as lower priority so it is placed after higher-priority leads. Useful for aged or lower-value leads so they don't delay your fresh ones. During busy periods a lower-priority initial call may wait longer before being placed. Defaults to `false`; has no effect for text or email-first profiles.
          example: true
      required:
        - firstName
        - lastName
        - phone
        - customerProfile
      example:
        firstName: 'John'
        lastName: 'Doe'
        phone: '+15555555555'
        customerProfile: 'My Customer Profile'
        timeZone: 'America/New_York'
        state: 'CA'
        zipCode: '94105'
        email: 'example@gmail.com'
        outboundPhone: '+15551234567'
        scheduledImportAt: '2027-12-15T18:30:00Z'
        additionalInfo:
          key1: 'value1'
          key2: 'value2'
    incrementalLead:
      type: object
      description: 'A lead record returned from the incremental list endpoint. Includes lifecycle fields (`updatedAt`, `deleted`, `deletedAt`) needed for data-warehouse sync.'
      properties:
        id:
          type: string
          description: 'Stable unique identifier for the lead.'
        firstName:
          type: string
          description: 'The first name of the lead. May be null on soft-deleted leads after PII scrub.'
          nullable: true
        lastName:
          type: string
          nullable: true
        phone:
          type: string
          nullable: true
        email:
          type: string
          nullable: true
        customerProfile:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
          description: 'Bumped whenever ANY field on this lead changes — profile edits, tag changes, or a new/updated/deleted event. Use as the cursor for `GET /leads?updatedSince=T`.'
        lastActivityAt:
          type: string
          format: date-time
          nullable: true
          description: 'Bumped only when an interaction event on this lead changes — a new event, an edit (e.g. message delivery status), or a soft-delete. Use to skip `GET /leads/{leadId}/events` for leads whose `updatedAt` bumped only due to profile/tag changes.'
        additionalInfo:
          type: object
          nullable: true
        hasReplied:
          type: boolean
        hasRepliedEmail:
          type: boolean
          description: 'Indicates whether the lead has replied to any emails from Apten.'
        hasAiCall:
          type: boolean
          description: 'Indicates whether the lead has spoken with Apten on a phone call.'
        isHot:
          type: boolean
        isQualified:
          type: boolean
        hasTransferCall:
          type: boolean
          description: 'Indicates whether Apten classified the lead as having had a successful transfer call.'
        isUnsubscribed:
          type: boolean
        isUnsubscribedEmail:
          type: boolean
        isAiOn:
          type: boolean
        summary:
          type: string
          nullable: true
        tags:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              name:
                type: string
              condition:
                type: string
        externalLeadId:
          type: string
          nullable: true
        deleted:
          type: boolean
          description: 'True if the lead has been deleted. Treat as a removal in your sync target.'
        deletedAt:
          type: string
          format: date-time
          nullable: true
          description: 'ISO 8601 timestamp of when the lead was deleted, or null if not deleted.'
      required:
        - id
        - deleted
    interactionEvent:
      description: |
        An interaction event between a lead and your organization. The `type` discriminator determines the shape of `data`.

        New interaction event types may be added in the future. Integrations must accept responses containing undocumented `type` values or restrict ingestion with the `types` query parameter on `GET /leads/{leadId}/events`.
      oneOf:
        - $ref: '#/components/schemas/messageInteractionEvent'
        - $ref: '#/components/schemas/callInteractionEvent'
        - $ref: '#/components/schemas/emailInteractionEvent'
        - $ref: '#/components/schemas/toolCallInteractionEvent'
        - $ref: '#/components/schemas/customInteractionEvent'
        - $ref: '#/components/schemas/complianceBlockedInteractionEvent'
      discriminator:
        propertyName: type
        mapping:
          MESSAGE: '#/components/schemas/messageInteractionEvent'
          CALL: '#/components/schemas/callInteractionEvent'
          EMAIL: '#/components/schemas/emailInteractionEvent'
          TOOL_CALL: '#/components/schemas/toolCallInteractionEvent'
          CUSTOM: '#/components/schemas/customInteractionEvent'
          COMPLIANCE_BLOCKED: '#/components/schemas/complianceBlockedInteractionEvent'
    baseInteractionEvent:
      type: object
      properties:
        id:
          type: string
          description: 'Stable unique identifier for the event (e.g. `msg_abc`). Unique on its own — pair with `type` if you need to disambiguate.'
        roomId:
          type: string
          description: 'ID of the lead this event belongs to.'
        orgId:
          type: string
          description: 'Your organization ID.'
        timestamp:
          type: string
          format: date-time
          description: 'When the event occurred. Immutable.'
        updatedAt:
          type: string
          format: date-time
          description: 'Last time the event row was written (creation, edit, or soft-delete). Equal to `timestamp` for events that have never changed.'
      required:
        - id
        - roomId
        - orgId
        - timestamp
        - updatedAt
    messageInteractionEvent:
      allOf:
        - $ref: '#/components/schemas/baseInteractionEvent'
        - type: object
          properties:
            type:
              type: string
              enum: [MESSAGE]
            data:
              $ref: '#/components/schemas/messageEventData'
          required:
            - type
            - data
    callInteractionEvent:
      allOf:
        - $ref: '#/components/schemas/baseInteractionEvent'
        - type: object
          properties:
            type:
              type: string
              enum: [CALL]
            data:
              $ref: '#/components/schemas/callEventData'
          required:
            - type
            - data
    emailInteractionEvent:
      allOf:
        - $ref: '#/components/schemas/baseInteractionEvent'
        - type: object
          properties:
            type:
              type: string
              enum: [EMAIL]
            data:
              $ref: '#/components/schemas/emailEventData'
          required:
            - type
            - data
    toolCallInteractionEvent:
      allOf:
        - $ref: '#/components/schemas/baseInteractionEvent'
        - type: object
          properties:
            type:
              type: string
              enum: [TOOL_CALL]
            data:
              $ref: '#/components/schemas/toolCallEventData'
          required:
            - type
            - data
    customInteractionEvent:
      allOf:
        - $ref: '#/components/schemas/baseInteractionEvent'
        - type: object
          properties:
            type:
              type: string
              enum: [CUSTOM]
            data:
              $ref: '#/components/schemas/customEventData'
          required:
            - type
            - data
    complianceBlockedInteractionEvent:
      allOf:
        - $ref: '#/components/schemas/baseInteractionEvent'
        - type: object
          properties:
            type:
              type: string
              enum: [COMPLIANCE_BLOCKED]
            data:
              $ref: '#/components/schemas/complianceBlockedEventData'
          required:
            - type
            - data
    messageEventData:
      type: object
      description: 'Payload for a MESSAGE event (SMS, iMessage, or similar text channel).'
      properties:
        direction:
          type: string
          enum: [INBOUND, OUTBOUND]
          description: '`INBOUND` if the message was sent by the lead; `OUTBOUND` if sent by your AI or team.'
        content:
          type: object
          properties:
            text:
              type: string
              description: 'The message body.'
          required: [text]
        isAiGenerated:
          type: boolean
          description: '`true` for messages drafted by Apten AI; `false` for human-sent or inbound messages.'
        deliveryChannel:
          type: string
          description: 'Channel the message was delivered over (e.g. `SMS`). Absent until the message is sent.'
        sentAt:
          type: string
          format: date-time
          description: 'When the message left our system. OUTBOUND only.'
        deliveredAt:
          type: string
          format: date-time
          description: 'When the carrier confirmed delivery. OUTBOUND only, if reported.'
        readAt:
          type: string
          format: date-time
          description: 'When the recipient read the message. OUTBOUND only, if reported.'
        guardrailTriggered:
          type: boolean
          description: '`true` if a content guardrail blocked or flagged this message.'
        violatedGuardrails:
          type: array
          items:
            type: string
          description: 'Names of guardrails this message violated, if any.'
        followUpStrategyId:
          type: string
          description: 'ID of the follow-up strategy that triggered this message, if any.'
        outreachType:
          type: string
          enum: [initial, reply, follow_up, manual, reminder, immediate_transfer, no_answer_follow_up, scheduled_transfer]
          description: 'Purpose of the outreach, independent of the delivery channel. May be absent on older events.'
      required:
        - direction
        - content
        - isAiGenerated
    callEventData:
      type: object
      description: 'Payload for a CALL event. Written when the call reaches a terminal state. Does not include transcript or summary — use `GET /calls/{callId}` to fetch those.'
      properties:
        direction:
          type: string
          enum: [INBOUND, OUTBOUND]
        callStatus:
          type: string
          description: 'Final status of the call (e.g. `COMPLETE`, `NO_ANSWER`, `BUSY`, `FAILED`).'
        durationSeconds:
          type: integer
          description: 'Length of the call in seconds.'
        isTransfer:
          type: boolean
          description: '`true` if this was a transfer to a human agent.'
        midCallTransfer:
          type: boolean
          description: '`true` if a transfer happened mid-call (vs. a pure transfer from the start).'
        endedBy:
          type: string
          enum: [LEAD, AGENT, UNKNOWN]
          description: >-
            Who ended the call. Only populated for warm transfers (where the lead is bridged into a live conference with a human agent); omitted for cold transfers and ordinary calls. `LEAD` — the lead hung up. `AGENT` — the human agent hung up. `UNKNOWN` — the call ended but the hangup could not be attributed to either party.
        hasVoicemail:
          type: boolean
          description: '`true` if the call left a voicemail.'
        transferAnswerStatus:
          type: string
          enum: [answered, no_answer, pending]
          description: 'Whether the transfer recipient picked up. Only set for transfer calls.'
        isFollowUp:
          type: boolean
          description: '`true` if this call was placed as part of a follow-up strategy.'
        isAiInitiated:
          type: boolean
          description: '`true` if Apten AI initiated this call (vs. a human agent).'
      required:
        - direction
        - callStatus
    emailEventData:
      type: object
      description: 'Payload for an EMAIL event.'
      properties:
        direction:
          type: string
          enum: [INBOUND, OUTBOUND]
        subject:
          type: string
        fromAddress:
          type: string
        toAddresses:
          type: array
          items:
            type: string
        plainTextBody:
          type: string
          description: 'Plain-text body of the email.'
        hasAttachments:
          type: boolean
        attachmentMetadata:
          type: array
          items:
            type: object
            description: 'Per-attachment metadata (filename, content type, size). Does not contain the file bytes.'
        guardrailTriggered:
          type: boolean
        violatedGuardrails:
          type: array
          items:
            type: string
        followUpStrategyId:
          type: string
      required:
        - direction
        - hasAttachments
    toolCallEventData:
      type: object
      description: 'Payload for a TOOL_CALL event — recorded when Apten AI invokes one of your configured tools during a conversation.'
      properties:
        toolId:
          type: string
          description: 'Identifier of the tool that was invoked.'
        toolInput:
          type: object
          description: 'Arguments the AI passed to the tool. Free-form, tool-specific.'
        toolResponse:
          type: object
          description: 'Response the tool returned. Free-form, tool-specific.'
      required:
        - toolId
    customEventData:
      type: object
      description: 'Payload for a CUSTOM event — created by your application via `POST /leads/{leadId}/addEvent`.'
      properties:
        text:
          type: string
          description: 'The free-form text supplied when the event was created.'
      required:
        - text
    complianceBlockedEventData:
      type: object
      description: |
        Payload for a `COMPLIANCE_BLOCKED` event. Emitted when Apten suppresses an outbound SMS, email, or voice attempt because Apten-managed DNC or an external compliance API check did not allow the send. There is no corresponding `MESSAGE`, `EMAIL`, or `CALL` event for the suppressed attempt.

        Allowed checks are not exported — only blocks appear on this endpoint. Use the top-level event `id` to correlate with Apten support or audit exports. For configuration and middleware contract details, see [External Compliance Checks](/compliance/external-checks).
      properties:
        channel:
          type: string
          enum: [sms, voice, email]
          description: 'Outbound channel that was blocked.'
        outreachType:
          type: string
          enum: [initial, reply, follow_up, manual, reminder, immediate_transfer, no_answer_follow_up, scheduled_transfer]
          description: 'Purpose of the blocked outreach, independent of the outbound channel. May be absent on older events.'
        reason:
          type: string
          description: |
            Machine-readable block reason. Common Apten codes include `DNC` (internal list), `COMPLIANCE_TARGET_MISSING`, `COMPLIANCE_PROVIDER_ERROR_FAIL_CLOSED`, `COMPLIANCE_PROVIDER_ERROR_FAIL_OPEN`, and configuration errors such as `COMPLIANCE_HTTP_ENDPOINT_MISSING`. When your HTTP compliance API returns `{ "allowed": false }`, `reason` is whatever your API returned (e.g. a suppression code from your middleware).
        source:
          type: string
          description: |
            Which leg of the compliance stack produced the block — e.g. `http` (external API), `apten-dnc`, `configuration`, or `provider-error`.
        blockType:
          type: string
          enum: [permanent, temporary]
          description: |
            `permanent` — later follow-ups to this lead on this channel are skipped. `temporary` — only this send was blocked; the next send is checked again. Provider errors under Fail Closed and configuration errors are always `temporary`. May be absent on older events. See [Permanent and temporary blocks](/compliance/external-checks#permanent-and-temporary-blocks).
        triggeredBy:
          type: string
          description: 'Optional label for the Apten code path that initiated the check (e.g. `llm`, `send-email`, `queue-call`).'
      required:
        - channel
        - source
    callDetail:
      type: object
      description: 'Full call record returned by `GET /calls/{callId}`.'
      properties:
        id:
          type: string
          description: 'Stable unique identifier for the call.'
        leadId:
          type: string
          description: 'The lead (room) this call belongs to. Absent for webchat-only calls.'
        sessionId:
          type: string
          description: 'The webchat session ID. Present for webchat voice calls instead of `leadId`.'
        direction:
          type: string
          enum: [INBOUND, OUTBOUND]
        callStatus:
          type: string
          description: 'Current or final status of the call (e.g. `COMPLETE`, `NO_ANSWER`, `IN_PROGRESS`).'
        durationSeconds:
          type: integer
          description: 'Length of the call in seconds.'
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        summary:
          type: string
          description: 'AI-generated summary of the conversation. Present after the call is processed; may be absent immediately after a terminal webhook if processing is still in flight.'
        transcript:
          type: array
          description: 'Ordered transcript turns. Empty array if no transcript is stored yet.'
          items:
            $ref: '#/components/schemas/transcriptMessage'
        isTransfer:
          type: boolean
          description: '`true` if this was a transfer to a human agent.'
        midCallTransfer:
          type: boolean
          description: '`true` if a transfer happened mid-call.'
        hasVoicemail:
          type: boolean
          description: '`true` if the call left a voicemail.'
        transferNumber:
          type: string
          description: 'E.164 number the call was transferred to, if applicable.'
        fallbackTransferNumber:
          type: string
          description: 'Fallback transfer number used when the primary transfer failed.'
        transferAnswerStatus:
          type: string
          enum: [answered, no_answer, pending]
        transferAnsweredBy:
          type: string
          enum: [human, machine_start, machine_end_beep, machine_end_silence, machine_end_other, unknown]
        transferFallbackAttempted:
          type: boolean
        transferFallbackAnswerStatus:
          type: string
          enum: [answered, no_answer, pending]
        transferFallbackAnsweredBy:
          type: string
          enum: [human, machine_start, machine_end_beep, machine_end_silence, machine_end_other, unknown]
        midCallTransferDuration:
          type: integer
          description: 'Seconds the lead was connected to a transfer recipient during a mid-call transfer.'
        endedBy:
          type: string
          enum: [LEAD, AGENT, UNKNOWN]
          description: >-
            Who ended the call. Only populated for warm transfers (where the lead is bridged into a live conference with a human agent); omitted for cold transfers and ordinary calls. `LEAD` — the lead hung up. `AGENT` — the human agent hung up. `UNKNOWN` — the call ended but the hangup could not be attributed to either party.
        transferInitiatedAt:
          type: string
          format: date-time
          description: 'When Apten began the transfer attempt and the lead began waiting. Absent if no transfer was started.'
        agentConnected:
          type: boolean
          description: '`true` if a warm-transfer agent was bridged to the lead.'
        transferFailureCode:
          type: string
          enum: [agent_not_connected, lead_disconnected]
          description: 'Why the transfer failed after the agent was dialed. Absent if the transfer connected or was never started.'
        transferWaitDurationSeconds:
          type: integer
          minimum: 0
          description: 'Seconds from transfer initiation until Apten recorded the terminal failure, rounded to the nearest whole second. Present when failure timing is available.'
        transferStatus:
          type: string
          enum: [connected, failed]
          description: 'Terminal transfer outcome after an agent was dialed. Absent if no transfer was started.'
        aiCallWithoutHumanResponse:
          type: boolean
          description: '`true` if the lead never spoke during an AI-initiated call.'
        isFollowUp:
          type: boolean
        isAiInitiated:
          type: boolean
        hitCallScreening:
          type: boolean
        reachedHumanAfterScreening:
          type: boolean
        isForward:
          type: boolean
      required:
        - id
        - direction
        - callStatus
        - durationSeconds
        - createdAt
        - updatedAt
        - transcript
    recordingDownload:
      type: object
      description: 'A short-lived URL for downloading a call recording.'
      properties:
        url:
          type: string
          format: uri
          description: 'Temporary URL for downloading the recording. Download the file immediately; do not store this URL.'
        expiresAt:
          type: string
          format: date-time
          description: 'When the temporary URL expires. Request a new URL after this time.'
        contentType:
          type: string
          enum: [audio/mpeg]
          description: 'MIME type of the downloaded recording.'
      required:
        - url
        - expiresAt
        - contentType
    transcriptMessage:
      type: object
      properties:
        role:
          type: string
          enum: [assistant, user]
          description: '`assistant` for Apten AI; `user` for the lead or caller.'
        content:
          type: string
        startTime:
          type: string
          format: date-time
        endTime:
          type: string
          format: date-time
      required:
        - role
        - content
  securitySchemes:
    api_key:
      type: apiKey
      name: x-api-key
      in: header
x-original-swagger-version: '2.0'
