How to Integrate with the Horus Software API

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.

GJGJ

GJ

9 min read
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 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:

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:

{
  "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.

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:

{
  "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:

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

Error handling

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

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

Standard HTTP status codes used:

CodeMeaning
200OK
400Bad Request β€” invalid data or reference to a non-existing resource
401Unauthorized β€” invalid, expired, or missing access token
403Forbidden β€” the user is not allowed to access this resource
404Not Found
500Internal 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:

{
  "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

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 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 offers a unified accounting API that normalises data from Horus, QuickBooks, Xero, Sage, and many others into a single integration.

Ready to get started?

Scale your integration strategy and deliver the integrations your customers need in record time.

Ready to get started?
Talk to an expert

Trusted by fast-moving product & engineering teams

JobNimbus
Blue Zinc
Drata
Octa
Nmbrs
Apideck Blog

Insights, guides, and updates from Apideck

Discover company news, API insights, and expert blog posts. Explore practical integration guides and tech articles to make the most of Apideck's platform.