Skip to content
Hypamoon
API docs

Start for free. Sign up without a card and you'll get a test project and a test key right away. Once your company (or, for a personal account, you) is verified, you can create a live key in the portal.

Calls

The call object, the summary, the transcript and how to page through calls.

A call is one phone call to one of your project's numbers. The list, the detail view and the webhooks all use the same object with the same field names, so what you store from a webhook matches what you fetch later.

The call object

JSON
{
  "id": "7f1c2d3e-4b5a-4c6d-8e7f-901a2b3c4d5e",
  "direction": "inbound",
  "from": "+447700900456",
  "to": "+3185•••••42",
  "status": "ai_handled",
  "created_at": "2026-09-21T14:10:02.118Z",
  "started_at": "2026-09-21T14:10:02.118Z",
  "answered_at": "2026-09-21T14:10:03.004Z",
  "ended_at": "2026-09-21T14:12:51.770Z",
  "duration_sec": 169,
  "hangup_cause": "normal_clearing",
  "has_summary": true,
  "summary_ready_at": null,
  "test": false
}
Field Type
id string (UUID) The call. Use it with GET /calls/{id}.
direction string or null inbound or outbound. Version 1 only has inbound calls.
from string or null The caller, in E.164. Can be null for a withheld number.
to string or null Your number that was called, in E.164.
status string or null See statuses.
created_at timestamp When the call reached us. Calls are sorted by this.
started_at timestamp or null When the call started.
answered_at timestamp or null When the platform answered. See the note below.
ended_at timestamp or null When the call ended.
duration_sec integer or null Length of the call in seconds.
hangup_cause string or null Why the call ended, as reported by the network, for example normal_clearing.
has_summary boolean Whether the call has a summary.
summary_ready_at timestamp or null When post-call processing finished. Null for calls the AI assistant handled; check has_summary instead.
test boolean true for a call simulated in test mode. Also included in webhooks, so you can keep test traffic separate.

Statuses

Status Meaning
ringing The call is coming in and has not been answered yet.
active The call is in progress.
completed The call was answered and has ended.
missed Nobody answered.
failed The call could not be connected, or it dropped.
voicemail The caller left a voicemail.
ai_handled The voice AI assistant handled the call.

New statuses may be added. Make sure your code handles a status it doesn't recognize, for example by showing it as is.

The summary

JSON
{
  "text": "Caller wants a table for four on Friday at 19:00.",
  "topics": ["reservation"],
  "sentiment": "positive",
  "action_items": ["Confirm the reservation by text message"],
  "next_step": "Send confirmation"
}
Field Type
text string or null A short summary of the call.
topics string[] What the call was about.
sentiment string or null positive, neutral or negative.
action_items string[] Things someone should do after the call.
next_step string or null The single most important follow-up.

The transcript

The transcript is plain text and only available on GET /calls/{id}. It's never sent in a webhook: a webhook can be stored and retried for almost two days, and a transcript shouldn't outlive the call's retention period. After a call.summary.ready webhook, fetch the call if you need the transcript.

When a call's retention period ends, the call is deleted, along with its transcript and summary. Copy anything you need to keep.

List calls

GET /calls returns the project's calls, newest first. Requires the calls:read scope. The list doesn't include transcripts or summaries; has_summary tells you whether a call is worth fetching.

Query
limit 1 to 100, default 25.
cursor The next_cursor from the previous page. Omit it for the first page.
JSON
{
  "data": [
    {
      "id": "7f1c2d3e-4b5a-4c6d-8e7f-901a2b3c4d5e",
      "direction": "inbound",
      "from": "+447700900456",
      "to": "+3185•••••42",
      "status": "ai_handled",
      "created_at": "2026-09-21T14:10:02.118Z",
      "started_at": "2026-09-21T14:10:02.118Z",
      "answered_at": "2026-09-21T14:10:03.004Z",
      "ended_at": "2026-09-21T14:12:51.770Z",
      "duration_sec": 169,
      "hangup_cause": "normal_clearing",
      "has_summary": true,
      "summary_ready_at": null,
      "test": false
    }
  ],
  "has_more": true,
  "next_cursor": "eyJ0IjoiMjAyNi0wOS0yMVQxNDoxMDowMi4xMTgrMDA6MDAiLCJpZCI6IjdmMWMyZDNlIn0"
}

Keep requesting with cursor=<next_cursor> until has_more is false. The cursor is opaque; don't construct or modify it. A cursor keeps working as new calls come in: they appear on the first page, not partway through your paging.

curl "https://api.hypamoon.com/api/v1/calls?limit=100" \
  -H "Authorization: Bearer $HYPAMOON_API_KEY"
async function* allCalls() {
  let cursor: string | null = null;
  do {
    const url = new URL("https://api.hypamoon.com/api/v1/calls");
    url.searchParams.set("limit", "100");
    if (cursor) url.searchParams.set("cursor", cursor);
    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.HYPAMOON_API_KEY}` },
    });
    if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
    const page = await res.json();
    yield* page.data;
    cursor = page.has_more ? page.next_cursor : null;
  } while (cursor);
}
def all_calls():
    cursor = None
    while True:
        params = {"limit": 100}
        if cursor:
            params["cursor"] = cursor
        res = requests.get(
            "https://api.hypamoon.com/api/v1/calls",
            params=params,
            headers={"Authorization": f"Bearer {os.environ['HYPAMOON_API_KEY']}"},
        )
        res.raise_for_status()
        page = res.json()
        yield from page["data"]
        if not page["has_more"]:
            break
        cursor = page["next_cursor"]

Get one call

GET /calls/{id} returns the call object plus transcript and summary (both can be null). Requires the calls:read scope.

JSON
{
  "id": "7f1c2d3e-4b5a-4c6d-8e7f-901a2b3c4d5e",
  "direction": "inbound",
  "from": "+447700900456",
  "to": "+3185•••••42",
  "status": "ai_handled",
  "created_at": "2026-09-21T14:10:02.118Z",
  "started_at": "2026-09-21T14:10:02.118Z",
  "answered_at": "2026-09-21T14:10:03.004Z",
  "ended_at": "2026-09-21T14:12:51.770Z",
  "duration_sec": 169,
  "hangup_cause": "normal_clearing",
  "has_summary": true,
  "summary_ready_at": null,
  "test": false,
  "transcript": "Assistent: U spreekt met de digitale assistent van Acme Bookings. Waarmee kan ik u helpen?\nBeller: Hi, I'd like a table for four on Friday.",
  "summary": {
    "text": "Caller wants a table for four on Friday at 19:00.",
    "topics": ["reservation"],
    "sentiment": "positive",
    "action_items": ["Confirm the reservation by text message"],
    "next_step": "Send confirmation"
  }
}

A call that doesn't exist, or belongs to another project, returns 404 (not-found). We don't say which, so IDs from other projects can't be probed.

What is not in the API

Internal call legs (forwarding to your team's app or browser phone) don't appear as separate calls in the API. You see one call per caller, not one per phone that rang.

Hypamoon is the infrastructure your chat or agent builds on for a business. Hypamoon API, version 1.hypamoon.com