curl --request POST \
--url https://api.upliftai.org/v1/contact-lists/uploads \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form file='@example-file'import requests
url = "https://api.upliftai.org/v1/contact-lists/uploads"
files = { "file": ("example-file", open("example-file", "rb")) }
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('file', '<string>');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.upliftai.org/v1/contact-lists/uploads', 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://api.upliftai.org/v1/contact-lists/uploads",
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=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$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://api.upliftai.org/v1/contact-lists/uploads"
payload := strings.NewReader("-----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--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.upliftai.org/v1/contact-lists/uploads")
.header("Authorization", "Bearer <token>")
.body("-----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--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.upliftai.org/v1/contact-lists/uploads")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----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--"
response = http.request(request)
puts response.read_body{
"uploadId": "dac00330-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"summary": {
"willBeCalled": 2,
"excluded": {
"invalidPhone": 0,
"missingName": 0,
"duplicate": 0,
"dnc": 0,
"recentlyCalled": 0
},
"errors": []
},
"detectedMapping": {
"nameColumn": "Name",
"phoneColumn": "Phone",
"attributeColumns": [
"City"
]
},
"sample": [
{
"name": "عائشہ صدیقی",
"phone": "+923000000001",
"attributes": {
"City": "لاہور"
}
},
{
"name": "بلال احمد",
"phone": "+923000000002",
"attributes": {
"City": "کراچی"
}
}
],
"total": 2,
"columns": [
"Name",
"Phone",
"City"
],
"mappingIssues": []
}{
"message": "multipart `file` is required",
"error": "Bad Request",
"statusCode": 400
}{
"message": "invalid authorization",
"error": "Unauthorized",
"statusCode": 401
}{
"message": "Forbidden resource",
"error": "Forbidden",
"statusCode": 403
}{
"message": "File too large",
"error": "Payload Too Large",
"statusCode": 413
}Upload a contact file
Step one of the file flow. The API detects CSV vs .xlsx from the contents, reads only the first sheet of a workbook, parses the rows and stores them — no contact list yet. Pass the returned uploadId to /contact-lists to create one, or re-preview first if the detected mapping needs correcting. A missing name or phone column is not an upload error: it lands in mappingIssues and those rows count as excluded. Column naming rules: Special column names.
curl --request POST \
--url https://api.upliftai.org/v1/contact-lists/uploads \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form file='@example-file'import requests
url = "https://api.upliftai.org/v1/contact-lists/uploads"
files = { "file": ("example-file", open("example-file", "rb")) }
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('file', '<string>');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.upliftai.org/v1/contact-lists/uploads', 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://api.upliftai.org/v1/contact-lists/uploads",
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=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$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://api.upliftai.org/v1/contact-lists/uploads"
payload := strings.NewReader("-----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--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.upliftai.org/v1/contact-lists/uploads")
.header("Authorization", "Bearer <token>")
.body("-----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--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.upliftai.org/v1/contact-lists/uploads")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----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--"
response = http.request(request)
puts response.read_body{
"uploadId": "dac00330-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"summary": {
"willBeCalled": 2,
"excluded": {
"invalidPhone": 0,
"missingName": 0,
"duplicate": 0,
"dnc": 0,
"recentlyCalled": 0
},
"errors": []
},
"detectedMapping": {
"nameColumn": "Name",
"phoneColumn": "Phone",
"attributeColumns": [
"City"
]
},
"sample": [
{
"name": "عائشہ صدیقی",
"phone": "+923000000001",
"attributes": {
"City": "لاہور"
}
},
{
"name": "بلال احمد",
"phone": "+923000000002",
"attributes": {
"City": "کراچی"
}
}
],
"total": 2,
"columns": [
"Name",
"Phone",
"City"
],
"mappingIssues": []
}{
"message": "multipart `file` is required",
"error": "Bad Request",
"statusCode": 400
}{
"message": "invalid authorization",
"error": "Unauthorized",
"statusCode": 401
}{
"message": "Forbidden resource",
"error": "Forbidden",
"statusCode": 403
}{
"message": "File too large",
"error": "Payload Too Large",
"statusCode": 413
}Authorizations
Project API key (sk_api_…).
Body
The contact file, up to 10 MB. CSV or .xlsx, detected from the contents rather than the filename. Only the first sheet of a workbook is read.
Response
The stored upload and what parsing it produced. A missing name or phone column is not an error — it lands in mappingIssues and the affected rows count as excluded.
Show child attributes
Show child attributes
The mapping applied. Overrides you sent are resolved to header names — an A1 letter comes back as its header — and the rest is auto-detected.
Show child attributes
Show child attributes
First 10 contacts that would be called, after normalization.
Show child attributes
Show child attributes
Data rows read, excluding the header. summary.willBeCalled and the summary.excluded counts add up to this.
Headers as parsed, in file order — the choices for a mapping override.
Columns that resolved to nothing. Check this first when everything is excluded — an unmapped phone column makes every number look invalid.
phoneColumnNotFound, nameColumnNotFound Handle for the stored rows. Uploads are scoped to the API key's project and cannot be deleted.
