Heppu AI

Batch Schedule Calls

Schedule up to 1,000 calls at once with timezone-aware scheduling and partial success handling

Batch Schedule Calls

Schedule up to 1,000 outbound calls in a single request with advanced features including timezone-aware scheduling, retry configuration, and partial success handling.

Endpoint

POST /api/v1/calls/batch

Authentication

Supports API key or session-based authentication:

# API Key
x-api-key: YOUR_API_KEY

# Bearer Token
Authorization: Bearer YOUR_API_KEY

Request Body

Required Fields

FieldTypeDescription
agentIdstring (UUID)ID of the voice agent to use for all calls
callsarrayArray of call objects (max: 1,000)

Optional Fields

FieldTypeDefaultDescription
defaultPriorityinteger (0-10)5Default priority for all calls
defaultScheduledAtstring (ISO 8601)nullDefault scheduled time for calls without individual scheduledAt
stopOnErrorbooleanfalseStop processing remaining calls if one fails
retryConfigobjectSee belowRetry configuration for all calls
timezoneAwareConfigobjectnullTimezone-aware scheduling configuration

Call Object

Each call in the calls array:

FieldTypeRequiredDescription
contactPhonestringYesPhone number in E.164 format
contactNamestringNoContact's full name
contactEmailstringNoContact's email address
scheduledAtstringNoOverride default scheduled time
priorityinteger (0-10)NoOverride default priority
timezonestringNoIANA timezone (e.g., "America/New_York")
localScheduleTimestringNoLocal time to call (e.g., "09:00")
callMetadataobjectNoRich contact and call context (same as Schedule Call)
metadataobjectNoAdditional custom metadata

Retry Configuration

You can configure automatic retries using either relative intervals or fixed schedules:

{
  retryConfig: {
    maxRetries: number;        // Auto-calculated from retryIntervals length if not set
    retryOnNoAnswer: boolean;  // Default: true
    retryOnBusy: boolean;      // Default: true
    retryOnFailed: boolean;    // Default: false
    retryIntervals: [          // Relative retry intervals
      { value: 2, unit: "hours" },   // 1st retry: 2 hours after initial call
      { value: 1, unit: "days" },    // 2nd retry: 1 day after 1st retry
      { value: 3, unit: "days" }     // 3rd retry: 3 days after 2nd retry
    ]
  }
}

Supported units: "minutes", "hours", "days"

Using Fixed Schedule

{
  retrySchedule: [           // Array of ISO 8601 timestamps
    "2025-12-02T14:00:00Z",  // 1st retry at specific time
    "2025-12-03T10:00:00Z",  // 2nd retry at specific time
    "2025-12-04T16:00:00Z"   // 3rd retry at specific time
  ],
  retryConfig: {
    retryOnNoAnswer: boolean;  // Default: true
    retryOnBusy: boolean;      // Default: true
    retryOnFailed: boolean;    // Default: false
  }
}

Retry Behavior

  • maxRetries is automatically calculated from retryIntervals or retrySchedule length if not explicitly set
  • If no retry configuration is provided, calls will not be retried on failure
  • Use retryIntervals for multi-timezone campaigns (relative timing)
  • Use retrySchedule for single-timezone campaigns (absolute timing)

Timezone-Aware Configuration

{
  enabled: boolean;                    // Enable timezone-aware scheduling
  localScheduleTime: string;           // Local time to call (e.g., "10:00")
  respectBusinessHours: boolean;       // Only call during business hours
  businessHoursStart: string;          // Start of business hours (e.g., "09:00")
  businessHoursEnd: string;            // End of business hours (e.g., "17:00")
  excludeWeekends: boolean;            // Skip Saturday and Sunday
  allowedDays: number[];               // Days of week (0=Sunday, 6=Saturday)
  maxCallsPerDay: number;              // Maximum calls per day
  staggering: {
    enabled: boolean;
    intervalMinutes: number;           // Minutes between calls
  };
}

Response

Response Schema

{
  "data": {
    "agentId": "string",
    "agent": {
      "id": "string",
      "name": "string"
    },
    "summary": {
      "totalRequested": "integer",
      "successful": "integer",
      "failed": "integer",
      "partialSuccess": "boolean",
      "stopped": "boolean"
    },
    "results": [
      {
        "index": "integer",
        "success": "boolean",
        "callId": "string",
        "error": "string",
        "contactPhone": "string"
      }
    ]
  },
  "message": "string"
}

HTTP Status Codes:

  • 201 - All calls scheduled successfully
  • 207 - Partial success (some calls failed)
  • 400 - All calls failed

Examples

Basic Batch Schedule

curl -X POST https://v2.heppu.ai/api/v1/calls/batch \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "550e8400-e29b-41d4-a716-446655440000",
    "calls": [
      {
        "contactPhone": "+14155552671",
        "contactName": "John Doe"
      },
      {
        "contactPhone": "+14155552672",
        "contactName": "Jane Smith"
      },
      {
        "contactPhone": "+14155552673",
        "contactName": "Bob Johnson"
      }
    ]
  }'
const response = await fetch('https://v2.heppu.ai/api/v1/calls/batch', {
  method: 'POST',
  headers: {
    'x-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    agentId: '550e8400-e29b-41d4-a716-446655440000',
    calls: [
      { contactPhone: '+14155552671', contactName: 'John Doe' },
      { contactPhone: '+14155552672', contactName: 'Jane Smith' },
      { contactPhone: '+14155552673', contactName: 'Bob Johnson' }
    ]
  })
});

const data = await response.json();
console.log(`Scheduled: ${data.data.summary.successful}/${data.data.summary.totalRequested}`);
import requests

response = requests.post(
    'https://v2.heppu.ai/api/v1/calls/batch',
    headers={'x-api-key': 'YOUR_API_KEY'},
    json={
        'agentId': '550e8400-e29b-41d4-a716-446655440000',
        'calls': [
            {'contactPhone': '+14155552671', 'contactName': 'John Doe'},
            {'contactPhone': '+14155552672', 'contactName': 'Jane Smith'},
            {'contactPhone': '+14155552673', 'contactName': 'Bob Johnson'}
        ]
    }
)

data = response.json()
summary = data['data']['summary']
print(f"Scheduled: {summary['successful']}/{summary['totalRequested']}")

Scheduled Batch with Priority

curl -X POST https://v2.heppu.ai/api/v1/calls/batch \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "550e8400-e29b-41d4-a716-446655440000",
    "defaultPriority": 3,
    "defaultScheduledAt": "2025-12-02T10:00:00Z",
    "calls": [
      {
        "contactPhone": "+14155552671",
        "contactName": "VIP Customer",
        "priority": 0
      },
      {
        "contactPhone": "+14155552672",
        "contactName": "Regular Customer"
      }
    ]
  }'
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(10, 0, 0, 0);

const response = await fetch('https://v2.heppu.ai/api/v1/calls/batch', {
  method: 'POST',
  headers: {
    'x-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    agentId: '550e8400-e29b-41d4-a716-446655440000',
    defaultPriority: 3,
    defaultScheduledAt: tomorrow.toISOString(),
    calls: [
      {
        contactPhone: '+14155552671',
        contactName: 'VIP Customer',
        priority: 0  // Override with high priority
      },
      {
        contactPhone: '+14155552672',
        contactName: 'Regular Customer'
        // Will use defaultPriority (3)
      }
    ]
  })
});
from datetime import datetime, timedelta

tomorrow = datetime.utcnow() + timedelta(days=1)
tomorrow = tomorrow.replace(hour=10, minute=0, second=0, microsecond=0)

response = requests.post(
    'https://v2.heppu.ai/api/v1/calls/batch',
    headers={'x-api-key': 'YOUR_API_KEY'},
    json={
        'agentId': '550e8400-e29b-41d4-a716-446655440000',
        'defaultPriority': 3,
        'defaultScheduledAt': tomorrow.isoformat() + 'Z',
        'calls': [
            {
                'contactPhone': '+14155552671',
                'contactName': 'VIP Customer',
                'priority': 0  # Override with high priority
            },
            {
                'contactPhone': '+14155552672',
                'contactName': 'Regular Customer'
                # Will use defaultPriority (3)
            }
        ]
    }
)

Timezone-Aware Batch Scheduling

curl -X POST https://v2.heppu.ai/api/v1/calls/batch \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "550e8400-e29b-41d4-a716-446655440000",
    "timezoneAwareConfig": {
      "enabled": true,
      "localScheduleTime": "10:00",
      "respectBusinessHours": true,
      "businessHoursStart": "09:00",
      "businessHoursEnd": "17:00",
      "excludeWeekends": true,
      "staggering": {
        "enabled": true,
        "intervalMinutes": 5
      }
    },
    "calls": [
      {
        "contactPhone": "+14155552671",
        "contactName": "John (NYC)",
        "timezone": "America/New_York"
      },
      {
        "contactPhone": "+442071838750",
        "contactName": "Jane (London)",
        "timezone": "Europe/London"
      },
      {
        "contactPhone": "+61291234567",
        "contactName": "Bob (Sydney)",
        "timezone": "Australia/Sydney"
      }
    ]
  }'
const response = await fetch('https://v2.heppu.ai/api/v1/calls/batch', {
  method: 'POST',
  headers: {
    'x-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    agentId: '550e8400-e29b-41d4-a716-446655440000',
    timezoneAwareConfig: {
      enabled: true,
      localScheduleTime: '10:00',
      respectBusinessHours: true,
      businessHoursStart: '09:00',
      businessHoursEnd: '17:00',
      excludeWeekends: true,
      staggering: {
        enabled: true,
        intervalMinutes: 5
      }
    },
    calls: [
      {
        contactPhone: '+14155552671',
        contactName: 'John (NYC)',
        timezone: 'America/New_York'
      },
      {
        contactPhone: '+442071838750',
        contactName: 'Jane (London)',
        timezone: 'Europe/London'
      },
      {
        contactPhone: '+61291234567',
        contactName: 'Bob (Sydney)',
        timezone: 'Australia/Sydney'
      }
    ]
  })
});

const data = await response.json();
console.log('Timezone-aware scheduling complete:', data.data.summary);
response = requests.post(
    'https://v2.heppu.ai/api/v1/calls/batch',
    headers={'x-api-key': 'YOUR_API_KEY'},
    json={
        'agentId': '550e8400-e29b-41d4-a716-446655440000',
        'timezoneAwareConfig': {
            'enabled': True,
            'localScheduleTime': '10:00',
            'respectBusinessHours': True,
            'businessHoursStart': '09:00',
            'businessHoursEnd': '17:00',
            'excludeWeekends': True,
            'staggering': {
                'enabled': True,
                'intervalMinutes': 5
            }
        },
        'calls': [
            {
                'contactPhone': '+14155552671',
                'contactName': 'John (NYC)',
                'timezone': 'America/New_York'
            },
            {
                'contactPhone': '+442071838750',
                'contactName': 'Jane (London)',
                'timezone': 'Europe/London'
            },
            {
                'contactPhone': '+61291234567',
                'contactName': 'Bob (Sydney)',
                'timezone': 'Australia/Sydney'
            }
        ]
    }
)

print('Timezone-aware scheduling complete')

Batch with Retry Intervals

curl -X POST https://v2.heppu.ai/api/v1/calls/batch \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "550e8400-e29b-41d4-a716-446655440000",
    "retryConfig": {
      "retryOnNoAnswer": true,
      "retryOnBusy": true,
      "retryIntervals": [
        { "value": 2, "unit": "hours" },
        { "value": 1, "unit": "days" }
      ]
    },
    "calls": [
      {
        "contactPhone": "+14155552671",
        "contactName": "John Doe",
        "contactEmail": "john@example.com",
        "callMetadata": {
          "firstName": "John",
          "lastName": "Doe",
          "company": "Acme Corp",
          "callPurpose": "product_demo",
          "campaignId": "holiday-2025"
        }
      }
    ]
  }'
const response = await fetch('https://v2.heppu.ai/api/v1/calls/batch', {
  method: 'POST',
  headers: {
    'x-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    agentId: '550e8400-e29b-41d4-a716-446655440000',
    retryConfig: {
      retryOnNoAnswer: true,
      retryOnBusy: true,
      retryIntervals: [
        { value: 2, unit: 'hours' },   // Retry after 2 hours
        { value: 1, unit: 'days' }     // Then retry after 1 day
      ]
    },
    calls: [
      {
        contactPhone: '+14155552671',
        contactName: 'John Doe',
        contactEmail: 'john@example.com',
        callMetadata: {
          firstName: 'John',
          lastName: 'Doe',
          company: 'Acme Corp',
          callPurpose: 'product_demo',
          campaignId: 'holiday-2025'
        }
      }
    ]
  })
});
response = requests.post(
    'https://v2.heppu.ai/api/v1/calls/batch',
    headers={'x-api-key': 'YOUR_API_KEY'},
    json={
        'agentId': '550e8400-e29b-41d4-a716-446655440000',
        'retryConfig': {
            'retryOnNoAnswer': True,
            'retryOnBusy': True,
            'retryIntervals': [
                {'value': 2, 'unit': 'hours'},   # Retry after 2 hours
                {'value': 1, 'unit': 'days'}     # Then retry after 1 day
            ]
        },
        'calls': [
            {
                'contactPhone': '+14155552671',
                'contactName': 'John Doe',
                'contactEmail': 'john@example.com',
                'callMetadata': {
                    'firstName': 'John',
                    'lastName': 'Doe',
                    'company': 'Acme Corp',
                    'callPurpose': 'product_demo',
                    'campaignId': 'holiday-2025'
                }
            }
        ]
    }
)

Batch with Fixed Retry Schedule

For single-timezone campaigns where you want retries at specific times:

curl -X POST https://v2.heppu.ai/api/v1/calls/batch \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "550e8400-e29b-41d4-a716-446655440000",
    "retrySchedule": [
      "2025-12-02T14:00:00Z",
      "2025-12-03T10:00:00Z"
    ],
    "retryConfig": {
      "retryOnNoAnswer": true,
      "retryOnBusy": true
    },
    "calls": [
      {
        "contactPhone": "+14155552671",
        "contactName": "John Doe"
      }
    ]
  }'
const response = await fetch('https://v2.heppu.ai/api/v1/calls/batch', {
  method: 'POST',
  headers: {
    'x-api-key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    agentId: '550e8400-e29b-41d4-a716-446655440000',
    retrySchedule: [
      '2025-12-02T14:00:00Z',  // 1st retry at 2 PM UTC
      '2025-12-03T10:00:00Z'   // 2nd retry next day at 10 AM UTC
    ],
    retryConfig: {
      retryOnNoAnswer: true,
      retryOnBusy: true
    },
    calls: [
      {
        contactPhone: '+14155552671',
        contactName: 'John Doe'
      }
    ]
  })
});
response = requests.post(
    'https://v2.heppu.ai/api/v1/calls/batch',
    headers={'x-api-key': 'YOUR_API_KEY'},
    json={
        'agentId': '550e8400-e29b-41d4-a716-446655440000',
        'retrySchedule': [
            '2025-12-02T14:00:00Z',  # 1st retry at 2 PM UTC
            '2025-12-03T10:00:00Z'   # 2nd retry next day at 10 AM UTC
        ],
        'retryConfig': {
            'retryOnNoAnswer': True,
            'retryOnBusy': True
        },
        'calls': [
            {
                'contactPhone': '+14155552671',
                'contactName': 'John Doe'
            }
        ]
    }
)

Response Examples

Complete Success (201)

{
  "data": {
    "agentId": "550e8400-e29b-41d4-a716-446655440000",
    "agent": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Sales Agent"
    },
    "summary": {
      "totalRequested": 3,
      "successful": 3,
      "failed": 0,
      "partialSuccess": false,
      "stopped": false
    },
    "results": [
      {
        "index": 0,
        "success": true,
        "callId": "call_abc123",
        "contactPhone": "+14155552671"
      },
      {
        "index": 1,
        "success": true,
        "callId": "call_def456",
        "contactPhone": "+14155552672"
      },
      {
        "index": 2,
        "success": true,
        "callId": "call_ghi789",
        "contactPhone": "+14155552673"
      }
    ]
  },
  "message": "All 3 calls scheduled successfully"
}

Partial Success (207)

{
  "data": {
    "agentId": "550e8400-e29b-41d4-a716-446655440000",
    "agent": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Sales Agent"
    },
    "summary": {
      "totalRequested": 3,
      "successful": 2,
      "failed": 1,
      "partialSuccess": true,
      "stopped": false
    },
    "results": [
      {
        "index": 0,
        "success": true,
        "callId": "call_abc123",
        "contactPhone": "+14155552671"
      },
      {
        "index": 1,
        "success": false,
        "error": "Invalid phone number format. Must be E.164 format",
        "contactPhone": "4155552672"
      },
      {
        "index": 2,
        "success": true,
        "callId": "call_ghi789",
        "contactPhone": "+14155552673"
      }
    ]
  },
  "message": "Scheduled 2 calls, 1 failed"
}

Error Responses

Missing Required Fields

{
  "error": {
    "message": "Missing required fields: agentId and calls array are required",
    "code": 400
  }
}

Empty Calls Array

{
  "error": {
    "message": "Calls array cannot be empty",
    "code": 400
  }
}

Exceeds Maximum Batch Size

{
  "error": {
    "message": "Batch size exceeds maximum limit of 1000 calls",
    "code": 400
  }
}

Invalid Timezone Configuration

{
  "error": {
    "message": "Invalid timezone configuration: localScheduleTime must be in HH:mm format",
    "code": 400
  }
}

Use Cases

Import Contacts from CSV

import csv
import requests

def schedule_calls_from_csv(csv_file, agent_id):
    """Schedule batch calls from CSV file"""

    calls = []

    with open(csv_file, 'r') as file:
        reader = csv.DictReader(file)

        for row in reader:
            calls.append({
                'contactPhone': row['phone'],
                'contactName': row['name'],
                'contactEmail': row.get('email'),
                'callMetadata': {
                    'firstName': row.get('first_name'),
                    'lastName': row.get('last_name'),
                    'company': row.get('company'),
                    'timezone': row.get('timezone'),
                    'campaignId': row.get('campaign_id')
                }
            })

    # Process in batches of 1000
    batch_size = 1000
    results = []

    for i in range(0, len(calls), batch_size):
        batch = calls[i:i+batch_size]

        response = requests.post(
            'https://v2.heppu.ai/api/v1/calls/batch',
            headers={'x-api-key': 'YOUR_API_KEY'},
            json={
                'agentId': agent_id,
                'calls': batch,
                'retryConfig': {
                    'maxRetries': 3,
                    'retryOnNoAnswer': True,
                    'retryOnBusy': True
                }
            }
        )

        data = response.json()
        results.append(data['data']['summary'])

        print(f"Batch {i//batch_size + 1}: {data['data']['summary']['successful']} scheduled")

    return results

Global Campaign with Timezone Awareness

async function scheduleGlobalCampaign(contacts, agentId) {
  const response = await fetch('https://v2.heppu.ai/api/v1/calls/batch', {
    method: 'POST',
    headers: {
      'x-api-key': 'YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      agentId: agentId,
      timezoneAwareConfig: {
        enabled: true,
        localScheduleTime: '10:00',
        respectBusinessHours: true,
        businessHoursStart: '09:00',
        businessHoursEnd: '17:00',
        excludeWeekends: true,
        allowedDays: [1, 2, 3, 4, 5], // Monday-Friday
        staggering: {
          enabled: true,
          intervalMinutes: 3
        }
      },
      calls: contacts.map(contact => ({
        contactPhone: contact.phone,
        contactName: contact.name,
        contactEmail: contact.email,
        timezone: contact.timezone,
        callMetadata: {
          firstName: contact.firstName,
          lastName: contact.lastName,
          company: contact.company,
          preferredLanguage: contact.language,
          campaignId: 'global-outreach-2025'
        }
      })),
      retryConfig: {
        maxRetries: 2,
        retryOnNoAnswer: true,
        retryOnBusy: false
      }
    })
  });

  return response.json();
}

Handle Partial Success

async function scheduleBatchWithErrorHandling(calls, agentId) {
  const response = await fetch('https://v2.heppu.ai/api/v1/calls/batch', {
    method: 'POST',
    headers: {
      'x-api-key': 'YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      agentId: agentId,
      calls: calls
    })
  });

  const data = await response.json();
  const { summary, results } = data.data;

  console.log(`Total: ${summary.totalRequested}`);
  console.log(`Success: ${summary.successful}`);
  console.log(`Failed: ${summary.failed}`);

  // Log failures
  const failures = results.filter(r => !r.success);
  if (failures.length > 0) {
    console.log('\nFailed calls:');
    failures.forEach(failure => {
      console.log(`- ${failure.contactPhone}: ${failure.error}`);
    });

    // Optionally retry failures with corrected data
    const retryData = failures
      .filter(f => f.error.includes('phone number'))
      .map(f => ({
        contactPhone: correctPhoneFormat(f.contactPhone),
        // ... other fields
      }));

    if (retryData.length > 0) {
      console.log(`\nRetrying ${retryData.length} failed calls...`);
      return scheduleBatchWithErrorHandling(retryData, agentId);
    }
  }

  return data;
}

function correctPhoneFormat(phone) {
  // Remove spaces, dashes, etc.
  const cleaned = phone.replace(/[\s\-\(\)]/g, '');

  // Add + if missing
  return cleaned.startsWith('+') ? cleaned : `+${cleaned}`;
}

Validation Rules

Batch Size Limits

  • Minimum: 1 call
  • Maximum: 1,000 calls per request

Phone Number Validation

Each phone number must be in E.164 format (^\+[1-9]\d{1,14}$)

Timezone Configuration Validation

  • localScheduleTime must be in HH:mm format (e.g., "10:00")
  • Business hours must be valid time ranges
  • Allowed days must be 0-6 (0=Sunday, 6=Saturday)

Best Practices

  1. Validate phone numbers - Ensure all numbers are E.164 format before sending
  2. Use timezone-aware scheduling - Better contact rates when calling at appropriate times
  3. Enable staggering - Prevent overwhelming your system with simultaneous calls
  4. Handle partial success - Check individual results even on 207 status
  5. Implement retry logic - Network issues may cause timeouts
  6. Monitor batch limits - Split large lists into 1,000-call batches
  7. Use appropriate priority - Balance urgency with system capacity
  8. Include rich metadata - More context improves agent performance

On this page