Oman POS Report
One endpoint reads back the counter sales and refund credit notes your organisation has issued through GoRoute's Oman POS routes, either as a day book an auditor can read line by line, or as a cash-up reconciliation that shows what has and has not reached the platform.
All paths below are relative to https://app.goroute.ai/peppol-api. Every call is scoped to
the organisation of the API key you send.
It returns all four kinds of counter receipt: fuel pump sales, forecourt refunds, retail store sales and refunds, and money-exchange commission receipts. There is no parameter that restricts it to one counter, and no field in a row that names the counter it came from.
That is not a choice the report makes. Every Oman POS route shares one finalising step, and that step records the same channel value on every receipt it writes, whichever counter issued it. The report selects on that channel value, so all of them match. If you have only a forecourt, this distinction costs you nothing. If you run a store or an exchange counter as well, a total from this report is your whole POS estate, not your fuel business — do not reconcile it against a forecourt figure.
You can still separate them, using the values each counter writes into the station and product fields. How to tell the counters apart below sets out exactly what each one puts there.
The endpoint
| Method and path | What it does |
|---|---|
GET /api/v1/reports/pos | POS counter sales and credit notes for a period, summarised by station, tender and product |
It requires the transactions:read permission — the same permission that lets you read
your transactions one at a time. There is no separate reporting scope, because the report
aggregates documents you could already fetch one by one.
Query parameters
| Name | Required | Type | Meaning |
|---|---|---|---|
period_start | yes | date (YYYY-MM-DD) | First day counted, included |
period_end | yes | date (YYYY-MM-DD) | Last day counted, included |
station_id | no | string | Restrict to one station, matched exactly against the station_id recorded with each sale |
kind | no | daybook or reconciliation | Which of the two reports you want. Defaults to daybook |
Both dates are whole days and both ends are inclusive, so a single day is
period_start=2027-03-01&period_end=2027-03-01.
curl -H "X-API-Key: $GOROUTE_API_KEY" \
"https://app.goroute.ai/peppol-api/api/v1/reports/pos?period_start=2027-03-01&period_end=2027-03-31&kind=reconciliation"
The two kinds are not a long and a short report
This is the one thing on this page nobody would guess, so it comes before the response shape.
daybookreturns every counter document in the period, sales and credit notes together, in the order they were recorded.reconciliationreturns the same summary over the same period, but itsrowshold only the documents that have not reached the platform.
A reconciliation rows array is therefore an exception list to work through at cash-up, not
a shortened day book. An empty rows on a reconciliation is the good outcome: everything in
the period got through. The summary block is identical in both kinds, so the totals do not
change when you switch.
The period is a UTC day range, and it is the recording time
Each day runs from 00:00:00 to 23:59:59.999999 UTC. Oman is four hours ahead, so a
sale made in Muscat between midnight and 04:00 is recorded against the previous UTC day
and falls into the previous day's report. This is the same trap as the
retail usage count,
and it matters more here, because a forecourt trades through the night.
The timestamp being compared is when GoRoute recorded the document, not a till time you supplied. A receipt printed at 23:58 and submitted at 00:03 belongs to the later day.
Two refusals, both 422
| What you sent | What happens |
|---|---|
period_end before period_start | 422, period_end must not precede period_start |
| A period longer than 400 days | 422, period must not exceed 400 days |
Both arrive in the platform's standard error envelope:
{
"error": "error",
"message": "period must not exceed 400 days",
"request_id": "5f0c9f6a-2c19-4a2e-8b77-0d6f1f9b4c31"
}
Quote the request_id to support if you need the call traced.
A kind that is neither daybook nor reconciliation is also refused with 422, but by
request validation before the handler is reached, so that one response carries a field-level
detail list instead of the envelope above.
Response shape
{
"kind": "daybook",
"period": { "start": "2027-03-01", "end": "2027-03-31" },
"station_id": null,
"summary": {
"sales": 18342,
"credit_notes": 57,
"gross": "412870.500",
"net": "393210.000",
"vat": "19660.500",
"credited_gross": "1284.750",
"reported": 18376,
"not_reported": 23,
"by_tender": {
"CASH": { "count": 9120, "gross": "198433.250" },
"CARD": { "count": 9279, "gross": "214437.250" }
},
"by_station": {
"ST-014": { "count": 7411, "gross": "165920.000", "name": "Al Khuwair" }
},
"by_product": {
"M95": { "count": 11288, "gross": "243118.500" }
}
},
"generated_at": "2027-04-01T06:12:44.118231+00:00",
"rows": [
{
"transaction_id": "9a2f1c9d-0a6b-4e21-8c77-7c1f7d542a3f",
"issued_at": "2027-03-14T07:41:09.512000+00:00",
"document": "sale",
"invoice_number": "FS-2027-0098231",
"receipt_no": "0098231",
"station_id": "ST-014",
"station_name": "Al Khuwair",
"pump_no": "4",
"product": "M95",
"invoice_type": "simplified",
"payment_method": "cash",
"cashier_name": "Salim",
"net_amount": "11.905",
"vat_amount": "0.595",
"gross_amount": "12.500",
"currency": "OMR",
"status": "delivered",
"credits_invoice": null,
"credit_reason": null,
"invoice_uuid": "1c9d0a6b-5e21-4a51-9a2f-7c1f7d542a3f"
}
]
}
The summary, field by field
| Field | What it is |
|---|---|
sales / credit_notes | How many documents of each kind fell in the period |
gross / net / vat | Period totals. Credit notes are subtracted |
credited_gross | The value of the credit notes, accumulated as a positive figure |
reported / not_reported | Documents that have reached the platform, and those that have not |
by_tender | Count and gross per payment method, the key upper-cased |
by_station | Count and gross per station, with the station's name |
by_product | Count and gross per product code |
credited_gross sitting beside a reduced gross looks contradictory until you know the
rule: a refund pulls gross, net and vat down, and is counted up in
credited_gross. So gross is what you actually took, and credited_gross is how much was
handed back. Do not add them together.
Every money figure in the summary, including the ones inside the three breakdowns, is a
string with exactly three decimal places, because OMR runs to 1000 baisa. A sale with no
payment method, station or product recorded is grouped under the key — rather than being
dropped, so the breakdowns always add up to the period totals.
The totals and all three breakdowns cover every counter, so if you run more than a
forecourt they are mixed. by_station will carry your stores and exchange branches beside
your stations, and by_product will carry goods and service descriptions beside fuel grades.
The breakdown keys are what lets you separate them again; the next section says which key
belongs to which counter.
reported means it reached our platform, not that it reached the Tax Authorityreported counts the documents whose platform status is delivered, and nothing else.
Every other status — queued, submitted, accepted, retrying, failed — is counted in
not_reported, because the document is still in flight. A document that has not reached the
platform must not be presented as filed.
delivered is a statement about GoRoute, not about the Oman Tax Authority. A validated
consumer sale is cleared by GoRoute as the certified Oman Access Point and marked
delivered at that moment; the Tax Data Document behind it is generated, stored and
auditable, and is forwarded to OTA Fawtara once Fawtara is accepting submissions. So read
reported as "cleared through GoRoute", and do not put it in front of a tax inspector as a
filing record.
That Tax Data Document is a separate record on the platform. It is not returned as a row here — the rows are the receipts your counters issued, not the reports made about them.
The rows, field by field
| Field | What it is |
|---|---|
transaction_id | The GoRoute transaction, for use against the Transactions API |
issued_at | When GoRoute recorded the document, in UTC |
document | sale, or credit_note for a refund |
invoice_number | The document number you issued |
receipt_no | The till receipt number, as printed |
station_id / station_name | The forecourt on a fuel sale. On the other counters these carry the store or branch instead — see How to tell the counters apart |
pump_no | The pump on a fuel sale. The teller on an exchange receipt, and - on a retail one |
product | The fuel product code on a fuel sale. The service or the goods sold on the other counters |
invoice_type | The kind of invoice issued, as supplied with the sale |
payment_method | The tender — the figure by_tender groups on |
cashier_name | Who served the sale |
net_amount / vat_amount / gross_amount | The document's own figures, exactly as recorded with the sale |
currency | The document currency. OMR where none was recorded |
status | The platform status, the value reported and not_reported are decided from |
credits_invoice | For a credit note, the invoice number it credits. null on a sale |
credit_reason | For a credit note, why it was issued. null on a sale |
invoice_uuid | The document's own unique identifier, which is also the message identifier GoRoute stored it under |
Row amounts are returned as they were recorded for the receipt, so treat them as the
document's own figures. The three-decimal rounding is applied to the totals in summary, not
to the rows.
A field that was never supplied with the sale comes back as null. Nothing is inferred for
it — a missing cashier or pump is reported as missing rather than guessed.
How to tell the counters apart
No row says which counter issued it. What each counter does is fill the station, pump and product fields with its own terms, so those three values are how you separate a mixed report.
| Fuel forecourt | Retail store | Money exchange | |
|---|---|---|---|
station_id | Your station code, as supplied | web, pos or app — the retail channel | The branch code |
station_name | The station name, as supplied | Always Online store | The branch name |
pump_no | The pump, as supplied | Always - | The teller |
product | The fuel grade, for example M95 | The first line's description, with (+2 more) appended when the sale has more lines | The service, for example Money exchange service |
payment_method | The tender, as supplied | Not recorded, so the row groups under — | The tender, defaulting to CASH |
The practical test on a mixed report is station_name: retail sales are the ones reading
Online store, and everything else is a physical site. To pull one forecourt out on its own,
pass station_id — it is matched exactly, and a station code will not collide with web,
pos or app.
A retail product is a description typed by whoever listed the goods, not a code from a
list, so treat by_product on a mixed report as a grouping of text rather than a product
breakdown.
Related pages
- Oman Retail POS — issuing store and kiosk sales, and the retail usage count that excludes fuel
- VAT Return Report — the same documents aggregated into the Oman VAT return boxes
- Oman country guide — the mandate dates, PINT OM validation rules and the QR code