Usando a API para ler dados de uma campanha
Para exportar dados de ligações usando a API é preciso utilizar a interface do sistema para gerar um token de acesso da API e fazer ligações.
Cumprindo esses requisitos, siga os passos descritos abaixo:
1. Obter ligações completadas incluindo dados do contato
Um caso de uso comum é obter as ligações completadas de um dia, trazendo junto com elas os dados preenchidos dos seus contatos. Para isso é necessário usar o parâmetro include e a entidade campaign_contact como o dado a ser incluído. Isso fará com que o fim do JSON de ligações tenha um JSON aninhado com todos os dados de contato.
Exemplo de chamada
| Key | Value |
|---|
var axios = require("axios").default; var options = { method: 'GET', url: 'https://exemplo.callix.com.br/api/v1/campaign_completed_calls', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer {seu_token }'} }; axios.request(options).then(function (response) { console.log(response.data); }).catch(function (error) { console.error(error); });
Para testar, configure na página de Autenticação.
Exemplo de resposta
{
"meta": {
"count": 1
},
"links": {
"first": "/api/v1/campaign_completed_calls?filter%5Bstarted_at%5D=2021-08-30T03%3A00%3A00.000Z%2C2021-08-31T02%3A59%3A59.000Z&include=campaign_contact&page%5Blimit%5D=100&page%5Boffset%5D=0",
"last": "/api/v1/campaign_completed_calls?filter%5Bstarted_at%5D=2021-08-30T03%3A00%3A00.000Z%2C2021-08-31T02%3A59%3A59.000Z&include=campaign_contact&page%5Blimit%5D=100&page%5Boffset%5D=0"
},
"data": [
{
"type": "campaign_completed_calls",
"id": "82518",
"attributes": {
"started_at": "2021-08-31T02:23:57.013000Z",
"ended_at": "2021-08-31T02:24:16.453000Z",
"duration": 19,
"destination_phone": "+5511993198168",
"protocol": 3038,
"note": null,
"answered_at": "2021-08-31T02:24:08.713000Z",
"hangup_cause": 2,
"service_duration": 8,
"has_audio": true,
"audio_link": "/api/v1/campaign_completed_calls/82518/audio"
},
"relationships": {
"campaign": {
"data": {
"type": "campaigns",
"id": "84"
},
"links": {
"self": "/api/v1/campaign_completed_calls/82518/relationships/campaign",
"related": "/api/v1/campaign_completed_calls/82518/campaign"
}
},
"campaign_list": {
"data": {
"type": "campaign_lists",
"id": "168"
},
"links": {
"self": "/api/v1/campaign_completed_calls/82518/relationships/campaign_list",
"related": "/api/v1/campaign_completed_calls/82518/campaign_list"
}
},
"campaign_contact": {
"data": {
"type": "campaign_contacts",
"id": "663468"
},
"links": {
"self": "/api/v1/campaign_completed_calls/82518/relationships/campaign_contact",
"related": "/api/v1/campaign_completed_calls/82518/campaign_contact"
}
},
"qualification": {
"data": {
"type": "qualifications",
"id": "49"
},
"links": {
"self": "/api/v1/campaign_completed_calls/82518/relationships/qualification",
"related": "/api/v1/campaign_completed_calls/82518/qualification"
}
},
"agent": {
"data": {
"type": "users",
"id": "25"
},
"links": {
"self": "/api/v1/campaign_completed_calls/82518/relationships/agent",
"related": "/api/v1/campaign_completed_calls/82518/agent"
}
}
},
"links": {
"self": "/api/v1/campaign_completed_calls/82518"
}
}
],
"included": [
{
"type": "campaign_contacts",
"id": "663468",
"attributes": {
"codigo": "1522",
"telefone": "+5511993198168"
},
"links": {
"self": "/api/v1/campaign_contacts/663468"
}
}
]
}Formato de data
Os valores de data e hora são formatados de acordo com o padrão ISO-8601 , ou seja, YYYY-MM-DDThh:mm:ss.sssZ. As datas retornadas nas respostas estão convertidas no fuso horário UTC+0, indicado pela letra Z. As datas enviadas também devem seguir esse padrão.
No exemplo acima, estão sendo solicitadas todas as ligações completadas de campanha que foram feitas no mês de outubro/2025. Ou seja, ligações feitas no seguinte intervalo:
- 01/10/2025 - 00:00:00 UTC (
2025-10-01T00:00:00.000Z) - 31/10/2025 - 23:59:59 UTC (
2025-10-31T23:59:59.999Z)
Dados do contato
Usando a chamada acima, com o parâmetro include=campaign_contact, os dados do cliente vem no atributo included da resposta:
"included": [
{
"type": "campaign_contacts",
"id": "663468",
"attributes": {
"codigo": "1522",
"telefone": "+5511993198168"
},
"links": {
"self": "/api/v1/campaign_contacts/663468"
}
}
]2. Paginação de resultados
Exemplo de chamada
| Key | Value |
|---|
var axios = require("axios").default; var options = { method: 'GET', url: 'https://exemplo.callix.com.br/api/v1/campaign_completed_calls', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer {seu_token }'} }; axios.request(options).then(function (response) { console.log(response.data); }).catch(function (error) { console.error(error); });
Para testar, configure na página de Autenticação.
Exemplo de resposta
{
"meta": {
"count": 245
},
"links": {
"first": "/api/v1/campaign_completed_calls?filter%5Bstarted_at%5D=2021-01-05T03%3A00%3A00.000Z%2C2021-09-07T02%3A59%3A59.000Z&page%5Blimit%5D=4&page%5Boffset%5D=0",
"last": "/api/v1/campaign_completed_calls?filter%5Bstarted_at%5D=2021-01-05T03%3A00%3A00.000Z%2C2021-09-07T02%3A59%3A59.000Z&page%5Blimit%5D=4&page%5Boffset%5D=244",
"next": "/api/v1/campaign_completed_calls?filter%5Bstarted_at%5D=2021-01-05T03%3A00%3A00.000Z%2C2021-09-07T02%3A59%3A59.000Z&page%5Blimit%5D=4&page%5Boffset%5D=4"
},
"data": [
{
"type": "campaign_completed_calls",
"id": "6153",
"attributes": {
"started_at": "2021-01-06T20:57:50.997000Z",
"ended_at": "2021-01-06T21:02:14.157000Z",
"duration": 263,
"destination_phone": "+5537999743290",
"protocol": 555,
"note": null,
"answered_at": "2021-01-06T20:57:58.377000Z",
"hangup_cause": 1,
"service_duration": 256,
"has_audio": true,
"audio_link": "/api/v1/campaign_completed_calls/6153/audio"
},
"relationships": {
"campaign": {
"data": {
"type": "campaigns",
"id": "6"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6153/relationships/campaign",
"related": "/api/v1/campaign_completed_calls/6153/campaign"
}
},
"campaign_list": {
"data": {
"type": "campaign_lists",
"id": "9"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6153/relationships/campaign_list",
"related": "/api/v1/campaign_completed_calls/6153/campaign_list"
}
},
"campaign_contact": {
"data": {
"type": "campaign_contacts",
"id": "560643"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6153/relationships/campaign_contact",
"related": "/api/v1/campaign_completed_calls/6153/campaign_contact"
}
},
"qualification": {
"links": {
"self": "/api/v1/campaign_completed_calls/6153/relationships/qualification",
"related": "/api/v1/campaign_completed_calls/6153/qualification"
}
},
"agent": {
"data": {
"type": "users",
"id": "5"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6153/relationships/agent",
"related": "/api/v1/campaign_completed_calls/6153/agent"
}
}
},
"links": {
"self": "/api/v1/campaign_completed_calls/6153"
}
},
{
"type": "campaign_completed_calls",
"id": "6169",
"attributes": {
"started_at": "2021-01-07T15:06:07.809000Z",
"ended_at": "2021-01-07T15:06:35.009000Z",
"duration": 27,
"destination_phone": "+5511946825709",
"protocol": 567,
"note": null,
"answered_at": "2021-01-07T15:06:25.069000Z",
"hangup_cause": 2,
"service_duration": 10,
"has_audio": true,
"audio_link": "/api/v1/campaign_completed_calls/6169/audio"
},
"relationships": {
"campaign": {
"data": {
"type": "campaigns",
"id": "6"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6169/relationships/campaign",
"related": "/api/v1/campaign_completed_calls/6169/campaign"
}
},
"campaign_list": {
"data": {
"type": "campaign_lists",
"id": "9"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6169/relationships/campaign_list",
"related": "/api/v1/campaign_completed_calls/6169/campaign_list"
}
},
"campaign_contact": {
"data": {
"type": "campaign_contacts",
"id": "560641"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6169/relationships/campaign_contact",
"related": "/api/v1/campaign_completed_calls/6169/campaign_contact"
}
},
"qualification": {
"data": {
"type": "qualifications",
"id": "5"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6169/relationships/qualification",
"related": "/api/v1/campaign_completed_calls/6169/qualification"
}
},
"agent": {
"data": {
"type": "users",
"id": "5"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6169/relationships/agent",
"related": "/api/v1/campaign_completed_calls/6169/agent"
}
}
},
"links": {
"self": "/api/v1/campaign_completed_calls/6169"
}
},
{
"type": "campaign_completed_calls",
"id": "6180",
"attributes": {
"started_at": "2021-01-07T19:13:46.469000Z",
"ended_at": "2021-01-07T19:14:00.429000Z",
"duration": 13,
"destination_phone": "+5514999011006",
"protocol": 574,
"note": null,
"answered_at": "2021-01-07T19:13:52.789000Z",
"hangup_cause": 1,
"service_duration": 8,
"has_audio": true,
"audio_link": "/api/v1/campaign_completed_calls/6180/audio"
},
"relationships": {
"campaign": {
"data": {
"type": "campaigns",
"id": "6"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6180/relationships/campaign",
"related": "/api/v1/campaign_completed_calls/6180/campaign"
}
},
"campaign_list": {
"data": {
"type": "campaign_lists",
"id": "9"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6180/relationships/campaign_list",
"related": "/api/v1/campaign_completed_calls/6180/campaign_list"
}
},
"campaign_contact": {
"data": {
"type": "campaign_contacts",
"id": "560644"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6180/relationships/campaign_contact",
"related": "/api/v1/campaign_completed_calls/6180/campaign_contact"
}
},
"qualification": {
"data": {
"type": "qualifications",
"id": "5"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6180/relationships/qualification",
"related": "/api/v1/campaign_completed_calls/6180/qualification"
}
},
"agent": {
"data": {
"type": "users",
"id": "5"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6180/relationships/agent",
"related": "/api/v1/campaign_completed_calls/6180/agent"
}
}
},
"links": {
"self": "/api/v1/campaign_completed_calls/6180"
}
},
{
"type": "campaign_completed_calls",
"id": "6181",
"attributes": {
"started_at": "2021-01-07T19:24:19.012000Z",
"ended_at": "2021-01-07T19:25:46.452000Z",
"duration": 87,
"destination_phone": "+5582991781462",
"protocol": 575,
"note": null,
"answered_at": "2021-01-07T19:24:29.152000Z",
"hangup_cause": 1,
"service_duration": 78,
"has_audio": true,
"audio_link": "/api/v1/campaign_completed_calls/6181/audio"
},
"relationships": {
"campaign": {
"data": {
"type": "campaigns",
"id": "6"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6181/relationships/campaign",
"related": "/api/v1/campaign_completed_calls/6181/campaign"
}
},
"campaign_list": {
"data": {
"type": "campaign_lists",
"id": "9"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6181/relationships/campaign_list",
"related": "/api/v1/campaign_completed_calls/6181/campaign_list"
}
},
"campaign_contact": {
"data": {
"type": "campaign_contacts",
"id": "560645"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6181/relationships/campaign_contact",
"related": "/api/v1/campaign_completed_calls/6181/campaign_contact"
}
},
"qualification": {
"data": {
"type": "qualifications",
"id": "6"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6181/relationships/qualification",
"related": "/api/v1/campaign_completed_calls/6181/qualification"
}
},
"agent": {
"data": {
"type": "users",
"id": "5"
},
"links": {
"self": "/api/v1/campaign_completed_calls/6181/relationships/agent",
"related": "/api/v1/campaign_completed_calls/6181/agent"
}
}
},
"links": {
"self": "/api/v1/campaign_completed_calls/6181"
}
}
]
}Parâmetros
Para paginar a requisição deve-se adicionar dois parâmetros, são eles:
page[limit]que é um valor inteiro maior que 0 e representa a quantidade de registros a ser recuperada em cada requisição. Na ausência desse parâmetro, seu valor padrão é 100.page[offset]que é um valor inteiro maior ou igual a 0 e representa o deslocamento de registros. Na ausência desse parâmetro seu valor padrão é 0.
A resposta de requisição trás a contagem do total de registros de acordo com os filtros
aplicados (meta.count). Com esse valor, é possível calcular a paginação.
"meta": {
"count": 245
},Exemplos
-
Primeira página com paginação de tamanho 10.
page[limit]=10page[offset]=0
-
Terceira página com paginação de tamanho 100.
page[limit]=100page[offset]=200
3. Ler Áudio da Chamada
Retorna o áudio da chamada em formato base64.
Exemplo de chamada
| Key | Value |
|---|
var axios = require("axios").default; var options = { method: 'GET', url: 'https://exemplo.callix.com.br/api/v1/campaign_completed_calls/1/audio', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer {seu_token }'} }; axios.request(options).then(function (response) { console.log(response.data); }).catch(function (error) { console.error(error); });
Para testar, configure na página de Autenticação.
Parâmetros
Para obter o áudio de uma chamada, deve-se adicionar apenas o ID da chamada. O link obtido através do atributo audio_link na resposta das chamadas completadas já contém o ID da chamada, então basta utilizá-lo.
A resposta dessa requisição trás o audio da chamada em formato base64. Com esse formato, é possível criar um arquivo de áudio de extensão .mp3.
4. Programa de Exemplo
O script Python abaixo contém uma implementação de exemplo usando Python para consumir as APIs de obter ligações completadas e não completadas de campanha, bem como a API de download dos audios. Ele foi construído ao redor de dois casos de uso comuns:
a) Baixar as ligações do dia anterior: agenda-se a execução do script para a madrugada (por exemplo 03:00 AM) do dia corrente, baixando as ligações do dia anterior para fazer algum relatório
b) Baixar as ligações de uma faixa de dias: deseja-se baixar um histórico de todas as ligações, por exemplo mensalmente, para fins de armazenamento local. Então por exemplo no dia primeiro de cada mês agenda-se a execução do script para baixar as ligações do mês anterior.
Para facilitar a operação, o script aceita alguns parâmetros, explicados abaixo:
uso:
python export_calls_v2.py [-h] [-y] [-d] [-m] -t TENANT -b BEARER [start_date] [end_date]Busca e escreve dados de chamadas de campanha usando a API Callix.
O script cria um subdiretório abaixo do diretório corrente chamado \call_audios, onde cria dois arquivos CSV, um para chamadas completadas (completed_calls.csv) e outro para chamadas não completadas (missed_calls.csv), e caso o parâmetro -d seja especificado, baixa o audio em formato mp3 de cada chamada completada.
parâmetros posicionais:
- start_date : Data de início no formato dd/mm/aaaa (requerido se o parâmetro -y não for usado)
- end_date : Data de fim no formato dd/mm/aaaa (requerido se o parâmetro -y não for usado)
outros parâmetros:
-h, —help : mostra ajuda para os comandos
-y, —yesterday : Use a data de ontem como data de início e fim, para facilitar o uso de baixar ligações do dia anterior
-d, —download : Parâmetro que indica se é desejado fazer o download dos audios das ligações completadas (default: False)
-m, —missed : Parâmetro que indica se é desejado fazer o download das ligações não completadas (default: False)
-t TENANT, —tenant TENANT : Qual o prefixo do tenant (Cliente) para a URL de base da API (exemplo: ‘globo’ para https://globo.callix.com.br )
-b BEARER, —bearer BEARER : Bearer token da Autorização (crie o Token em Configurações>APIs>Acesso à API e conecte-o a um Perfil de Acesso adequado) (por exemplo, ‘14ae293d-f1f0-4f0c-a220-c4bc9438a855’)
O programa é fornecido “as-is”, como um exemplo de implementação de consumo de API da Callix.
Campos de campanha precisam ser adaptados no script
Como os campos de formulário de campanha são variáveis, para exportar dados de contato com os campos que você deseja você precisa editar a função “process_included_entities()” e incluir os campos que você deseja. O exemplo mostra um caso com 5 campos de formulário e como mapeá-los para os campos no CSV. Além de alterar esta função, você também precisa alterar o array “relationship_fieldnames” que fica dentro da função fetch_and_write_calls(), este array define quais campos serão colocados no CSV.
A Callix não se propõe a dar suporte neste script e fornece-o simplesmente para dar um “ponta pé inicial” em esforços de integração com a Callix.
Instruções para executar o script
Para executar o script você precisa: Ter a última versão do Python instalado. Você pode baixar a última versão navegando para https://www.python.org/downloads/
Uma vez instalado o Python, instale os módulos requests e pytz usando o pip. Na linha de comando:
pip install requests
pip install pytzPara rodar o script copie-o para algum diretório e chame-o da linha de comando, por exemplo:
python export_calls_v2.py -y -t globo -b 14ae293d-f1f0-4f0c-a220-c4bc9438a855 -d(faz o download das ligações completadas, além dos seus audios. Ligações não completadas não são baixadas (falta -m))
Script Python de exemplo
# modules that need install : run [pip install <module>] on the command line
import requests
import pytz
# modules included in Python default libraries
import json
import csv
from datetime import datetime, timedelta
import time
import base64
import os
import sys
import argparse
def convert_to_brt(utc_string):
utc_zone = pytz.utc
brt_zone = pytz.timezone('America/Sao_Paulo')
utc_dt = utc_zone.localize(datetime.fromisoformat(utc_string.replace('Z', '')))
brt_dt = utc_dt.astimezone(brt_zone)
return brt_dt.strftime('%d-%m-%Y %H:%M:%S')
def process_included_entities(calls, included):
# Create a lookup dictionary for included entities
entity_map = {(item['type'], item['id']): item['attributes'] for item in included}
# Process each call and add related entity attributes
for call in calls:
attrs = call['attributes']
relationships = call['relationships']
# Convert UTC times to BRT
attrs['started_at'] = convert_to_brt(attrs['started_at'])
attrs['ended_at'] = convert_to_brt(attrs['ended_at'])
if 'answered_at' in attrs:
attrs['answered_at'] = convert_to_brt(attrs['answered_at'])
# Add campaign_contact attributes
# contact attributes are DYNAMIC, so lines mapping contact attributes to CSV fields should be changed/added according to what contact fields one wants on the CSV
# As an EXAMPLE five contact attributes are included: "nome", "cpf", "produto" , "publico", "telefone", and they are mapped to the "contact_nome", "contact_cpf" and so on as CSV fields
contact_data = relationships.get('campaign_contact', {}).get('data')
if contact_data and (contact_data['type'], contact_data['id']) in entity_map:
contact_attrs = entity_map[(contact_data['type'], contact_data['id'])]
attrs['contact_nome'] = contact_attrs.get('nome', '')
attrs['contact_cpf'] = contact_attrs.get('cpf', '')
attrs['contact_produto'] = contact_attrs.get('produto', '')
attrs['contact_publico'] = contact_attrs.get('publico', '')
attrs['contact_telefone'] = contact_attrs.get('telefone', '')
# Add qualification attributes if they exist
qual_data = relationships.get('qualification', {}).get('data')
if qual_data and (qual_data['type'], qual_data['id']) in entity_map:
qual_attrs = entity_map[(qual_data['type'], qual_data['id'])]
attrs['qualification_name'] = qual_attrs.get('name', '')
attrs['qualification_cpc'] = qual_attrs.get('cpc', '')
attrs['qualification_success'] = qual_attrs.get('success', '')
attrs['qualification_is_system_qualification'] = qual_attrs.get('is_system_qualification', '')
# Add user (agent) attributes if they exist
agent_data = relationships.get('agent', {}).get('data')
if agent_data and (agent_data['type'], agent_data['id']) in entity_map:
agent_attrs = entity_map[(agent_data['type'], agent_data['id'])]
attrs['agent_name'] = agent_attrs.get('name', '')
attrs['agent_email'] = agent_attrs.get('email', '')
attrs['agent_login'] = agent_attrs.get('login', '')
return calls
def download_call_audio(audio_link, protocol, headers, base_url, output_dir="call_audios"):
if not os.path.exists(output_dir):
os.makedirs(output_dir)
filename = f"{output_dir}/{protocol}.mp3"
# Check if audio file already exists locally
if os.path.exists(filename) and os.path.getsize(filename) > 0:
print(f"Audio for protocol {protocol} already exists and is non-empty, skipping download")
return True
full_audio_url = base_url + audio_link
try:
response = requests.get(full_audio_url, headers=headers)
#sleeps for 500ms to respect API rate limit
time.sleep(0.5)
if response.status_code == 200:
content_type = response.headers.get('Content-Type', '').lower()
if 'application/json' in content_type:
audio_data = response.json().get('audio', '')
if audio_data:
audio_bytes = base64.b64decode(audio_data)
with open(filename, 'wb') as audio_file:
audio_file.write(audio_bytes)
print(f"Downloaded audio for protocol {protocol} (base64 JSON)")
return True
else:
print(f"No audio data in JSON response for protocol {protocol}")
return False
else:
with open(filename, 'wb') as audio_file:
audio_file.write(response.content)
print(f"Downloaded audio for protocol {protocol} (direct binary)")
return True
else:
print(f"Failed to download audio for protocol {protocol}: Status {response.status_code}: {response.text}")
return False
except Exception as e:
print(f"Error downloading audio for protocol {protocol}: {e}")
return False
def fetch_and_write_calls(base_url, export_type, headers, start_date, end_date, download_audio=False, output_dir="call_audios"):
if export_type not in ["completed", "missed"]:
raise ValueError("export_type must be 'completed' or 'missed'")
# Infer endpoint, include, and filename based on export_type
if export_type == "completed":
endpoint = "/api/v1/campaign_completed_calls"
include = "campaign_contact,qualification,agent"
filename = "completed_calls.csv"
rate_limit_delay = 7 # 10/minute = 6s + 1s buffer
else: # export_type == "missed"
endpoint = "/api/v1/campaign_missed_calls"
include = "campaign_contact"
filename = "missed_calls.csv"
rate_limit_delay = 61 # 1/minute = 60s + 1s buffer
total_calls = 0
is_first_file = True
csv_path = os.path.join(output_dir, filename)
if not os.path.exists(output_dir):
os.makedirs(output_dir)
try:
current_date = start_date
while current_date <= end_date:
day_start = current_date.replace(hour=0, minute=0, second=0, microsecond=0)
day_end = current_date.replace(hour=23, minute=59, second=59, microsecond=999999)
start_str = day_start.strftime('%Y-%m-%dT%H:%M:%S.000Z')
end_str = day_end.strftime('%Y-%m-%dT%H:%M:%S.000Z')
params = {
"filter[started_at]": f"{start_str},{end_str}",
"page[limit]": 2000,
"page[offset]": 0,
"include": include
}
print(f"Processing {export_type} calls for {current_date.strftime('%Y-%m-%d')}...")
is_first_page = True
current_url = endpoint
while current_url:
response = requests.get(
base_url + endpoint if current_url == endpoint else current_url,
headers=headers,
params=params if current_url == endpoint else None
)
if response.status_code != 200:
print(f"Error on {current_date.strftime('%Y-%m-%d')}: Received status code {response.status_code}: {response.text}")
break
data = response.json()
calls = data['data']
if not calls:
print(f"No {export_type} calls found for {current_date.strftime('%Y-%m-%d')}")
break
included = data.get('included', [])
if included:
calls = process_included_entities(calls, included)
if download_audio and export_type == "completed":
for call in calls:
attrs = call['attributes']
if attrs.get('has_audio', False) and 'audio_link' in attrs:
download_call_audio(attrs['audio_link'], attrs['protocol'], headers, base_url, output_dir)
original_fieldnames = list(calls[0]['attributes'].keys())
relationship_fieldnames = (
['contact_nome', 'contact_cpf', 'contact_produto', 'contact_publico', 'contact_telefone', 'qualification_name','qualification_cpc', 'qualification_success', 'qualification_is_system_qualification', 'agent_name', 'agent_login', 'agent_email']
if export_type == "completed"
else ['contact_nome', 'contact_cpf', 'contact_produto', 'contact_publico', 'contact_telefone']
)
all_fieldnames = original_fieldnames + [f for f in relationship_fieldnames if f not in original_fieldnames]
try:
mode = 'w' if is_first_file and is_first_page else 'a'
with open(csv_path, mode, newline='', encoding='utf-8') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=all_fieldnames, delimiter=';')
if is_first_page and is_first_file:
writer.writeheader()
is_first_file = False
for call in calls:
writer.writerow(call['attributes'])
except PermissionError as pe:
print(f"Permission denied when writing to {csv_path}: {pe}. Please ensure the file is not open elsewhere.")
return
except Exception as e:
print(f"Error writing to {csv_path}: {e}")
return
total_calls += len(calls)
print(f"Fetched and wrote {len(calls)} {export_type} calls for {current_date.strftime('%Y-%m-%d')}, total so far: {total_calls}")
time.sleep(rate_limit_delay)
next_page = data['links'].get('next')
current_url = base_url + next_page if next_page else None
current_date += timedelta(days=1)
except requests.RequestException as e:
print(f"Error: {e}")
print(f"Finished processing {export_type} calls. Total calls saved: {total_calls}")
def main():
parser = argparse.ArgumentParser(description="Fetch and write call data from Callix API.")
parser.add_argument('-y', '--yesterday', action='store_true', help="Use yesterday's date for start and end date")
parser.add_argument('-d', '--download', action='store_true', help="Download audio files for completed calls (default: False)")
parser.add_argument('-m', '--missed', action='store_true', help="Also export missed calls (default: only completed calls)")
parser.add_argument('-t', '--tenant', required=True, help="Tenant prefix for base URL (e.g., 'globo' for https://globo.callix.com.br)")
parser.add_argument('-b', '--bearer', required=True, help="Bearer token for Authorization (e.g., '14ae293d-f1f0-4f0c-a220-c4bc9438a855')")
parser.add_argument('start_date', nargs='?', help="Start date in dd/mm/aaaa format (required if -y not used)")
parser.add_argument('end_date', nargs='?', help="End date in dd/mm/aaaa format (required if -y not used)")
args = parser.parse_args()
base_url = f"https://{args.tenant}.callix.com.br"
headers = {
'Content-Type': "application/json",
'Authorization': f"Bearer {args.bearer}"
}
if args.yesterday:
yesterday = datetime.now(pytz.utc) - timedelta(days=1)
start_date = yesterday.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=pytz.utc)
end_date = yesterday.replace(hour=23, minute=59, second=59, microsecond=999999, tzinfo=pytz.utc)
else:
if not args.start_date or not args.end_date:
print("Error: Start date and end date are required unless -y is used.")
print("Usage: python export_calls_v2.py -t <tenant> -b <bearer> [-y] [-d] [-m] [<start_date> <end_date>]")
print("Example: python export_calls_v2.py -t globo -b 14ae293d-f1f0-4f0c-a220-c4bc9438a855 31/03/2025 03/04/2025")
print(" python export_calls_v2.py -t globo -b 14ae293d-f1f0-4f0c-a220-c4bc9438a855 -y -d")
print("Dates must be in dd/mm/aaaa format.")
sys.exit(1)
try:
start_date = datetime.strptime(args.start_date, '%d/%m/%Y').replace(tzinfo=pytz.utc)
end_date = datetime.strptime(args.end_date, '%d/%m/%Y').replace(tzinfo=pytz.utc)
except ValueError as e:
print(f"Error: Invalid date format. {e}")
print("Usage: python export_calls_v2 -t <tenant> -b <bearer> [-y] [-d] [-m] [<start_date> <end_date>]")
print("Example: python export_calls_v2.py -t globo -b 14ae293d-f1f0-4f0c-a220-c4bc9438a855 31/03/2025 03/04/2025")
print(" python export_calls_v2.py -t globo -b 14ae293d-f1f0-4f0c-a220-c4bc9438a855 -y -d")
print("Dates must be in dd/mm/aaaa format.")
sys.exit(1)
print("Starting to fetch and write completed calls...")
fetch_and_write_calls(base_url, "completed", headers, start_date, end_date, download_audio=args.download)
if args.missed:
print("Starting to fetch and write missed calls...")
fetch_and_write_calls(base_url, "missed", headers, start_date, end_date, download_audio=False)
if __name__ == "__main__":
main()