Contact and entity batch imports
Submit contact, deal, and appointment imports and poll their status.
Use the entity-specific batch import endpoints when you need to submit multiple contacts, deals, or appointments for asynchronous processing.
| Entity | Create a batch | Poll one batch | List batches |
|---|---|---|---|
| Contacts | POST /v1/contacts/imports/batch | GET /v1/contacts/imports/{id} | GET /v1/contacts/imports |
| Deals | POST /v1/deals/imports/batch | GET /v1/deals/imports/{id} | GET /v1/deals/imports |
| Appointments | POST /v1/appointments/imports/batch | GET /v1/appointments/imports/{id} | GET /v1/appointments/imports |
Every batch request creates a new import job. The API does not deduplicate
requests by payload or external_id, so retrying a successful request creates
another job.
Submit an inline payload
Batch requests use JSON and include a source discriminated by source.type.
An inline source contains a non-empty records array; a file source contains a
canonical FileInput under file. Each inline record is an entity import
record. The request body limit is 5 MB. Multipart uploads are not supported by
these endpoints.
curl https://api.getmateo.com/v1/contacts/imports/batch \
--request POST \
--header "Authorization: Bearer $MATEO_API_TOKEN" \
--header "hellomateo-version: 2026-07-01" \
--header "Content-Type: application/json" \
--data '{
"external_id": "erp-contact-import-2026-07-14",
"contact_list_external_id": "newsletter",
"source": {
"type": "inline",
"records": [
{
"external_id": "customer-1001",
"full_name": "Ada Lovelace",
"identities": [
{
"channel_type": "email",
"type": "email_address",
"value": "ada@example.com"
}
]
},
{
"external_id": "customer-1002",
"full_name": "Grace Hopper",
"identities": [
{
"channel_type": "sms",
"type": "phone_number",
"value": {
"candidates": ["079 123 45 67", "+41791234567"],
"country_codes": ["CH"]
}
}
]
}
]
}
}'For contact batches, use at most one of contact_list_id and
contact_list_external_id. Contact handles belong in identities. SMS and
WhatsApp identity values accept either one string or an object containing
ordered candidates and optional country_codes. Country codes must be valid
ISO 3166-1 alpha-2 codes and are tried in order. An invalid phone produces a
nonfatal import hint while the remaining contact fields are still imported.
Submit an uploaded file
For larger imports, request a signed upload URL, upload a CSV or JSON file with a
raw PUT, and submit the returned File ID as source.file for any entity batch
endpoint. Preserve Content-Type: text/csv or Content-Type: application/json
on the upload because the import worker uses the provider metadata to select the
parser.
UPLOAD_RESPONSE=$(curl https://api.getmateo.com/v1/files/upload_url \
--request POST \
--header "Authorization: Bearer $MATEO_API_TOKEN" \
--header "hellomateo-version: 2026-07-01" \
--header "Content-Type: application/json" \
--data '{"purpose":"import_payload","filename":"deals.csv"}')
UPLOAD_URL=$(echo "$UPLOAD_RESPONSE" | jq -r '.url')
FILE_ID=$(echo "$UPLOAD_RESPONSE" | jq -r '.file_id')
curl "$UPLOAD_URL" \
--request PUT \
--header "Content-Type: text/csv" \
--data-binary @deals.csv
curl https://api.getmateo.com/v1/deals/imports/batch \
--request POST \
--header "Authorization: Bearer $MATEO_API_TOKEN" \
--header "hellomateo-version: 2026-07-01" \
--header "Content-Type: application/json" \
--data "{\"source\":{\"type\":\"file\",\"file\":{\"file_id\":\"$FILE_ID\"}},\"allow_reassignment\":false}"The same { "source": { "type": "file", "file": { "file_id": "..." } } }
input works for contact, deal, and appointment batch imports. The discriminator
ensures that file sources cannot include inline records. The batch endpoint
copies the uploaded source into the import's storage path. Both the uploaded source and the import-owned copy keep the immutable
import_payload purpose.
Deal and appointment batches use the same envelope and accept their respective record fields:
{
"external_id": "erp-deal-import-2026-07-14",
"allow_reassignment": false,
"source": {
"type": "inline",
"records": [
{
"external_id": "deal-1001",
"name": "Annual renewal",
"contact_external_id": "customer-1001"
}
]
}
}Send that body to POST /v1/deals/imports/batch. For appointments, send the
appointment records to POST /v1/appointments/imports/batch. Both endpoints
use allow_reassignment; the route determines which entity is imported.
Batch routes return 415 Unsupported Media Type when Content-Type is missing
or is not a JSON media type. They return 413 Payload Too Large when the JSON
body exceeds 5 MB. Malformed JSON and payload validation failures return 400 Bad Request.
A successful submission returns 201 Created and the new import job. This
abbreviated example highlights the fields used for polling:
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"external_id": "erp-contact-import-2026-07-14",
"status": "pending",
"started_at": null,
"completed_at": null,
"total_count": null,
"success_count": null,
"failed_count": null,
"failure": null
}Poll the import
Build the detail URL from the returned id and the same entity collection used
to create the job:
curl https://api.getmateo.com/v1/contacts/imports/3c90c3cc-0d44-4b50-8888-8dd25736052a \
--header "Authorization: Bearer $MATEO_API_TOKEN" \
--header "hellomateo-version: 2026-07-01"Batch 1 exposes coarse status semantics:
pendingmeans at least one result count is not available yet.completedmeanstotal_count,success_count, andfailed_countare all available.
A completed job may still contain failed records. Check success_count and
failed_count rather than treating completed as “every record succeeded.”
The status schema also reserves processing, failed, and unknown for the
persisted lifecycle rollout. The following response is abbreviated:
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"status": "completed",
"total_count": 2,
"success_count": 1,
"failed_count": 1,
"failure": null
}Poll with backoff until the job reaches a terminal status. Do not repeatedly submit the batch to check whether it finished.
List and filter imports
Entity import history is separate for contacts, deals, and appointments. Lists are ordered by creation time and use cursor pagination.
curl --globoff 'https://api.getmateo.com/v1/contacts/imports?status[in]=pending,completed&limit=20' \
--header "Authorization: Bearer $MATEO_API_TOKEN" \
--header "hellomateo-version: 2026-07-01"Follow next_page_url when it is present. You can also filter by one
external_id or use external_id[in] for multiple values.