Create a customer (V2 API)


V3 API Available (Beta)

A new V3 Customer API is available in beta. Key differences:

  • Simpler workflow: Primary address included in customer creation (no separate request needed)
  • More address fields: GLN, department, delivery terms (Incoterms)
  • Better filtering: name field now supports contains, startsWith, endsWith

Important:

  • V3 requires feature flag activation (api-v3-customers) - contact [email protected]
  • V3 is still in beta - minor changes may occur before stable release
  • Existing V2 integrations will continue to work

See Create Customer (V3) for the new API.


Contents


Overview

This guide shows you how to create a customer via the Xentral API. You'll learn the difference between person and company customers, how to add addresses, and how to configure financial settings.

Use Cases:

  • Import customers from an online shop (Shopify, WooCommerce, etc.) into Xentral
  • Synchronize customer data from a CRM system
  • Create B2B customers from a partner portal
  • Migrate customers from another ERP system

New to the Xentral API? Read first:


ERP Context

Important for developers without ERP experience: A customer in Xentral is essentially an address with the "customer" role assigned to it. An address can have different roles (customer, supplier, employee), but each role is managed via separate API endpoints.

What is linked to a customer?

In Xentral, a customer record is connected to:

  • Sales Orders - Orders placed by this customer
  • Invoices - Billing documents and open items
  • Sales Prices - Customer-specific pricing (can also be linked to customer groups)
  • Customer Groups - For pricing and categorization
  • Offers - Quotations for this customer
  • Delivery Notes - Shipping documents
  • Credit Notes - Refunds and corrections
  • Return Orders - Returns processing
  • Proforma Invoices - Pre-invoices
  • Productions - Manufacturing orders

What is configured ON a customer?

These are settings stored on the customer record (not separate linked entities):

  • Payment method and payment terms
  • Tax settings (VAT ID, taxation type)
  • Document delivery preferences (email vs. postal)
  • Shipping method defaults
  • Bank account information (for SEPA)

Why is this important?

Understanding this helps you:

  • Know which data must be present (customer type, name)
  • Know which data should be added (payment method, tax info, shipping, custom fields)
  • Understand that addresses defined on the customer serve as defaults - you can override them on individual documents

At a Glance

EndpointPOST /api/v2/customers
MethodPOST
AuthBearer Token (Documentation)
Required Scopecustomer:create

Minimal example (Person):

curl -X POST "https://{instance}.xentral.biz/api/v2/customers" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "customerType": "person",
    "firstname": "Max",
    "lastname": "Mustermann"
  }'

Minimal example (Company):

curl -X POST "https://{instance}.xentral.biz/api/v2/customers" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "customerType": "company",
    "name": "ACME GmbH"
  }'

Response: 201 Created - The new customer ID is in the Location header (e.g., /api/v2/customers/11).

Full API Reference: Create Customer


Customer Number Assignment

Automatic Assignment (Default)

If you don't provide a number field, Xentral automatically assigns the next available customer number from the configured number range:

ScenarioNumber Range Used
No project specifiedSystem-wide number range
Project without custom rangesSystem-wide number range
Project with custom ranges enabledProject's own number range

Tip: Check your number range configuration in Xentral: Settings > General Settings > Number ranges

Providing Custom Numbers (Data Migration)

For data migration or legacy imports, you can provide your own customer number:

curl -X POST "https://{instance}.xentral.biz/api/v2/customers" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "customerType": "company",
    "name": "Migrated Customer GmbH",
    "number": "12345"
  }'
⚠️

Warning: Custom numbers must be unique. If the number already exists, you'll get a 409 Conflict error:

{"title":"Customer already exists.","messages":["Customer number \"12345\" already exists in project 0"]}

Best Practices for Number Ranges

ScenarioRecommendation
New integrationLet Xentral assign numbers automatically
Data migrationProvide existing numbers, ensure no gaps/duplicates
DATEV usersUse numeric-only numbers, same digit count (5-6 digits)
High volume B2CConsider project-specific number ranges

DATEV Compatibility

If you export to DATEV (German accounting software), follow these rules:

  • No letters in customer numbers (only digits)
  • Same digit count for all customers and suppliers
  • SKR04 example: Customers 10000-69999, Suppliers 70000-99999
  • Never use deviatingCustomerNumber for shop references (reserved for DATEV!)

Reference: Nummernkreise verwenden


Prerequisites

  • Xentral account with API access
  • Personal Access Token (PAT) with scope customer:create (Guide)

Before You Start

Decision 1: Person or Company?

The customerType determines which fields are required:

Customer TypeRequired FieldsUse Case
personfirstname, lastnameB2C customers, individuals
companynameB2B customers, businesses

Note: You can change the customer type later via PATCH request or in the UI, but it's best to choose correctly from the start.

Decision 2: Add addresses now or later?

Addresses cannot be included when creating the customer. You must add them in separate requests after the customer is created.

Option A: Minimal approach

  • Create customer with basic data only
  • Define addresses directly on documents (sales orders) when needed
  • Good for: Quick imports, data migration, one-time customers

Option B: Complete approach

  • Create customer with payment method, tax settings, shipping defaults
  • Add masterdata address (becomes default for billing AND delivery)
  • Optionally add separate billing/delivery addresses if they differ
  • Good for: B2B customers, regular buyers

Note: If no billing or delivery address is defined, the masterdata address is used as both billing and delivery address. Separate addresses are only needed when they differ from the masterdata address.

Decision 3: Store external reference?

If you're syncing customers from an external system (shop, CRM), you need a way to link them:

Options:

  • Use a custom field (customFields.free1 to free20) for your shop's customer ID
  • Store the mapping in your middleware/database
⚠️

Warning: Do NOT use deviatingCustomerNumber for shop references! This field is exclusively for DATEV export (German accounting software) and using it for other purposes can cause serious accounting issues.

Best Practice: Always store a mapping between your external customer ID and the Xentral customer ID. Use custom fields or your own database for this mapping.


Workflow

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  1. Check   │ ──▶ │  2. Create  │ ──▶ │  3. Add     │ ──▶ │  4. Verify  │
│  Existing?  │     │  Customer   │     │  Addresses  │     │  Customer   │
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘
       │                   │
       ▼                   ▼
┌─────────────┐     ┌─────────────┐
│   Found?    │     │  Store ID   │
│   Use ID!   │     │   Mapping   │
└─────────────┘     └─────────────┘

Step-by-Step Guide

Step 1: Check if Customer Exists

Before creating a new customer, check if they already exist to avoid duplicates.

Search by email (V2 API):

Required Scope: customer:read

curl -s "https://{instance}.xentral.biz/api/v2/customers?filter[0][key]=email&filter[0][op]=equals&filter[0][value][email protected]" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"

API Reference: List Customers

Response:

{
  "data": [],
  "extra": {"totalCount": 0, "page": {"number": 1, "size": 10}}
}

If totalCount > 0, the customer already exists. Use the existing ID instead of creating a new one.

Search alternatives:

  • By name: filter[0][key]=name&filter[0][op]=equals&filter[0][value]=Max%20Mustermann
  • By customer number: filter[0][key]=number&filter[0][op]=equals&filter[0][value]=10001
  • By address: filter[0][key]=city&filter[0][op]=equals&filter[0][value]=Berlin

Combined filters (AND logic):

# Search by name AND email (more precise matching)
curl -s "https://{instance}.xentral.biz/api/v2/customers?filter[0][key]=name&filter[0][op]=equals&filter[0][value]=Max%20Mustermann&filter[1][key]=email&filter[1][op]=equals&filter[1][value][email protected]" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"

Note: In V2, the name field only supports the equals operator. For contains and startsWith, use the V3 API.


Step 2: Create Customer

Create a person (B2C):

Required Scope: customer:create

curl -X POST "https://{instance}.xentral.biz/api/v2/customers" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "customerType": "person",
    "firstname": "Max",
    "lastname": "Mustermann",
    "contactDetails": {
      "email": "[email protected]",
      "phone": "+49 30 123456"
    }
  }'

API Reference: Create Customer

Response: 201 Created

The customer ID is in the Location header:

Location: https://{instance}.xentral.biz/api/v2/customers/11

Create a company (B2B):

curl -X POST "https://{instance}.xentral.biz/api/v2/customers" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "customerType": "company",
    "name": "ACME GmbH",
    "contactDetails": {
      "email": "[email protected]",
      "phone": "+49 89 987654"
    }
  }'

Note: If you don't specify a number, Xentral automatically assigns the next available customer number from the configured number range.

Important Optional Parameters

While only customerType and name fields are required, these parameters are important for business processes:

ParameterPurposeWhen to use
projectAssign to a project/mandantMulti-project setups
groupsCustomer groups (for pricing)B2B with special prices
customFieldsFree fields 1-20Store external references
financials.taxTaxation, VAT IDB2B, EU customers
financials.paymentMethodDefault payment methodAll customers
financials.paymentTermsPayment deadline, discountPayment method "Invoice"
documentDeliveryAuto-create documentsAutomated workflows
shippingDefault shipping methodRecurring customers

API Reference: Create Customer for all available fields


Step 3: Add Masterdata Address

The masterdata address is the customer's primary address. If no separate billing or delivery address is defined, this address is used for both.

Required Scope: customerAddr:create

curl -X POST "https://{instance}.xentral.biz/api/v2/customers/{customerId}/addresses" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "masterdata",
    "street": "Hauptstraße 1",
    "zip": "10115",
    "city": "Berlin",
    "country": "DE"
  }'

API Reference: Create Customer Address

Response: 201 Created

Location: https://{instance}.xentral.biz/api/v2/customers/11/addresses/masterdata

Note: Each customer has exactly one masterdata address. It is accessed via the URL path /addresses/masterdata (not a numeric ID).


Step 4: Add Delivery Address (optional)

Add a separate delivery address only if it differs from the masterdata address.

Note: Only ONE delivery address can have defaultDeliveryAddress: true. Setting this flag on a new address automatically removes it from the previous default.

Required Scope: customerAddr:create

curl -X POST "https://{instance}.xentral.biz/api/v2/customers/{customerId}/addresses" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "deliveryaddress",
    "name": "Max Mustermann",
    "street": "Lieferstraße 42",
    "zip": "10115",
    "city": "Berlin",
    "country": "DE",
    "deliveryDetails": {
      "defaultDeliveryAddress": true,
      "taxType": "domestic"
    }
  }'

API Reference: Create Customer Address

Response: 201 Created

Location: https://{instance}.xentral.biz/api/v2/customers/11/addresses/2
⚠️

Important: When using deliveryDetails, the taxType field is required. Allowed values: domestic, eu-delivery, export, free.


Step 5: Add Billing Address (optional)

Add a separate billing address only if it differs from the masterdata address.

Required Scope: customerAddr:create

curl -X POST "https://{instance}.xentral.biz/api/v2/customers/{customerId}/addresses" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "billingaddress",
    "name": "ACME GmbH - Buchhaltung",
    "street": "Finanzstraße 99",
    "zip": "80331",
    "city": "München",
    "country": "DE"
  }'

Note: Each customer has exactly one billing address. If not defined, the masterdata address is used for billing.


Step 6: Verify Customer (optional)

Required Scope: customer:read

curl -s "https://{instance}.xentral.biz/api/v2/customers/{customerId}" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"

API Reference: View Customer

Response:

{
  "data": {
    "id": "11",
    "number": "10005",
    "customerType": "person",
    "name": "Max Mustermann",
    "firstname": "Max",
    "lastname": "Mustermann",
    "contactDetails": {
      "email": "[email protected]",
      "phone": "+49 30 123456"
    },
    "createdAt": "2026-02-02T14:06:09Z",
    "updatedAt": "2026-02-02T14:06:09Z"
  }
}

List addresses:

Required Scope: customerAddr:read

curl -s "https://{instance}.xentral.biz/api/v2/customers/{customerId}/addresses" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"

API Reference: List Customer Addresses

Response:

{
  "data": [
    {
      "id": "masterdata",
      "type": "masterdata",
      "street": "Hauptstraße 1",
      "zip": "10115",
      "city": "Berlin",
      "country": "DE"
    },
    {
      "id": "billingaddress",
      "type": "billingaddress",
      "name": "ACME GmbH - Buchhaltung",
      "street": "Finanzstraße 99",
      "zip": "80331",
      "city": "München",
      "country": "DE"
    },
    {
      "id": "2",
      "type": "deliveryaddress",
      "name": "Max Mustermann",
      "street": "Lieferstraße 42",
      "zip": "10115",
      "city": "Berlin",
      "country": "DE",
      "deliveryDetails": {
        "defaultDeliveryAddress": true,
        "taxType": "domestic"
      }
    }
  ],
  "extra": {
    "totalCount": 3,
    "page": {"number": 1, "size": 10}
  }
}

Update Customer (PATCH)

To update customer data after creation:

Required Scope: customer:update

curl -X PATCH "https://{instance}.xentral.biz/api/v2/customers/{customerId}" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"contactDetails": {"email": "[email protected]"}}'

API Reference: Update Customer

Response: 204 No Content - The update was successful. No body is returned.


Address Handling

Xentral uses three address types:

TypeDescriptionCountID Format
masterdataPrimary/main address1 per customer"masterdata" (URL path)
billingaddressInvoice address (optional)0-1 per customer"billingaddress" (URL path)
deliveryaddressShipping address (optional)0-n per customerNumeric (e.g., "2")

Important: If no billing or delivery address is defined, the masterdata address is used for both. You only need separate addresses when they differ from the masterdata address.

API Reference: Create Customer Address for required fields per type

UPSERT Behavior for masterdata and billingaddress

⚠️

Important: POST requests to masterdata or billingaddress behave as UPSERT operations. If an address of that type already exists, it will be overwritten, not rejected.

This means:

  • Creating a second masterdata address replaces the existing one
  • Creating a second billingaddress address replaces the existing one
  • No error is returned - you receive 201 Created
  • Only deliveryaddress allows creating multiple addresses (each gets a numeric ID)

The Default Delivery Address Behavior

Understanding how delivery addresses work:

Scenario 1: No delivery address defined

  • The masterdata address is used as delivery address
  • This works fine for most cases

Scenario 2: Multiple delivery addresses, none set as default

  • The sales order uses the masterdata address (not any of the delivery addresses)
  • The separate delivery addresses are available but not auto-selected

Scenario 3: One delivery address with defaultDeliveryAddress: true

  • This address is automatically used for new sales orders
  • Only ONE address can have this flag - setting it on a new address removes it from the previous one

Best Practice: If you add separate delivery addresses, mark one as default:

{
  "deliveryDetails": {
    "defaultDeliveryAddress": true,
    "taxType": "domestic"
  }
}

Financial Settings

When creating a customer, you can optionally set financial parameters. These settings are used as defaults when creating documents (orders, invoices) for this customer.

curl -X POST "https://{instance}.xentral.biz/api/v2/customers" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "customerType": "company",
    "name": "ACME GmbH",
    "financials": {
      "tax": {
        "taxation": "domestic",
        "vatId": "DE123456789"
      },
      "paymentTerms": {
        "paymentTargetDays": 14,
        "paymentTargetDiscount": 2.0,
        "paymentTargetDiscountDays": 7
      },
      "paymentMethod": {"id": "3"},
      "creditLimit": 5000
    }
  }'

Payment Method

To set a default payment method, first retrieve available methods:

curl -s "https://{instance}.xentral.biz/api/v2/paymentMethods" \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"

API Reference: List Payment Methods

Then use the ID in financials.paymentMethod.id.

Tax Settings

FieldValuesDescription
taxationdomestic, eu-delivery, export, freeTax treatment
vatIde.g., "DE123456789"VAT identification number
taxNumbere.g., "12/345/67890"Tax number

Payment Terms

Note: Payment terms are only relevant when using payment method "Invoice" (Rechnung).

FieldTypeDescription
paymentTargetDaysintegerDays until payment is due
paymentTargetDiscountfloatDiscount percentage for early payment
paymentTargetDiscountDaysintegerDays to qualify for discount

Preventing Duplicates

See Step 1: Check if Customer Exists for how to search before creating.

Best Practices

StrategyRecommendation
Check firstAlways search by email AND/OR name before creating
Store mappingKeep external ID → Xentral ID in your own database
Use custom fieldsStore shop customer ID in customFields.free1 (NOT in deviatingCustomerNumber!)
⚠️

Reminder: Do NOT use deviatingCustomerNumber for shop references - this field is reserved for DATEV export.


Error Handling

StatusMeaningCommon Cause
400Bad RequestValidation error (missing required fields, invalid format)
401UnauthorizedInvalid or expired token
403ForbiddenMissing required scope (e.g., customer:create)
404Not FoundCustomer ID doesn't exist (when adding addresses)
406Not AcceptableMissing Accept: application/json header
409ConflictCustomer number already exists
429Too Many RequestsRate limit exceeded

Note: Validation errors return 400 Bad Request, not 422.

Common Validation Errors

Missing required fields (person):

{
  "messages": {
    "firstname": ["The firstname field is required when customer type is person."],
    "lastname": ["The lastname field is required when customer type is person."]
  }
}

Missing taxType in deliveryDetails:

{
  "messages": {
    "deliveryDetails.taxType": ["delivery details.tax type must be filled in if delivery details is filled in."]
  }
}

Rate Limiting Best Practice

Monitor the X-RateLimit-Remaining header to avoid hitting limits:

RemainingRecommended Action
100-50Full speed (no delay)
50-25Add a small delay (e.g., 50ms) between requests
< 25Add a larger delay (e.g., 200ms) between requests

Related Resources

API Documentation

Related Guides