Contacts and Internal Representatives
Billabex accounts can have two types of people associated with them: contacts (your customers) and internal representatives (your team members). This guide explains how to manage both through the API.
Overview
When Billabex’s dunning agent sends payment reminders, it needs to know:
- Who to contact at the customer’s organization (contacts)
- Who from your team is responsible for the account (internal representatives)
Both types share similar properties but serve different purposes in the dunning workflow.
Contacts
Contacts are people at the customer’s organization. They receive payment reminders and other communications from Billabex.
Common contact roles:
- Accounts Payable Manager
- Finance Director
- Billing Administrator
- Office Manager
Contact Properties
| Property | Type | Description |
|---|---|---|
id | string | Unique identifier (UUID) |
fullName | string | Person’s full name |
email | object | Email address and validation status |
language | string | ISO 639-1 code for communication language |
role | string | Job title or role (optional) |
notes | string | Additional notes (optional) |
isPrimary | boolean | Whether this is the primary contact |
Email Status
The email object contains the address and its validation status:
{
"address": "john@acme.com",
"status": "Valid"
}
Possible status values:
| Status | Description |
|---|---|
Valid | Email is deliverable |
Bounced | Email bounced (undeliverable) |
Complained | Recipient marked emails as spam |
Email status is read-only and updated automatically based on delivery results.
Internal Representatives
Internal representatives are people from your organization who manage the customer relationship. They are kept informed about dunning activities but don’t receive payment reminders.
Common internal representative roles:
- Account Manager
- Sales Representative
- Customer Success Manager
- Collections Specialist
Internal representatives use the same properties as contacts.
The Party Concept
Party is a unified concept that encompasses both contacts and internal representatives. The party upsert endpoint automatically determines the type based on email domain:
- If the email domain matches your organization’s domain → Internal Representative
- Otherwise → Contact
This is useful when importing data from systems that don’t distinguish between contact types.
API Endpoints
Contacts
| Method | Endpoint | Description |
|---|---|---|
| GET | /accounts/{accountId}/contacts | List all contacts |
| POST | /accounts/{accountId}/contacts | Create a contact |
| PATCH | /accounts/{accountId}/contacts/{contactId} | Update a contact |
| DELETE | /accounts/{accountId}/contacts/{contactId} | Delete a contact |
| PUT | /accounts/{accountId}/contacts/upsert | Create or update |
Internal Representatives
| Method | Endpoint | Description |
|---|---|---|
| GET | /accounts/{accountId}/internal-representatives | List all |
| POST | /accounts/{accountId}/internal-representatives | Create |
| PATCH | /accounts/{accountId}/internal-representatives/{id} | Update |
| DELETE | /accounts/{accountId}/internal-representatives/{id} | Delete |
| PUT | /accounts/{accountId}/internal-representatives/upsert | Create or update |
Party (Auto-Detection)
| Method | Endpoint | Description |
|---|---|---|
| PUT | /accounts/{accountId}/party/upsert | Create or update with auto-detection |
Creating a Contact
const response = await fetch('[baseURL]/api/public/v1/accounts/ACCOUNT_ID/contacts', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
fullName: 'John Smith',
email: 'john.smith@acme.com',
language: 'en',
role: 'Billing Manager',
isPrimary: true,
}),
});
const contact = await response.json();
Using cURL
curl -X POST "[baseURL]/api/public/v1/accounts/ACCOUNT_ID/contacts" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"fullName": "John Smith",
"email": "john.smith@acme.com",
"language": "en",
"role": "Billing Manager",
"isPrimary": true
}'
Required Fields
| Field | Required | Description |
|---|---|---|
fullName | Yes | Person’s full name |
language | Yes | ISO 639-1 code (e.g., en, fr, de) |
email | No | Email address |
role | No | Job title or role |
notes | No | Additional notes |
isPrimary | No | Primary contact flag (default: false) |
Updating a Contact
Use PATCH to update specific fields:
const response = await fetch('[baseURL]/api/public/v1/accounts/ACCOUNT_ID/contacts/CONTACT_ID', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
role: 'Finance Director',
isPrimary: true,
}),
});
Only the provided fields will be updated.
Listing Contacts
const response = await fetch('[baseURL]/api/public/v1/accounts/ACCOUNT_ID/contacts?first=10', {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
const data = await response.json();
// data.nodes contains the contacts array
// data.pageInfo contains pagination info
Deleting a Contact
const response = await fetch('[baseURL]/api/public/v1/accounts/ACCOUNT_ID/contacts/CONTACT_ID', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
// Returns 204 No Content on success
Primary Contact
Each account should have one primary contact. The primary contact:
- Receives payment reminders first
- Is the default recipient for dunning communications
- Appears prominently in the Billabex UI
To set a contact as primary:
await fetch('[baseURL]/api/public/v1/accounts/ACCOUNT_ID/contacts/CONTACT_ID', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
isPrimary: true,
}),
});
Setting a new primary contact automatically removes the primary flag from other contacts on the same account.
Language
The language field determines which language Billabex uses when communicating with the contact. Use ISO 639-1 two-letter codes:
| Code | Language |
|---|---|
en | English |
fr | French |
de | German |
es | Spanish |
it | Italian |
nl | Dutch |
pt | Portuguese |
The dunning agent automatically translates payment reminders to the contact’s preferred language.
Using Upsert for Sync
When synchronizing contacts from external systems, use the upsert endpoints to simplify your logic:
// Sync contacts from your CRM
for (const crmContact of crmContacts) {
await fetch(`[baseURL]/api/public/v1/accounts/${crmContact.billabexAccountId}/contacts/upsert`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
fullName: crmContact.name,
email: crmContact.email,
language: crmContact.preferredLanguage || 'en',
role: crmContact.title,
isPrimary: crmContact.isPrimaryBillingContact,
}),
});
}
See the Upsert Operations guide for details on matching logic.
Party Auto-Detection Example
When you don’t know whether someone should be a contact or internal representative:
// If alex@yourcompany.com and your org domain is yourcompany.com
// This becomes an internal representative
await fetch('[baseURL]/api/public/v1/accounts/ACCOUNT_ID/party/upsert', {
method: 'PUT',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
fullName: 'Alex Brown',
email: 'alex@yourcompany.com',
language: 'en',
}),
});
// If john@customerdomain.com and your org domain is yourcompany.com
// This becomes a contact
await fetch('[baseURL]/api/public/v1/accounts/ACCOUNT_ID/party/upsert', {
method: 'PUT',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
fullName: 'John Doe',
email: 'john@customerdomain.com',
language: 'en',
}),
});
Best Practices
Always Set a Primary Contact
Ensure each account has a primary contact to receive payment reminders:
// When creating the first contact, make them primary
{
"fullName": "Jane Doe",
"email": "jane@customer.com",
"language": "en",
"isPrimary": true
}
Use Meaningful Roles
Clear role descriptions help identify the right person:
// Good
"role": "Accounts Payable Manager"
"role": "Finance Director"
// Less helpful
"role": "Contact"
"role": "User"
Set Correct Languages
Accurate language settings ensure customers receive communications they can understand:
// Match the contact's actual language preference
"language": "fr" // For French-speaking contacts
Handle Email Bounces
Monitor the email.status field and update contacts when emails bounce:
if (contact.email?.status === 'Bounced') {
// Alert your team to update the contact's email
// Or automatically mark as needing attention
}
Next Steps
- Upsert Operations – Create or update in one request
- Account Sources – Link accounts to external systems
- Getting Started – API basics
- API Reference – Full endpoint documentation
Support
Questions about managing contacts?
Contact us via the website contact form.