Description
Overview
The customers package provides comprehensive functionality for managing customers, their team members, shareholders, and signatories. It includes features for invitation management, document generation, and verification processes.
Security Architecture
Authentication & Authorization
All customer endpoints require JWT-based authentication. Access control is enforced through RBAC (Role-Based Access Control) at the service layer, ensuring users can only access and modify records they have permission to view.
Rate Limiting Strategy
Critical endpoints implement Redis-backed rate limiting to prevent abuse:
- Name availability checks: Limited to 3 requests per minute per authenticated user
- Purpose: Prevents database enumeration, brute-force attacks, and resource exhaustion
- Tracking: Rate limits are tracked by user ID (not IP) for authenticated endpoints
- Reset: Counters automatically reset at the end of each time window
Audit & Compliance
All operations are logged with full request traceability:
request_id- Unique identifier for each API requestuser_id- Authenticated user performing the actionip_address- Client IP addressaction- Operation performedtimestamp- When the operation occurred
This ensures complete audit trails for regulatory compliance and security investigations.
Data Structure
Core Fields
| Field | Type | Description |
|---|---|---|
name | string | The full name of the customer |
description | string | A detailed description of the customer |
abbreviation | string | Short form or acronym for the customer |
logo | string | Base64 encoded image data of the customer's logo |
active | boolean | Indicates if the record is active for operational use |
metadata | JSON | Additional flexible metadata stored as JSON |
Legal Information
| Field | Type | Description |
|---|---|---|
uid | string | Unique identifier for the customer |
dateOfRegistration | date | Date when the customer was officially registered |
legalForm | string | Legal structure of the organization |
seat | string | Official registered seat of the company |
status | string | Current status of the customer |
principalId | UUID | Reference to the user who initially registered the account |
counterpartyId | UUID | Mirror-linked counterparty created for same customer entity; use it when flows need counterparty context for that customer |
documents | JSON | Associated documents stored in JSON format |
Status Values
| Status | Description |
|---|---|
KYB | Initial status when a company has started the registration process |
SIGN | Status when signatories have been invited to sign documents |
REVIEW | Status when signed documents are under review by a Compliance officer |
ACTIVE | Status when approval is given and the company can operate |
SUSPENDED | Status when a company's activity has been suspended |
API Methods
Name Validation
Check Company Name Availability
POST /v1/customers/check-name
Validates whether a company name is available for registration before creating a new customer record.
Authentication: Required (JWT Bearer token)
Authorization: Accessible to all authenticated users with User role
Rate Limiting: 3 requests per minute per user
Purpose:
- Prevents duplicate company registrations
- Provides immediate feedback during onboarding
- Improves user experience by validating names upfront
Request Parameters:
| Field | Type | Required | Constraints |
|---|---|---|---|
name | string | Yes | 2-255 characters |
Response:
| Field | Type | Description |
|---|---|---|
available | boolean | true if name is available, false if already taken |
name | string | Normalized company name that was checked |
Validation Rules:
- Case-insensitive matching (prevents duplicates with different casing)
- Automatic whitespace trimming
- Minimum length: 2 characters
- Maximum length: 255 characters
Error Scenarios:
400- Invalid company name (too short, too long, or empty)401- Unauthorized (missing or invalid JWT token)429- Rate limit exceeded (max 3 requests per minute)500- Database or system error
Typical Workflow:
- User authenticates and receives JWT token
- User enters company name in registration form
- Frontend calls
/v1/customers/check-nameto validate availability - If available, user proceeds to complete registration
- User submits full customer creation via
/v1/customers
Customers
Create Customer
POST /v1/customers
Creates a new customer record.
Request
{
"abbr": "EXE",
"logo": "base64_encoded_string",
"documents": {
"corp_struct": "base64_encoded_string",
"share_reg": "base64_encoded_string"
},
"uid": "CHE450748806",
"date_of_registration": "2023-09-15",
"legal_form": "Corporation",
"seat": "Zurich",
"status": "Active",
"principal_id": "uuid",
"name": "Example Company AG",
"description": "",
"active": true,
"metadata": {}
}Response
{
"abbr": "EXE",
"logo": "base64_encoded_string",
"documents": {
"share_reg": "base64_encoded_string",
"corp_struct": "base64_encoded_string"
},
"uid": "CHE450748806",
"date_of_registration": "2023-09-15",
"legal_form": "Corporation",
"seat": "Zurich",
"status": "Active",
"principal_id": "uuid",
"counterparty_id": "uuid",
"signing_room_id": "uuid",
"chatroom_id": "uuid",
"tariff_id": "uuid",
"fx_tariff_id": "uuid",
"name": "Example Company AG",
"description": null,
"id": "uuid",
"created_at": "2024-03-20T10:00:00Z",
"created_by": "uuid",
"modified_at": "2024-03-20T10:00:00Z",
"modified_by": "uuid",
"active": true,
"metadata": {
"route": "/company"
},
"addresses": [
{
"rec_id": "uuid",
"rec_type": "customers",
"type": "registration",
"street": "Example Street",
"number": "123",
"postal_code": "12345",
"city": "Example City",
"country": "CH",
"is_primary": false,
"id": "uuid",
"created_at": "2024-03-20T10:00:00Z",
"created_by": "uuid",
"modified_at": "2024-03-20T10:00:00Z",
"modified_by": "uuid",
"active": true,
"metadata": null
}
]
}Patch Customer (Partial Update)
PATCH /v1/customers/{id}
Updates one or more of the following fields for a customer. Only provided fields are updated:
tariff_id(UUID)fx_tariff_id(UUID)type(string)status(string)signing_room_id(UUID)principal_id(UUID)
Request Example
{
"tariff_id": "uuid",
"status": "ACTIVE"
}Response Example
{
"id": "uuid",
"status": "ACTIVE",
"tariff_id": "uuid"
// ... other fields ...
}Team Management
Create Team Member
POST /v1/customers/{id}/teams
Adds a new team member to a customer's team.
Request
{
"email": "team.member@example.com",
"role": "admin",
"permissions": ["read", "write"],
"metadata": {
"department": "finance"
}
}Response
{
"id": "uuid",
"customer_id": "uuid",
"email": "team.member@example.com",
"role": "admin",
"status": "invited",
"permissions": ["read", "write"],
"metadata": {
"department": "finance"
},
"created_at": "2024-03-20T10:00:00Z"
}Update Team Member
PATCH /v1/customers/{id}/teams/{user_id}
Updates team member details.
Request
{
"role": "member",
"permissions": ["read"],
"metadata": {
"department": "operations"
}
}Shareholders
Create Shareholder
POST /v1/customers/{id}/shareholders
Creates a new shareholder record.
Request
{
"type": "individual",
"name": "Jane Smith",
"email": "jane.smith@example.com",
"share_percentage": 25.5,
"nationality": "CH",
"tax_residence": "CH",
"documents": [
{
"type": "passport",
"number": "X123456",
"country": "CH",
"expiry_date": "2030-12-31"
}
]
}Response
{
"id": "uuid",
"customer_id": "uuid",
"type": "individual",
"name": "Jane Smith",
"email": "jane.smith@example.com",
"share_percentage": 25.5,
"nationality": "CH",
"tax_residence": "CH",
"status": "pending_verification",
"documents": [
{
"id": "uuid",
"type": "passport",
"number": "X123456",
"country": "CH",
"expiry_date": "2030-12-31",
"status": "pending_verification"
}
],
"created_at": "2024-03-20T10:00:00Z",
"updated_at": "2024-03-20T10:00:00Z"
}Signatories
Create Signatory
POST /v1/customers/{id}/signatories
Creates a new signatory record.
Request
{
"name": "Robert Johnson",
"email": "robert.johnson@example.com",
"phone": "+41791234567",
"signing_authority": "individual",
"signing_limit": {
"amount": 100000,
"currency": "CHF"
},
"documents_required": ["passport", "proof_of_address"]
}Response
{
"id": "uuid",
"customer_id": "uuid",
"name": "Robert Johnson",
"email": "robert.johnson@example.com",
"phone": "+41791234567",
"signing_authority": "individual",
"signing_limit": {
"amount": 100000,
"currency": "CHF"
},
"status": "pending_documents",
"documents_required": ["passport", "proof_of_address"],
"documents_submitted": [],
"created_at": "2024-03-20T10:00:00Z",
"updated_at": "2024-03-20T10:00:00Z"
}Invite Signatories
POST /v1/customers/{id}/signatories/invite
Sends invitations to signatories.
Request
{
"signatory_ids": ["uuid1", "uuid2"],
"message": "Please complete your verification",
"expires_in": 7200
}Response
{
"invitations": [
{
"signatory_id": "uuid1",
"email": "signatory1@example.com",
"status": "sent",
"expires_at": "2024-03-22T10:00:00Z"
},
{
"signatory_id": "uuid2",
"email": "signatory2@example.com",
"status": "sent",
"expires_at": "2024-03-22T10:00:00Z"
}
]
}Error Codes
| Code | Description |
|---|---|
| customers_m.invalid_company_name | Invalid company name (validation failed) |
| customers_m.failed_to_check_name_availability | Failed to check company name availability |
| customers_m.rate_limit_exceeded | Rate limit exceeded for name availability checks |
| customers_m.not_found | Customer not found |
| customers_m.failed_to_create | Failed to create customer |
| customers_m.failed_to_get | Failed to retrieve customer |
| customers_m.not_linked | Customer not linked |
| customers_m.customer_type_not_found | Customer type not found |
| customers_m.persona_already_exists | Persona already linked to a customer |
| customers_m.address_not_found | Address not found |
| customers_m.failed_to_create_roles | Failed to create customer roles |
| customers_m.customer_roles_config_not_found | Customer roles configuration not found |
| customers_m.invalid_roles_config | Invalid customer roles configuration |
| customers_m.failed_to_create_persona | Failed to create persona |
| customers_m.failed_to_create_organization | Failed to create organization |
| customers_m.failed_to_fetch_entity | Failed to fetch related entity |
| customers_m.failed_to_create_shareholder | Failed to create shareholder |
| customers_m.failed_to_create_address | Failed to create address |
| customers_m.failed_to_create_link | Failed to create link between records |
| customers_m.failed_to_check_permission | Failed to check permissions |
| customers_m.failed_to_get_signatory_ids | Failed to get signatory IDs |
| customers_m.failed_to_get_signatories | Failed to get signatories |
| customers_m.company_type_invalid | Invalid company type |
| common.failed_to_serialize | Failed to serialize or deserialize request/response payload |
| common.duplicate_entity | Duplicate entity detected |
| common.permission_denied | Access denied – insufficient permissions |
| common.database_error | Internal database error |
| common.record_not_found | Requested record not found |
Dependencies
common/errs- Error handlingcommon/auth- Authenticationcommon/logger- Logging functionalitycommon/rbac- Role-based access controlcommon/links- Entity relationship managementmodules/countries- Country validationmodules/uploads- Document upload handlingmodules/kyc- KYC verification processesmodules/personas- Person/Organization managementmodules/roles- Role managementmodules/users- User management