Account Sources
When synchronizing accounts from an external system (ERP, CRM, billing platform), you can link each Billabex account to its corresponding record in your system using a source reference. This guide explains how sources work and how to manage them via the API.
What is a Source?
A source is a reference that links a Billabex account to an external system. It tells Billabex:
- Where the account data comes from (your integration identifier)
- Which record it maps to (the external system ID)
- When it was last synchronized
When an account has a source, Billabex treats it as externally managed. This affects how the account can be modified and deleted.
Source Properties
A source reference contains four properties:
| Property | Type | Description |
|---|---|---|
connectionId | string | Identifier for your integration (e.g., my-erp-sync) |
sourceId | string | The account’s ID in your external system (e.g., CUST-00123) |
connectorType | string | Always Custom for API-managed sources |
lastUpdate | datetime | When the source was last linked or updated |
The connectionId groups accounts from the same integration. Use a consistent value across all accounts from the same source system.
The sourceId uniquely identifies the account in your external system. This should match the primary key or unique identifier in your source database.
Linking a Source
Use the PUT endpoint to link a source to an existing account:
PUT /api/public/v1/accounts/{accountId}/source
This endpoint is idempotent: if you call it with the same source (matching connectionId and sourceId), it will simply update the lastUpdate date instead of returning an error.
Request
const response = await fetch('[baseURL]/api/public/v1/accounts/ACCOUNT_ID/source', {
method: 'PUT',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
connectionId: 'my-erp-integration',
sourceId: 'CUST-00123',
// Optional: specify a custom lastUpdate date
// lastUpdate: '2024-01-15T10:30:00.000Z',
}),
});
const account = await response.json();
Using cURL
curl -X PUT "[baseURL]/api/public/v1/accounts/ACCOUNT_ID/source" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"connectionId": "my-erp-integration",
"sourceId": "CUST-00123"
}'
Request Body
| Property | Type | Required | Description |
|---|---|---|---|
connectionId | string | Yes | Identifier for your integration |
sourceId | string | Yes | The account’s ID in your external system |
lastUpdate | datetime | No | When the source was last synchronized (defaults to now) |
Response
The endpoint returns the updated account with the source reference:
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"fullName": "Acme Corporation",
"source": {
"connectionId": "my-erp-integration",
"sourceId": "CUST-00123",
"connectorType": "Custom",
"lastUpdate": "2024-01-15T10:30:00.000Z"
}
}
Note that connectorType is always set to Custom. If lastUpdate is not provided, it defaults to the current server time.
Updating the Synchronization Date
Use the PATCH endpoint to update only the lastUpdate date of an existing source:
PATCH /api/public/v1/accounts/{accountId}/source
This is useful when you want to mark an account as “recently synchronized” without re-linking the entire source.
Request
const response = await fetch('[baseURL]/api/public/v1/accounts/ACCOUNT_ID/source', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
// Optional: specify a custom lastUpdate date
// If omitted, defaults to the current time
lastUpdate: '2024-01-15T10:30:00.000Z',
}),
});
const account = await response.json();
Using cURL
# Update with current timestamp
curl -X PATCH "[baseURL]/api/public/v1/accounts/ACCOUNT_ID/source" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
# Update with specific timestamp
curl -X PATCH "[baseURL]/api/public/v1/accounts/ACCOUNT_ID/source" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"lastUpdate": "2024-01-15T10:30:00.000Z"
}'
Request Body
| Property | Type | Required | Description |
|---|---|---|---|
lastUpdate | datetime | No | The new synchronization date (defaults to current time) |
Response
The endpoint returns the updated account:
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"fullName": "Acme Corporation",
"source": {
"connectionId": "my-erp-integration",
"sourceId": "CUST-00123",
"connectorType": "Custom",
"lastUpdate": "2024-01-15T10:30:00.000Z"
}
}
Note: This endpoint only works for accounts that already have a source linked. If the account has no source, you’ll receive a
400 Bad Requesterror.
Creating an Account with Source
You can also include the source when creating a new account:
const response = await fetch('[baseURL]/api/public/v1/accounts', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
organizationId: 'YOUR_ORG_ID',
fullName: 'Acme Corporation',
currency: { code: 'EUR' },
billingAddress: {
street: '123 Main Street',
city: 'Paris',
postalCode: '75001',
country: 'FR',
},
source: {
connectionId: 'my-erp-integration',
sourceId: 'CUST-00123',
},
}),
});
This creates the account and links the source in a single request.
Unlinking a Source
To remove the source link from an account, use the DELETE endpoint:
DELETE /api/public/v1/accounts/{accountId}/source
Request
const response = await fetch('[baseURL]/api/public/v1/accounts/ACCOUNT_ID/source', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
// Returns 204 No Content on success
Using cURL
curl -X DELETE "[baseURL]/api/public/v1/accounts/ACCOUNT_ID/source" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
The endpoint returns 204 No Content on success.
Implications in Billabex
Linking a source to an account has several important implications:
1. Deletion Protection
Accounts with a source cannot be deleted through the Billabex UI. Users must first unlink the source before the account can be removed. This prevents accidental deletion of synchronized data.
2. UI Exclusion Disabled
Accounts with a Custom source cannot be excluded (blacklisted) through the Billabex UI. The exclusion feature is designed for connector-managed accounts (like Pennylane or Zoho Books) where users might want to filter which accounts are synced.
For API-managed accounts, you control which accounts exist in Billabex directly through your integration.
3. Orphan Status After Unlink
When you unlink a source, the account becomes an “orphan” - it no longer has a connection to any external system. The account remains in Billabex and can then be:
- Deleted manually through the UI
- Re-linked to a different source via the API
- Managed independently
4. Only Custom Sources Can Be Unlinked via API
The unlink endpoint only works for accounts with a Custom source. Accounts linked through Billabex connectors (Pennylane, Zoho Books) must be managed through their respective connection settings.
Best Practices
Use Consistent Connection IDs
Choose a meaningful connectionId that identifies your integration:
// Good - identifies the integration clearly
connectionId: 'salesforce-billing-sync';
connectionId: 'netsuite-production';
// Avoid - too generic or inconsistent
connectionId: 'sync';
connectionId: 'integration-1';
Use Stable Source IDs
The sourceId should be the primary key or stable identifier from your system:
// Good - stable identifiers
sourceId: 'CUST-00123'; // Customer number
sourceId: 'acc_1234567890'; // Database ID
// Avoid - values that might change
sourceId: 'acme-corp'; // Company name (can change)
Link Before Creating Related Data
When importing data, link the source first, then create invoices and contacts:
- Create or update the account with source
- Create contacts and internal representatives
- Create invoices referencing the account
This ensures all data is properly associated with the external system reference.
Handle Re-linking Gracefully
The link endpoint (PUT) is idempotent. If you call it with the same connectionId and sourceId:
- The
lastUpdatedate will be updated - No error will be returned
If you need to change the source to a different external record, you must first unlink the current source, then link the new one:
// 1. Unlink current source
await fetch('[baseURL]/api/public/v1/accounts/ACCOUNT_ID/source', {
method: 'DELETE',
headers: { Authorization: `Bearer ${accessToken}` },
});
// 2. Link new source
await fetch('[baseURL]/api/public/v1/accounts/ACCOUNT_ID/source', {
method: 'PUT',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
connectionId: 'new-erp-integration',
sourceId: 'NEW-CUST-456',
}),
});
Next Steps
- Upsert Operations – Create or update contacts in a single request
- Contacts & Representatives – Manage people associated with accounts
- Getting Started – API basics and authentication
- API Reference – Full endpoint documentation
Support
Questions about account sources?
Contact us via the website contact form.