{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/hellou-ai/json-schema/hellou-ai-call-schema.json",
  "title": "call",
  "description": "A phone call, as kept in the call history -- a WhatsApp call or a call an AI agent answered.",
  "x-generated": "2026-11-11",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/hellou-ai-openapi.json#/components/schemas/call",
  "type": "object",
  "properties": {
    "id": {
      "type": "integer"
    },
    "provider": {
      "type": "string",
      "enum": [
        "whatsapp",
        "ai_agent"
      ],
      "description": "`ai_agent` for a phone call an AI agent answered on one of the account's phone lines."
    },
    "direction": {
      "type": "string",
      "enum": [
        "incoming",
        "outgoing"
      ]
    },
    "status": {
      "type": "string",
      "enum": [
        "ringing",
        "in_progress",
        "completed",
        "no_answer",
        "failed",
        "rejected"
      ],
      "description": "`ringing` and `in_progress` are live calls; the rest are final. `completed` was answered,\n`no_answer` rang out or was hung up before pickup, `rejected` was declined.\n"
    },
    "end_reason": {
      "type": [
        "string",
        "null"
      ],
      "description": "Why the call ended, for example `agent_hangup` or `contact_hangup`."
    },
    "duration_seconds": {
      "type": [
        "integer",
        "null"
      ],
      "description": "Talk time, from pickup to hang-up."
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "When the call started ringing."
    },
    "started_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time",
      "description": "When it was picked up."
    },
    "ended_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "from_number": {
      "type": [
        "string",
        "null"
      ]
    },
    "to_number": {
      "type": [
        "string",
        "null"
      ]
    },
    "inbox": {
      "type": "object",
      "properties": {
        "id": {
          "type": "integer"
        },
        "name": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "name"
      ]
    },
    "contact": {
      "type": "object",
      "properties": {
        "id": {
          "type": "integer"
        },
        "name": {
          "type": [
            "string",
            "null"
          ]
        },
        "phone_number": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "id"
      ]
    },
    "conversation_id": {
      "type": "integer",
      "description": "Display ID of the conversation the call belongs to."
    },
    "message_id": {
      "type": [
        "integer",
        "null"
      ],
      "description": "The call's entry in the conversation."
    },
    "accepted_by_agent": {
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "name": {
          "type": "string"
        }
      }
    },
    "recording_url": {
      "type": [
        "string",
        "null"
      ],
      "description": "A link to the recording, when the agent's browser uploaded one."
    },
    "recording_duration_s": {
      "type": [
        "number",
        "null"
      ],
      "description": "Length of the recording in seconds, as measured by the edge. Null when unknown."
    },
    "ai_handled": {
      "type": "boolean",
      "description": "Whether an AI agent answered the call. Its words are at the call's `transcript` path."
    },
    "engine": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "realtime",
        "live",
        "premium",
        null
      ],
      "description": "The voice engine that answered an AI call: `realtime` (speech to speech), `live` (full duplex, answered by the agent's own model) or `premium` (regional-accent voices, answered by the agent's own model). Until 2026-10-20 this engine went out under another value; match `premium`."
    },
    "agent_bot": {
      "type": [
        "object",
        "null"
      ],
      "description": "The AI agent that answered.",
      "properties": {
        "id": {
          "type": "integer"
        },
        "name": {
          "type": "string"
        }
      }
    },
    "voice_line_id": {
      "type": [
        "integer",
        "null"
      ],
      "description": "The phone line the call came in on."
    },
    "parent_call_id": {
      "type": [
        "integer",
        "null"
      ],
      "description": "For a call an AI agent handed to another AI agent, the call's first leg; each leg is its own call. Null for a first leg and for any other call."
    },
    "ai_transfer_hop": {
      "type": [
        "integer",
        "null"
      ],
      "description": "On a leg that follows a hand-off between AI agents, the number of the pass that started it. Null on any other call."
    },
    "sentiment": {
      "type": [
        "object",
        "null"
      ],
      "description": "The caller's mood at the start and at the end of an AI call, read the way Sentiment reads a chat. Null until the call is read.",
      "properties": {
        "opening": {
          "type": "string",
          "enum": [
            "positive",
            "neutral",
            "negative"
          ]
        },
        "closing": {
          "type": "string",
          "enum": [
            "positive",
            "neutral",
            "negative"
          ]
        }
      }
    },
    "call_outcome": {
      "$ref": "#/$defs/call_outcome"
    },
    "dial_cause": {
      "$ref": "#/$defs/call_dial_cause"
    },
    "disposition_gap": {
      "$ref": "#/$defs/call_disposition_gap"
    },
    "campaign_id": {
      "type": [
        "integer",
        "null"
      ],
      "description": "The voice campaign the call was placed for."
    },
    "campaign_contact_id": {
      "type": [
        "integer",
        "null"
      ],
      "description": "The campaign contact the call was placed to."
    },
    "campaign_interest": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "interested",
        "undecided",
        "not_interested",
        "not_presented",
        null
      ],
      "description": "The interest recorded for the campaign contact this call was placed to. Null when the call has no campaign contact or no interest yet."
    },
    "updated_at": {
      "type": "string",
      "format": "date-time"
    }
  },
  "required": [
    "id",
    "provider",
    "direction",
    "status",
    "created_at",
    "inbox",
    "contact",
    "conversation_id",
    "ai_handled"
  ],
  "$defs": {
    "call_dial_cause": {
      "type": [
        "object",
        "null"
      ],
      "description": "Why a call HellouOne placed reached nobody. `null` for a call under way, one a person talked on, or one that is not an outbound AI call.",
      "properties": {
        "code": {
          "type": "string",
          "enum": [
            "voicemail_detected",
            "busy",
            "declined",
            "unavailable",
            "no_answer",
            "number_not_found",
            "no_route",
            "carrier_rejected",
            "timeout",
            "platform_error",
            "unknown"
          ],
          "description": "The cause. `voicemail_detected` is a machine that answered, or a call the edge cancelled once the line started forwarding."
        },
        "reason": {
          "type": "string",
          "description": "The cause in words, localized, for example \"Number does not exist\"."
        },
        "sip_code": {
          "type": "integer",
          "description": "The final SIP response code of the dial, when one came back."
        },
        "sip_final": {
          "type": "string",
          "description": "The final SIP response code with its standard (RFC 3261) reason phrase, for example `404 Not Found`; a code the standard does not name is given alone."
        },
        "who": {
          "type": "string",
          "enum": [
            "network",
            "carrier",
            "caller"
          ],
          "description": "Who gave the final response. `network` is the phone network the call left from, `carrier` the contact's carrier, `caller` the side that placed the call."
        },
        "edge_action": {
          "type": "string",
          "enum": [
            "cancel_on_181",
            "caller_cancel"
          ],
          "description": "What the phone edge did to the contact's leg on its own. `cancel_on_181` is its voicemail guard; `caller_cancel` is a leg dropped before anyone answered."
        },
        "ring_seconds": {
          "type": "integer",
          "description": "How long the contact's phone could have rung, from the dial request to the end of the call."
        },
        "source": {
          "type": "string",
          "enum": [
            "edge",
            "platform"
          ],
          "description": "Where the cause was reported from, the phone edge or the voice platform."
        }
      },
      "required": [
        "code",
        "reason"
      ]
    },
    "call_disposition_gap": {
      "type": [
        "object",
        "null"
      ],
      "description": "Why a voice campaign call ended with no disposition, when neither the AI agent nor the call summary chose one. `null` for any other call, a call the platform settled with its own disposition included.",
      "properties": {
        "reason": {
          "type": "string",
          "enum": [
            "undetermined",
            "missing",
            "unknown",
            "failed"
          ],
          "description": "`undetermined`: the summary could not tell which disposition applied. `missing`: the summary named none. `unknown`: it named one the campaign does not have. `failed`: the summary could not be made."
        },
        "at": {
          "type": "string",
          "format": "date-time",
          "description": "When the call was flagged."
        }
      },
      "required": [
        "reason",
        "at"
      ]
    },
    "call_outcome": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "contained",
        "escalated",
        "voicemail",
        "screening",
        "unanswered",
        "unreachable",
        "unknown",
        "error",
        null
      ],
      "description": "What an AI call came to. `null` for a call under way, one a person talked on (contained or escalated: the AI call report\ncounts those, the call record does not), or a call that is not an AI call. `voicemail`: a machine answered. `screening`: a call screener answered. `unanswered`:\nbusy, declined, off or rang out. `unreachable`: the number does not exist, has no route or was rejected by the carrier, or a carrier announcement said it is not available.\n`unknown`: the call's last event never arrived. `error`: the dial, the accept or the voice platform failed.\n"
    }
  }
}