Get started

Client SDKs

Use production-ready SDK patterns for JavaScript, Python, PHP, Go, and REST clients.

Back to documentation
Get started12 min
1

Use the API surface that is already live

QuickCallAI SDKs are thin server-side clients around the same authenticated call pipeline used by the dashboard. Keep tokens on your server, call the production API, then poll status or listen to webhooks for the final outcome.

  • Default API base: https://ai.quickcall.tech/api
  • Create calls through POST /calls/demo/initiate
  • Read lifecycle state through GET /calls/demo/{call_id}/status
  • End a live call through POST /calls/demo/{call_id}/end
2

Choose your language

Each SDK keeps the same request fields so teams can switch between languages without changing the call workflow.

JavaScript / TypeScript SDK
type QuickCallConfig = {
  apiKey: string
  baseUrl?: string
}

type InitiateCallInput = {
  phoneNumber: string
  customerName: string
  prompt: string
  language?: string
  voiceModel?: string
  provider?: "twilio" | "vobiz" | "frejun" | "livekit"
  agentId?: string
  transferPhoneNumber?: string
}

type QuickCallApiResponse = {
  success?: boolean
  message?: string
  error?: string
  [key: string]: unknown
}

type CreateCallResponse = QuickCallApiResponse & {
  call_id?: string | number
  call_sid?: string
  status?: string
}

type CallStatusResponse = QuickCallApiResponse & {
  call_id?: string | number
  call_sid?: string
  provider_call_id?: string
  status?: string
  lifecycle_state?: string
  transcript?: unknown
  summary?: string
}

export class QuickCallAI {
  private readonly apiKey: string
  private readonly baseUrl: string

  constructor(config: QuickCallConfig) {
    this.apiKey = config.apiKey
    this.baseUrl = (config.baseUrl || "https://ai.quickcall.tech/api").replace(/\/+$/, "")
  }

  private async request<T extends QuickCallApiResponse = QuickCallApiResponse>(
    path: string,
    init: RequestInit = {},
  ): Promise<T> {
    const response = await fetch(`${this.baseUrl}${path}`, {
      ...init,
      headers: {
        Authorization: `Bearer ${this.apiKey}`,
        "Content-Type": "application/json",
        ...(init.headers || {}),
      },
    })
    const data = await response.json().catch(() => ({})) as QuickCallApiResponse
    if (!response.ok || data.success === false) {
      throw new Error(data.message || data.error || `QuickCallAI API error: ${response.status}`)
    }
    return data as T
  }

  initiateCall(input: InitiateCallInput): Promise<CreateCallResponse> {
    return this.request<CreateCallResponse>("/calls/demo/initiate", {
      method: "POST",
      body: JSON.stringify({
        phone_number: input.phoneNumber,
        customer_name: input.customerName,
        prompt: input.prompt,
        language: input.language || "en-US",
        voice_model: input.voiceModel || "aura-asteria-en",
        provider: input.provider || "twilio",
        agent_id: input.agentId || null,
        transfer_phone_number: input.transferPhoneNumber || null,
      }),
    })
  }

  getCallStatus(callId: string): Promise<CallStatusResponse> {
    return this.request<CallStatusResponse>(`/calls/demo/${encodeURIComponent(callId)}/status`)
  }

  endCall(callId: string): Promise<QuickCallApiResponse> {
    return this.request(`/calls/demo/${encodeURIComponent(callId)}/end`, { method: "POST" })
  }
}
Python SDK
from urllib.parse import quote

import requests


class QuickCallError(RuntimeError):
    pass


class QuickCallAI:
    def __init__(self, api_key: str, base_url: str = "https://ai.quickcall.tech/api", timeout: int = 30):
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout

    def _request(self, method: str, path: str, json=None):
        response = requests.request(
            method,
            f"{self.base_url}{path}",
            headers={
                "Authorization": f"Bearer {self.api_key}",
                "Content-Type": "application/json",
            },
            json=json,
            timeout=self.timeout,
        )
        try:
            data = response.json() if response.content else {}
        except ValueError:
            data = {}
        if not response.ok or data.get("success") is False:
            raise QuickCallError(data.get("message") or data.get("error") or response.text)
        return data

    def initiate_call(self, phone_number: str, customer_name: str, prompt: str, **options):
        return self._request("POST", "/calls/demo/initiate", {
            "phone_number": phone_number,
            "customer_name": customer_name,
            "prompt": prompt,
            "language": options.get("language", "en-US"),
            "voice_model": options.get("voice_model", "aura-asteria-en"),
            "provider": options.get("provider", "twilio"),
            "agent_id": options.get("agent_id"),
            "transfer_phone_number": options.get("transfer_phone_number"),
        })

    def get_call_status(self, call_id: str):
        return self._request("GET", f"/calls/demo/{quote(call_id, safe='')}/status")

    def end_call(self, call_id: str):
        return self._request("POST", f"/calls/demo/{quote(call_id, safe='')}/end")
PHP SDK
<?php

final class QuickCallAI
{
    public function __construct(
        private string $apiKey,
        private string $baseUrl = 'https://ai.quickcall.tech/api'
    ) {
        $this->baseUrl = rtrim($this->baseUrl, '/');
    }

    private function request(string $method, string $path, ?array $payload = null): array
    {
        $ch = curl_init($this->baseUrl . $path);
        curl_setopt_array($ch, [
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                'Authorization: Bearer ' . $this->apiKey,
                'Content-Type: application/json',
            ],
        ]);
        if ($payload !== null) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
        }

        $body = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        if ($body === false) {
            throw new RuntimeException(curl_error($ch));
        }
        curl_close($ch);

        $data = json_decode($body, true) ?: [];
        if ($status >= 400 || ($data['success'] ?? true) === false) {
            throw new RuntimeException($data['message'] ?? $data['error'] ?? $body);
        }
        return $data;
    }

    public function initiateCall(array $input): array
    {
        return $this->request('POST', '/calls/demo/initiate', [
            'phone_number' => $input['phone_number'],
            'customer_name' => $input['customer_name'] ?? 'Customer',
            'prompt' => $input['prompt'],
            'language' => $input['language'] ?? 'en-US',
            'voice_model' => $input['voice_model'] ?? 'aura-asteria-en',
            'provider' => $input['provider'] ?? 'twilio',
            'agent_id' => $input['agent_id'] ?? null,
        ]);
    }

    public function getCallStatus(string $callId): array
    {
        return $this->request('GET', '/calls/demo/' . rawurlencode($callId) . '/status');
    }

    public function endCall(string $callId): array
    {
        return $this->request('POST', '/calls/demo/' . rawurlencode($callId) . '/end');
    }
}
Go SDK
package quickcallai

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"
)

type Client struct {
	APIKey  string
	BaseURL string
	HTTP    *http.Client
}

type InitiateCallRequest struct {
	PhoneNumber  string  `json:"phone_number"`
	CustomerName string  `json:"customer_name"`
	Prompt       string  `json:"prompt"`
	Language     string  `json:"language,omitempty"`
	VoiceModel   string  `json:"voice_model,omitempty"`
	Provider     string  `json:"provider,omitempty"`
	AgentID      *string `json:"agent_id,omitempty"`
}

func New(apiKey string) *Client {
	return &Client{
		APIKey:  apiKey,
		BaseURL: "https://ai.quickcall.tech/api",
		HTTP:    &http.Client{Timeout: 30 * time.Second},
	}
}

func (c *Client) do(method, path string, payload any, out any) error {
	var body bytes.Buffer
	if payload != nil {
		if err := json.NewEncoder(&body).Encode(payload); err != nil {
			return err
		}
	}
	req, err := http.NewRequest(method, strings.TrimRight(c.BaseURL, "/")+path, &body)
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", "Bearer "+c.APIKey)
	req.Header.Set("Content-Type", "application/json")

	resp, err := c.HTTP.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	if len(respBody) == 0 {
		return nil
	}

	var apiData map[string]any
	if err := json.Unmarshal(respBody, &apiData); err != nil {
		return err
	}
	if resp.StatusCode >= 400 || apiData["success"] == false {
		return fmt.Errorf("quickcallai api error: %s %v", resp.Status, apiData)
	}
	if out == nil {
		return nil
	}
	return json.Unmarshal(respBody, out)
}

func (c *Client) InitiateCall(input InitiateCallRequest, out any) error {
	if input.Language == "" {
		input.Language = "en-US"
	}
	if input.VoiceModel == "" {
		input.VoiceModel = "aura-asteria-en"
	}
	if input.Provider == "" {
		input.Provider = "twilio"
	}
	return c.do(http.MethodPost, "/calls/demo/initiate", input, out)
}

func (c *Client) GetCallStatus(callID string, out any) error {
	return c.do(http.MethodGet, "/calls/demo/"+url.PathEscape(callID)+"/status", nil, out)
}

func (c *Client) EndCall(callID string, out any) error {
	return c.do(http.MethodPost, "/calls/demo/"+url.PathEscape(callID)+"/end", nil, out)
}
REST / cURL
curl -X POST "https://ai.quickcall.tech/api/calls/demo/initiate" \
  -H "Authorization: Bearer $QUICKCALLAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+15551234567",
    "customer_name": "Asha Patel",
    "prompt": "Confirm interest, answer questions, and book a demo slot.",
    "language": "en-IN",
    "voice_model": "google:en-IN-Chirp3-HD-Charon",
    "provider": "twilio"
  }'

curl "https://ai.quickcall.tech/api/calls/demo/CALL_ID/status" \
  -H "Authorization: Bearer $QUICKCALLAI_API_KEY"
3

Production guardrails

A production SDK must preserve the runtime lock that protects the real call path from drift.

  • Always send E.164 phone numbers with a leading plus sign.
  • Use exact provider and voice choices only after testing the route in the dashboard.
  • Do not send API keys from browser code.
  • Store call_id and call_sid so analytics, transcripts, recordings, and billing can reconcile later.