Upload large campaign CSV (Beta)
Upload a 5,000+ row campaign CSV for asynchronous processing.
curl --request POST \
--url https://prod-api.ringg.ai/ca/api/v0/campaign/upload-csv \
--header 'Content-Type: multipart/form-data' \
--header 'X-API-KEY: <x-api-key>' \
--form 'variables_map={"mobile_number":"Mobile Number","callee_name":"Name"}' \
--form agent_id=830f767a-397e-4b39-82ff-235cd344e2f9 \
--form 'country_code=+91' \
--form 'campaign_name=Renewal reminders - large upload' \
--form file='@example-file' \
--form remove_invalid_rows=true \
--form transliterate_callee_name=falseimport requests
url = "https://prod-api.ringg.ai/ca/api/v0/campaign/upload-csv"
files = { "file": ("example-file", open("example-file", "rb")) }
payload = {
"variables_map": "{\"mobile_number\":\"Mobile Number\",\"callee_name\":\"Name\"}",
"agent_id": "830f767a-397e-4b39-82ff-235cd344e2f9",
"country_code": "+91",
"campaign_name": "Renewal reminders - large upload",
"remove_invalid_rows": "true",
"transliterate_callee_name": "false"
}
headers = {"X-API-KEY": "<x-api-key>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('variables_map', '{"mobile_number":"Mobile Number","callee_name":"Name"}');
form.append('agent_id', '830f767a-397e-4b39-82ff-235cd344e2f9');
form.append('country_code', '+91');
form.append('campaign_name', 'Renewal reminders - large upload');
form.append('file', '<string>');
form.append('remove_invalid_rows', 'true');
form.append('transliterate_callee_name', 'false');
const options = {method: 'POST', headers: {'X-API-KEY': '<x-api-key>'}};
options.body = form;
fetch('https://prod-api.ringg.ai/ca/api/v0/campaign/upload-csv', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://prod-api.ringg.ai/ca/api/v0/campaign/upload-csv",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"variables_map\"\r\n\r\n{\"mobile_number\":\"Mobile Number\",\"callee_name\":\"Name\"}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"agent_id\"\r\n\r\n830f767a-397e-4b39-82ff-235cd344e2f9\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"country_code\"\r\n\r\n+91\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"campaign_name\"\r\n\r\nRenewal reminders - large upload\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"remove_invalid_rows\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"transliterate_callee_name\"\r\n\r\nfalse\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Content-Type: multipart/form-data",
"X-API-KEY: <x-api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://prod-api.ringg.ai/ca/api/v0/campaign/upload-csv"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"variables_map\"\r\n\r\n{\"mobile_number\":\"Mobile Number\",\"callee_name\":\"Name\"}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"agent_id\"\r\n\r\n830f767a-397e-4b39-82ff-235cd344e2f9\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"country_code\"\r\n\r\n+91\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"campaign_name\"\r\n\r\nRenewal reminders - large upload\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"remove_invalid_rows\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"transliterate_callee_name\"\r\n\r\nfalse\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-KEY", "<x-api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://prod-api.ringg.ai/ca/api/v0/campaign/upload-csv")
.header("X-API-KEY", "<x-api-key>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"variables_map\"\r\n\r\n{\"mobile_number\":\"Mobile Number\",\"callee_name\":\"Name\"}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"agent_id\"\r\n\r\n830f767a-397e-4b39-82ff-235cd344e2f9\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"country_code\"\r\n\r\n+91\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"campaign_name\"\r\n\r\nRenewal reminders - large upload\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"remove_invalid_rows\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"transliterate_callee_name\"\r\n\r\nfalse\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://prod-api.ringg.ai/ca/api/v0/campaign/upload-csv")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-KEY"] = '<x-api-key>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"variables_map\"\r\n\r\n{\"mobile_number\":\"Mobile Number\",\"callee_name\":\"Name\"}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"agent_id\"\r\n\r\n830f767a-397e-4b39-82ff-235cd344e2f9\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"country_code\"\r\n\r\n+91\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"campaign_name\"\r\n\r\nRenewal reminders - large upload\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"remove_invalid_rows\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"transliterate_callee_name\"\r\n\r\nfalse\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"bulk_list_id": "123e4567-e89b-12d3-a456-426614174000",
"status": "verifying",
"campaign_name": "Renewal reminders - large upload",
"total_callees": 0,
"invalidated_rows_count": 0,
"invalidated_rows_csv_url": null
}{
"detail": "Invalid CSV file or variable mapping."
}POST /campaign/upload-csv starts the beta large-campaign flow. It accepts a large CSV and campaign configuration, then returns a bulk_list_id that you use for status polling, balance checking, and async call scheduling.
Endpoint
POST /campaign/upload-csv
When to use
Use this endpoint for CSVs with 5,000 or more rows. It lets Ringg process and validate the upload asynchronously instead of making the initial upload request wait for the full import.Request guidance
Sendmultipart/form-data. Use the same CSV template and campaign fields as the standard POST /campaign/save flow.
| Field | Required | Notes |
|---|---|---|
file | Yes | CSV file with Mobile Number and assistant variable columns |
agent_id | Yes | Assistant that will handle the campaign calls |
campaign_name | Yes | Human-readable campaign name |
country_code | Yes | Dialing prefix, such as +91 or +1 |
variables_map | Yes | JSON string mapping assistant variables to CSV columns |
campaign_start_time | Yes | Campaign start time in DD/MM/YYYY, HH:MM format |
campaign_end_time | Yes | Campaign end time in DD/MM/YYYY, HH:MM format |
call_config | Yes | JSON string for call window, retry, and call limit settings |
remove_invalid_rows | No | Set to true to skip invalid rows when supported |
transliterate_callee_name | No | Set to true when callee names should be transliterated |
curl --request POST "https://prod-api.ringg.ai/ca/api/v0/campaign/upload-csv" \
--header "X-API-KEY: $RINGG_API_KEY" \
--form 'agent_id="your-agent-id"' \
--form 'campaign_name="Renewal reminders - large batch"' \
--form 'country_code="+91"' \
--form 'variables_map="{\"mobile_number\":\"Mobile Number\",\"callee_name\":\"Name\"}"' \
--form 'campaign_start_time="18/10/2026, 09:00"' \
--form 'campaign_end_time="18/10/2026, 18:00"' \
--form 'call_config="{\"call_time\":{\"call_start_time\":\"09:00\",\"call_end_time\":\"18:00\",\"timezone\":\"Asia/Kolkata\"}}"' \
--form "file=@contacts-5000-plus.csv"
Content-Type manually in browser or Node FormData clients. Let the client include the multipart boundary.Response guidance
Persist the returnedbulk_list_id. It identifies the uploaded list for every later beta endpoint.
{
"bulk_list_id": "123e4567-e89b-12d3-a456-426614174000",
"status": "uploaded",
"message": "CSV upload accepted for processing"
}
Next step
Poll Get large campaign upload status until the upload is processed. Do not check balance or start calls until processing is complete.Headers
(Required) Your Ringg AI API key.
"7251cb4b-3373-43a4-844c-b27a1d45e0c9"
Body
(Required) JSON string mapping assistant variable names to CSV columns.
"{\"mobile_number\":\"Mobile Number\",\"callee_name\":\"Name\"}"
(Required) Assistant ID used to validate campaign variables.
"830f767a-397e-4b39-82ff-235cd344e2f9"
(Required) Phone number country code, for example +91 or +1.
"+91"
(Required) Name of the campaign draft.
"Renewal reminders - large upload"
(Required) CSV file containing campaign contacts.
When true, invalid rows are removed instead of failing the upload.
true
When true, callee names are transliterated for the assistant language.
false
Response
CSV upload accepted for asynchronous validation.
"123e4567-e89b-12d3-a456-426614174000"
"verifying"
"Renewal reminders - large upload"
5000
12
"https://storage.example.com/invalid-rows.csv"
curl --request POST \
--url https://prod-api.ringg.ai/ca/api/v0/campaign/upload-csv \
--header 'Content-Type: multipart/form-data' \
--header 'X-API-KEY: <x-api-key>' \
--form 'variables_map={"mobile_number":"Mobile Number","callee_name":"Name"}' \
--form agent_id=830f767a-397e-4b39-82ff-235cd344e2f9 \
--form 'country_code=+91' \
--form 'campaign_name=Renewal reminders - large upload' \
--form file='@example-file' \
--form remove_invalid_rows=true \
--form transliterate_callee_name=falseimport requests
url = "https://prod-api.ringg.ai/ca/api/v0/campaign/upload-csv"
files = { "file": ("example-file", open("example-file", "rb")) }
payload = {
"variables_map": "{\"mobile_number\":\"Mobile Number\",\"callee_name\":\"Name\"}",
"agent_id": "830f767a-397e-4b39-82ff-235cd344e2f9",
"country_code": "+91",
"campaign_name": "Renewal reminders - large upload",
"remove_invalid_rows": "true",
"transliterate_callee_name": "false"
}
headers = {"X-API-KEY": "<x-api-key>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('variables_map', '{"mobile_number":"Mobile Number","callee_name":"Name"}');
form.append('agent_id', '830f767a-397e-4b39-82ff-235cd344e2f9');
form.append('country_code', '+91');
form.append('campaign_name', 'Renewal reminders - large upload');
form.append('file', '<string>');
form.append('remove_invalid_rows', 'true');
form.append('transliterate_callee_name', 'false');
const options = {method: 'POST', headers: {'X-API-KEY': '<x-api-key>'}};
options.body = form;
fetch('https://prod-api.ringg.ai/ca/api/v0/campaign/upload-csv', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://prod-api.ringg.ai/ca/api/v0/campaign/upload-csv",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"variables_map\"\r\n\r\n{\"mobile_number\":\"Mobile Number\",\"callee_name\":\"Name\"}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"agent_id\"\r\n\r\n830f767a-397e-4b39-82ff-235cd344e2f9\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"country_code\"\r\n\r\n+91\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"campaign_name\"\r\n\r\nRenewal reminders - large upload\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"remove_invalid_rows\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"transliterate_callee_name\"\r\n\r\nfalse\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Content-Type: multipart/form-data",
"X-API-KEY: <x-api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://prod-api.ringg.ai/ca/api/v0/campaign/upload-csv"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"variables_map\"\r\n\r\n{\"mobile_number\":\"Mobile Number\",\"callee_name\":\"Name\"}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"agent_id\"\r\n\r\n830f767a-397e-4b39-82ff-235cd344e2f9\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"country_code\"\r\n\r\n+91\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"campaign_name\"\r\n\r\nRenewal reminders - large upload\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"remove_invalid_rows\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"transliterate_callee_name\"\r\n\r\nfalse\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-KEY", "<x-api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://prod-api.ringg.ai/ca/api/v0/campaign/upload-csv")
.header("X-API-KEY", "<x-api-key>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"variables_map\"\r\n\r\n{\"mobile_number\":\"Mobile Number\",\"callee_name\":\"Name\"}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"agent_id\"\r\n\r\n830f767a-397e-4b39-82ff-235cd344e2f9\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"country_code\"\r\n\r\n+91\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"campaign_name\"\r\n\r\nRenewal reminders - large upload\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"remove_invalid_rows\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"transliterate_callee_name\"\r\n\r\nfalse\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://prod-api.ringg.ai/ca/api/v0/campaign/upload-csv")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-KEY"] = '<x-api-key>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"variables_map\"\r\n\r\n{\"mobile_number\":\"Mobile Number\",\"callee_name\":\"Name\"}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"agent_id\"\r\n\r\n830f767a-397e-4b39-82ff-235cd344e2f9\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"country_code\"\r\n\r\n+91\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"campaign_name\"\r\n\r\nRenewal reminders - large upload\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"remove_invalid_rows\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"transliterate_callee_name\"\r\n\r\nfalse\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"bulk_list_id": "123e4567-e89b-12d3-a456-426614174000",
"status": "verifying",
"campaign_name": "Renewal reminders - large upload",
"total_callees": 0,
"invalidated_rows_count": 0,
"invalidated_rows_csv_url": null
}{
"detail": "Invalid CSV file or variable mapping."
}