Companies
Push your customer base into a tenant in bulk. The endpoint accepts an array
of company records — each with its legal data and an embedded list of
addresses — validates the payload, enqueues a background job, and returns a
job_id you can poll. Records are matched on external_code, so re-sending
the same feed updates the existing companies (idempotent upsert).
Authentication
Both endpoints accept either a tenant API key or a B2B portal session cookie.
| Field | Type | Required | Description |
|---|---|---|---|
API key | headers | Optional | x-auth-method: api-key + x-api-key-id: ak_... + x-api-secret: sk_... — the key must have the customers permission. |
Session cookie | cookie | Optional | vinc_b2b_session — set by the B2B portal login flow. |
Endpoints
/api/b2b/customers/import/apiauth: api-key/api/b2b/customers/import/api/{job_id}auth: api-keyImport companies
/api/b2b/customers/import/apiauth: api-keyQueues an array of companies for upsert. Returns 202 Accepted immediately;
the actual write happens in a background worker.
Constraints
- 1 ≤
customers.length≤ 5000 per request. - Every entry must carry a non-empty
external_code. - Recommended batch size: ≤ 500 companies per call; split larger feeds
into multiple requests sharing a
batch_metadata.batch_id.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
customers | CustomerImportItem[] | Required | Array of company payloads — see the field table below. |
merge_mode | 'replace' | 'partial' | Optional | 'replace' overwrites every provided field (and the whole address list when addresses is sent); 'partial' deep-merges only the fields you send and matches addresses by external_code.(default: 'replace') |
channel | string | Optional | Default sales channel for companies created in this batch. A per-company channel takes priority. Falls back to the tenant default channel. |
batch_metadata | object | Optional | Optional { batch_id, batch_part, batch_total_parts, batch_total_items } — persisted on the ImportJob so multi-part feeds can be correlated. |
CustomerImportItem
| Field | Type | Required | Description |
|---|---|---|---|
external_code | string | Required | Your stable id for the company (e.g. the ERP customer code). Used as the upsert key and referenced by portal users. |
public_code | string | Optional | Human-facing customer code shown in the portal. Auto-generated on create if omitted. |
customer_type | 'business' | 'private' | 'reseller' | Optional | Category of the company. |
channel | string | Optional | Sales channel for this company (e.g. vinc-b2b). Overrides the batch-level channel. |
company_name | string | Optional | Legal / trade name (ragione sociale). |
first_name | string | Optional | Contact first name — useful for private customers. |
last_name | string | Optional | Contact last name. |
email | string | Optional | Primary email for the company. |
phone | string | Optional | Primary phone. |
legal_info | object | Optional | { vat_number, fiscal_code, pec_email, sdi_code } — all optional, each validated when present (see below). |
tags | string[] | Optional | Free-form labels. In partial mode tags are added (additive upsert); in replace mode they overwrite. Used for filtering, segmentation, and price-list rules. |
addresses | CustomerImportAddress[] | Optional | Embedded delivery / billing addresses — see the address field table. |
CustomerImportAddress
| Field | Type | Required | Description |
|---|---|---|---|
street_address | string | Required | Street + number. Required for the address to be persisted. |
city | string | Required | City. Required. |
province | string | Required | Province / state code. Required. |
external_code | string | Optional | Your id for the address. Strongly recommended: it is the match key in partial mode and lets a portal user be scoped to specific addresses. |
address_type | 'delivery' | 'billing' | 'both' | Optional | Use of the address.(default: 'delivery') |
label | string | Optional | Display label, e.g. “Warehouse – Napoli (NA)”. |
recipient_name | string | Optional | Name the goods / invoice are addressed to. |
street_address_2 | string | Optional | Second address line. |
postal_code | string | Optional | ZIP / CAP. Stored as an empty string if omitted. |
country | string | Optional | ISO 3166-1 alpha-2 country code.(default: 'IT') |
phone | string | Optional | Address-specific phone. |
delivery_notes | string | Optional | Free-text notes for the courier. |
is_default | boolean | Optional | Marks this as the company's default address.(default: false) |
tag_overrides | string[] | Optional | Address-level tags that override the company tags (only applied when external_code is also set). |
Response — 202 Accepted
| Field | Type | Required | Description |
|---|---|---|---|
success | boolean | Optional | true when the batch was accepted and queued. |
job_id | string | Optional | ImportJob id, prefix 'cust_import_'. Poll it for progress and per-row errors. |
total | integer | Optional | Number of companies queued. |
merge_mode | 'replace' | 'partial' | Optional | Echo of the mode applied. |
message | string | Optional | Human-readable confirmation. |
400 is returned for an empty / oversized customers array, an invalid
merge_mode, or a company missing external_code (the error names the offending
index). Per-row failures during processing (e.g. bad legal_info) do not
fail the request — they surface on the job under import_errors.
Example
curl -X POST https://cs.vendereincloud.it/api/b2b/customers/import/api \
-H "Content-Type: application/json" \
-H "x-auth-method: api-key" \
-H "x-api-key-id: ak_acme_live_1234" \
-H "x-api-secret: sk_live_abcdef..." \
-d '{
"merge_mode": "partial",
"channel": "vinc-b2b",
"customers": [
{
"external_code": "000123",
"public_code": "000123",
"customer_type": "reseller",
"company_name": "ACME S.R.L.",
"email": "info@acme.it",
"phone": "+39 081 1234567",
"legal_info": {
"vat_number": "IT01234567890",
"fiscal_code": "RSSMRA80A01F839X",
"sdi_code": "ABCDEFG"
},
"tags": ["categoria-clienti:R1"],
"addresses": [
{
"external_code": "000000",
"address_type": "both",
"label": "HQ – Napoli (NA)",
"recipient_name": "ACME S.R.L.",
"street_address": "Via Roma 10",
"city": "Napoli",
"province": "NA",
"postal_code": "80100",
"country": "IT",
"is_default": true
}
]
},
{
"external_code": "000124",
"customer_type": "business",
"company_name": "Beta Impianti S.n.c.",
"addresses": []
}
]
}'{
"success": true,
"job_id": "cust_import_1714142335123_a4f9k2",
"total": 2,
"merge_mode": "partial",
"message": "Queued 2 customers for import"
}partial vs replace
// Existing company:
// { external_code: "000123", company_name: "ACME S.R.L.", email: "old@acme.it", addresses: [{ external_code: "000000", city: "Napoli", ... }] }
// Incoming:
{ "external_code": "000123", "email": "info@acme.it",
"addresses": [{ "external_code": "000000", "label": "HQ - Napoli (NA)" }] }
// Result: email updated, address 000000 keeps its fields with the new label, every other field untouched.// Same existing company, same incoming payload:
// email is updated; the addresses array is replaced by [{ external_code: "000000", label: "HQ - Napoli (NA)" }]
// — and that address is dropped because it is missing street_address / city / province.Check job status
/api/b2b/customers/import/api/{job_id}auth: api-keyReturns the ImportJob created by the POST above. Poll until status is
completed or failed.
| Field | Type | Required | Description |
|---|---|---|---|
job.job_id | string | Optional | Echoes the requested id. |
job.status | 'pending' | 'processing' | 'completed' | 'failed' | Optional | Lifecycle state. |
job.total_rows | integer | Optional | Companies in the batch. |
job.processed_rows | integer | Optional | Companies processed so far. |
job.successful_rows | integer | Optional | Companies upserted. |
job.failed_rows | integer | Optional | Companies rejected — see import_errors. |
job.import_errors | { row, entity_code, error, raw_data }[] | Optional | Per-row failures with the offending payload. |
job.batch_id / batch_part / batch_total_parts | string / integer | Optional | Echoed from batch_metadata when supplied. |
job.started_at / completed_at / duration_seconds | string / number | Optional | Processing timing. |
curl https://cs.vendereincloud.it/api/b2b/customers/import/api/cust_import_1714142335123_a4f9k2 \
-H "x-auth-method: api-key" \
-H "x-api-key-id: ak_acme_live_1234" \
-H "x-api-secret: sk_live_abcdef..."{
"success": true,
"job": {
"job_id": "cust_import_1714142335123_a4f9k2",
"status": "completed",
"total_rows": 2,
"processed_rows": 2,
"successful_rows": 2,
"failed_rows": 0,
"import_errors": [],
"started_at": "2026-04-24T12:58:55.500Z",
"completed_at": "2026-04-24T12:58:56.020Z",
"duration_seconds": 0.52,
"batch_id": null,
"created_at": "2026-04-24T12:58:55.123Z"
}
}A 404 means no customer_import job exists with that id for the
authenticated tenant.