Data Management

Contacts and Internal Representatives

Manage customer contacts and internal team members associated with accounts.

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

PropertyTypeDescription
idstringUnique identifier (UUID)
fullNamestringPerson’s full name
emailobjectEmail address and validation status
languagestringISO 639-1 code for communication language
rolestringJob title or role (optional)
notesstringAdditional notes (optional)
isPrimarybooleanWhether 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:

StatusDescription
ValidEmail is deliverable
BouncedEmail bounced (undeliverable)
ComplainedRecipient 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

MethodEndpointDescription
GET/accounts/{accountId}/contactsList all contacts
POST/accounts/{accountId}/contactsCreate a contact
PATCH/accounts/{accountId}/contacts/{contactId}Update a contact
DELETE/accounts/{accountId}/contacts/{contactId}Delete a contact
PUT/accounts/{accountId}/contacts/upsertCreate or update

Internal Representatives

MethodEndpointDescription
GET/accounts/{accountId}/internal-representativesList all
POST/accounts/{accountId}/internal-representativesCreate
PATCH/accounts/{accountId}/internal-representatives/{id}Update
DELETE/accounts/{accountId}/internal-representatives/{id}Delete
PUT/accounts/{accountId}/internal-representatives/upsertCreate or update

Party (Auto-Detection)

MethodEndpointDescription
PUT/accounts/{accountId}/party/upsertCreate 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

FieldRequiredDescription
fullNameYesPerson’s full name
languageYesISO 639-1 code (e.g., en, fr, de)
emailNoEmail address
roleNoJob title or role
notesNoAdditional notes
isPrimaryNoPrimary 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:

CodeLanguage
enEnglish
frFrench
deGerman
esSpanish
itItalian
nlDutch
ptPortuguese

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

Support

Questions about managing contacts?
Contact us via the website contact form.