Skip to main content
Business & GrowthAccounting Software226 lines

Sage Business Cloud Accounting API

You are a senior developer integrating with the Sage Business Cloud Accounting API. You build integrations for contacts, invoices, payments, ledger accounts, and banking using Sage's RESTful API with

Quick Summary24 lines
You are a senior developer integrating with the Sage Business Cloud Accounting API. You build integrations for contacts, invoices, payments, ledger accounts, and banking using Sage's RESTful API with OAuth 2.0 authentication.

## Key Points

1. **Use `attributes` parameter** — Filter response fields to reduce payload: `?attributes=name,email,balance`.
2. **Follow HAL `_links`** — Use `$next`, `$prev` links for pagination instead of manually constructing URLs.
3. **Check country-specific tax rates** — Query `/tax_rates` to get valid tax rate IDs for the business's country.
4. **Use `If-Modified-Since`** — Sage supports conditional GET for efficient sync.
5. **Validate ledger account visibility** — Not all accounts are valid for all transaction types. Check `visible_in` field.
- **Country parameter in auth URL** — Must match the user's Sage subscription country or auth fails.
- **HAL pagination** — Results are in `$items` array, not a top-level array. Total count is in `$total`.
- **Tax rate IDs are country-specific** — US, UK, and other countries have different tax rate ID formats.
- **Ledger account types** — Using wrong account type for a transaction silently misclassifies it in reports.
- **Ignoring `_links` in responses** — Sage provides hypermedia links for a reason. Use them for navigation.
- **Hardcoding tax rates** — Tax rates change. Query them dynamically.
- **Creating contacts without checking duplicates** — Search by name/email first.

## Quick Example

```bash
npm install axios
```
skilldb get accounting-software-skills/Sage Business Cloud Accounting APIFull skill: 226 lines
Paste into your CLAUDE.md or agent config

Sage Business Cloud Accounting API

You are a senior developer integrating with the Sage Business Cloud Accounting API. You build integrations for contacts, invoices, payments, ledger accounts, and banking using Sage's RESTful API with OAuth 2.0 authentication.

Core Philosophy

Country-Specific Compliance

Sage adapts its accounting model per country. Tax types, document types, and required fields vary by region. Always check the country of the business before assuming field requirements.

Ledger-Centric Design

Sage exposes the full general ledger. Every transaction posts to specific ledger accounts. Understanding the Chart of Accounts is essential for correct integration.

HAL+JSON Responses

Sage API returns HAL (Hypertext Application Language) formatted JSON with _links for navigation. Use these links for pagination and related resource discovery.

Setup

Dependencies

npm install axios

OAuth 2.0 Flow

import axios from 'axios';

const SAGE_AUTH_URL = 'https://www.sageone.com/oauth2/auth/central';
const SAGE_TOKEN_URL = 'https://oauth.accounting.sage.com/token';
const SAGE_API_URL = 'https://api.accounting.sage.com/v3.1';

function getAuthUrl(): string {
  const params = new URLSearchParams({
    response_type: 'code',
    client_id: process.env.SAGE_CLIENT_ID!,
    redirect_uri: 'https://yourapp.com/callback',
    scope: 'full_access',
    state: 'random-state-value',
    country: 'us', // us, gb, ca, etc.
  });
  return `${SAGE_AUTH_URL}?${params}`;
}

async function exchangeCode(code: string) {
  const response = await axios.post(SAGE_TOKEN_URL, new URLSearchParams({
    grant_type: 'authorization_code',
    client_id: process.env.SAGE_CLIENT_ID!,
    client_secret: process.env.SAGE_CLIENT_SECRET!,
    code,
    redirect_uri: 'https://yourapp.com/callback',
  }));
  return response.data;
}

const sageApi = axios.create({
  baseURL: SAGE_API_URL,
  headers: { 'Content-Type': 'application/json' },
});

sageApi.interceptors.request.use((config) => {
  config.headers.Authorization = `Bearer ${getAccessToken()}`;
  return config;
});

Key Techniques

1. Managing Contacts

async function createContact() {
  const response = await sageApi.post('/contacts', {
    contact: {
      name: 'Acme Corp',
      contact_type_ids: ['CUSTOMER'], // CUSTOMER, VENDOR, or both
      main_address: {
        address_line_1: '123 Main Street',
        city: 'San Francisco',
        region: 'CA',
        postal_code: '94105',
        country_id: 'US',
      },
      email: 'billing@acme.com',
      telephone: '+1-555-0100',
      tax_number: '12-3456789',
    },
  });
  return response.data;
}

async function listContacts(page = 1) {
  const response = await sageApi.get('/contacts', {
    params: {
      contact_type_id: 'CUSTOMER',
      items_per_page: 50,
      page,
      attributes: 'name,email,balance',
      search: 'Acme', // free text search
    },
  });
  return response.data;
}

2. Creating Sales Invoices

async function createSalesInvoice(contactId: string) {
  const response = await sageApi.post('/sales_invoices', {
    sales_invoice: {
      contact_id: contactId,
      date: '2026-03-25',
      due_date: '2026-04-25',
      reference: 'INV-2026-042',
      notes: 'March consulting services',
      invoice_lines: [
        {
          description: 'Software consulting',
          ledger_account_id: 'revenue-ledger-id',
          quantity: 20,
          unit_price: 150.00,
          tax_rate_id: 'US_STANDARD',
        },
      ],
      main_address: {
        address_line_1: '123 Main Street',
        city: 'San Francisco',
        postal_code: '94105',
        country_id: 'US',
      },
    },
  });
  return response.data;
}

3. Recording Payments

async function createSalesPayment(invoiceId: string, bankAccountId: string) {
  const response = await sageApi.post('/contact_payments', {
    contact_payment: {
      transaction_type_id: 'CUSTOMER_RECEIPT',
      contact_id: 'contact-id',
      bank_account_id: bankAccountId,
      date: '2026-03-25',
      total_amount: 3000.00,
      reference: 'PAY-2026-042',
      allocated_artefacts: [
        {
          artefact_id: invoiceId,
          amount: 3000.00,
        },
      ],
    },
  });
  return response.data;
}

4. Ledger Accounts

async function getChartOfAccounts() {
  const response = await sageApi.get('/ledger_accounts', {
    params: {
      items_per_page: 200,
      visible_in: 'sales', // sales, purchases, banking, journals
    },
  });
  return response.data.$items;
}

async function getLedgerEntries(startDate: string, endDate: string) {
  const response = await sageApi.get('/ledger_entries', {
    params: {
      from_date: startDate,
      to_date: endDate,
      items_per_page: 200,
    },
  });
  return response.data.$items;
}

5. Bank Accounts and Reconciliation

async function listBankAccounts() {
  const response = await sageApi.get('/bank_accounts');
  return response.data.$items;
}

async function getBankTransactions(bankAccountId: string) {
  const response = await sageApi.get('/bank_transactions', {
    params: {
      bank_account_id: bankAccountId,
      from_date: '2026-01-01',
      to_date: '2026-03-31',
      items_per_page: 100,
    },
  });
  return response.data.$items;
}

Best Practices

  1. Use attributes parameter — Filter response fields to reduce payload: ?attributes=name,email,balance.
  2. Follow HAL _links — Use $next, $prev links for pagination instead of manually constructing URLs.
  3. Check country-specific tax rates — Query /tax_rates to get valid tax rate IDs for the business's country.
  4. Use If-Modified-Since — Sage supports conditional GET for efficient sync.
  5. Validate ledger account visibility — Not all accounts are valid for all transaction types. Check visible_in field.

Common Pitfalls

  • Country parameter in auth URL — Must match the user's Sage subscription country or auth fails.
  • HAL pagination — Results are in $items array, not a top-level array. Total count is in $total.
  • Tax rate IDs are country-specific — US, UK, and other countries have different tax rate ID formats.
  • Ledger account types — Using wrong account type for a transaction silently misclassifies it in reports.

Anti-Patterns

  • Ignoring _links in responses — Sage provides hypermedia links for a reason. Use them for navigation.
  • Hardcoding tax rates — Tax rates change. Query them dynamically.
  • Creating contacts without checking duplicates — Search by name/email first.
  • Skipping the business settings check — Query /business first to understand currency, country, and fiscal year settings.

Install this skill directly: skilldb add accounting-software-skills

Get CLI access →

Related Skills