Multi-Company Support in Accounting Integrations

Introduction

Many accounting platforms allow users to manage multiple companies, tenants, or entities within a single account. Apideck's Accounting API supports switching between these companies at request time using the x-apideck-company-id header, eliminating the need for separate connections per company.

How It Works

When a consumer connects their accounting platform through Vault, they select a default company. This default is used for all API requests unless you explicitly override it using the x-apideck-company-id header.

The x-apideck-company-id Header

Include this optional header in your Unified API requests to route the request to a specific company:

curl -X GET "https://unify.apideck.com/accounting/invoices" \
  -H "Authorization: Bearer {api-key}" \
  -H "x-apideck-app-id: {app-id}" \
  -H "x-apideck-consumer-id: {consumer-id}" \
  -H "x-apideck-service-id: xero" \
  -H "x-apideck-company-id: {company-id}"

When the header is:

  • Provided: The request is routed to the specified company
  • Omitted: The request uses the default company configured during connection setup

Supported Connectors

The following accounting connectors support multi-company at request time:

ConnectorCompany ConceptNotes
XeroTenantOne OAuth connection accesses all tenants
Sage Business CloudBusinessMultiple businesses per account
Sage IntacctEntityMulti-entity support within organization
Microsoft Dynamics 365 BCCompanyMultiple companies per environment
Exact OnlineDivisionMultiple divisions per account

Listing Available Companies

To retrieve the list of companies available for a connection, use the Companies endpoint:

curl -X GET "https://unify.apideck.com/accounting/companies" \
  -H "Authorization: Bearer {api-key}" \
  -H "x-apideck-app-id: {app-id}" \
  -H "x-apideck-consumer-id: {consumer-id}" \
  -H "x-apideck-service-id: xero"

Response:

{
  "status_code": 200,
  "status": "OK",
  "data": [
    {
      "id": "c3d1b4e5-6789-4abc-def0-123456789abc",
      "name": "Acme Corp - US"
    },
    {
      "id": "f7e8d9c0-1234-5678-9abc-def012345678",
      "name": "Acme Corp - UK"
    }
  ]
}

Use the id value as the x-apideck-company-id header value in subsequent requests.

Identifying the Active Company via Metadata

Each connection exposes a standardized metadata.company_id field that reflects which company the connection is currently scoped to. This value is automatically synced whenever the company setting changes — whether through the Vault UI, the API, or after an OAuth authorization flow.

Why This Matters

Multi-company connectors each use a different internal setting for company selection (e.g., Xero uses tenant_id, Sage Business Cloud uses business_id, Dynamics 365 BC uses company_id). Instead of having to know the connector-specific key, you can always read metadata.company_id for a consistent, connector-agnostic reference.

Reading metadata.company_id

The field is available on the connection object returned by the Vault API:

curl -X GET "https://unify.apideck.com/vault/connections/accounting/xero" \
  -H "Authorization: Bearer {api-key}" \
  -H "x-apideck-app-id: {app-id}" \
  -H "x-apideck-consumer-id: {consumer-id}"

The response includes the metadata:

{
  "data": {
    "id": "accounting+xero",
    "service_id": "xero",
    "metadata": {
      "company_id": "c3d1b4e5-6789-4abc-def0-123456789abc"
    }
  }
}

When It Syncs

metadata.company_id is updated automatically in these scenarios:

  • Settings update: When a consumer changes the company selection via Vault or the API
  • OAuth authorization: When a token-success hook auto-selects a company (e.g., Xero selecting the only available tenant)

Connector Setting Key Mapping

For reference, here's which setting key maps to metadata.company_id for each connector:

ConnectorSetting Key
Xerotenant_id
Sage Business Cloudbusiness_id
Sage Intacctentity
Microsoft Dynamics 365 BCcompany_id
Exact OnlinesystemDivisionId

Common Use Cases

1. Multi-Tenant SaaS Applications

If your application serves users who manage multiple companies, you can let them switch between companies without re-authenticating:

// Fetch invoices from a specific company
const getInvoices = async (consumerId: string, companyId: string) => {
  const response = await fetch('https://unify.apideck.com/accounting/invoices', {
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'x-apideck-app-id': appId,
      'x-apideck-consumer-id': consumerId,
      'x-apideck-service-id': 'xero',
      'x-apideck-company-id': companyId
    }
  })
  return response.json()
}

2. Consolidated Reporting

Aggregate data across multiple companies by iterating through available companies:

const getConsolidatedData = async (consumerId: string) => {
  // First, get all available companies
  const companiesResponse = await fetch('https://unify.apideck.com/accounting/companies', {
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'x-apideck-app-id': appId,
      'x-apideck-consumer-id': consumerId,
      'x-apideck-service-id': 'xero'
    }
  })
  const { data: companies } = await companiesResponse.json()

  // Fetch invoices from each company
  const allInvoices = await Promise.all(
    companies.map(async (company) => {
      const invoices = await getInvoices(consumerId, company.id)
      return { companyId: company.id, companyName: company.name, invoices: invoices.data }
    })
  )

  return allInvoices
}

3. Company Selector in Your UI

Let users select which company to work with:

// Populate a dropdown with available companies
const populateCompanySelector = async (consumerId: string) => {
  const response = await fetch('https://unify.apideck.com/accounting/companies', {
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'x-apideck-app-id': appId,
      'x-apideck-consumer-id': consumerId,
      'x-apideck-service-id': 'xero'
    }
  })
  const { data: companies } = await response.json()

  // companies array can be used to populate a <select> element
  return companies.map((c) => ({ value: c.id, label: c.name }))
}

Virtual Webhooks and Multi-Company

Virtual Webhooks currently only sync data from the default company configured during connection setup. The x-apideck-company-id header is not supported for webhook subscriptions.

If you need to receive webhook events from multiple companies, you have two options:

  1. Create separate connections: Set up a connection per company, each with its own default company selection
  2. Use polling: Query the API directly with the x-apideck-company-id header to fetch data from specific companies on demand

Error Handling

Invalid Company ID

The x-apideck-company-id value is passed directly to the downstream API without validation. If you provide an invalid or inaccessible company ID, the error response will come from the downstream connector and vary depending on the service.

For example, Xero might return:

{
  "status_code": 403,
  "error": "Forbidden",
  "message": "AuthorizationUnsuccessful: The organisation is not authorised for this request"
}

Always use the Companies endpoint to retrieve valid company IDs before making requests.