---
title: "How to Integrate with the Horus Software API"
description: "A practical guide for developers building integrations with Horus Office. Covers OAuth 2 registration, authentication flow, API conventions, sideloading, and all key resources including folders, companies, book entries, invoices, and account histories."
author: "GJ"
published: "2026-03-13T08:00+00:00"
updated: "2026-03-13T08:21:28.106Z"
url: "https://www.apideck.com/blog/horus-software-api-integration-guide"
category: "Accounting"
tags: ["Accounting", "Guides & Tutorials"]
---

# How to Integrate with the Horus Software API

> **Disclaimer:** Apideck does not currently support Horus Office as a connector yet. This guide is published for informational purposes to help developers who are building a direct integration with the Horus API.

# How to Integrate with the Horus Software API

*A practical guide for developers building integrations with Horus Office*

[Horus Office](https://www.horussoftware.be/nl/) is an accounting and business management platform used by fiduciaries and SMBs primarily in Belgium and Luxembourg. Its API gives developers programmatic access to folders, companies, book entries, invoices, account histories, and more, making it possible to build tight integrations between your application and your clients' accounting data.

If you are looking to connect multiple accounting platforms through a single, unified interface, Apideck provides a unified accounting API with connectors for dozens of platforms. But if Horus Office is your specific target, read on — this guide covers everything you need.

We will walk through registering your integration, completing the OAuth 2 authorization flow, understanding the API's request and response conventions, and working with the key resources available.

## 1. Getting Started: Registering Your Integration

The Horus API uses OAuth 2, and before any of that can happen you need to register your integration. Unlike APIs that offer instant self-service sign-up, Horus requires a registration request by email to:

**developer@horus-software.be**

Use the subject line "New Api Integration Request" and include:

- The name of your integration
- A contact person and their email address
- A short description of how and why you intend to use the API
- Your redirect URL(s) that will be used in the OAuth flow

Once approved, you will receive a `client_id` and a `client_secret`. Keep the `client_secret` server-side at all times — never expose it in client-side code, mobile apps, or public repositories.

> **Key concept:** Each fiduciary or SMB running Horus Office has its own dedicated database with its own API endpoint. Your client credentials work across all of them, but the per-user API endpoint is only revealed during the OAuth flow.

## 2. Authentication: The OAuth 2 Flow

Horus uses the standard authorization code flow. Here is the full sequence.

### Step 1: Redirect the user to the authorization page

Send the user to the Horus authorization endpoint. This always starts at the central `my-horus.com` domain regardless of which client they are connecting:

```
https://my-horus.com/fr/api/oauth2/authorize
  ?client_id=ck_5ekn8gsdr4h95fa
  &response_type=code
  &state=random_unique_string
  &redirect_uri=https://your-app.com/oauth/callback
```

The `state` parameter is optional but recommended as a CSRF protection measure. The user will log in, select which license (company database) to connect, and grant access to your integration.

### Step 2: Handle the redirect callback

If the user approves, Horus redirects back to your `redirect_uri` with three query parameters:

- `code` — the short-lived authorization code
- `state` — echoed back if you sent one
- `api_url` — the specific API endpoint for this user's license

```
https://your-app.com/oauth/callback
  ?code=Hjhfn45k
  &state=random_unique_string
  &api_url=https://horusapi.myfiduciary.com
```

Store the `api_url`. Every subsequent API call for this user must go to their specific endpoint. If the user denies access, the redirect instead includes an `error=access_denied` parameter.

> **Important:** The authorization code is valid for only 2 minutes. Exchange it for an access token immediately.

### Step 3: Exchange the code for an access token

POST to the `oauth2/access_token` path at the `api_url` you just received:

```bash
curl -X POST https://horusapi.myfiduciary.com/oauth2/access_token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'client_id=ck_XXXX' \
  -d 'client_secret=cs_XXXX' \
  -d 'code=Hjhfn45k' \
  -d 'grant_type=auth_code' \
  -d 'redirect_uri=https://your-app.com/oauth/callback'
```

The response is a standard Bearer token payload:

```json
{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "h4vtwzwlfip68zx...",
  "refresh_token": "r3fr3sh..."
}
```

Attach the access token to subsequent requests using the Authorization header:

```
Authorization: Bearer h4vtwzwlfip68zx...
```

### Step 4: Refreshing the access token

Access tokens expire after 1 hour. Use the refresh token to get a new pair without requiring the user to re-authorize. Refresh tokens are single-use — each refresh issues a fresh pair.

```bash
curl -X POST https://horusapi.myfiduciary.com/oauth2/access_token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'client_id=ck_XXXX' \
  -d 'client_secret=cs_XXXX' \
  -d 'refresh_token=r3fr3sh...' \
  -d 'grant_type=refresh_token'
```

Refresh tokens remain valid until the user revokes them or uninstalls your integration.

## 3. API Conventions

### Endpoint format

The API uses a JSON-RPC-style naming convention. Endpoints follow the pattern:

```
https://{api_url}/RESOURCE.ACTION
```

For example: `/folders.list`, `/companies.info`, `/invoices.sales.new`. All requests must use HTTPS.

### GET vs POST

- Simple reads use GET with query parameters (e.g., `/users.me`, `/folders.info?Id=...`)
- Filtered or complex reads use POST with a JSON body (e.g., `/companies.list`, `/book-entries.list`)
- All create and update operations use POST with a JSON body

For POST requests, set the `Content-Type` header to `application/json`. URL-encoded form data is also accepted but JSON is strongly recommended, especially for nested structures like invoice lines.

### Response structure

All successful read responses wrap their payload in a top-level `Data` field:

```json
{
  "Data": {
    "Id": "14feb291-8b47-44c6-8cbb-97a3f24c33c8",
    "FirstName": "Robert",
    "LastName": "Doe",
    "IsAccountant": true
  }
}
```

Create and update operations return only the `Id` of the affected entity:

```json
{
  "Id": "14feb291-8b47-44c6-8cbb-97a3f24c33c8"
}
```

### Error handling

Non-authorization errors return a structured object with three fields:

```json
{
  "ErrorType": "not_found",
  "Details": "The requested book entry was not found.",
  "Message": "Element was not found"
}
```

Standard HTTP status codes used:

| Code | Meaning |
|------|---------|
| 200 | OK |
| 400 | Bad Request — invalid data or reference to a non-existing resource |
| 401 | Unauthorized — invalid, expired, or missing access token |
| 403 | Forbidden — the user is not allowed to access this resource |
| 404 | Not Found |
| 500 | Internal Server Error |

### Gzip compression

All endpoints support Gzip. Add a `Content-Encoding: gzip` header to your request and responses will be compressed automatically, which makes a noticeable difference on larger listing responses.

## 4. Sideloading Related Data

Sideloading lets you fetch related entities as part of a single request, avoiding extra round trips. For example, when listing book entries you can also pull the associated company in one call.

To sideload a relationship, add an `Include` parameter to your POST body using dot notation:

```json
{
  "FolderId": "83f26e75-f474-4b09-a1d3-aa698e18fbf0",
  "Include": "CompanyDetail.Company"
}
```

Sideloaded data comes back in a separate `Included` section, keyed by type. Multiple relationships can be sideloaded at once using comma separation: `"Include": "CompanyDetail.Company,Daybook,Folder"`

Sideloading is supported on: `book-entries.list`, `account-histories.list`, `companies.list`, and `invoices.sales.outstanding.list`.

![Horus API Docs](//images.ctfassets.net/d6o5ai4eeewt/6dapInxP5bd9a3FX2QpxQK/5d2ef2f3f9ac309c8036e42cecdcf059/Screenshot_2026-03-12_at_22.49.42.png)

## 5. Key Resources

### Licenses and Users

After authentication, your first calls establish context: which license is connected and who the authenticated user is.

- GET `/licenses.me` — Get the license for the authenticated user
- GET `/licenses.main` — Get the fiduciary's main license
- GET `/users.me` — Get the current authenticated user
- GET `/users.list` — List all users on this license
- GET `/users.info` — Get a specific user by Id

### Folders

A folder represents a company's accounting configuration. Fiduciaries manage many folders, one per client company. Most subsequent API calls require a `FolderId`.

- POST `/folders.list` — List folders (filter by name, VAT number, legislation, etc.)
- GET `/folders.info` — Get details for a specific folder

When listing folders, pass a `Mode` parameter: `0` for all accessible folders, `1` for folders you are responsible for.

### Companies

Companies are the legal entities associated with a folder — customers, suppliers, or beneficiaries. They hold VAT numbers, IBAN details, payment modes, and contact information.

- POST `/companies.list` — List companies with rich filtering
- GET `/companies.info` — Get a specific company by Id
- POST `/companies.new` — Create a new company
- POST `/companies.update` — Update an existing company

Note the naming convention: creation uses `.new`, not `.create`. This is consistent across all writable endpoints in the API.

### Book Entries

Book entries are the core accounting records. There is no single generic `/book-entries.create` endpoint — each entry type has its own endpoint:

- POST `/book-entries.list` — List book entries (sideloading supported)
- GET `/book-entries.info` — Get a specific book entry
- POST `/invoices.purchases.new` — Create a purchase invoice
- POST `/invoices.sales.new` — Create a sales invoice
- POST `/book-entries.banks.new` — Create a bank entry
- POST `/book-entries.financials.new` — Create a financial/credit card entry
- POST `/book-entries.miscellaneous-operations.new` — Create a miscellaneous operation

### Invoices

- POST `/invoices.sales.new` — Create a sales invoice
- POST `/invoices.sales.update` — Update a sales invoice
- POST `/invoices.sales.outstanding.list` — List open sales invoices with payment status
- POST `/invoices.purchases.new` — Create a purchase invoice
- POST `/invoices.purchases.update` — Update a purchase invoice

Both new endpoints accept optional attached documents. Use `multipart/form-data` with a `file` part and a `body` part (JSON) when attaching a PDF.

### Account Histories and Matching

Account histories track individual debit and credit movements. The matching endpoints let you reconcile open items.

- POST `/account-histories.list` — List account history entries (sideloading supported)
- GET `/account-histories.info` — Get a specific account history entry
- GET `/matching.next-number.customer` — Get the next available match number
- POST `/matching` — Match a set of account history entries

## 6. A Practical Integration Walkthrough

Here is a typical sequence for building a read-only sync:

1. Complete the OAuth flow and store the `api_url` and `refresh_token` securely, indexed by user.
2. Call `/users.me` and `/licenses.me` to establish context.
3. Call `/folders.list` to retrieve all folders the user has access to. Store folder IDs.
4. For each folder, call `/companies.list` to sync company records. Use `ModifiedAfter` on subsequent syncs.
5. Pull `/book-entries.list` with sideloading to get enriched transaction data. Or use `/invoices.sales.outstanding.list` to focus on open receivables.
6. Implement a background token refresh job. Access tokens expire after 1 hour; refresh proactively rather than waiting for a 401.

If you are building this pattern across multiple accounting platforms, [Apideck's accounting API](https://www.apideck.com/accounting) normalises these concepts into a single unified schema, so you write the integration once and connect to Horus, QuickBooks, Xero, and others through one interface.

## 7. Tips and Best Practices

- Store both the `access_token` and `api_url` per user. The `api_url` is permanent for a given license.
- Implement proactive token refresh. Tokens expire after 1 hour; do not wait for a 401 to find out.
- Use Gzip compression on all requests. Large listing responses compress significantly.
- Prefer JSON request bodies over URL-encoded, especially for nested structures like invoice lines.
- Use `ModifiedAfter` filters when syncing large datasets incrementally.
- Leverage sideloading to reduce round trips.
- The API is backwards-compatible by design: new optional fields and endpoints are non-breaking. Breaking changes are communicated by email in advance.

## Wrapping Up

The Horus Software API covers the full breadth of accounting operations: reading and writing folders, companies, book entries, invoices, account histories, and matching. The OAuth 2 flow with per-license API endpoints is the most unusual aspect initially, but once that is in place the rest follows consistent conventions.

For questions or to register your integration, reach out to developer@horus-software.be. The full API reference is available at horussoftwareapi.docs.apiary.io.

If your product needs to connect to multiple accounting platforms beyond Horus, [Apideck](https://www.apideck.com) offers a unified accounting API that normalises data from Horus, QuickBooks, Xero, Sage, and many others into a single integration.