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.

Test mode

Build against the real API without a real number, real calls or real costs. Test keys, test numbers, simulated calls and going live.

In test mode, you build against the same API, with the same routes, checks and errors, but nothing reaches the phone network and nothing is charged. You don't need a payment method. Sites and data are real: a site you publish with a test key is online for everyone, also on your own domain once its nameservers point to Hypamoon. Your numbers are test numbers, and calls are simulated on request, complete with a transcript, a summary and the real webhooks. SMS, email, payments and buying or transferring a domain are simulated too.

You get a test key as soon as you sign up at https://app.hypamoon.com.

New projects since October 2026 are one project: sites, data and apps are always real, and phone, outbound calls, SMS, email to other people, payments and buying domains each go live on their own at https://app.hypamoon.com/go-live. A hm_test_ key can never do anything real. Older projects can still have a separate test project; the rest of this page describes that pair.

Start every integration here. Once it works end to end, swap the test key for the live key; nothing else in your code changes.

Test keys and test projects

Every project comes as a pair:

Test project Live project
Key hm_test_... hm_live_...
How you get it as soon as you sign up in the portal, once you're verified
Scopes all of them the ones you select when you create it
Sites and data real and online real and online
Numbers test numbers, active immediately real numbers
Calls simulated with POST /test/calls real callers
Costs none, no payment method needed within your spending cap
Voice AI settings are saved and checked, not built always included

The two never mix. Test numbers, call plans, webhooks and simulated calls stay in the test project, and the live key never sees them. A test key on the live project, or a live key on the test project, returns 403 key_mode_mismatch. When the live project is deleted, its test keys stop working too.

GET /project tells you which mode you're in:

curl https://api.hypamoon.com/api/v1/project \
  -H "Authorization: Bearer $HYPAMOON_API_KEY"
const res = await fetch("https://api.hypamoon.com/api/v1/project", {
  headers: { Authorization: `Bearer ${process.env.HYPAMOON_API_KEY}` },
});
const project = await res.json(); // { mode: "test", live_project_id: "...", spending_limit_eur: null, ... }
project = requests.get(
    "https://api.hypamoon.com/api/v1/project",
    headers={"Authorization": f"Bearer {os.environ['HYPAMOON_API_KEY']}"},
).json()  # {"mode": "test", "live_project_id": "...", "spending_limit_eur": None, ...}

Test numbers

Test numbers run from +447700900000 to +447700900999, the range Ofcom reserves for use in drama. No phone anywhere has one of these numbers, so a test can never ring a real person. All test projects share this range.

  • GET /numbers/available returns free test numbers with test: true.
  • POST /numbers claims one, with { "phone_number": "+447700900042" } from the search. The Idempotency-Key works the same as it does live. A test number is active immediately.
  • A test project can have up to three test numbers (422 test_number_limit_reached). Reuse the ones you have; simulated calls work on any of them.

Of the checks that run before an order, only the company details check applies in test mode (422 company_details_missing), so the steps match live. There's no spending cap, payment method or billing check, and nothing is ordered from the carrier.

Every number and every call has a test field. It's true for test numbers and simulated calls, including inside webhooks, so your code can keep test traffic separate.

What else works differently

Request In test mode
PUT /project/company Checked and saved the same way; nothing is sent to the carrier.
PUT /assistant Saved and checked; sync is skipped_test_mode. The assistant is built when you go live.
PUT /numbers/{id}/call-plan Checked and saved; assistant_sync is skipped_test_mode. A plan with ai_agent still needs the assistant first, but not voice AI.
Forward targets in a plan Only test numbers: free ones from the range, or your own test numbers (forward_target_not_allowed).
Webhooks, GET /calls, GET /calls/{id} The same as live.

Simulate a call

Nobody can call a test number, so you trigger the call with POST /test/calls. Pick a scenario:

Scenario What happens Webhooks
reservation A caller books a table. call.completed, then call.summary.ready
callback_request A caller asks to be called back. call.completed, then call.summary.ready
question A caller asks whether you're open on Sunday. call.completed, then call.summary.ready
missed Nobody picked up: no transcript and no summary. call.completed only
curl -X POST https://api.hypamoon.com/api/v1/test/calls \
  -H "Authorization: Bearer $HYPAMOON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "number_id": "'$NUMBER_ID'", "scenario": "callback_request" }'
const res = await fetch("https://api.hypamoon.com/api/v1/test/calls", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.HYPAMOON_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ number_id: numberId, scenario: "callback_request" }),
});
const call = await res.json(); // 201, the call as GET /calls/{id} returns it
call = requests.post(
    "https://api.hypamoon.com/api/v1/test/calls",
    headers={"Authorization": f"Bearer {os.environ['HYPAMOON_API_KEY']}"},
    json={"number_id": number_id, "scenario": "callback_request"},
).json()  # 201, the call as GET /calls/{id} returns it

The response is 201 with the full call, including transcript, summary and test: true. The webhooks go out to your endpoints just as they would for a real call, signed the same way, and arrive within about a minute.

  • number_id is one of your test numbers, from GET /numbers.
  • from is optional: the caller's number, from +447700900000 to +447700900999.
  • The transcript and the summary are fixed sample texts, in English when your assistant's first language is en and in Dutch otherwise. The first line is your assistant's actual greeting.
  • It requires a test key and the calls:read scope. A live key gets 403 test_key_required.
  • At most 30 simulated calls per hour per test project.

Going live

Once the webhook arrives, your signature check passes and you've stored the summary, you're ready:

  1. Set up the live side yourself in the portal at https://app.hypamoon.com: complete verification (Verification), top up your balance or save a card (Balance), set the spending cap (Billing), and create a live key with the scopes you need (API keys). If any of these is missing, you'll get 402 payment_mandate_missing, 402 insufficient_balance, 422 spend_cap_missing or 403 missing_scope.
  2. Replace the test key with the live key (hm_live_...). Connected through OAuth in a chat app instead? Then no key is involved: in the portal, open Connect your chat and choose Switch to live next to that chat, or reconnect Hypamoon and choose the live project on the consent screen. Never paste an API key into a chat. Your code stays the same; only the data changes. Forward targets become real phone numbers, and the number order uses a new Idempotency-Key because it's a new order.
  3. Set up the live project: company details, a number, the assistant, the call plan and your webhook endpoint. It starts empty; nothing carries over from the test project. During the beta, a live project can have up to three numbers.
  4. Call your number from your own phone. POST /test/calls does not exist in live mode.

Keep the test key for your own tests and CI. The Quickstart walks through all of this with code.

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