Check the connection, identify your company and upload an expense document — in about five minutes.
In this guide you will verify the connection, read your company details, list partners and finally upload an expense document. The whole flow takes about five minutes.
curl, or any HTTP client (HTTPie, Postman, …)Export the token as an environment variable so the examples below are runnable:
export QUICK_API_TOKEN="XXXXXXXX.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
Never commit your token
The API token grants full access to your company's accounting data. It must never end up in version control, a client-side bundle or an error report.
Start with GET /1/pulse/ — it validates the token and returns the state of the company's cash and bank accounts:
curl https://api.quick.zenheads.hu/1/pulse/ \
-H "Authorization: Token $QUICK_API_TOKEN"
{
"summary": 1284500,
"accounts": [
{
"id": 12,
"name": "Bank – HUF",
"account_number": "12345678-00000000-00000000",
"currency": "HUF",
"current_balance": "1284500.00",
"last_updated": "2026-07-14T08:12:00Z"
}
]
}
If the response is 401 Unauthorized, the token is wrong or missing — double-check the Authorization header.
GET /2/company-info/ returns the base data of the token's company. The id field is your company identifier, which you can pass in the Quick-Company-Id header when needed.
curl https://api.quick.zenheads.hu/2/company-info/ \
-H "Authorization: Token $QUICK_API_TOKEN"
{
"id": 4821,
"name": "Riport Applications Kft.",
"tax_account_number": "12345678-2-42",
"default_currency_name": "HUF",
"advanced_accounting": true
}
List endpoints return a paginated envelope — see the Pagination section below for how to page through the rest:
curl "https://api.quick.zenheads.hu/1/partners/?page_size=2" \
-H "Authorization: Token $QUICK_API_TOKEN"
{
"count": 128,
"next": "https://api.quick.zenheads.hu/1/partners/?page=2&page_size=2",
"previous": null,
"results": [
{
"id": 5501,
"name": "Example Customer Ltd.",
"tax_number": "12345678-2-42",
"city": "Budapest",
"zip_code": "1051",
"is_customer": true,
"is_vendor": false
}
]
}
Expenses are recorded by uploading the document itself: POST /2/expenses/create/ expects the file contents Base64-encoded in the content field, together with the file name. A single call accepts at most 5 documents.
CONTENT=$(base64 -i invoice.pdf)
curl -X POST https://api.quick.zenheads.hu/2/expenses/create/ \
-H "Authorization: Token $QUICK_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"expenses\": [
{ \"filename\": \"invoice.pdf\", \"content\": \"$CONTENT\" }
],
\"source\": \"public_api\"
}"
A successful call returns 201 Created with the identifiers of the expenses accepted for processing:
{
"processed": [90231]
}
All documents in the call are validated up front — one rejected file fails the whole call with a 400 and no expenses are created, rather than leaving you with a partial batch.
Data extraction runs asynchronously after this call returns. The system extracts the expense data from the uploaded document; the extracted fields become available on the expense once processing completes, and can be read with GET /1/expenses/ or GET /2/expenses/{expenseId}/.
There is no single rule splitting /1/ from /2/ — it reflects when each resource was added, not a read/write or basic/advanced split. Bulk expense actions (approve, check, export) are /1/ only; plain reads like company info and companies are /2/ only. Use this table to find where a resource actually lives:
| Resource | List | Detail | Create | Update |
|---|---|---|---|---|
| Expenses | GET /2/expenses/ |
GET /2/expenses/{expenseId}/ |
POST /2/expenses/create/ |
PATCH /2/expenses/{expenseId}/update/ |
| Incomes | GET /1/incomes/ |
GET /1/incomes/{incomeId}/ |
— | PATCH /2/incomes/{incomeId}/update/ |
| Documents | GET /1/documents/ |
GET /2/documents/{documentId}/ |
POST /2/documents/create/ |
POST /2/documents/update/{documentId}/ |
| Document files / artifacts | GET /1/documents/files/ |
— | POST /1/artifacts/expense/, POST /1/artifacts/income/ |
— |
| Partners | GET /1/partners/ |
— | — | — |
| Payments / accounts | GET /1/payments/, GET /1/accounts/ |
— | — | — |
| Monthly taxes | GET /2/monthly-taxes/ |
— | POST /2/taxes/create/ |
POST /2/taxes/update/ |
| Monthly salaries | GET /2/monthly-salaries/ |
— | POST /2/salaries/create/ |
POST /2/salaries/update/ |
| Tax codes | GET /1/tax-codes/ |
— | — | — |
| Ledger numbers, VAT categories, tags, expense/revenue types | GET /2/… |
— | POST /2/…/create/ |
POST/PATCH /2/…/update/ |
| Companies, company info, user profile, audit XML | GET /2/… |
— | — | PATCH /2/company-info/update/{companyId}/ |
| Pulse | GET /1/pulse/ |
— | — | — |
Worth knowing alongside the table:
approve, unapprove, check, uncheck, export, quarantine-accept) are /1/ only and have no /2/ equivalent. GET /1/expenses/ and GET /1/expenses/{expenseId}/ are deprecated — use the /2/ list and detail./2/ list or detail exists yet, so you read via /1/ and write via /2/.POST /2/documents/search/ and POST /2/expenses/artifact-search/ are duplicate-detection lookups (by name and size), not list endpoints.GET /1/monthly-taxes/, GET /1/monthly-salaries/ and the /1/taxes/* and /1/salaries/* create/update/delete endpoints are deprecated — use the /2/ forms./2/ only, no /1/ equivalent ever existed.A single integration is free to mix /1/ and /2/ calls — there's no version-wide switch to flip.
List endpoints are paginated, but the response shape depends on which version the endpoint belongs to.
/1/ endpoints: page-basedControl the page with page and page_size. The response includes a total count, and next/previous are page-numbered URLs you can construct yourself:
curl "https://api.quick.zenheads.hu/1/partners/?page_size=2" \
-H "Authorization: Token $QUICK_API_TOKEN"
{
"count": 128,
"next": "https://api.quick.zenheads.hu/1/partners/?page=2&page_size=2",
"previous": null,
"results": [
{
"id": 5501,
"name": "Example Customer Ltd.",
"tax_number": "12345678-2-42",
"city": "Budapest",
"zip_code": "1051",
"is_customer": true,
"is_vendor": false
}
]
}
/2/ endpoints: cursor-basedOnly page_size is yours to set. next/previous are opaque cursor URLs — follow them as given, do not construct or decode the cursor value yourself. There is no count: cursor pagination cannot compute a total efficiently.
curl "https://api.quick.zenheads.hu/2/accounting/ledger-numbers/?page_size=2" \
-H "Authorization: Token $QUICK_API_TOKEN"
{
"next": "https://api.quick.zenheads.hu/2/accounting/ledger-numbers/?cursor=cD0yMDI2LTA4LTAxKzEwJTNBMTIlM0EwMA%3D%3D&page_size=2",
"previous": null,
"results": [
{ "id": 12, "name": "Bank", "code": "384" },
{ "id": 13, "name": "Cash", "code": "381" }
]
}
Not every /2/ endpoint paginates: master-data lists such as GET /2/tags/, GET /2/expense-types/ and GET /2/revenue-types/ return their rows directly, with no envelope. The API Reference shows which is which — a paginated one declares next/previous/results.
No more pages
Both styles signal the end the same way: next is null.
results, not an arrayMost lists put an array of records in results. These five put a single object there instead — the paginated page under its own key, next to the reference sets needed to resolve the ids those records carry:
| Endpoint | The page sits under | Reference sets alongside it |
|---|---|---|
GET /1/expenses/ |
expenses |
partners, currencies, tags, expense_types, accounts |
GET /1/incomes/ |
incomes |
partners, currencies, tags, revenue_types |
GET /2/expenses/ |
expenses |
partners, currencies, tags, expense_types, accounts |
GET /2/monthly-salaries/ |
monthly_salaries |
tags |
GET /2/monthly-taxes/ |
monthly_taxes |
tags |
The expense list is the fullest example — the page of invoices sits under expenses, and partner, currency, expense_type and account ids are numbers, not embedded objects:
{
"next": "https://api.quick.zenheads.hu/2/expenses/?cursor=cD0yMDI2LTA4LTAxKzEwJTNBMTIlM0EwMA%3D%3D&page_size=2",
"previous": null,
"results": {
"expenses": [
{
"id": 90231,
"invoice_number": "INV-2026-001",
"partner": 5501,
"partner_name": "Example Kft.",
"currency": 1,
"accounting_status": "pending"
}
],
"partners": [{ "id": 5501, "name": "Example Kft." }],
"currencies": [{ "id": 1, "name": "HUF" }],
"tags": [],
"expense_types": [],
"accounts": []
}
}
Only results.expenses is paginated — the reference sets describe the whole page, so page_size applies to the invoices alone. Requesting fields=accounting adds vats and vat_categories to the same object.
The other four follow the same rule with their own keys. GET /1/expenses/ and GET /1/incomes/ are page-based, so their envelope also carries a count; the /2/ ones are cursor-based and do not.