Customers & B2B

Customers

Customer profiles, addresses, segments, GDPR redaction, and B2B VAT support.

Lifecycle states

guest         created at checkout from email — no password
invited       staff sent an invite, not yet accepted
enabled       full account, can log in
disabled      staff blocked
declined      invite refused
redacted      GDPR right-to-be-forgotten applied

A guest customer can be upgraded to enabled later (when they set a password). Orders made as a guest stay attributed via email.

Create a customer

curl -X POST "$API/customers.json" \
  -H "Authorization: Bearer $NEVIOS_KEY" -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "first_name": "Anna",
    "last_name": "Nováková",
    "phone": "+420...",
    "accepts_marketing": true,
    "marketing_opt_in_level": "confirmed_opt_in",
    "tags": ["vip", "wholesale"]
  }'

Per (account, email) is unique — duplicate emails return 409.

Addresses

1:N per customer. Pickup-point fields (pickup_point_carrier, pickup_point_id, pickup_point_name) are an all-or-nothing group: either the address is a pickup point with all three set, or it isn't (regular street address).

is_default is exclusive within a customer — promoting a new default automatically demotes the previous one. The customer's default_address_id always points at the active default.

B2B VAT

Customers carry vat_number, vat_country_code, vat_valid, vat_validated_at. Validation happens via VIES at checkout time.

The combination (vat_valid=true AND non-domestic shipping country AND commerce.tax.b2b_reverse_charge=true) triggers reverse charge on the order: zero VAT, prices presented net, invoice notes added.

Segments (DSL)

Customer segments are dynamic groups defined by a JSONB rule:

{
  "name": "VIP",
  "definition": {
    "all": [
      { "field": "customer.total_spent_cents", "op": "gt", "value": 100000 },
      { "field": "customer.tags", "op": "contains", "value": "wholesale" }
    ]
  }
}

Operators include eq | neq | gt | gte | lt | lte | in | not_in | contains | starts_with | within_days | before | after. Field allow-list prevents arbitrary SQL. Members are materialized via POST /{handle}/customer_segments/{id}/evaluate.json and stored in customer_segment_members.

GDPR redaction

POST /admin/2026-01/{handle}/customers/{id}/redact.json triggers a full PII anonymization: email becomes REDACTED-{id}@redacted.local, names become REDACTED, addresses are deleted, marketing flags are cleared, state='redacted', archived_at set. Order history preserved with customer_id still attached for audit.

A redacted customer cannot be updated (409) and cannot receive new orders or messages.

Customer order spend tracking

When a payment hits paid, the orders service auto-bumps the customer's total_spent_cents, orders_count, last_order_at, last_order_id, last_order_name. first_order_at is set on the very first conversion.

Endpoints

VerbPath
POST/{handle}/customers.json
GET/{handle}/customers.json (filter: state, tag, email, marketing)
GET/{handle}/customers/{id_or_email}.json
PUT/{handle}/customers/{id}.json (optimistic version)
POST/{handle}/customers/{id}/redact.json
POST/{handle}/customers/{id}/addresses.json
DELETE/{handle}/customers/{id}/addresses/{addr_id}.json
POST/{handle}/customer_segments.json
POST/{handle}/customer_segments/{id}/evaluate.json
GET/{handle}/customer_segments/{id}/members.json

Next: Discounts & vouchers.