---
title: "Bulk-import contacts (JSON array, CSV, URL, storage). merge_strategy: 'merge' (default; tags UNION, attrs deep-merge), '"
method: POST
path: "/api/v1/imports/contacts"
tags: ["imports"]
---

# Bulk-import contacts (JSON array, CSV, URL, storage). merge_strategy: 'merge' (default; tags UNION, attrs deep-merge), '

`POST /api/v1/imports/contacts`

## Request

- Base URL: `https://crispy.sh`
- URL: `https://crispy.sh/api/v1/imports/contacts`
- Auth: HTTP bearer

## Request body

- object
  - `contacts` object[]: Array of contact objects. Each must have a LinkedIn URL. Other fields (first_name, email, company, title, etc.) are auto-mapped from common naming conventions. Max 1000/call.
  - `csv_content` string: Raw CSV or TSV text. Server parses headers and rows automatically. No row limit.
  - `csv_url` string: HTTPS URL of a CSV/TSV file (public HTTPS only). Server fetches it server-side — no size limit, no context overhead. Works with Supabase Storage signed URLs, Google Sheets export URLs, etc.
  - `storage_path` string: Path inside the 'imports' Supabase Storage bucket. Must be under your own user folder (e.g. '<your-user-id>/contacts.csv'); cross-tenant paths are rejected. Upload the file under that prefix first, then call this tool with the path.
  - `source` string: Where these contacts came from (e.g. 'lemlist', 'heyreach', 'expandi', 'csv'). Stored for tracking.
  - `list_id` string: Optional list ID — imported contacts are automatically added to this list. Returns list_size (total in list after import).
  - `merge_strategy` 'merge' | 'replace' | 'skip': How to handle duplicate linkedin_url: 'merge' (default; tags UNION, attrs deep-merge), 'replace' (overwrite + null unspecified user-facing fields: first_name,last_name,email,company,title,phone,industry,location,country,status,source,source_detail,tags,custom_attrs,external_ids), 'skip' (no-op dupes).
  - `preview` boolean: If true, project the import without persisting. Returns {would_import, would_merge, would_replace, would_skip, confirm_token} — Plan 04 wires this.
  - `confirm_token` string: Token from a prior preview=true call to commit the projected import.

## Response `200`

Success

## Other responses

- `401`: Missing or expired API key
- `403`: Insufficient key scope or permission denied
- `429`: Rate limit exceeded

---

[API](https://skmtc.dev/crispy/apis/crispy-rest-api.md) · [All operations](https://skmtc.dev/crispy/apis/crispy-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/crispy/apis/crispy-rest-api/revisions/6bed0974d9ce?raw)
