This register documents every data field included in the Boncard customer export. It is published before contract conclusion per Art. 26(b) Data Act so customers and destination providers can plan migration in advance. Export endpoints and migration instructions are documented at /data-portability.
All data structures as defined in the internal schema-truth file src/lib/db/types.ts. Every field has its type and description.
Customer Customer (end customer of the tenant)| Field | Type | Description |
|---|---|---|
| id | string | nanoid(16) primary key |
| barbershopId | string | Tenant scope (foreign key to Barbershop.id) |
| firstName | string | Customer first name (required) |
| lastName | string? | Customer last name (optional) |
| phone | string? | Phone number, E.164 preferred (optional) |
| string? | Customer email address (optional) | |
| birthday | string? | ISO date YYYY-MM-DD (optional) |
| notes | string? | Free-text notes by owner/staff (optional) |
| gdprConsent | boolean | Art. 6(1)(a) GDPR base consent |
| marketingConsent | boolean | Marketing email consent (double opt-in) |
| birthdayConsent | boolean | Birthday greeting consent (double opt-in) |
| marketingConsentConfirmedAt | string? | ISO timestamp of marketing double opt-in |
| birthdayConsentConfirmedAt | string? | ISO timestamp of birthday double opt-in |
| unsubscribeToken | string? | OMITTED from export (live credential) |
| lastMarketingEmailAt | string? | ISO timestamp of last marketing email sent |
| lastBirthdayEmailAt | string? | ISO timestamp of last birthday email sent |
| consentGivenAt | string? | ISO timestamp when consent was given |
| consentSource | enum? | CARD_ACTIVATION_FORM | ADMIN_MANUAL_ENTRY | IMPORT | PUBLIC_WEB_FORM |
| consentIpAddress | string? | IP address captured at consent (Art. 7(1) proof) |
| consentUserAgent | string? | User agent captured at consent |
| isAnonymized | boolean | True if personal data has been erased (Art. 17) |
| createdAt | string | ISO timestamp record created |
| updatedAt | string | ISO timestamp record last updated |
Card Card (loyalty card)| Field | Type | Description |
|---|---|---|
| id | string | nanoid(16) primary key |
| barbershopId | string | Tenant scope |
| customerId | string? | Owner of the card (optional until activation) |
| cardCode | string | Unique card code printed on the physical card |
| status | enum | NOT_ACTIVATED | ACTIVE | BLOCKED | LOST | EXPIRED |
| pointsBalance | number | Current points balance |
| totalPointsEarned | number | Lifetime points earned |
| totalPointsRedeemed | number | Lifetime points redeemed |
| activatedAt | string? | ISO timestamp of activation |
| blockedAt | string? | ISO timestamp the card was blocked |
| expiresAt | string? | ISO timestamp the card expires |
| replacedByCardId | string? | If replaced, points to the new card.id |
| createdAt | string | ISO timestamp record created |
| updatedAt | string | ISO timestamp record last updated |
Transaction Transaction (points transaction)| Field | Type | Description |
|---|---|---|
| id | string | nanoid(16) primary key |
| barbershopId | string | Tenant scope |
| cardId | string | Card the points moved on |
| customerId | string? | Customer reference (denormalized for queries) |
| userId | string? | Staff/owner who performed the action |
| type | enum | EARN | REDEEM | MANUAL_ADJUSTMENT | EXPIRE | CANCEL |
| points | number | Signed points delta (positive = earn, negative = redeem) |
| balanceBefore | number | Card balance before this transaction |
| balanceAfter | number | Card balance after this transaction |
| amountEuro | number? | Euro amount of the underlying purchase (for PER_EURO mode) |
| reason | string | Human-readable reason (service name, manual reason, etc.) |
| rewardId | string? | Reward reference when type=REDEEM |
| metadata | object? | Arbitrary key-value metadata for future fields |
| createdAt | string | ISO timestamp of the transaction |
Service Service (offered service)| Field | Type | Description |
|---|---|---|
| id | string | nanoid(16) primary key |
| barbershopId | string | Tenant scope |
| categoryId | string | Foreign key to ServiceCategory.id |
| name | string | Service name as shown to staff |
| description | string? | Service description (optional) |
| priceEur | number | Base price in EUR |
| pointsAward | number | Points granted when this service is delivered |
| durationMin | number? | Approximate duration in minutes (optional) |
| sortIndex | number | Display ordering within category |
| active | boolean | True if currently bookable / shown to staff |
| createdAt | string | ISO timestamp record created |
| updatedAt | string | ISO timestamp record last updated |
Reward Reward (loyalty reward)| Field | Type | Description |
|---|---|---|
| id | string | nanoid(16) primary key |
| barbershopId | string | Tenant scope |
| categoryId | string? | Optional reward category reference |
| title | string | Reward title shown to customers |
| description | string? | Reward description (optional) |
| pointsCost | number | Points required to redeem this reward |
| type | enum | DISCOUNT | FREE_SERVICE | GIFT | CUSTOM |
| discountAmount | number? | Fixed discount in EUR for DISCOUNT type |
| discountPercent | number? | Percentage discount for DISCOUNT type |
| active | boolean | True if currently available for redemption |
| validFrom | string? | ISO date — reward available from this date |
| validUntil | string? | ISO date — reward available until this date |
| maxRedemptions | number? | Hard cap on total redemptions (optional) |
| iconName | string? | Curated lucide-react icon key |
| createdAt | string | ISO timestamp record created |
| updatedAt | string | ISO timestamp record last updated |
Redemption Redemption (reward redemption)| Field | Type | Description |
|---|---|---|
| id | string | nanoid(16) primary key |
| barbershopId | string | Tenant scope |
| rewardId | string | Reward that was redeemed |
| cardId | string | Card the points came off |
| customerId | string | Customer who redeemed |
| userId | string? | Staff/owner who confirmed the redemption |
| pointsSpent | number | Points debited for this redemption |
| status | enum | COMPLETED | CANCELLED |
| cancelledAt | string? | ISO timestamp if status=CANCELLED |
| createdAt | string | ISO timestamp of the redemption |
Barbershop Barbershop (tenant / business entity)| Field | Type | Description |
|---|---|---|
| id | string | nanoid(16) primary key |
| name | string | Business display name |
| legalName | string? | Full legal company name (optional) |
| ownerName | string? | Contact person (optional) |
| address | string | Street and house number |
| postalCode | string | Postal code |
| city | string | City |
| country | string | ISO country code, default DE |
| phone | string | Business phone number |
| string | Business contact email | |
| website | string? | Business website URL (optional) |
| logoUrl | string? | Logo image URL (R2/CDN) |
| primaryColor | string? | Brand color in hex (default #C9A04A) |
| pointsPerEuro | number | Points credited per euro spent (PER_EURO mode) |
| pointsPerVisit | number | Points credited per visit (PER_VISIT mode) |
| defaultEarningMode | enum | PER_EURO | PER_VISIT | MANUAL — default earning mode |
| currency | string | ISO 4217 currency code (default EUR) |
| timezone | string | IANA timezone (default Europe/Berlin) |
| programRules | string? | Public program rules / terms of participation |
| birthdayMessage | string? | Birthday greeting email body |
| status | enum | ACTIVE | SUSPENDED | ARCHIVED | SCHEDULED_FOR_DELETION |
| cardDesign | object? | Two-sided card design (front + back + QR placement) |
| planId | enum? | free | pro | enterprise |
| limitOverrides | object? | Per-tenant plan limit overrides |
| businessHours | array? | Weekly opening hours per weekday |
| instagramUrl | string? | Instagram profile URL |
| facebookUrl | string? | Facebook profile URL |
| googleMapsUrl | string? | Google Maps profile URL |
| descriptionDe | string? | Public profile description (German, max 800 chars) |
| descriptionEn | string? | Public profile description (English, max 800 chars) |
| descriptionUk | string? | Public profile description (Ukrainian, max 800 chars) |
| descriptionRu | string? | Public profile description (Russian, max 800 chars) |
| emailSignature | string? | Appended to outgoing marketing/birthday emails (max 500 chars) |
| autoAnonymizeMonths | number? | GDPR Art. 5 retention threshold in months (0 = disabled) |
| autoAnonymizeNotifyDays | string? | Comma-separated days before anonymize (e.g. '30,14,7') |
| customGdprConsentDe | string? | Per-tenant override of GDPR consent text (German) |
| customGdprConsentEn | string? | Per-tenant override of GDPR consent text (English) |
| customGdprConsentUk | string? | Per-tenant override of GDPR consent text (Ukrainian) |
| customGdprConsentRu | string? | Per-tenant override of GDPR consent text (Russian) |
| avvAcceptedAt | string? | ISO timestamp of AVV (DPA) acceptance |
| avvAcceptedIp | string? | IP address captured at AVV acceptance |
| avvAcceptedUserAgent | string? | User agent captured at AVV acceptance |
| avvVersion | string? | Version of AVV accepted |
| policyAcceptances | array? | Append-only log of AVV/AUP/AGB/Widerruf/data-act-switching acceptances |
| createdAt | string | ISO timestamp record created |
| updatedAt | string | ISO timestamp record last updated |
All exports are produced as JSON per RFC 8259, UTF-8 encoded, with 2-space indentation. Schema versions: boncard.tenant.v1 (tenant export, whole business) and boncard.customer.v1 (single end-customer export). Date fields are ISO-8601 strings; optional fields are omitted rather than emitted as null.
Points calculation logic: PER_EURO multiplies pointsPerEuro by the transaction amount in euros. PER_VISIT awards pointsPerVisit per visit regardless of amount. MANUAL leaves the points decision to staff at scan time. AVV obligations: before contract conclusion an electronic data processing agreement (Art. 28 GDPR) is concluded; passwords are bcrypt-hashed and audit logs retained 90 days. Retention periods: marketing email events 90 days, transactional 1 year, legal notices 3 years; consent logs 3 years after revocation; deletion-log archive 3 years.
Pursuant to Art. 28(1) Regulation (EU) 2023/2854 (Data Act), Boncard discloses the jurisdiction in which the ICT infrastructure used to provide the service is operated (point (a)) and the measures in place against unlawful third-country governmental access to non-personal data (point (b)).
This disclosure fulfils Art. 28(1)(a)/(b) Data Act. The up-to-date sub-processor list: /subprocessors.
Schema bumps are either backwards-compatible (additional optional fields) or announced 90 days in advance. Active tenants receive a LEGAL_NOTICE email about any material change. The active schema version is included in every export file as the schemaVersion field.
Technical questions about schema and export: sahajaret@gmail.com. Competent authority per Art. 37(1)(a) Data Act: Federal Network Agency (Bundesnetzagentur / BNetzA), Tulpenfeld 4, 53113 Bonn, https://www.bundesnetzagentur.de. Data-protection supervisory authority: State Commissioner for Data Protection and Freedom of Information NRW (LDI NRW), https://www.ldi.nrw.de.
Data Portability (Art. 20 / EU Data Act) · Data Processing Agreement (DPA) · Privacy notice · Sub-processors