GL transactions
Direct journal entries. The most powerful endpoint here, and the one with the strictest rules.
A transaction set must balance: the signed amounts must sum to exactly zero. An unbalanced set is rejected with 422 gl_set_unbalanced and nothing is written.
A set needs at least two lines. A one-line journal entry is not a journal entry.
The object#
| Field | Type | Description |
|---|---|---|
| idread-only | integer | NolaPro id. |
| voucher | string(50) | Voucher number identifying the entry set. |
| description | string(50) | Description of the journal entry. |
| comments | string(50) | Free-form comments on the entry. |
| wherefrom | integer | Module that wrote the voucher: 1 = AP, 2 = AR, 3 = GL journal entry, 4 = inventory, 6 = payroll. |
| statusread-only | integer | 0 = unposted, 1 = posted, 2 = year-end closing entry. |
| cancel | boolean | Cancelled. NolaPro cancels rather than deletes, so a cancelled row is still readable. |
| standardset | boolean | True when the voucher is a recurring standard journal set - a template that generates entries, not a posted entry itself. |
| entrydate | string | When the record was created. |
| posteddateread-only | string | When the voucher was posted. |
| post2date | string | The GL period the entry landed in. |
| lastchangedateread-only | string | Last modification. Drives modifiedsince. |
| postuserid | integer | Postuserid. Id only. |
| postuser | string(50) | The name of the referenced genusers, instead of the id. Send this or postuserid, not both unless they agree. |
| bankdepositid | integer | Bankdepositid. Id only. |
| referencetable | string(255) | Table of the document that produced the voucher (arinvoice, apbill, ...); empty for manual journal entries. |
| referenceid | integer | Referenceid. Id only. |
| taj | boolean | A year-end tax adjusting entry. These are allowed into a closed MONTH, but never a closed year. |
| canceledbyvoucherid | integer | Canceledbyvoucherid. Id only. |
| canceledbyvoucher | string(50) | The voucher of the referenced gltransactions, instead of the id. Send this or canceledbyvoucherid, not both unless they agree. |
| cancelsvoucherid | integer | Cancelsvoucherid. Id only. |
| cancelsvoucher | string(50) | The voucher of the referenced gltransactions, instead of the id. Send this or cancelsvoucherid, not both unless they agree. |
| reversedbyvoucherid | integer | Reversedbyvoucherid. Id only. |
| reversedbyvoucher | string(50) | The voucher of the referenced gltransactions, instead of the id. Send this or reversedbyvoucherid, not both unless they agree. |
| reversesvoucherid | integer | Reversesvoucherid. Id only. |
| reversesvoucher | string(50) | The voucher of the referenced gltransactions, instead of the id. Send this or reversesvoucherid, not both unless they agree. |
| standardsetperiodends | string | Date the recurrence stops. |
| standardsetperiodendscount | integer | Stop after this many recurrences. |
| standardsetperiodstarts | string | Date the recurrence starts. |
| standardsetperiodtype | integer | How the standard set recurs: 0 = manually as needed, 1 = daily, 2 = weekly, 3 = monthly, 4 = yearly. |
| standardsetperiodvalue | integer | Recurs every X days/weeks/months/years - this is the X. |
| standardsetposthow | boolean | True posts the generated entries as one set; false posts them individually. |
| entrydayread-only | string | Entry date as a plain date, derived from entrydate for period math. Maintained by the model, so it cannot be written through the API. |
| entrymonthread-only | string | First day of the entry month, derived from entrydate for period math. Maintained by the model, so it cannot be written through the API. |
| standardsetzeroactivepost2periodread-only | integer | STORED GENERATED in the database. Read-only - MySQL computes it. |
| externalid | string(100) | Your own key. Scoped to your company. |
| lines | array of GlLine |
Endpoints#
The lines of one journal voucher.
Every debit and credit the voucher wrote.
/gltransactions publishes the voucher header - who wrote it, when, what it totals. This is what it actually did to the ledger, which is what reconciliation and trial-balance work needs. Amounts are signed and the lines of one voucher sum to zero.
The rows carry no company of their own; they are scoped through the voucher, so a voucher in another company answers 404 rather than an empty page.
Requires scope gltransactions:read.
| Name | In | Type | Notes |
|---|---|---|---|
| idrequired | path | integer | The voucher id. |
| Status | Code | Meaning |
|---|---|---|
| 422 | unknown_value | No such parent record in this company. The same answer whether it does not exist or belongs to someone else, so a caller cannot probe another company's ids. |
curl \ 'https://acme.nolapro.com/!/api/v2/gltransactions/104/lines' \ -H 'Authorization: Bearer $NP_TOKEN'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/gltransactions/104/lines'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.get( "https://acme.nolapro.com/!/api/v2/gltransactions/104/lines", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/gltransactions/104/lines', { method: 'GET', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var res = await http.GetAsync( "https://acme.nolapro.com/!/api/v2/gltransactions/104/lines"); res.EnsureSuccessStatusCode();
Post a journal entry.
Writes and posts a balanced journal entry in one transaction.
This is the strictest endpoint in the API, and deliberately so - an unbalanced or half-written set makes the trial balance wrong and keeps it wrong. Every rule below is checked before anything is written:
| Rule | Refusal |
|---|---|
| The signed amounts sum to exactly zero | 422 gl_set_unbalanced, with the difference |
| At least two lines | 400 invalid_request |
| Every account exists in your company | 422 unknown_value, naming the line |
| The period is open | 422 period_closed |
| Amounts are decimal strings | 400 invalid_type |
Idempotency-Key matters more here than anywhere else: a retry after a lost response must not post the entry twice.
Requires scope gltransactions:write.
| Name | In | Type | Notes |
|---|---|---|---|
| Field | Type | Description |
|---|---|---|
| daterequiredrequest-only | string | The date the entry posts. Determines the GL period, so posting a July-dated entry in August is normal; posting into a closed period is refused with 422 period_closed. Request-only: stored as post2date (the period) and entrydate. |
| memorequest-only | string(50) | Header description, stored as gltransvoucher.description. Request-only: stored as description. |
| commentsrequest-only | string(50) | |
| voucherrequest-only | string(50) | Your own voucher number. NolaPro assigns one when you leave it out. |
| tajrequest-only | boolean | A year-end tax adjusting entry. These are allowed into a closed MONTH, but never a closed year. |
| externalid | string(100) | Your own key for this entry. Scoped to your company. |
| linesrequiredrequest-only | array of GlLine | At least two. One line is not a journal entry, even when it sums to zero. |
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Malformed: no date, a float amount, lines missing entirely. |
| 422 | conflicting_reference | An id and its name sibling were both sent and disagree. |
| 422 | gl_set_too_short | Fewer than two lines. A one-sided entry is not a journal entry. |
| 422 | gl_set_unbalanced | The signed amounts do not sum to zero. |
| 422 | period_closed | The GL period containing the date is closed. |
| 422 | unknown_value | A reference did not resolve in this company. |
curl -X POST \ 'https://acme.nolapro.com/!/api/v2/gltransactions' \ -H 'Authorization: Bearer $NP_TOKEN' \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: your-unique-key' \ -d '{ "date": "2026-08-01", "lines": [ { "amount": "125.0000" } ] }'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/gltransactions'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POSTFIELDS => json_encode([ 'date' => '2026-08-01', 'lines' => [ [ 'amount' => '125.0000', ], ], ]), ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.post( "https://acme.nolapro.com/!/api/v2/gltransactions", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, json={ "date": "2026-08-01", "lines": [ { "amount": "125.0000", }, ], }, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/gltransactions', { method: 'POST', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, body: JSON.stringify({ date: "2026-08-01", lines: [ { amount: "125.0000", }, ], }), } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var body = new { date = "2026-08-01", lines = new[] { new { amount = "125.0000", }, }, }; var req = new HttpRequestMessage(HttpMethod.Post, "https://acme.nolapro.com/!/api/v2/gltransactions") { Content = JsonContent.Create(body), }; req.Headers.Add("Idempotency-Key", "your-unique-key"); var res = await http.SendAsync(req); res.EnsureSuccessStatusCode();
{
"lines": [
[]
]
}
Journal entries.
Vouchers in this company, newest id first. The lines are on /glaccounts/{id}/activity.
Requires scope gltransactions:read.
| Name | In | Type | Notes |
|---|---|---|---|
| page | query | integer | |
| perpage | query | integer | |
| cancel | query | string | Defaults to false, which hides cancelled records. Pass true for only cancelled, or any for both. |
| externalid | query | string | Look up by your own key instead of by id. Unique per company and per system tag. |
| modifiedsince | query | string | RFC 3339 UTC timestamp. Returns only records changed since then, including cancelled ones - so cancel defaults to any rather than false when this is used. The response carries _meta.synced_through; store it and send it back next time. |
| include | query | string | Comma-separated extras to embed. Only custom_fields is available: the extra fields this install has defined on the record. Off by default, and an unrecognised value is refused rather than ignored. See Conventions. |
curl \ 'https://acme.nolapro.com/!/api/v2/gltransactions' \ -H 'Authorization: Bearer $NP_TOKEN'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/gltransactions'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.get( "https://acme.nolapro.com/!/api/v2/gltransactions", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/gltransactions', { method: 'GET', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var res = await http.GetAsync( "https://acme.nolapro.com/!/api/v2/gltransactions"); res.EnsureSuccessStatusCode();
{
"lines": [
{
"amount": "125.0000"
}
]
}
One journal entry.
The voucher a POST created. Its Location header points here.
Requires scope gltransactions:read.
| Name | In | Type | Notes |
|---|---|---|---|
| idrequired | path | integer | |
| include | query | string | Comma-separated extras to embed. Only custom_fields is available: the extra fields this install has defined on the record. Off by default, and an unrecognised value is refused rather than ignored. See Conventions. |
| Status | Code | Meaning |
|---|---|---|
| 404 | not_found | No voucher with that id. |
curl \ 'https://acme.nolapro.com/!/api/v2/gltransactions/104' \ -H 'Authorization: Bearer $NP_TOKEN'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/gltransactions/104'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.get( "https://acme.nolapro.com/!/api/v2/gltransactions/104", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/gltransactions/104', { method: 'GET', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var res = await http.GetAsync( "https://acme.nolapro.com/!/api/v2/gltransactions/104"); res.EnsureSuccessStatusCode();
{
"lines": [
{
"amount": "125.0000"
}
]
}
Reverse a journal entry.
Unposts the entry, which writes the reversing lines, then cancels the voucher. The ledger keeps both, which is the point.
There is no PATCH. You do not amend a posted journal entry - you reverse it and post a correct one. An endpoint that edited a posted entry would be a way to make the trial balance disagree with its own history.
Idempotent: an entry that is already cancelled returns 200 and changes nothing. The reversal posts into the CURRENT period, so that is the period which must be open, not the one the original landed in.
Requires scope gltransactions:cancel.
| Name | In | Type | Notes |
|---|---|---|---|
| idrequired | path | integer | The voucher id. |
| Status | Code | Meaning |
|---|---|---|
| 409 | cannot_cancel | NolaPro refused to reverse it. message carries its reason. |
| 422 | period_closed | The current GL period is closed, so the reversal has nowhere to post. |
| 422 | unknown_value | No such voucher in this company. |
curl -X DELETE \ 'https://acme.nolapro.com/!/api/v2/gltransactions/104' \ -H 'Authorization: Bearer $NP_TOKEN'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/gltransactions/104'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'DELETE', ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.delete( "https://acme.nolapro.com/!/api/v2/gltransactions/104", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/gltransactions/104', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var req = new HttpRequestMessage(HttpMethod.Delete, "https://acme.nolapro.com/!/api/v2/gltransactions/104"); var res = await http.SendAsync(req); res.EnsureSuccessStatusCode();
{
"voucher": "Example voucher",
"cancelled": false,
"unposted": false,
"lines": 1
}
Journal entry as a PDF
The journal entry behind a voucher, with both sides of every line.
Answers application/pdf, not JSON. Because the body is binary it cannot be carried inside a /batch results array; asking for it there returns 415 not_batchable.
Requires scope gltransactions:read.
| Name | In | Type | Notes |
|---|---|---|---|
| idrequired | path | integer |
curl \ 'https://acme.nolapro.com/!/api/v2/gltransactions/104/pdf' \ -H 'Authorization: Bearer $NP_TOKEN'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/gltransactions/104/pdf'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.get( "https://acme.nolapro.com/!/api/v2/gltransactions/104/pdf", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/gltransactions/104/pdf', { method: 'GET', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var res = await http.GetAsync( "https://acme.nolapro.com/!/api/v2/gltransactions/104/pdf"); res.EnsureSuccessStatusCode();
Income statement as a PDF
The profit and loss statement for a period. Defaults to the year to date.
Answers application/pdf, not JSON. Because the body is binary it cannot be carried inside a /batch results array; asking for it there returns 415 not_batchable.
Requires scope gltransactions:read.
| Name | In | Type | Notes |
|---|---|---|---|
| begindate | query | string | Start of the period, YYYY-MM-DD. Defaults to 1 January. |
| enddate | query | string | End of the period, YYYY-MM-DD. Defaults to today. |
curl \ 'https://acme.nolapro.com/!/api/v2/gltransactions/incomestatement' \ -H 'Authorization: Bearer $NP_TOKEN'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/gltransactions/incomestatement'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.get( "https://acme.nolapro.com/!/api/v2/gltransactions/incomestatement", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/gltransactions/incomestatement', { method: 'GET', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var res = await http.GetAsync( "https://acme.nolapro.com/!/api/v2/gltransactions/incomestatement"); res.EnsureSuccessStatusCode();