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.

Accepted credentials
FieldTypeRequiredDescription
API keyheadersOptionalx-auth-method: api-key + x-api-key-id: ak_... + x-api-secret: sk_... — the key must have the customers permission.
Session cookiecookieOptionalvinc_b2b_session — set by the B2B portal login flow.

Endpoints

POST/api/b2b/customers/import/apiauth: api-key
GET/api/b2b/customers/import/api/{job_id}auth: api-key

Import companies

POST/api/b2b/customers/import/apiauth: api-key

Queues an array of companies for upsert. Returns 202 Accepted immediately; the actual write happens in a background worker.

Constraints

  • 1 ≤ customers.length5000 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

FieldTypeRequiredDescription
customersCustomerImportItem[]RequiredArray 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')
channelstringOptionalDefault sales channel for companies created in this batch. A per-company channel takes priority. Falls back to the tenant default channel.
batch_metadataobjectOptionalOptional { batch_id, batch_part, batch_total_parts, batch_total_items } — persisted on the ImportJob so multi-part feeds can be correlated.

CustomerImportItem

Company fields
FieldTypeRequiredDescription
external_codestringRequiredYour stable id for the company (e.g. the ERP customer code). Used as the upsert key and referenced by portal users.
public_codestringOptionalHuman-facing customer code shown in the portal. Auto-generated on create if omitted.
customer_type'business' | 'private' | 'reseller'OptionalCategory of the company.
channelstringOptionalSales channel for this company (e.g. vinc-b2b). Overrides the batch-level channel.
company_namestringOptionalLegal / trade name (ragione sociale).
first_namestringOptionalContact first name — useful for private customers.
last_namestringOptionalContact last name.
emailstringOptionalPrimary email for the company.
phonestringOptionalPrimary phone.
legal_infoobjectOptional{ vat_number, fiscal_code, pec_email, sdi_code } — all optional, each validated when present (see below).
tagsstring[]OptionalFree-form labels. In partial mode tags are added (additive upsert); in replace mode they overwrite. Used for filtering, segmentation, and price-list rules.
addressesCustomerImportAddress[]OptionalEmbedded delivery / billing addresses — see the address field table.

CustomerImportAddress

Address fields
FieldTypeRequiredDescription
street_addressstringRequiredStreet + number. Required for the address to be persisted.
citystringRequiredCity. Required.
provincestringRequiredProvince / state code. Required.
external_codestringOptionalYour 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'OptionalUse of the address.(default: 'delivery')
labelstringOptionalDisplay label, e.g. “Warehouse – Napoli (NA)”.
recipient_namestringOptionalName the goods / invoice are addressed to.
street_address_2stringOptionalSecond address line.
postal_codestringOptionalZIP / CAP. Stored as an empty string if omitted.
countrystringOptionalISO 3166-1 alpha-2 country code.(default: 'IT')
phonestringOptionalAddress-specific phone.
delivery_notesstringOptionalFree-text notes for the courier.
is_defaultbooleanOptionalMarks this as the company's default address.(default: false)
tag_overridesstring[]OptionalAddress-level tags that override the company tags (only applied when external_code is also set).

Response — 202 Accepted

FieldTypeRequiredDescription
successbooleanOptionaltrue when the batch was accepted and queued.
job_idstringOptionalImportJob id, prefix 'cust_import_'. Poll it for progress and per-row errors.
totalintegerOptionalNumber of companies queued.
merge_mode'replace' | 'partial'OptionalEcho of the mode applied.
messagestringOptionalHuman-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 — upsert two companies (partial mode)
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": []
    }
  ]
}'
202 Accepted
{
"success": true,
"job_id": "cust_import_1714142335123_a4f9k2",
"total": 2,
"merge_mode": "partial",
"message": "Queued 2 customers for import"
}

partial vs replace

merge_mode: 'partial' — preserves omitted fields & merges addresses by external_code
// 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.
merge_mode: 'replace' (default) — provided fields win, addresses replaced wholesale
// 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

GET/api/b2b/customers/import/api/{job_id}auth: api-key

Returns the ImportJob created by the POST above. Poll until status is completed or failed.

FieldTypeRequiredDescription
job.job_idstringOptionalEchoes the requested id.
job.status'pending' | 'processing' | 'completed' | 'failed'OptionalLifecycle state.
job.total_rowsintegerOptionalCompanies in the batch.
job.processed_rowsintegerOptionalCompanies processed so far.
job.successful_rowsintegerOptionalCompanies upserted.
job.failed_rowsintegerOptionalCompanies rejected — see import_errors.
job.import_errors{ row, entity_code, error, raw_data }[]OptionalPer-row failures with the offending payload.
job.batch_id / batch_part / batch_total_partsstring / integerOptionalEchoed from batch_metadata when supplied.
job.started_at / completed_at / duration_secondsstring / numberOptionalProcessing timing.
curl — poll the job
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..."
200 OK
{
"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.