yeganemehr/hesabfa-api

PHP client for the Hesabfa accounting app frontend API (core.hesabfa.com).

Maintainers

Package info

github.com/yeganemehr/hesabfa-api

pkg:composer/yeganemehr/hesabfa-api

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-master 2026-08-06 18:46 UTC

This package is auto-updated.

Last update: 2026-08-06 18:51:03 UTC


README

A standalone PHP (Guzzle) client for the Hesabfa accounting app's frontend API (https://core.hesabfa.com/api/). It speaks the same protocol the web app uses: an API-TOKEN session cookie, an optional X-XSRF-TOKEN, and a HESABFA-BUSINESS-KEY header that scopes calls to one business + financial year.

Install

composer require yeganemehr/hesabfa-api

Authenticate

Log in to obtain fresh credentials:

use Yeganemehr\Hesabfa\Hesabfa;

$hesabfa = Hesabfa::login('you@example.com', 'password');
// persist these to skip re-login next time:
$apiToken  = $hesabfa->apiToken();
$xsrfToken = $hesabfa->xsrfToken();

Or reuse a token you already have (e.g. copied from the browser's API-TOKEN cookie via DevTools → Application → Cookies). Session tokens expire, so refresh when calls return 401:

$hesabfa = Hesabfa::withToken($apiToken, $xsrfToken);

Select a business

Business-level calls (contacts, products, documents, …) need a business key. List your businesses first (account-level, no key), then scope the client:

$businesses = $hesabfa->businesses()->list();               // account-level; each has a "key"
$years      = $hesabfa->businesses()->loadBusinessAndYears($businesses[0]['key']);
$scoped     = $hesabfa->forBusiness($years['financialYears'][0]['key']);   // scope to a financial year

loadBusinessAndYears() needs the business key (from list()); it returns that business plus its financialYears, each of which has its own key. Use a year key with forBusiness() for transactional calls. forBusiness() returns a new immutable instance — the original stays unscoped, so one process can talk to several businesses at once.

Use the resources

$scoped->contacts()->list(['take' => 20, 'skip' => 0]);   // DevExtreme loadOptions
$scoped->contacts()->get(42);
$scoped->contacts()->save(['Name' => 'Acme', 'Code' => 'C-100']);
$scoped->contacts()->delete(42);

$scoped->products()->activeList();
$scoped->products()->getQuantity(['ids' => [1, 2, 3]]);
$scoped->documents()->findByNumber(1001);
$scoped->receipts()->itemsList(['take' => 50]);
$scoped->banks()->list();
$scoped->accounts()->merge(mainId: 5, accountId: 9);
$scoped->settings()->getSettings(['InvoiceSetting']);

Invoices & vouchers

Hesabfa keeps all invoices and money vouchers on two endpoints (receipt/*, payment/*) keyed by type. Each typed resource below applies the right endpoint + filter for you (the raw list endpoints reject an unfiltered request):

$scoped->sales()->list(['take' => 20]);            // sales invoices
$scoped->bills()->list();                          // purchase invoices
$scoped->salesReturns()->list();
$scoped->purchaseReturns()->list();
$scoped->payments()->list();                       // money-pay vouchers
$scoped->expenses()->list();
$scoped->receipts()->list();                       // money-receive vouchers

A caller filter is combined with the resource's own filter, e.g. $scoped->sales()->list(['filter' => ['contactId', '=', 42]]). get(), save(), and delete() target the underlying receipt/* / payment/* document. Note: save() writes to your real ledger and its payload shape is unverified — set the type fields yourself and test on a draft first.

Typed convenience methods exist for contacts, products, documents, banks, accounts, businesses, settings, and the invoice/voucher resources above. Every other endpoint is reachable by path:

$scoped->get('contact/get/42');
$scoped->post('contact/list', ['take' => 20]);
$scoped->delete('product/delete/9');
$scoped->download('report/export/...');   // raw bytes

Responses are returned as decoded arrays (*/list endpoints return ['data' => [...], 'total' => N]).

Errors

Non-2xx responses throw a Yeganemehr\Hesabfa\Exceptions\HesabfaException subclass:

Exception When
AuthenticationException 401 — token missing/expired
RateLimitException 499, or 400 Execution-Limit-Retry-In<sec> (see ->retryAfter)
ValidationException other 400 (raw error code in ->body)
ApiException 404 and other 4xx
ServerException 5xx
TransportException network/connection failure

All carry ->status and ->body.

Development

composer check   # pint (style) + phpstan + phpunit

Tests use Guzzle's MockHandler — they make no network calls.