POST /v1/contacts/import
Bulk-imports contacts in a single API call. Existing contacts matched by email are silently skipped (ON CONFLICT DO NOTHING). Requires editor role or higher.

Parameters

Authorization
string
required
Bearer <api_key>
contacts
array
required
Array of contact objects. Maximum 10,000 per call. Each object must include at least one of email or phone — rows missing both are silently dropped.
FieldTypeDescription
emailstringEmail address
phonestringPhone number in international format
first_namestringFirst name
last_namestringLast name

Request

curl -sS -X POST "https://api.arowana.app/v1/contacts/import" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contacts": [
      {
        "email": "jana@example.com",
        "phone": "+4917612345678",
        "first_name": "Jana",
        "last_name": "Müller"
      },
      {
        "email": "max@example.com",
        "first_name": "Max",
        "last_name": "Schmidt"
      }
    ]
  }'

Response

{ "imported": 1842 }
imported reflects only newly written contacts. Duplicates matched by email are not counted.

Chunking large lists

For files larger than 10,000 rows, split into chunks and call the endpoint sequentially:
async function importAll(contacts) {
  const CHUNK = 10_000;
  for (let i = 0; i < contacts.length; i += CHUNK) {
    const chunk = contacts.slice(i, i + CHUNK);
    const res = await fetch("https://api.arowana.app/v1/contacts/import", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ contacts: chunk }),
    });
    const { imported } = await res.json();
    console.log(`Chunk ${i / CHUNK + 1}: ${imported} imported`);
  }
}
After a large import, use campaign segments or tags to target only newly added contacts in your first send.