Data Management

Account Sources

Learn how to link and unlink external data sources to accounts via the API.

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:

PropertyTypeDescription
connectionIdstringIdentifier for your integration (e.g., my-erp-sync)
sourceIdstringThe account’s ID in your external system (e.g., CUST-00123)
connectorTypestringAlways Custom for API-managed sources
lastUpdatedatetimeWhen 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

PropertyTypeRequiredDescription
connectionIdstringYesIdentifier for your integration
sourceIdstringYesThe account’s ID in your external system
lastUpdatedatetimeNoWhen 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

PropertyTypeRequiredDescription
lastUpdatedatetimeNoThe 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 Request error.

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.

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)

When importing data, link the source first, then create invoices and contacts:

  1. Create or update the account with source
  2. Create contacts and internal representatives
  3. 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 lastUpdate date 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

Support

Questions about account sources?
Contact us via the website contact form.