Skip to main content
POST
/
api
/
campaigns
/
{id}
/
leads
/
upload-csv
Upload CSV/XLSX for column mapping
curl --request POST \
  --url https://app.puffle.ai/api/campaigns/{id}/leads/upload-csv \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "file": "<string>"
}
'
{ "lead_list_id": "dddd1111-1111-1111-1111-111111111111", "columns": [ "Email", "First Name", "Last Name", "Company", "Title" ], "auto_mapping": { "email": "Email", "first_name": "First Name", "last_name": "Last Name", "company": "Company", "position": "Title" }, "preview_rows": [ { "Email": "[email protected]", "First Name": "Jane", "Last Name": "Doe", "Company": "Acme", "Title": "VP of Sales" } ], "total_rows": 120, "csv_text": "Email,First Name,Last Name,Company,Title\[email protected],Jane,Doe,Acme,VP of Sales\n" }

Overview

This is the first of two steps for CSV import. The call parses the uploaded file, creates a campaign_lead_lists record, runs a best-effort auto-mapping (pattern match first, then an LLM fallback for anything unmapped), and echoes the CSV back to you for the confirm step. Nothing is written to campaign_prospects yet — agents should review (or blindly trust) the mapping and then call confirmCampaignCsv with the returned lead_list_id, csv_text, and final mapping. Request format. multipart/form-data with a single file part — NOT application/json. The CSV/XLSX binary goes under the key file. Maximum file size is 10 MB. Supported extensions are .csv, .xlsx, .xls. Campaign must be in draft.

AI agent notes

Auto-mapping coverage. Pattern matching handles the common cases (Email, First Name, Company, etc.). For esoteric headers the server calls an LLM (csvColumnMapping model) to map remaining fields against the first 3 data rows. If OPENROUTER_API_KEY isn’t set, only the pattern-matched columns are returned.Mapping shape. Keys are target fields (email, first_name, last_name, company, position, linkedin_url, headline). Values are the exact source column name, or null if no match was found. Agents should review, fill in any null entries where they’re confident, and pass the edited map to confirmCampaignCsv.Unmapped columns become custom fields. Anything in columns that isn’t in the final mapping values is rolled into each prospect’s custom_fields during confirm. Source headers are normalized (lowercased, spaces → underscores, non-alphanum stripped) for the JSON key.Round-tripping the content. The csv_text field is the raw CSV body — pass it verbatim to confirmCampaignCsv. The server re-parses with the confirmed mapping; no need to reconstruct the file.Preview cap. preview_rows contains the first 5 data rows keyed by source header. Use for UI preview only.

Authorizations

Authorization
string
header
required

Bearer authentication header of the form Bearer <token>, where <token> is your auth token.

Path Parameters

id
string<uuid>
required

Campaign UUID

Pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$

Body

application/json

Send as multipart/form-data (NOT JSON). Attach the file under the key file. Example curl: -F [email protected].

file
string
required

The CSV (.csv) or Excel (.xlsx, .xls) file to upload. Maximum 10 MB. First row must be a header row; at least one data row is required.

Response

File parsed. Returns lead_list_id, column headers, auto-detected mapping, preview rows, and the raw CSV text for the confirm step.

lead_list_id
string<uuid>
required

Newly-created campaign_lead_lists row. Pass this to confirmCampaignCsv along with the final mapping to actually insert prospects.

Pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
columns
string[]
required

Header names detected in the uploaded file, in source order.

auto_mapping
object
required

Best-effort map from target field (email, first_name, last_name, company, position, linkedin_url, headline) to the source column name. Uses pattern matching first, then an LLM fallback for unmapped columns. Values are the exact source header, or null if no match. Review and override before confirming.

preview_rows
object[]
required

First 5 data rows, keyed by source header. For UI preview.

total_rows
integer
required

Total non-empty data rows in the file.

Required range: 0 <= x <= 9007199254740991
csv_text
string
required

Round-trip the file contents back as CSV text. Pass this verbatim to confirmCampaignCsv so the server can re-parse with the confirmed mapping.