Cash Records

7 views Markdown

The six endpoints that keep an installation's income and expense book.

Overview

The cash book holds an installation's income and expenses. Two kinds of record sit in it together: the ones invoices open by themselves once paid, and the ones an operator enters by hand.

The system mark tells them apart. A record from an invoice is a reflection: it cannot be changed or removed, because its source is the invoice itself. Records entered by hand can be edited.

The summary gives this month's income, expenses and the difference between them. Amounts in other currencies are converted into the local one before adding.

Reference

Listing the Records

get/api/v1/admin/invoices/cash
Invoices/GetCashEntries admin

Returns the income and expense records, filtered.

Query 10
pageintWhich page.
limitintRecords per page. A value out of range falls back to the default.
searchstringSearches the description, the staff member and the invoice number.
typestringLimits it to income or to expenses.
currency_idintFilters by currency.
staff_idintFilters by staff member.
amountstringFilters by amount.
amount_opstringWhich way the amount comparison runs.
descriptionstringSearches the description.
daterangestringFilters between two dates.
Response fields data[] — 13 + meta — 4
idintThe record id.
invoice_idintThe invoice it belongs to. Zero says the record was entered by hand.
typestringWhether it is income or an expense: income, expense.
amountfloatThe amount.
currency_idintThe currency it is in.
staff_idintThe staff member who entered it.
payment_methodstring | nullHow the money came in or went out.
descriptionstringWhat the record is for.
created_atstring | nullThe record date.
is_systemboolWhether it came from an invoice. When true the record cannot be changed.
invoice_numberstring | nullThe number of the invoice it belongs to.
staff_namestring | nullThe staff member's name.
clientobject | nullThe client on that invoice. It comes back in the list alone.
totalintHow many match the filter. It comes back under meta.
pageintThe page you are on.
limitintThe page size.
next_pageintThe next page. Zero means you are on the last one.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/invoices/cash?type=expense' \
  -H "Authorization: Bearer $API_KEY"
const url = new URL('https://panel.example.com/api/v1/admin/invoices/cash');
url.searchParams.set('type', 'expense');

const res  = await fetch(url, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/invoices/cash?' . http_build_query(['type' => 'expense']));
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The list carries records FROM INVOICES too; filter on the system mark for hand-entered ones.
$manual = array_filter(
    Api::Invoices()->GetCashEntries()['data'],
    fn ($e) => ! $e['is_system'],
);

Adding a Record

post/api/v1/admin/invoices/cash
Invoices/CreateCashEntry admin

Puts an income or expense record into the book by hand.

Body 7
typestringreqWhether it is income or an expense: income, expense.
currency_idintreqThe currency of the amount.
amountfloatreqThe amount. It has to be above zero.
descriptionstringWhat the record is for.
payment_methodstringHow the money moved. A method that is switched off ends up empty.
staff_idintThe staff member entering it. Left out, the key's owner is written.
created_atstringThe record date. Left out, the moment of the call is used.
Response fields 201 — data — 13
dataobjectThe record opened. Same shape as a list item.
Errors 5
invalid_type422The type is neither income nor expense.
currency_required422No currency was given.
invalid_amount422The amount is zero or below.
create_failed422The record could not be opened.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/invoices/cash' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"type":"expense","currency_id":840,"amount":49.9,"description":"Sunucu gideri"}'
const res = await fetch('https://panel.example.com/api/v1/admin/invoices/cash', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    type: 'expense',
    currency_id: 840,
    amount: 49.9,
    description: 'Server cost',
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/invoices/cash');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'type'        => 'expense',
        'currency_id' => 840,
        'amount'      => 49.9,
        'description' => 'Server cost',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// A hand-entered record IS NOT TIED to an invoice; those only come from the payment flow.
Api::Invoices()->CreateCashEntry([
    'type'        => 'expense',
    'currency_id' => 840,
    'amount'      => 49.9,
]);

Reading the Cash Summary

get/api/v1/admin/invoices/cash/summary
Invoices/GetCashSummary admin

Returns this month's income, expenses and balance.

Response fields data — 4
currency_idintThe currency the totals are in.
incomefloatThis month's income.
expensefloatThis month's expenses.
balancefloatThe expenses taken from the income.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/invoices/cash/summary' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/invoices/cash/summary', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/invoices/cash/summary');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The summary is ALWAYS this month; for another period pull the list by date and add it up.
$sum = Api::Invoices()->GetCashSummary()['data'];

Reading One Record

get/api/v1/admin/invoices/cash/{cash_id}
Invoices/GetCashEntry admin

Returns a single cash record.

Response fields data — 13
idintThe record id.
invoice_idintThe invoice it belongs to. Zero says the record was entered by hand.
typestringWhether it is income or an expense: income, expense.
amountfloatThe amount.
currency_idintThe currency it is in.
staff_idintThe staff member who entered it.
payment_methodstring | nullHow the money came in or went out.
descriptionstringWhat the record is for.
created_atstring | nullThe record date.
is_systemboolWhether it came from an invoice. When true the record cannot be changed.
invoice_numberstring | nullThe number of the invoice it belongs to.
staff_namestring | nullThe staff member's name.
clientobject | nullThe client on that invoice. It comes back in the list alone.
Errors 2
not_found404No such cash record.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/invoices/cash/51' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch(`https://panel.example.com/api/v1/admin/invoices/cash/${cashId}`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/invoices/cash/' . $cashId);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The client details DO NOT arrive here; that field is filled by the list join alone.
$entry = Api::Invoices()->GetCashEntry(['cash_id' => $cashId])['data'];

Updating a Record

patch/api/v1/admin/invoices/cash/{cash_id}
Invoices/UpdateCashEntry admin invoice records are locked

Changes a cash record entered by hand.

Body 7
typestringWhether it is income or an expense.
currency_idintThe currency of the amount.
amountfloatThe amount.
descriptionstringWhat the record is for.
payment_methodstringHow the money moved.
staff_idintThe staff member who entered it.
created_atstringThe record date.
Response fields data — 13
dataobjectThe record as it now stands. Same shape as a list item.
Errors 6
not_found404No such cash record.
system_record422A record tied to an invoice cannot be touched.
invalid_type422The type is neither income nor expense.
currency_required422No currency was given.
invalid_amount422The amount is zero or below.
insufficient_scope403The key lacks the required scope.
Request
curl -X PATCH 'https://panel.example.com/api/v1/admin/invoices/cash/51' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"amount":59.9,"description":"Sunucu gideri (guncellendi)"}'
const res = await fetch(`https://panel.example.com/api/v1/admin/invoices/cash/${cashId}`, {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ amount: 59.9, description: 'Server cost (updated)' }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/invoices/cash/' . $cashId);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['amount' => 59.9]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// A record marked as system gives a 422; in a bulk fix, filter on that field first.
$entry = Api::Invoices()->GetCashEntry(['cash_id' => $cashId])['data'];
if (! $entry['is_system'])
    Api::Invoices()->UpdateCashEntry(['cash_id' => $cashId, 'amount' => 59.9]);

Removing a Record

delete/api/v1/admin/invoices/cash/{cash_id}
Invoices/DeleteCashEntry admin

Removes a cash record entered by hand.

Response fields data — 2
deletedboolWhether the delete ran.
idintThe id of the record removed.
Errors 4
not_found404No such cash record.
system_record422A record tied to an invoice cannot be touched.
delete_failed422The record could not be removed.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/invoices/cash/52' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch(`https://panel.example.com/api/v1/admin/invoices/cash/${cashId}`, {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/invoices/cash/' . $cashId);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// A record from an invoice is removed on the INVOICE side; it cannot go from here.
Api::Invoices()->DeleteCashEntry(['cash_id' => $cashId]);

Pitfalls

A record from an invoice cannot be changed

A cash record tied to an invoice is read-only: the update and delete calls are refused. That is deliberate, because the record reflects the invoice and cannot contradict it. To correct an amount, go back to the invoice and the cash record follows.

The list mixes the two kinds

The listing returns hand-entered records and invoice ones together, with no filter to separate them. To add up only the hand-entered expenses in a report, sift on the system mark yourself; otherwise invoice income lands in the sum.

The summary is always this month

The summary takes no period and always gives the month you are in. For last month or a quarter, pull the list by date and add it up yourself. You also have to handle the currency conversion, because the list does not arrive converted the way the summary does.

The detail carries no client

The client on the linked invoice arrives in the list alone, and reading one record leaves that field empty. The list performs a join and the detail does not. To get the client for a single record, read the invoice from its number.

A hand-entered record cannot be tied to an invoice

The create endpoint always opens a standalone record, and no invoice number ties one to an invoice. Records with that link come from the payment flow alone. So entering income by hand and also marking the same invoice paid shows the same money twice in the book.

Was this helpful?

Thanks for your feedback!

Still Need Help?

Our support team is here around the clock for anything you can't find above.