Skip to main content
Change of provenance

The repository or configuration that created this release differ from a previous release. View details.

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), IE (+353), HK (+852)
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)
  • Ireland (+353)
  • Hong Kong (+852)

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

Metadata

Release files for pine-voice 0.1.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Change of provenance

The repository or configuration that created these release files differ from that of a previous release.

We recommend inspecting the repository and configuration before proceeding. View details.

Source distribution (sdist)

Source distribution for pine-voice 0.1.5
File Size Uploaded Provenance Actions
pine_voice-0.1.5.tar.gz 13.1 kB With provenance Details for pine_voice-0.1.5.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pine-voice 0.1.5
File Interpreter ABI Platform Actions
pine_voice-0.1.5-py3-none-any.whl Python 3 none any Details for pine_voice-0.1.5-py3-none-any.whl

Total release size: 28.9 kB

Release files / pine_voice-0.1.5.tar.gz

Change of provenance

This file belongs to a release with changed provenance: the repository or configuration that created the release files differ from that of a previous release.

We recommend inspecting the repository and configuration before proceeding. View details.

Download URL pine_voice-0.1.5.tar.gz
Size 13.1 kB
Tags Source
SHA-256 checksum
How to use checksums
41bc9eaf1ccaef621f35c5400a316600aaf64be10fa2e27dde9328d10afc83c8
BLAKE2b-256 checksum
How to use checksums
eefa061b326f4707687c33095052382a03928857c6fbd55205721aea3fe67d79
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Feb 18, 2026.

Transparency log

Release files / pine_voice-0.1.5-py3-none-any.whl

Change of provenance

This file belongs to a release with changed provenance: the repository or configuration that created the release files differ from that of a previous release.

We recommend inspecting the repository and configuration before proceeding. View details.

Download URL pine_voice-0.1.5-py3-none-any.whl
Size 15.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
198d2498394af20aa69fa38962c64eccb9a8091de46e799e5475454fb4165721
BLAKE2b-256 checksum
How to use checksums
c8f83d8cce3fd180185e5019d60d4d0df827336c1b81987218b993660481bb7f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Feb 18, 2026.

Transparency log

Security for pine-voice 0.1.5

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Change of provenance

The repository and configuration that created this release differ from a previous release.

While this could reflect a routine change by the project maintainers, it may also indicate that the release files have been compromised.

Select from 2 files

Attestation for pine_voice-0.1.5.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Feb 18, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page