Import from File
POST /io/contacts/contacts/file-import — upload a CSV or Excel file and upsert contacts, with a post-save format report.
Uploads a .csv, .xls, or .xlsx file and upserts every parsed row into the contacts table. Use this when a human hands you a spreadsheet; use POST /contacts/import when you already have structured JSON.
POST https://api.misar.io/io/contacts/contacts/file-importSave first, validate second
Rows are written to the database before format validation runs. Contacts with soft format issues (an over-long company name, an unexpected status) are still saved, and the issues come back in validation_errors so you can correct them afterwards. Only a file with zero parseable rows is rejected outright.
Authentication
Either mode works:
Authorization: Bearer <sso_access_token>x-audience-service-key: <AUDIENCE_SERVICE_KEY>With a service key you must also pass ?user_id=<uuid>.
/io/contacts/contacts/file-importThe request body is multipart/form-data, not JSON.
Query parameters
user_idstringqueryRequired with a service key; ignored with an SSO bearer. Must be a valid UUID.
Form fields
fileFilebodyrequiredThe upload. Extension must be .csv, .xls, or .xlsx. Maximum size 10 MB.
Response fields
successbooleantrue when the upsert completed.
total_in_filenumberRows the parser saw in the file, including ones it could not turn into a contact.
savednumberContacts written to the database.
validation_errorsArray<{row, field, value, message}>Per-row format issues found after saving. row is the 1-based row index in the source file.
warningsstringA human-readable summary, present only when validation_errors is non-empty.
curl -X POST "https://api.misar.io/io/contacts/contacts/file-import" \
-H "Authorization: Bearer $SSO_ACCESS_TOKEN" \
-F "file=@contacts.csv"curl -X POST "https://api.misar.io/io/contacts/contacts/file-import?user_id=3f1a2b4c-5d6e-4f70-8a91-b2c3d4e5f607" \
-H "x-audience-service-key: $AUDIENCE_SERVICE_KEY" \
-F "file=@contacts.xlsx"{
"success": true,
"total_in_file": 412,
"saved": 412,
"validation_errors": []
}{
"success": true,
"total_in_file": 412,
"saved": 412,
"validation_errors": [
{
"row": 87,
"field": "status",
"value": "unsub",
"message": "Invalid status \"unsub\" — must be one of: subscribed, unsubscribed, bounced, complained"
}
],
"warnings": "1 contact(s) have format issues — they were saved but you may want to correct them"
}{
"error": "No valid contacts found",
"parse_errors": ["Row 1: missing email"],
"total": 3
}Recognised columns
The parser maps spreadsheet headers onto the standard contact fields. Anything it does not recognise is preserved on custom_fields.
| Field | Limit |
|---|---|
email | Required. Must match a basic address shape. |
first_name | 100 chars |
last_name | 100 chars |
phone | 50 chars |
company | 150 chars |
job_title | 100 chars |
status | One of subscribed, unsubscribed, bounced, complained |
source | 100 chars. Defaults to import. |
social_profiles | Per-network URLs, 500 chars each |
custom_fields | 8 KB serialised |
Imported contacts are upserted on (user_id, email), so re-importing a file updates existing rows rather than duplicating them. lead_score is reset to 0 on import.
Status codes
| Code | Meaning |
|---|---|
200 | Import ran. Check validation_errors. |
400 | Malformed multipart body, no file field, file over 10 MB, unsupported extension, or no parseable contacts. |
401 | Neither a valid SSO bearer nor a valid x-audience-service-key was supplied. |
429 | Contacts rate limit exceeded (200 ops/user/60 s). Includes a Retry-After header. |
500 | The contacts could not be written. |
Limits
| Limit | Value |
|---|---|
| Maximum file size | 10 MB |
| Accepted extensions | .csv, .xls, .xlsx |
| Request timeout | 60 s |
| Rate limit | 200 ops/user/60 s |