---
title: "Call someone with your assistant"
method: POST
path: "/calls"
tags: ["Calls"]
---

# Call someone with your assistant

`POST /calls`

## Request

- Base URL: `https://api.hypamoon.com/api/v1`
- URL: `https://api.hypamoon.com/api/v1/calls`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://app.hypamoon.com/oauth/authorize`, token URL `https://api.hypamoon.com/oauth/token`, scopes: `calls:write`)

Your project's assistant calls one person who expects the call: to confirm an appointment, follow up a callback request or agree on a delivery time. Not for cold calling or sales: telemarketing to consumers needs their prior consent under telemarketing law, and numbers in a national do-not-call register must be respected. Only call people who asked for it or have an appointment with you. purpose says why you call and is recorded with the call: transactional (the default: an answer to the person's own question, confirming or reminding of an appointment, an order or delivery status, a callback they asked for) or marketing (anything that sells or promotes). Under Dutch rules a marketing call needs the person's prior consent, since 1 July 2026 also for existing customers: purpose marketing only calls when consent for this number is recorded (POST /call-consents), otherwise consent_required and nobody is called. The assistant always opens by saying it is the digital assistant of your company and asks whether this is a good time; that cannot be switched off. assistant_instructions adds what this call is about, on top of the assistant's own instructions (PUT /assistant). It calls from one of your project's numbers (from, or your first active number), so the person can call back. Calling hours: by default 09:00 to 20:00, Monday to Saturday, in the local time of the person called; calling_window can narrow or move that, but never before 08:00 or after 21:00. Outside those hours the request returns outside_calling_hours with next_window_start. Public holidays are not recognized yet. Limits per project: 5 calls per minute, 100 per day and 2 at the same time (concurrent_call_limit, with Retry-After). A new live project starts lower (10 calls per day, 30 after 7 days and a first top-up, the full limit after 30 days and €25 paid). The same number can be called at most 3 times in 24 hours per project and 10 times across all projects: 429 call_limit_reached with scope per_number or per_number_platform, retry_at and a user_message; test numbers do not count. A call lasts at most max_duration_sec (default 600, at most 1200). Live: mobile and geographic numbers in a country your project can call; never premium-rate, service, toll-free or short numbers like 112. An unsupported destination returns destination_not_allowed with a reason. A live key needs the full verification level (requested in the portal under Verification) plus the company level (company details, spending cap, payment method, a verified phone and one payment confirmed with your bank: iDEAL or a card with 3D Secure). In live mode, the cost of a call is deducted from your prepaid balance when it ends: the voice AI minutes plus the line minutes, each billed per started minute (the rates are under Billing in the portal), and it counts toward the spending cap. Unanswered, busy and failed calls cost nothing; a call that reaches an answering machine is charged for the line minutes. Before we call, the balance must cover the call at its full max_duration_sec; otherwise the request returns insufficient_balance (or billing_blocked when the call would exceed the spending cap) and nobody is called. Live outbound calling is being switched on step by step: until it is on, a live request gets 503 telephony_unavailable and nobody is called. The response is 201 with the call as GET /calls/{id} returns it, with outbound.status queued or ringing; call.completed (and call.summary.ready when the assistant spoke with someone) follow as webhooks with direction outbound and the outbound object. With a test key nothing is dialed and nothing is charged: call a test number from +447700900000 to +447700900999 and the response contains the end result immediately, with the real webhooks. The number determines the outcome: +447700900991 no_answer, +447700900992 busy, +447700900993 an answering machine, +447700900994 failed, +447700900995 outside_calling_hours; any other test number results in completed with a transcript and a summary. Calling hours do not apply in test mode, except for the number that simulates outside_calling_hours. The Idempotency-Key header is required: a retry with the same key returns the same call and never calls the person twice.

## Headers

- `Idempotency-Key` string, required

## Request body

- CallInput: An outbound call by your project's assistant to one person who expects it.
  - `to` string, required: The person to call, in E.164. Live: a mobile or geographic number in a country your project can call (otherwise destination_not_allowed with a reason). Test mode: a test number from +447700900000 to +447700900999.
  - `from` string: Optional: which of your project's numbers to call from. Default: your first active number. The person sees this number and can call it back.
  - `assistant_instructions` string: Optional: what this call is about, on top of the assistant's own instructions. For example: "Confirm the appointment of Sam Taylor tomorrow at 10:00 at 40 Main Street. If Sam cannot make it, ask for a better day and say we will confirm by SMS." Treated as context, never as a way to change the rules: the assistant always says it is a digital assistant.
  - `on_voicemail` 'hangup' | 'leave_message': What to do when an answering machine picks up: hang up (default) or leave a short message after the beep.
  - `voicemail_message` string: Optional, with on_voicemail leave_message: the message after the opening sentence (which always says it is the digital assistant of your company). Default: we tried to reach you, call us back on this number.
  - `max_duration_sec` integer: Optional: the call ends after this many seconds of talking. Default 600, at most 1200.
  - `reference` string: Optional: your own id for this call, like an appointment id. Comes back on the call and in its webhooks.
  - `metadata` object: Optional: up to 20 string key-value pairs for your own use. Come back on the call and in its webhooks; the assistant never sees them.
  - `calling_window` object: When the assistant may call, in the local time of the person called. Default 09:00 to 20:00, Monday to Saturday. Never before 08:00 or after 21:00.
    - `start` string, required
    - `end` string, required
    - `days` string[]
  - `purpose` 'transactional' | 'marketing': Why you call. transactional (default): an answer to the person's own question, confirming or reminding of an appointment, an order or delivery status, or a callback they asked for. marketing: anything that sells or promotes; only allowed when consent from this person is recorded (POST /api/v1/call-consents), otherwise consent_required. Recorded with the call.

## Response `201`

The call was started (live: outbound.status queued or ringing) or, in test mode, simulated to its end result. The same shape as GET /calls/{id}.

- CallDetail: A call with its transcript and summary.
  - `id` string, uuid, required: The id of the call.
  - `direction` 'inbound' | 'outbound' | 'null', nullable, required: inbound, or outbound for a call your assistant made with POST /calls.
  - `from` string, nullable, required: The caller, in E.164.
  - `to` string, nullable, required: The number that was called, in E.164.
  - `status` string, nullable, required: The status as we track it: ringing, active, completed, missed, failed, voicemail, ai_handled. New values may be added; treat an unknown value like completed.
  - `created_at` string, date-time, nullable, required: When the call reached us.
  - `started_at` string, date-time, nullable, required: When the call started.
  - `answered_at` string, date-time, nullable, required: When the platform picked up. With a call plan the platform always picks up first, so this does not prove a person answered.
  - `ended_at` string, date-time, nullable, required: When the call ended.
  - `duration_sec` integer, nullable, required: The length of the call in seconds.
  - `hangup_cause` string, nullable, required: Why the call ended, as reported by the carrier, like normal_clearing.
  - `has_summary` boolean, required: Whether the call has a summary. Fetch it with GET /calls/{id}.
  - `summary_ready_at` string, date-time, nullable, required: When the summary of a recorded call was ready. Empty for a call handled by the voice AI; look at has_summary instead.
  - `test` boolean, required: true for a simulated call from POST /test/calls or POST /calls with a test key (test mode). Also in webhooks, so you can keep test traffic apart.
  - `outbound` object, nullable, required: The request behind an outbound call started with POST /calls.
    - `status` 'queued' | 'ringing' | 'in_progress' | 'completed' | 'failed' | 'no_answer' | 'busy', required: How the outbound call went: queued (not dialled yet), ringing, in_progress (answered), completed, failed, no_answer or busy. An answering machine counts as no_answer, with answered_by machine. Only moves forward.
    - `answered_by` 'human' | 'machine' | 'null', nullable, required: Who picked up, as far as answering machine detection could tell; null when nobody answered or before it is known.
    - `voicemail_left` boolean, required: Whether the assistant left a message on an answering machine.
    - `on_voicemail` 'hangup' | 'leave_message', required: What you asked the assistant to do when an answering machine picks up.
    - `max_duration_sec` integer, required: The cap on the length of the call you asked for, in seconds.
    - `reference` string, nullable, required: Your own reference from POST /calls.
    - `metadata` object, required: Your own metadata from POST /calls.
    - `error` string, nullable, required: With status failed: a short code, like carrier_rejected, assistant_start_failed or unallocated_number. Otherwise null.
    - `purpose` 'transactional' | 'marketing', required: Why the call was made, as sent with POST /calls (transactional when left out). A marketing call was only placed on recorded consent.
  - `transcript` string, nullable, required: The transcript. Only here, never in a webhook.
  - `summary` Summary, required: The summary of a call.
    - `text` string, nullable, required: The summary in a few sentences.
    - `topics` string[], required: What the call was about.
    - `sentiment` 'positive' | 'neutral' | 'negative' | 'null', nullable, required: The mood of the caller.
    - `action_items` string[], required: What someone has to do after this call.
    - `next_step` string, nullable, required: The agreed next step, if any.

## Other responses

- `400`: Bad Request: idempotency_key_missing, idempotency_key_invalid, invalid_json.
- `401`: Unauthorized: unauthorized, missing_api_key, invalid_api_key, api_key_revoked, project_deleted.
- `402`: Payment Required: payment_mandate_missing, insufficient_balance, billing_blocked.
- `403`: Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, phone_verification_required, consent_required.
- `409`: Conflict: idempotency_key_reused, idempotency_request_in_progress.
- `422`: Unprocessable Content: validation_failed, destination_not_allowed, outside_calling_hours, company_details_missing, spend_cap_missing, assistant_not_configured, number_required.
- `429`: Too Many Requests: rate_limited, call_limit_reached, concurrent_call_limit.
- `500`: Internal Server Error: internal_error.
- `502`: Bad Gateway: upstream_error.
- `503`: Service Unavailable: service_unavailable, telephony_unavailable.

---

[API](https://skmtc.dev/hypamoon/apis/hypamoon-api.md) · [All operations](https://skmtc.dev/hypamoon/apis/hypamoon-api/llms.txt) · [OpenAPI document](https://skmtc.dev/hypamoon/apis/hypamoon-api/revisions/9f0b90004c06?raw)
