Skip to main content
PUT
/
api
/
lists
/
create-with-import
Create a list and import rows in one flow
curl --request PUT \
  --url https://app.puffle.ai/api/lists/create-with-import \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "name": "<string>",
  "dataType": "people",
  "csvText": "<string>",
  "personMapping": {},
  "companyMapping": {},
  "skipExistingCustomers": true
}
'
{
  "listId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
  "imported": 4503599627370495,
  "blocked": 4503599627370495,
  "crossListSkipped": 4503599627370495,
  "total": 4503599627370495,
  "dataType": "people",
  "companiesCreated": 4503599627370495
}

Overview

The second half of the create-with-import flow. Creates a new list owned by the caller and imports the previewed rows with the confirmed dataType + mappings produced by the preview step. For people imports the server extracts company firmographics from the CSV into list_company_rows and links each person to their company row. For companies imports only list_company_rows are created. After the rows land, the list is flipped to enrichment_status = "enriching" and the bright-data-enrich Trigger.dev task is dispatched to fill in missing company data.
This operation shares the URL path /api/lists/create-with-import with the preview verb. See Preview a create-with-import flow to upload the CSV/XLSX, auto-detect people vs. companies, and get mapping suggestions before committing.

Credit gating

This endpoint is credit-gated:
  • The server previews the cost at CREDIT_COSTS.IMPORT_LEAD per row.
  • If the caller cannot afford every row, the import is truncated to floor(balance / costPerLead) rows — the response still succeeds with a smaller imported count.
  • If the caller cannot afford even a single row, the response is 402 Insufficient credits.
  • Credits are deducted after insert, based on imported — skipped duplicates and blocked rows are free.

AI agent notes

Fields. name (required), dataType ("people" or "companies"), csvText (from the preview response), personMapping / companyMapping (either or both as appropriate), skipExistingCustomers (default true).When to use this vs. updateListImport. Use create-with-import when you want a brand-new list in one shot. Use PUT /api/lists/{id}/import when you already have a list and want to add to it.Follow-up. The response includes listId. Fetch GET /api/lists/{id} (via the core list routes) to observe enrichment_status transitioning from enriching back to idle.On 402. Stop and surface the credit balance to the user. Do not retry — you need credits first.Truncation is silent. If total > imported and there was no 402, the caller ran out of credits mid-batch. Compare total vs. imported to detect this.

Authorizations

Authorization
string
header
required

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

Body

application/json

JSON body. Either personMapping (for people) or companyMapping (for companies) should be populated; supply both for people imports when the CSV includes firmographic columns the server can lift into company rows.

name
string
required

Name for the new list. Required, non-empty.

Minimum string length: 1
dataType
enum<string>
required

Detected or declared data shape. people imports create list_rows plus inferred list_company_rows; companies imports create list_company_rows only.

Available options:
people,
companies
csvText
string
required

The exact csvText returned by the preview step. The server re-parses it.

Minimum string length: 1
personMapping
object

Field-name → CSV column-name map. null means no CSV column matched that field.

companyMapping
object

Field-name → CSV column-name map. null means no CSV column matched that field.

skipExistingCustomers
boolean

Default true. Skip rows present in other caller-owned lists (cross-list dedup) and rows matching the user's blocked_companies.

Response

List created; rows imported. Response shape depends on dataType.

Shape depends on dataType. Both variants include listId and imported.

listId
string<uuid>
required
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)$
imported
integer
required

New list_rows inserted (capped by available credits).

Required range: 0 <= x <= 9007199254740991
blocked
integer
required

Rows skipped because their company is blocked.

Required range: 0 <= x <= 9007199254740991
crossListSkipped
integer
required

Rows skipped because the person is already in another list.

Required range: 0 <= x <= 9007199254740991
total
integer
required

Rows in the parsed CSV.

Required range: 0 <= x <= 9007199254740991
dataType
enum<string>
required
Available options:
people
companiesCreated
integer
required

Company rows created from embedded firmographic data.

Required range: 0 <= x <= 9007199254740991