Official Pine AI Voice SDK — make phone calls via Pine's AI voice agent
Project description
Pine Voice SDK for Python
Official SDK for Pine AI voice calls. Make phone calls via Pine's AI voice agent from any Python application — no MCP client or OpenClaw required.
Supports both synchronous and asynchronous usage.
Install
pip install pine-voice
Quick start
from pine_voice import PineVoice
client = PineVoice(access_token="your-access-token", user_id="your-user-id")
result = client.calls.create_and_wait(
to="+14155551234",
name="Dr. Smith Office",
context=(
"Local dentist office. I'm an existing patient (Jane Doe, DOB 03/15/1990). "
"Open Mon-Fri 9am-5pm. Dr. Smith is my preferred dentist but Dr. Lee is also fine."
),
objective="Schedule a dental cleaning for next Tuesday afternoon, ideally 2-4pm",
instructions=(
"If Tuesday afternoon is unavailable, try Wednesday or Thursday afternoon. "
"If no afternoons are open this week, take the earliest available afternoon next week. "
"Confirm the appointment date, time, and dentist name before hanging up."
),
)
print(result.transcript)
Authentication
Option A: Pass credentials directly
client = PineVoice(access_token="your-access-token", user_id="your-user-id")
Option B: Use environment variables
export PINE_ACCESS_TOKEN="your-access-token"
export PINE_USER_ID="your-user-id"
client = PineVoice() # reads from env
Getting credentials
If you don't have credentials yet, use the auth helpers:
from pine_voice import PineVoice
# Step 1: Request a verification code (sent to your Pine AI account email)
request_token = PineVoice.auth.request_code("you@example.com")
# Step 2: Enter the code from your email
credentials = PineVoice.auth.verify_code("you@example.com", request_token, "1234")
# Step 3: Use the credentials
client = PineVoice(
access_token=credentials.access_token,
user_id=credentials.user_id,
)
Making calls
Fire and poll
# Initiate (returns immediately)
call = client.calls.create(
to="+14155552345",
name="Bay Area Auto Care",
context=(
"Local auto repair shop. My car is a 2019 Honda Civic, ~45,000 miles. "
"Due for a routine oil change and tire rotation. No warning lights or known issues."
),
objective=(
"Schedule an oil change and tire rotation for this Friday morning, ideally before noon"
),
instructions=(
"If Friday morning is full, try Friday afternoon. "
"If Friday is completely booked, try next Monday or Tuesday morning. "
"Ask for a price estimate for both services combined. "
"Ask how long the service will take so I know when to pick up the car. "
"Confirm the appointment date, time, services, and estimated cost before hanging up."
),
caller="communicator",
voice="female",
max_duration_minutes=10,
)
# Poll until complete
status = client.calls.get(call.call_id)
Call and wait (SSE with polling fallback)
result = client.calls.create_and_wait(
to="+14155559876",
name="Bella Italia Restaurant",
context=(
"Italian restaurant in downtown SF. Reservation for Mike Chen. "
"Party of 4 adults, no children. One guest is vegetarian, one has a nut allergy."
),
objective="Make a dinner reservation for tonight at 7pm for 4 people",
instructions=(
"If 7pm is not available, try 7:30pm or 8pm. "
"If tonight is fully booked, try tomorrow (Saturday) at the same times. "
"Request a booth or quiet table if possible, but not required. "
"Mention the nut allergy and ask if they can accommodate it. "
"Confirm the reservation date, time, party size, and name on the reservation."
),
# SSE is used by default to wait for the final result.
# Falls back to polling if SSE is unavailable.
poll_interval=10, # polling fallback interval (default 10s)
)
print(result.status) # "completed" | "failed" | "cancelled"
print(result.transcript) # full conversation
print(result.summary) # LLM summary (empty unless enable_summary=True)
print(result.credits_charged) # credits used
Async usage
from pine_voice import AsyncPineVoice
client = AsyncPineVoice(access_token="...", user_id="...")
# Auth (async)
request_token = await AsyncPineVoice.auth.request_code("you@example.com")
credentials = await AsyncPineVoice.auth.verify_code("you@example.com", request_token, "1234")
# Calls (async)
call = await client.calls.create(to="+14155551234", name="...", context="...", objective="...")
status = await client.calls.get(call.call_id)
result = await client.calls.create_and_wait(to="+14155551234", name="...", context="...", objective="...")
Both clients support context managers:
# Sync
with PineVoice(access_token="...", user_id="...") as client:
result = client.calls.create_and_wait(...)
# Async
async with AsyncPineVoice(access_token="...", user_id="...") as client:
result = await client.calls.create_and_wait(...)
Error handling
from pine_voice import PineVoice, AuthError, RateLimitError, CallError
try:
result = client.calls.create_and_wait(...)
except AuthError as e:
# Token expired or invalid — re-authenticate
print(f"Auth failed: {e.code} {e.message}")
except RateLimitError as e:
# Too many calls — wait and retry
print(f"Rate limited: {e.message}")
except CallError as e:
# Call-specific issue (invalid phone, DND, policy, etc.)
print(f"Call error: {e.code} {e.message}")
API reference
PineVoice(access_token?, user_id?, gateway_url?)
Synchronous client. Falls back to PINE_ACCESS_TOKEN and PINE_USER_ID env vars.
AsyncPineVoice(access_token?, user_id?, gateway_url?)
Asynchronous client. Same parameters as PineVoice.
PineVoice.auth.request_code(email) -> str
Request a verification code. Returns the request_token.
PineVoice.auth.verify_code(email, request_token, code) -> Credentials
Verify the code. Returns Credentials(access_token, user_id).
client.calls.create(...) -> CallInitiated
Initiate a call. Returns CallInitiated(call_id, status).
| Param | Type | Required | Description |
|---|---|---|---|
to |
str |
Yes | Phone number in E.164 format. Supported countries: US/CA/PR (+1), UK (+44), AU (+61), NZ (+64), SG (+65), HK (+852), CN (+86, connection not guaranteed) |
name |
str |
Yes | Name of the person or business being called |
context |
str |
Yes | Background context about the callee and info needed during the call |
objective |
str |
Yes | Specific goal the call should accomplish |
instructions |
str |
No | Detailed strategy and instructions for the voice agent |
caller |
str |
No | "negotiator" for complex negotiations (requires thorough strategy in context/instructions). "communicator" for general tasks. Default: "negotiator" |
voice |
str |
No | "male" or "female". Default: "female" |
max_duration_minutes |
int |
No | Max call duration in minutes (1-120). Default: 120 |
enable_summary |
bool |
No | Request an LLM-generated summary after the call. Default: False. Most AI agents can process the full transcript directly, so the summary is opt-in to save latency and cost. |
client.calls.get(call_id) -> CallStatus | CallResult
Get call status. Returns CallResult if terminal.
client.calls.create_and_wait(...) -> CallResult
Initiate and wait until complete. Returns CallResult.
Uses SSE to wait for the final call result. If the SSE connection fails or the server doesn't support it, automatically falls back to polling. Reconnects once on SSE connection drop before falling back.
Important: Real-time intermediate updates (partial transcripts, "call connected" events) are not currently available. The SSE stream delivers only the final transcript after the call completes. There are no intermediate progress events during the call.
| Extra Param | Type | Default | Description |
|---|---|---|---|
poll_interval |
int |
10 |
Seconds between polling requests (fallback only) |
use_sse |
bool |
True |
Try SSE first. Set False to force polling. |
on_progress |
Callable[[CallProgress], None] |
None |
Callback invoked with a CallProgress object after each poll cycle during polling fallback. Note: real-time progress events are not currently available. |
Supported countries
The voice agent can only speak English. Calls can be placed to the following countries:
- US, Canada, and Puerto Rico (+1)
- United Kingdom (+44)
- Australia (+61)
- New Zealand (+64)
- Singapore (+65)
- Hong Kong (+852)
- China (+86) — connection is not guaranteed; some numbers can be connected, but others cannot
Calls to numbers outside these country codes will be rejected with a POLICY_VIOLATION error.
Requirements
- Python 3.9+
- Pine AI Pro subscription (19pine.ai)
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pine_voice-0.1.3.tar.gz.
File metadata
- Download URL: pine_voice-0.1.3.tar.gz
- Upload date:
- Size: 13.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
27952ae21a63754288412698b31b5c83e8fcb47a0ed41245926c80662ba6d721
|
|
| MD5 |
1f9cf963fdf68273b6f9794aab724ac4
|
|
| BLAKE2b-256 |
cd9432b2a1cc95b9e5647fae6d0365ded3d3d19e271a3ebef1d7b9630098d166
|
Provenance
The following attestation bundles were made for pine_voice-0.1.3.tar.gz:
Publisher:
publish.yml on RunVid/pine-voice-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pine_voice-0.1.3.tar.gz -
Subject digest:
27952ae21a63754288412698b31b5c83e8fcb47a0ed41245926c80662ba6d721 - Sigstore transparency entry: 956293286
- Sigstore integration time:
-
Permalink:
RunVid/pine-voice-python@ffc9779e27027b8fc3d1bf51b2d9e93519f799b5 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/RunVid
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ffc9779e27027b8fc3d1bf51b2d9e93519f799b5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pine_voice-0.1.3-py3-none-any.whl.
File metadata
- Download URL: pine_voice-0.1.3-py3-none-any.whl
- Upload date:
- Size: 15.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ec9c30ffa237f6d7280ff95c44762326f4bcfa8dc55687f9a14e0ef6011b6a7
|
|
| MD5 |
f7643f68278bc66b1c71c6c82a7ff08c
|
|
| BLAKE2b-256 |
9886ab91eb034e3e877280477ffa131a972a716fcf593f76f79c9540f5ac24c7
|
Provenance
The following attestation bundles were made for pine_voice-0.1.3-py3-none-any.whl:
Publisher:
publish.yml on RunVid/pine-voice-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pine_voice-0.1.3-py3-none-any.whl -
Subject digest:
6ec9c30ffa237f6d7280ff95c44762326f4bcfa8dc55687f9a14e0ef6011b6a7 - Sigstore transparency entry: 956293291
- Sigstore integration time:
-
Permalink:
RunVid/pine-voice-python@ffc9779e27027b8fc3d1bf51b2d9e93519f799b5 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/RunVid
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ffc9779e27027b8fc3d1bf51b2d9e93519f799b5 -
Trigger Event:
push
-
Statement type: