Open source·Apache-2.0
AI phone calls you can run yourself.
Offhook places and answers real phone calls with an AI agent. Bring your own provider keys, run it on your own servers, and get a transcript and a full event log for every call.
- To
- +1 415 555 0123
- Agent
- Alex · Acme Clinic
- Context
- Priya · Tuesday at 3 PM
- queued00:00.0
- dialing00:00.4
- in progress00:06.1
- completed01:48.3
AgentHi, this is Alex from Acme Clinic. Am I speaking with Priya?
CallerYes, that's me.
AgentI'm calling to confirm your appointment with Dr. Rao on Tuesday at 3 PM. Does that still work?
CallerIt does, thanks.
{"confirmed": true}How it works
Three requests to a real phone call.
- 01
Connect your keys
Register your Deepgram, Cartesia, LLM and carrier keys as connections. Each one is encrypted with a key that belongs to your account, and decrypted only while a call is running.
- 02
Define an agent
Give it instructions, an opening line, a voice and a model, and pick the connections it runs on. Reuse the same agent across every call.
- 03
Place the call
Send a phone number and any facts the agent should know. A worker dials, the agent talks, and you get a webhook at every step until the call ends.
curl -X POST localhost:8000/v1/connections \
-H "Authorization: Bearer $OFFHOOK_KEY" \
-H "content-type: application/json" \
-d '{
"name": "my-groq",
"provider_kind": "llm",
"provider_name": "groq",
"secrets": {"api_key": "gsk_..."}
}'curl -X POST localhost:8000/v1/agents \
-H "Authorization: Bearer $OFFHOOK_KEY" \
-H "content-type: application/json" \
-d '{
"name": "Alex",
"company": "Acme Clinic",
"instructions": "Confirm the appointment; offer to reschedule if needed.",
"first_message": "Hi, this is Alex from Acme Clinic. Am I speaking with Priya?",
"stt_connection_id": "<stt-connection-id>",
"tts_connection_id": "<tts-connection-id>",
"llm_connection_id": "<llm-connection-id>",
"telephony_connection_id": "<telephony-connection-id>"
}'curl -X POST localhost:8000/v1/calls \
-H "Authorization: Bearer $OFFHOOK_KEY" \
-H "Idempotency-Key: confirm-priya-tue" \
-H "content-type: application/json" \
-d '{
"agent_id": "<agent-id>",
"to_number": "+14155550123",
"context": {"patient_name": "Priya", "appointment_time": "Tuesday at 3 PM"}
}' What's included
The plumbing a production call needs.
- Calls
- Outbound and inbound. An async dial queue, idempotency keys so a retried request never rings anyone twice, and clear states from queued to completed.
- Agents
- Instructions, an opening line, a voice and a model for each agent. Built-in tools to record results and end the call, plus your own HTTP tools for live data mid-call.
- Providers
- Deepgram for speech-to-text. Cartesia or OpenAI for text-to-speech. Groq, Gemini, OpenAI or OpenRouter for the model. Plivo or Twilio for the phone line.
- Call records
- An event timeline, transcript, recording, structured outcome and stable error code for every call. Signed webhooks with retries for every change.
- Security
- Provider keys stored with per-tenant envelope encryption, using a master key from the environment or AWS KMS. Hashed API keys, an SSRF guard and log redaction.
- Limits
- Per-tenant rate limits, concurrent-call caps and monthly quotas, with usage metered in billable seconds.
Quickstart
Talk to an agent in your browser first.
You don't need a phone number to start. The playground runs the same pipeline over your microphone. You need Python 3.13, Docker, and free-tier keys from Deepgram, Cartesia and Groq.
git clone https://github.com/welldunai/offhook.git
cd offhook
uv venv && source .venv/bin/activate
uv pip install -e .
# add your Deepgram, Cartesia and Groq keys
cp .env.example .env
# Postgres, migrations, a dev tenant and a demo agent
docker compose up -d
alembic upgrade head
python -m app.admin seed-dev \
--voice-id 05a69c2d-457a-4802-8f91-628cb158b522
# then open http://localhost:8000 and start talking
python run.py