Base URLs

The API uses two hosts. Authentication goes through the Pine AI main API; voice call endpoints go through the gateway.

Auth API https://www.19pine.ai
Voice API https://agent3-api-gateway-staging.19pine.ai

All endpoints accept and return JSON. Include Content-Type: application/json in all requests.

Authentication

Pine Voice uses a two-step email verification flow to issue access tokens. Tokens are then passed as Bearer tokens in subsequent API calls.

Authentication flow

1

Request a verification code

Send your Pine AI account email to receive a one-time verification code via email.

2

Verify the code

Submit the code from your email along with the request_token you received. You get back an access_token and user_id.

3

Use credentials in API calls

Include both the access token and user ID as headers in all voice call requests.

Required headers

After completing authentication, include these headers on every voice call API request:

HTTP Headers
Authorization: Bearer <access_token>
X-Pine-User-Id: <user_id>
Content-Type: application/json

Prerequisite: You need an active Pine AI Pro subscription and a registered phone number on your Pine account.

Request Verification Code

POST /api/v2/auth/email/request

Sends a one-time verification code to the provided email address. The email must be associated with an existing Pine AI account. Uses the Auth API host (https://www.19pine.ai).

Request body

ParameterTypeDescription
emailrequired string Your Pine AI account email address

Example request

cURL
curl -X POST https://www.19pine.ai/api/v2/auth/email/request \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]"
  }'

Success response 200 OK

JSON
{
  "status": "success",
  "data": {
    "request_token": "eyJhbGciOiJIUzI1NiIs..."
  }
}
FieldTypeDescription
status string Always "success"
data.request_token string Temporary token to use in the verify step. Expires after a few minutes.

Error responses

StatusCause
400Missing or invalid email address
422Email not registered with Pine AI

Verify Code & Get Token

POST /api/v2/auth/email/verify

Verifies the one-time code from your email and returns your access credentials. Store both access_token and id securely. Uses the Auth API host (https://www.19pine.ai).

Request body

ParameterTypeDescription
emailrequired string Same email used in the request step
request_tokenrequired string The request_token from the previous step
coderequired string The verification code received via email

Example request

cURL
curl -X POST https://www.19pine.ai/api/v2/auth/email/verify \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "request_token": "eyJhbGciOiJIUzI1NiIs...",
    "code": "1234"
  }'

Success response 200 OK

JSON
{
  "status": "success",
  "data": {
    "id": "1234567890",
    "access_token": "eyJhbGciOiJSUzI1NiIs..."
  }
}
FieldTypeDescription
data.id string Your Pine user ID. Use as the X-Pine-User-Id header.
data.access_token string Bearer token for API authentication. Expires periodically.

Error responses

StatusCause
400Invalid verification code
401Code verification failed
410Request token expired — restart the flow

Security: Store your access_token securely. Do not commit it to version control or log it in plaintext. Tokens expire and will need to be refreshed by repeating this flow.

Initiate a Phone Call

POST /api/v2/voice/call

Validates your request, runs safety and billing checks, and initiates a phone call using Pine's AI voice agent. The request blocks while the call is being set up (~2–5 seconds). Once the call is successfully initiated, it returns a call_id that you use to poll for progress and results.

Async results. This endpoint waits for the call to be initiated, then returns a call_id. The call itself runs in the background. To get the result, use the SSE stream endpoint (recommended) to wait for the final transcript, or poll the GET endpoint periodically. If you need a fully synchronous flow that blocks until the call finishes, use the MCP protocol with mode: "sync". Note: no real-time intermediate updates are available during the call.

Request headers

HeaderValueDescription
Authorizationrequired string Bearer <access_token>
X-Pine-User-Idrequired string Your Pine user ID from authentication
Content-Typerequired string application/json

Request body

ParameterTypeDescription
dialed_numberrequired string Phone number to call in E.164 format (e.g. +14155551234)
callee_namerequired string Name of the person or business being called
callee_contextrequired string Background context about the callee (e.g. "Local dentist office", "My internet service provider")
call_objectiverequired string Clear, specific goal for the call (e.g. "Schedule an appointment for next Tuesday")
detailed_instructionsoptional string Additional strategy or instructions for the voice agent
calleroptional string "negotiator" or "communicator". Default: "negotiator". See Caller Types below for detailed guidance on when to use each and what information to provide.
voiceoptional string "male" or "female". Default: "female"
max_duration_minutesoptional integer Maximum call duration in minutes (1–120). Default: 120
enable_summaryoptional boolean 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.

Example request

cURL
curl -X POST https://agent3-api-gateway-staging.19pine.ai/api/v2/voice/call \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -H "X-Pine-User-Id: 1234567890" \
  -H "Content-Type: application/json" \
  -d '{
    "dialed_number": "+14155551234",
    "callee_name": "Dr. Smith Office",
    "callee_context": "Local dentist office, open Mon-Fri 9am-5pm",
    "call_objective": "Schedule a dental cleaning appointment for next Tuesday afternoon",
    "caller": "communicator",
    "voice": "female",
    "max_duration_minutes": 10
  }'

Success response 201 Created

JSON
{
  "call_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "in_progress"
}
FieldTypeDescription
call_id string Unique identifier for the call. Use this to poll for status.
status string Initial status, always "in_progress"

Caller ID: The call will appear to the recipient as coming from your registered Pine phone number.

Caller Types: Negotiator vs Communicator

The caller parameter selects which Pine voice agent personality handles the call. Choosing the right type — and providing the right level of detail — is critical to call success.

Negotiator "negotiator"

Purpose-trained for complex negotiations, bill reductions, account disputes, contract discussions, and any call where the agent needs to persuade, counter-offer, or hold firm on your behalf.

Best for:

  • Bill negotiations and rate reductions
  • Service cancellation with retention offers
  • Contract renegotiations
  • Dispute resolution and refund requests
  • Insurance claim discussions
Required: You must include a thorough negotiation strategy in callee_context and detailed_instructions. Include: target outcome, acceptable range, leverage points, current terms, fallback positions, and any constraints (e.g. "don't change plan tier").

Communicator "communicator"

Handles general-purpose routine calls — scheduling, reservations, inquiries, confirmations, and straightforward requests where no persuasion or negotiation is needed.

Best for:

  • Scheduling appointments and meetings
  • Making restaurant or venue reservations
  • Business inquiries (hours, availability, pricing)
  • Simple account actions (confirm, update info)
  • Delivering messages or gathering information
Tip: Even for routine calls, provide clear context — who you are, what you need, any preferences or fallback options. The more detail you give, the better the outcome.

Negotiation calls require strategy details. If you use "negotiator", calls without a clear negotiation strategy (target price, current terms, acceptable range, fallback positions) are likely to fail or produce poor results. The voice agent cannot ask you for missing details mid-call — all strategy must be provided upfront in callee_context and detailed_instructions.

Example: Negotiator call

JSON — negotiator request body
{
  "dialed_number": "+18005551234",
  "callee_name": "Internet Provider Support",
  "callee_context": "My internet service provider. Account #987654321. Current plan: 200 Mbps at $89.99/mo, subscribed for 3 years. Competitor offer: same speed at $49.99/mo from a local provider.",
  "call_objective": "Negotiate the monthly bill down from $89.99 to $60/mo or less",
  "detailed_instructions": "Target: $60/mo. Acceptable range: up to $65/mo. Mention the competitor offer as leverage. If they offer a promotional rate, ensure it lasts at least 12 months. Do NOT agree to change the plan tier or speed. If they cannot offer any discount, ask to speak with the retention department. As a last resort, request to cancel and see if a retention offer comes.",
  "caller": "negotiator",
  "voice": "male",
  "max_duration_minutes": 30
}

Example: Communicator call

JSON — communicator request body
{
  "dialed_number": "+14155559876",
  "callee_name": "The Italian Place",
  "callee_context": "Italian restaurant on Main Street. Making a reservation for dinner.",
  "call_objective": "Make a dinner reservation for 4 people tonight at 7pm",
  "detailed_instructions": "If 7pm is not available, try 7:30 or 8pm. Prefer a booth if possible. Name for the reservation: Jane Doe.",
  "caller": "communicator",
  "voice": "female",
  "max_duration_minutes": 10
}

Get Call Status

GET /api/v2/voice/call/{call_id}

Retrieves the current status of a call. Poll this endpoint periodically until the call completes, or use the SSE stream endpoint to wait for the final result instead. Once the call completes, the response includes the full transcript (and summary if enable_summary was requested). Note: no real-time intermediate updates (partial transcripts, phase changes) are currently available while the call is in progress.

Path parameters

ParameterTypeDescription
call_idrequired string The call_id returned from the initiate call endpoint

Request headers

HeaderValue
Authorizationrequired Bearer <access_token>
X-Pine-User-Idrequired Your Pine user ID

Example request

cURL
curl https://agent3-api-gateway-staging.19pine.ai/api/v2/voice/call/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -H "X-Pine-User-Id: 1234567890"

Response — in progress 200 OK

JSON
{
  "call_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "in_progress",
  "duration_seconds": 60
}

Note: No real-time intermediate updates are available while the call is in progress. The SDK normalizes raw API statuses (e.g. HungupByPeer) to canonical values (completed, failed, cancelled).

Response — completed 200 OK

JSON
{
  "call_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "completed",
  "duration_seconds": 180,
  "summary": "Successfully scheduled a dental cleaning for Tuesday at 2pm.",
  "transcript": [
    { "speaker": "agent", "text": "Hello, I'm calling to schedule a dental cleaning appointment." },
    { "speaker": "user", "text": "Sure, let me check our availability." },
    { "speaker": "user", "text": "We have Tuesday at 2pm available." },
    { "speaker": "agent", "text": "That works perfectly. Please book it." }
  ],
  "credits_charged": 110
}

Response fields (completed call)

FieldTypeDescription
call_id string Unique call identifier
status string Call status (see Call Statuses)
duration_seconds integer Total call duration in seconds
summary string AI-generated summary of the call outcome. Only present when enable_summary was set to true in the request.
transcript array Full conversation transcript as an array of speaker/text objects
credits_charged integer Total credits deducted for this call

Stream Call Status (SSE)

GET /api/v2/voice/call/{call_id}/stream

Opens a Server-Sent Events (SSE) stream that waits for the final call result and delivers it as soon as it's available. This is a more efficient alternative to polling — instead of making repeated HTTP requests, the server notifies the client over a single long-lived connection when the call completes.

No real-time updates. The SSE stream does not provide real-time intermediate events such as partial transcripts, "call connected" notifications, or live conversation updates. The only event you will receive is the final result event containing the complete transcript after the call ends. There are no intermediate progress events during the call.

Recommended. The official Python and Node.js SDKs use this endpoint automatically when you call create_and_wait() / createAndWait(). If SSE fails, they fall back to polling the GET endpoint. No code changes needed.

Path parameters

ParameterTypeDescription
call_idrequired string The call_id returned from the initiate call endpoint

Request headers

HeaderValueDescription
Authorizationrequired string Bearer <access_token>
X-Pine-User-Idrequired string Your Pine user ID from authentication
Acceptoptional string text/event-stream
Last-Event-Idoptional string Event ID from a previous connection for reconnection. If the call already completed, the server sends the result immediately.

SSE events

The stream emits these event types:

EventDescription
result Final call result with full transcript (and summary if requested). This is the primary event you should listen for. Sent once when the call completes, then the connection closes.
error An error occurred (e.g., stream timeout). The connection closes after this event.

The server also sends SSE comment lines (: heartbeat) every ~15 seconds to keep the connection alive through proxies and load balancers.

Note: Intermediate status and transcript events are defined in the protocol but are not currently emitted. The server does not provide real-time partial transcripts, "call connected" events, or any other intermediate progress updates. The only meaningful event is result, delivered after the call ends.

Event format — result

SSE
id: 1
event: result
data: {"call_id":"a1b2c3d4-...","status":"completed","duration_seconds":180,"summary":"Successfully scheduled...","transcript":[...],"credits_charged":110}

Example — cURL

bash
curl -N https://agent3-api-gateway-staging.19pine.ai/api/v2/voice/call/CALL_ID/stream \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Pine-User-Id: YOUR_USER_ID" \
  -H "Accept: text/event-stream"

Example — JavaScript (fetch)

JavaScript
const res = await fetch(
  `https://agent3-api-gateway-staging.19pine.ai/api/v2/voice/call/${callId}/stream`,
  {
    headers: {
      "Authorization": `Bearer ${accessToken}`,
      "X-Pine-User-Id": userId,
      "Accept": "text/event-stream",
    },
  }
);

const reader = res.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  const chunk = decoder.decode(value, { stream: true });
  // Parse SSE lines: look for "event: result" + "data: {...}"
  console.log(chunk);
}

Reconnection

Each event includes an id field. If the connection drops, reconnect with the Last-Event-Id header set to the last received ID. If the call has already completed, the server will send the result immediately on reconnect.

Fallback. If SSE is unavailable (e.g., your proxy doesn't support streaming), fall back to polling GET /api/v2/voice/call/{call_id} every 10–30 seconds. The official SDKs do this automatically.

Complete Example: Raw API

Not recommended for production. The raw API examples below are provided for educational purposes only — to illustrate how the REST endpoints work under the hood. For production use, we strongly recommend using the official Python SDK or Node.js SDK, which handle authentication, status normalization, SSE streaming with automatic polling fallback, and error handling for you.

If you prefer to call the REST API directly without an SDK, here's the full flow. Enter your details below and the code examples will update automatically.

Your details

This Python script uses raw HTTP requests (no SDK) to walk through the full flow: authenticate, call your phone with a short test message, then poll until the call completes and print the transcript.

Python
import requests
import time

# ---- Configuration ----
EMAIL = "[email protected]"
MY_PHONE = "+14155551234"

AUTH_API = "https://www.19pine.ai"
GATEWAY  = "https://agent3-api-gateway-staging.19pine.ai"

# ---- Step 1: Request verification code ----
print("Requesting verification code...")
resp = requests.post(f"{AUTH_API}/api/v2/auth/email/request", json={
    "email": EMAIL
})
resp.raise_for_status()
request_token = resp.json()["data"]["request_token"]
print("Check your email for the verification code.")

# ---- Step 2: Verify code and get credentials ----
code = input("Enter the code from your email: ")
resp = requests.post(f"{AUTH_API}/api/v2/auth/email/verify", json={
    "email": EMAIL,
    "request_token": request_token,
    "code": code
})
resp.raise_for_status()
creds = resp.json()["data"]
access_token = creds["access_token"]
user_id = creds["id"]
print(f"Authenticated! user_id={user_id}")

# ---- Step 3: Make a test call to yourself ----
headers = {
    "Authorization": f"Bearer {access_token}",
    "X-Pine-User-Id": user_id,
    "Content-Type": "application/json",
}

print(f"Initiating test call to {MY_PHONE}...")
resp = requests.post(f"{GATEWAY}/api/v2/voice/call", headers=headers, json={
    "dialed_number": MY_PHONE,
    "callee_name": "Myself",
    "callee_context": "This is a test call to verify my Pine Voice API integration is working.",
    "call_objective": "Tell the person that Pine Voice is set up and working correctly. Confirm the setup is complete and say goodbye.",
    "caller": "communicator",
    "voice": "female",
    "max_duration_minutes": 5,
})
resp.raise_for_status()
call_id = resp.json()["call_id"]
print(f"Call initiated! call_id={call_id}")
print("Your phone should ring shortly. Answer to hear the test message.")

# ---- Step 4: Poll for result ----
print("Polling for result...")
while True:
    time.sleep(10)
    resp = requests.get(
        f"{GATEWAY}/api/v2/voice/call/{call_id}",
        headers=headers,
    )
    result = resp.json()
    status = result["status"]
    elapsed = result.get("duration_seconds", 0)
    print(f"  Status: {status} ({elapsed}s elapsed)")

    if status in ("completed", "failed", "cancelled"):
        break

# ---- Step 5: Print results ----
print(f"\n--- Call {status} ---")
print(f"Duration: {result.get('duration_seconds', 0)}s")
print(f"Credits:  {result.get('credits_charged', 0)}")
print(f"\nSummary:\n  {result.get('summary', 'N/A')}")
print(f"\nTranscript:")
for entry in result.get("transcript", []):
    print(f"  [{entry['speaker']}] {entry['text']}")

And the same flow using cURL:

bash
# Step 1: Request verification code
curl -s -X POST https://www.19pine.ai/api/v2/auth/email/request \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]"}'
# Save the request_token from the response

# Step 2: Verify code (replace REQUEST_TOKEN and CODE)
curl -s -X POST https://www.19pine.ai/api/v2/auth/email/verify \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]", "request_token": "REQUEST_TOKEN", "code": "CODE"}'
# Response: {"status":"success","data":{"id":"...","access_token":"..."}}
# Save data.id as YOUR_USER_ID and data.access_token as YOUR_ACCESS_TOKEN

# Step 3: Make a test call to yourself
curl -s -X POST https://agent3-api-gateway-staging.19pine.ai/api/v2/voice/call \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Pine-User-Id: YOUR_USER_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "dialed_number": "+14155551234",
    "callee_name": "Myself",
    "callee_context": "This is a test call to verify my Pine Voice API integration is working.",
    "call_objective": "Tell the person that Pine Voice is set up and working correctly. Confirm the setup is complete and say goodbye.",
    "caller": "communicator",
    "voice": "female",
    "max_duration_minutes": 5
  }'
# Save the call_id from the response

# Step 4: Poll for result (repeat until status is terminal)
curl -s https://agent3-api-gateway-staging.19pine.ai/api/v2/voice/call/CALL_ID \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Pine-User-Id: YOUR_USER_ID"

Quickstart

The easiest way to integrate Pine Voice is with the official SDKs. They handle authentication, request formatting, SSE result delivery with automatic polling fallback, and error handling for you.

Installation

🐍

Python

pip install pine-voice

Sync & async support. Python 3.9+.

⬡

Node.js / TypeScript

npm install pine-voice

Zero dependencies. Node 18+.

Python quickstart

Python
from pine_voice import PineVoice

# Authenticate (one-time setup)
request_token = PineVoice.auth.request_code("[email protected]")
code = input("Enter the code from your email: ")
credentials = PineVoice.auth.verify_code("[email protected]", request_token, code)

# Create client and make a call
client = PineVoice(
    access_token=credentials.access_token,
    user_id=credentials.user_id,
)
result = client.calls.create_and_wait(
    to="+14155551234",
    name="Dr. Smith Office",
    context="Local dentist office. Open Mon-Fri 9am-5pm.",
    objective="Schedule a dental cleaning for next Tuesday afternoon",
)
print(result.status, result.transcript)

Node.js / TypeScript quickstart

TypeScript
import { PineVoice } from "pine-voice";

// Authenticate (one-time setup)
const { requestToken } = await PineVoice.auth.requestCode("[email protected]");
const { accessToken, userId } = await PineVoice.auth.verifyCode(
  "[email protected]", requestToken, "1234"
);

// Create client and make a call
const client = new PineVoice({ accessToken, userId });
const result = await client.calls.createAndWait({
  to: "+14155551234",
  name: "Dr. Smith Office",
  context: "Local dentist office. Open Mon-Fri 9am-5pm.",
  objective: "Schedule a dental cleaning for next Tuesday afternoon",
});
console.log(result.status, result.transcript);

Tip: Once you have your access_token and user_id, save them as environment variables (PINE_ACCESS_TOKEN, PINE_USER_ID) and create the client with no arguments: PineVoice() / new PineVoice(). No need to repeat the auth flow.

Python SDK Reference

Full API reference for the pine-voice Python package. Supports both synchronous and asynchronous usage.

pip install pine-voice

PineVoice()

Creates a synchronous Pine Voice client. Credentials can be passed directly or resolved from environment variables.

ParameterTypeDescription
access_tokenoptional str Pine access token. Falls back to PINE_ACCESS_TOKEN env var.
user_idoptional str Pine user ID. Falls back to PINE_USER_ID env var.
gateway_urloptional str Voice API gateway URL. Falls back to PINE_GATEWAY_URL env var. Default: https://agent3-api-gateway-staging.19pine.ai
Python
# Option 1: Explicit credentials
client = PineVoice(access_token="...", user_id="...")

# Option 2: From environment variables
# export PINE_ACCESS_TOKEN=... PINE_USER_ID=...
client = PineVoice()

# Use as a context manager to auto-close
with PineVoice() as client:
    result = client.calls.create_and_wait(...)

PineVoice.auth

Static authentication helpers. No credentials required — used to obtain credentials.

auth.request_code(email)

Sends a verification code to the given email address. Returns the request_token (a string) needed for the verify step.

ParameterTypeDescription
emailrequiredstrYour Pine AI account email

Returns: str — the request_token.

auth.verify_code(email, request_token, code)

Verifies the email code and returns access credentials.

ParameterTypeDescription
emailrequiredstrSame email used in request step
request_tokenrequiredstrToken from request_code()
coderequiredstrVerification code from email

Returns: Credentials(access_token: str, user_id: str)

client.calls.create()

Initiates a phone call. Returns immediately with a call ID. Use get() to poll or create_and_wait() for a blocking call.

ParameterTypeDescription
torequiredstrPhone number in E.164 format
namerequiredstrName of the person or business being called
contextrequiredstrBackground context about the callee
objectiverequiredstrSpecific goal for the call
instructionsoptionalstrDetailed strategy and instructions
calleroptionalstr"negotiator" or "communicator"
voiceoptionalstr"male" or "female"
max_duration_minutesoptionalintMax call duration in minutes (1–120). Default: 120
enable_summaryoptionalboolRequest an AI summary. Default: False

Returns: CallInitiated(call_id: str, status: str)

client.calls.get(call_id)

Gets the current status of a call. Returns CallStatus while in progress, or CallResult when the call reaches a terminal state.

ParameterTypeDescription
call_idrequiredstrThe call ID from create()

Returns: CallStatus | CallResult

Python
import time
from pine_voice import PineVoice, CallResult

client = PineVoice()
initiated = client.calls.create(
    to="+14155551234",
    name="Dr. Smith Office",
    context="Local dentist office",
    objective="Schedule a cleaning",
)
print(f"Call started: {initiated.call_id}")

# Poll until complete
while True:
    time.sleep(10)
    result = client.calls.get(initiated.call_id)
    if result.status in ("completed", "failed", "cancelled"):
        break
    print(f"  Still in progress... {result.duration_seconds}s")

if isinstance(result, CallResult):
    for entry in result.transcript:
        print(f"[{entry.speaker}] {entry.text}")

client.calls.create_and_wait()

Initiates a call and blocks until it reaches a terminal state. Uses SSE streaming by default, with automatic fallback to polling if SSE fails. This is the recommended method for most use cases.

ParameterTypeDescription
torequiredstrPhone number in E.164 format
namerequiredstrName of the person or business being called
contextrequiredstrBackground context about the callee
objectiverequiredstrSpecific goal for the call
instructionsoptionalstrDetailed strategy and instructions
calleroptionalstr"negotiator" or "communicator"
voiceoptionalstr"male" or "female"
max_duration_minutesoptionalintMax call duration in minutes (1–120). Default: 120
enable_summaryoptionalboolRequest an AI summary. Default: False
poll_intervaloptionalintSeconds between polls for polling fallback. Default: 10
use_sseoptionalboolTry SSE first. Set False to force polling. Default: True
on_progressoptionalCallableCallback invoked with CallProgress after each poll cycle

Returns: CallResult

Python
result = client.calls.create_and_wait(
    to="+14155551234",
    name="Dr. Smith Office",
    context="Local dentist office. I'm Jane Doe, existing patient.",
    objective="Schedule a dental cleaning for next Tuesday afternoon",
    instructions="If Tuesday is unavailable, try Wednesday or Thursday.",
    enable_summary=True,
)

print(f"Status: {result.status}")
print(f"Duration: {result.duration_seconds}s")
print(f"Credits: {result.credits_charged}")
print(f"Summary: {result.summary}")
for entry in result.transcript:
    print(f"[{entry.speaker}] {entry.text}")

AsyncPineVoice

Asynchronous client with the same API as PineVoice, but all methods are async. Use with asyncio. Has its own AsyncPineVoice.auth with async request_code() and verify_code().

Python (async)
import asyncio
from pine_voice import AsyncPineVoice

async def main():
    async with AsyncPineVoice() as client:
        result = await client.calls.create_and_wait(
            to="+14155551234",
            name="Dr. Smith Office",
            context="Local dentist office",
            objective="Schedule a cleaning",
        )
        print(result.transcript)

asyncio.run(main())

Types

All types are dataclasses importable from pine_voice.

TypeFieldsDescription
Credentials access_token, user_id Returned by auth.verify_code()
CallInitiated call_id, status Returned by calls.create()
CallStatus call_id, status, duration_seconds Returned by calls.get() while in progress
CallResult call_id, status, duration_seconds, summary, transcript, credits_charged Returned when call reaches terminal state
CallProgress call_id, status, duration_seconds Passed to on_progress callback
TranscriptEntry speaker, text A single turn in the transcript

Errors

All exceptions inherit from PineVoiceError, which provides code, message, and status attributes.

ExceptionWhen
PineVoiceErrorBase class for all SDK errors
AuthErrorAuthentication failures (401, missing credentials, expired token)
CallErrorCall-specific errors (invalid phone, DND blocked, policy violation)
RateLimitErrorRate limit exceeded (429)
Python
from pine_voice import PineVoice, AuthError, CallError, RateLimitError

try:
    result = client.calls.create_and_wait(...)
except AuthError as e:
    print(f"Auth failed: {e.code} — {e.message}")
except RateLimitError:
    print("Too many concurrent calls, try again later")
except CallError as e:
    print(f"Call error: {e.code} — {e.message}")

Node.js / TypeScript SDK Reference

Full API reference for the pine-voice npm package. Zero dependencies, works with Node.js 18+ and any runtime with fetch.

npm install pine-voice

new PineVoice(config?)

Creates a Pine Voice client. Credentials can be passed in the config object or resolved from environment variables.

Config fieldTypeDescription
accessTokenoptional string Pine access token. Falls back to PINE_ACCESS_TOKEN env var.
userIdoptional string Pine user ID. Falls back to PINE_USER_ID env var.
gatewayUrloptional string Voice API gateway URL. Falls back to PINE_GATEWAY_URL env var.
TypeScript
import { PineVoice } from "pine-voice";

// Option 1: Explicit credentials
const client = new PineVoice({ accessToken: "...", userId: "..." });

// Option 2: From environment variables
// PINE_ACCESS_TOKEN=... PINE_USER_ID=...
const client = new PineVoice();

PineVoice.auth

Static authentication helpers. No credentials required — used to obtain credentials.

auth.requestCode(email)

Sends a verification code to the given email address.

ParameterTypeDescription
emailrequiredstringYour Pine AI account email

Returns: Promise<{ requestToken: string }>

auth.verifyCode(email, requestToken, code)

Verifies the email code and returns access credentials.

ParameterTypeDescription
emailrequiredstringSame email used in request step
requestTokenrequiredstringToken from requestCode()
coderequiredstringVerification code from email

Returns: Promise<{ accessToken: string, userId: string }>

client.calls.create(params)

Initiates a phone call. Returns immediately with a call ID.

ParameterTypeDescription
torequiredstringPhone number in E.164 format
namerequiredstringName of the person or business being called
contextrequiredstringBackground context about the callee
objectiverequiredstringSpecific goal for the call
instructionsoptionalstringDetailed strategy and instructions
calleroptionalstring"negotiator" or "communicator"
voiceoptionalstring"male" or "female"
maxDurationMinutesoptionalnumberMax call duration in minutes (1–120). Default: 120
enableSummaryoptionalbooleanRequest an AI summary. Default: false

Returns: Promise<CallInitiated> with callId and status

client.calls.get(callId)

Gets the current status of a call. Returns CallStatus while in progress, or CallResult when the call reaches a terminal state.

ParameterTypeDescription
callIdrequiredstringThe call ID from create()

Returns: Promise<CallStatus | CallResult>

TypeScript
const { callId } = await client.calls.create({
  to: "+14155551234",
  name: "Dr. Smith Office",
  context: "Local dentist office",
  objective: "Schedule a cleaning",
});

// Poll until complete
while (true) {
  await new Promise(r => setTimeout(r, 10000));
  const result = await client.calls.get(callId);
  if (["completed", "failed", "cancelled"].includes(result.status)) {
    console.log(result);
    break;
  }
}

client.calls.createAndWait(params, options?)

Initiates a call and waits until it reaches a terminal state. Uses SSE streaming by default, with automatic fallback to polling. This is the recommended method for most use cases.

The first argument is the same CreateCallParams object as create(). The second argument is an optional WaitOptions object:

OptionTypeDescription
pollIntervalMsoptionalnumberMilliseconds between polls for polling fallback. Default: 10000
signaloptionalAbortSignalAbort signal to cancel waiting
useSSEoptionalbooleanTry SSE first. Set false to force polling. Default: true
onProgressoptionalfunctionCallback invoked with CallProgress after each poll cycle

Returns: Promise<CallResult>

TypeScript
const result = await client.calls.createAndWait(
  {
    to: "+14155551234",
    name: "Dr. Smith Office",
    context: "Local dentist office. I'm Jane Doe, existing patient.",
    objective: "Schedule a dental cleaning for next Tuesday afternoon",
    enableSummary: true,
  },
  {
    // Optional: abort after 5 minutes
    signal: AbortSignal.timeout(300_000),
  }
);

console.log(result.status);        // "completed"
console.log(result.durationSeconds); // 45
console.log(result.creditsCharged);  // 70
console.log(result.summary);        // "Scheduled cleaning for Tuesday 2pm"
console.log(result.transcript);     // [{ speaker, text }, ...]

Types

All types are exported from pine-voice and available for import in TypeScript.

TypeFieldsDescription
PineVoiceConfig accessToken?, userId?, gatewayUrl? Constructor config
CreateCallParams to, name, context, objective, instructions?, caller?, voice?, maxDurationMinutes?, enableSummary? Parameters for create() and createAndWait()
CallInitiated callId, status Returned by create()
CallStatus callId, status, durationSeconds? Returned by get() while in progress
CallResult callId, status, durationSeconds, summary, transcript, creditsCharged Returned when call reaches terminal state
WaitOptions pollIntervalMs?, signal?, useSSE?, onProgress? Options for createAndWait()
CallProgress callId, status, durationSeconds? Passed to onProgress callback
TranscriptEntry speaker, text A single turn in the transcript

Errors

All errors extend PineVoiceError, which provides code, message, and status properties.

Error classWhen
PineVoiceErrorBase class for all SDK errors
AuthErrorAuthentication failures (401, missing credentials, expired token)
CallErrorCall-specific errors (invalid phone, DND blocked, policy violation)
RateLimitErrorRate limit exceeded (429)
TypeScript
import { PineVoice, AuthError, CallError, RateLimitError } from "pine-voice";

try {
  const result = await client.calls.createAndWait({...});
} catch (err) {
  if (err instanceof AuthError) {
    console.error(`Auth failed: ${err.code} — ${err.message}`);
  } else if (err instanceof RateLimitError) {
    console.error("Too many concurrent calls, try again later");
  } else if (err instanceof CallError) {
    console.error(`Call error: ${err.code} — ${err.message}`);
  }
}

Call Statuses

Status values

The status field in call responses indicates the current state of the call:

StatusDescription
initiated Call has been created and is about to dial
in_progress Call is currently active
completed Call finished successfully — transcript available (and summary if requested)
failed Call failed (no answer, network issue, safety rejection, etc.)
cancelled Call was cancelled before completion

Call phases

Not currently available. The phase field is defined in the schema but is not populated during in-progress calls. You will not receive phase updates such as "connected" (callee answered). The phases below are documented for reference only and may be supported in a future release.

When a call has status: "in_progress", the phase field would provide more detail on what stage the call is in:

PhaseDescription
initiated Call accepted, voice agent is dialing the number
connected Callee answered, conversation is active
completed Call finished normally
failed Call failed (no answer, network issue, etc.)
no_answer Nobody picked up the phone
declined Call was rejected by the callee

Evaluating call outcome

Do not rely on the status field to determine whether the call objective was achieved. A status like HungupByPeer only means the other party hung up — it does not mean success. You must read the full transcript to determine the actual outcome.

Treat the call as a failure if any of these are true:

  • Only Pine’s agent speaks and the other side is silent or unresponsive
  • The other party’s responses are automated or recorded messages (voicemail, IVR, “leave a message after the beep”)
  • System messages report extended silence from both sides
  • The callee hung up before the objective could be discussed

Transcript format

The transcript field is an array of objects, each representing one turn in the conversation:

JSON
[
  { "speaker": "agent", "text": "Hello, I'm calling to..." },
  { "speaker": "user",  "text": "Hi, how can I help?" }
]

The speaker field is either "agent" (Pine's voice agent) or "user" (the person who answered the call).

Error Codes

When an API request fails, the response body contains an error object with a machine-readable code and human-readable message:

Error response format
{
  "error": {
    "code": "ERROR_CODE",
    "message": "A human-readable description of the error."
  }
}
CodeHTTP StatusDescription
AUTH_REQUIRED 401 Missing or invalid Authorization header or X-Pine-User-Id
TOKEN_EXPIRED 401 Access token has expired — re-authenticate to get a new one
SUBSCRIPTION_REQUIRED 403 No active Pine AI Pro subscription found for this account
INSUFFICIENT_CREDITS 402 Not enough credits to make a call. Add credits at 19pine.ai.
INVALID_REQUEST 400 Request body is malformed or missing required fields
INVALID_PHONE 400 Phone number is not in valid E.164 format or cannot be called
INSUFFICIENT_DETAIL 400 Call objective or instructions are too vague for the agent to act on
RATE_LIMITED 429 Too many calls — exceeded concurrent call limits
DND_BLOCKED 403 Target number is on a do-not-call list
POLICY_VIOLATION 403 Call was rejected for violating usage policies
NOT_FOUND 404 The specified call_id does not exist
ACCESS_DENIED 403 You do not have permission to access this call
INTERNAL 500 Unexpected server error — retry or contact support

Rate Limits

To ensure fair usage and prevent abuse, the following limits are enforced:

LimitValueScope
Concurrent calls 5 Maximum in-progress calls at any given time per user
Max call duration 120 minutes Maximum duration for any single call

Exceeding rate limits returns a RATE_LIMITED error (HTTP 429). Wait before retrying.

English only. The voice agent currently supports English only. Calls can be placed to the US, Canada, Puerto Rico (+1), UK (+44), Australia (+61), New Zealand (+64), Singapore (+65), Ireland (+353), and Hong Kong (+852).

Phone format: All phone numbers must be in E.164 format, including the + prefix and country code (e.g. +14155551234 for US numbers).