All endpoints are served by the Spring Boot backend on port 8096 (default).
Base URL in development: http://localhost:8096.
The API has no authentication layer beyond the upload-link token mechanism described below.
Controllers marked DB-gated are only registered when spring.datasource.url is set
(i.e. when a PostgreSQL connection is configured).
DB-gated. Controller: AuthController — /api/auth
Registers a new user account. Returns 201 Created.
Request body
{ "username": "string", "password": "string" }
Response body
{ "id": 1, "username": "string" }
DB-gated. Controller: CustomerController — /api/customers
Returns all customers as a JSON array.
Response — array of customer objects:
[
{
"id": 1,
"name": "string",
"contactName": "string",
"contactEmail": "string",
"contactPhone": "string",
"createdAt": "2024-01-01T00:00:00",
"updatedAt": "2024-01-01T00:00:00"
}
]
Creates a new customer. Returns 201 Created.
Request body
{
"name": "string",
"contactName": "string",
"contactEmail": "string",
"contactPhone": "string"
}
Response — created customer object (same shape as GET item).
Returns a single customer. 404 if not found.
Replaces a customer's fields. 404 if not found.
Request body — same shape as POST.
Deletes a customer. 204 No Content. 404 if not found. 409 Conflict if the customer
has one or more dossiers — delete those first.
DB-gated. Controller: DossierController — /api/dossiers
Returns all dossiers. Optionally filter by customer:
| Query param | Type | Description |
|-------------|------|-------------|
| customerId | Long | Optional — return only dossiers for this customer |
Response — array of dossier objects:
[
{
"id": 1,
"customerId": 1,
"customerName": "string",
"engagementName": "string",
"engagementPeriodStart": "2024-01-01",
"engagementPeriodEnd": "2024-12-31",
"status": "OPEN",
"createdAt": "2024-01-01T00:00:00",
"updatedAt": "2024-01-01T00:00:00"
}
]
status values: OPEN, IN_PROGRESS, CLOSED.
Creates a dossier. Returns 201 Created.
Request body
{
"customerId": 1,
"engagementName": "string",
"engagementPeriodStart": "2024-01-01",
"engagementPeriodEnd": "2024-12-31",
"status": "OPEN"
}
status defaults to OPEN when omitted.
Returns a single dossier. 404 if not found.
Replaces a dossier's fields. 404 if the dossier or referenced customer is not found.
Request body — same shape as POST.
Deletes a dossier and all associated dossier files. 204 No Content. 404 if not found.
Returns metadata for all files attached to the dossier. 404 if the dossier is not found.
Response — array:
[
{
"id": 1,
"dossierId": 1,
"filename": "string",
"documentType": "string",
"uploadedAt": "2024-01-01T00:00:00"
}
]
Controllers: AnalysisController, mapped to /api/analysis.
All analysis endpoints operate on an in-memory analysis session identified by analysisId
(a UUID-like string returned when a XAF file is uploaded).
Uploads and parses a XAF audit file (Dutch auditfile XML format). Returns an analysisId
for all subsequent endpoints. Max file size: 50 MB.
Request — multipart/form-data, field file.
Response
{
"analysisId": "abc-123",
"summary": {
"totalTransactions": 12000,
"flaggedCount": 45,
"highRiskCount": 3,
"populationDebit": "9500000.00",
"populationCredit": "9500000.00"
}
}
Lists the built-in example XAF files available for demo purposes.
Response — array:
[{ "id": "demo_xaf_bruisfrisco", "name": "Bruis Frisco (demo)" }]
Loads a built-in example file and creates an analysis session, returning the same shape
as POST /api/analysis/upload. 404 if id is unknown.
Returns the population summary for the session, optionally date-filtered and/or with the DA-031 / DA-032 account-type exclusions applied. Any narrowing re-runs every detector on the reduced population, so population-relative checks (unusual users, low-activity accounts, large entries, duplicates, …) are recomputed rather than post-filtered.
| Query param | Type | Default | Description |
|-------------|------|---------|-------------|
| from | ISO date | — | Optional start date filter (yyyy-MM-dd) |
| to | ISO date | — | Optional end date filter (yyyy-MM-dd) |
| excludeRevenue | boolean | false | DA-031: drop postings on the mapped revenue accounts. 400 when no revenue accounts are mapped. |
| excludeExpense | boolean | false | DA-032: drop postings on the mapped expense accounts. 400 when no expense accounts are mapped. |
Response — PopulationSummaryDto with aggregate counts and totals, plus the DA-033
materiality figures: materialityThreshold (the reference the weighted scores were computed
against), materialityConfigured (true when it is the configured engagement materiality,
false when it is the population's 95th-percentile fallback) and aboveMaterialityCount.
Returns flagged transactions for the specified category. category is a FlagCategory
enum value (e.g. ROUND_AMOUNTS, PERIOD_END, OUTLIER, SAME_DAY_REVERSAL,
THRESHOLD_AVOIDANCE, MANUAL_ENTRY, ABNORMAL_DIRECTION, CIRCULAR_FLOW, HIGH_RISK).
| Query param | Type | Default | Description |
|-------------|------|---------|-------------|
| creditOnly | boolean | false | If true, returns only transactions with a positive credit amount |
| excludeRevenue | boolean | false | DA-031: re-run the analysis without postings on the mapped revenue accounts. 400 when none are mapped. |
| excludeExpense | boolean | false | DA-032: re-run the analysis without postings on the mapped expense accounts. 400 when none are mapped. |
Response — array of FlaggedTransactionDto, ordered by weightedRiskScore descending.
Each entry carries riskScore (number of triggered flag categories), the DA-033
weightedRiskScore, aboveMateriality and highRisk (weighted score ≥ the configured
high-risk score threshold).
Exports the drill-down category list as an .xlsx file (DA-082).
Same creditOnly, excludeRevenue and excludeExpense query params as the JSON endpoint above.
Response — application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,
Content-Disposition: attachment; filename=<category>_export.xlsx.
Generates and downloads a one-page audit summary PDF covering all flag categories and key findings.
Response — application/pdf, Content-Disposition: attachment; filename=athena-audit-report.pdf.
Returns the current in-memory analysis thresholds (resets to defaults on backend restart).
Response — AnalysisSettingsDto:
{
"roundAmountDivisor": 1000,
"cutoffWindowBusinessDays": 5,
"zScoreOutlierThreshold": 3.0,
"manualEntryAccountThresholdPct": 50.0,
"abnormalDirectionMinAmount": 10000.00,
"sameDayReversalTolerancePct": 0.01,
"thresholdAvoidanceMinEntries": 3,
"periodEndWindowBusinessDays": 5,
"periodEndConcentrationFactor": 2.0,
"circularFlowThresholdFactor": 0.95,
"materialityThreshold": 0,
"materialityWeights": {
"flagCountWeight": 1.0,
"amountWeight": 1.0,
"highRiskScoreThreshold": 2.0,
"accountTypeMultipliers": { "BALANCE": 1.0, "PROFIT_LOSS": 1.0, "UNKNOWN": 1.0 }
}
}
materialityThreshold is the engagement materiality (DA-033); 0 means "not configured", in
which case the engine uses the population's 95th-percentile amount as its reference.
materialityWeights is the DA-033 weight matrix: per entry,
weightedRiskScore = (flagCountWeight × flags + amountWeight × min(amount ÷ materiality, 1)) × accountTypeMultipliers[class],
and the entry is High Risk when that score reaches highRiskScoreThreshold. Account classes
missing from accountTypeMultipliers keep multiplier 1.0; highRiskScoreThreshold must be > 0.
Updates any subset of analysis thresholds in-memory. Only provided (non-null) fields are
applied. Returns the full updated AnalysisSettingsDto.
Request body — same shape as GET response; omit fields that should not change.
DA-031 / DA-032: returns the session's revenue/expense account mapping. Both lists are empty until
a mapping has been stored. Controller: AccountTypeMappingController.
Response — AccountTypeMappingDto:
{
"revenueAccountIds": ["8000", "8100"],
"expenseAccountIds": ["4000", "4100"]
}
DA-031 / DA-032: replaces the session's account-type mapping (in-memory, discarded with the session). Account IDs are trimmed and de-duplicated; blank entries are dropped. Both lists are required (may be empty). Returns the normalised mapping.
Request body — same shape as the GET response.
Errors: 400 when an account ID appears in both lists, 404 if the session is unknown.
Removes the analysis session, its account-type mapping and all associated supporting documents
from memory. Returns 204 No Content, or 404 if the session is unknown.
Controller: AccountController, mapped to /api/analysis.
Returns all general-ledger accounts from the XAF with posting counts and net amounts (DA-043).
Response — array of AccountSummaryDto:
[
{
"accountId": "1000",
"accountDescription": "Kas",
"accountType": "B",
"postingCount": 120,
"netAmount": "45000.00"
}
]
Returns a paginated posting history for one GL account with a running balance (DA-043).
accountId matching is case-insensitive and uses contains (partial match).
| Query param | Type | Default | Description |
|-------------|------|---------|-------------|
| page | int | 0 | Zero-based page index |
| size | int | 50 | Page size |
| dateFrom | ISO date | — | Optional start date filter |
| dateTo | ISO date | — | Optional end date filter |
Response — AccountPostingsPageDto:
{
"content": [
{
"transactionId": "MEM-001",
"transactionDate": "2024-03-15",
"description": "string",
"debitAmount": "1000.00",
"creditAmount": null,
"runningBalance": "1000.00",
"createdBy": "jdoe"
}
],
"page": 0,
"size": 50,
"totalElements": 120,
"totalPages": 3
}
Returns company master data and the full chart of accounts from the XAF header (DA-045).
Response — StamgegevensDto:
{
"bedrijfsgegevens": {
"companyName": "string",
"fiscalYear": "2024",
"startDate": "2024-01-01",
"endDate": "2024-12-31",
"currency": "EUR"
},
"rekeningen": [{ "accountId": "1000", "accountDescription": "Kas", "accountType": "B" }]
}
Controller: AnalyticsReportController, mapped to /api/analysis.
Computes financial KPI dashboard tiles from the GL population (DA-046).
Response — FinancialKpiDto with totals for revenue, cost, gross margin, etc.
Computes financial ratio analysis (kengetallen) from the GL population (DA-049).
Response — KengetallenDto with liquidity, solvency, and profitability ratios.
Returns an RGS (Referentie Grootboekschema) category breakdown (DA-050). Empty when the
auditfile contains no RGS leadCode values.
Response — RgsCategorieenDto.
Returns the top-N GL accounts ranked by credit volume (revenue-side) and debit volume (cost-side) (DA-047).
| Query param | Type | Default | Description |
|-------------|------|---------|-------------|
| topN | int | 10 | Number of accounts to return per ranking |
| dateFrom | ISO date | — | Optional start date filter |
| dateTo | ISO date | — | Optional end date filter |
Response — TopAccountsReportDto with topByCredit and topByDebit lists.
Returns revenue, cost, and net result per accounting period for trend analysis (DA-048).
Only P&L accounts (accountType containing profit or p&l) are included.
| Query param | Type | Default | Description |
|-------------|------|---------|-------------|
| dateFrom | ISO date | — | Optional start date filter |
| dateTo | ISO date | — | Optional end date filter |
| granularity | string | MONTH | Period grouping: MONTH, QUARTER, or YEAR |
Response — TrendReportDto with a periods array of TrendPeriodDto:
{
"dateFrom": null, "dateTo": null, "granularity": "MONTH",
"periods": [
{ "period": "2024-01", "revenue": "80000.00", "cost": "60000.00", "netResult": "20000.00" }
]
}
Controller: TransactionBrowserController, mapped to /api/analysis.
Returns a paginated, filterable view of all transactions in the session.
| Query param | Type | Default | Description |
|-------------|------|---------|-------------|
| page | int | 0 | Zero-based page index |
| size | int | 50 | Page size |
| account | string | — | Partial, case-insensitive match on accountId |
| dateFrom | ISO date | — | Start date filter |
| dateTo | ISO date | — | End date filter |
| search | string | — | Partial, case-insensitive match on description |
| createdBy | string | — | Partial, case-insensitive match on createdBy |
Response — TransactionPageDto (same pagination shape as account postings).
Identical to /transactions but pre-filtered to memorial (manual) journal entries only
(manualEntry=true) (DA-051). Supports the same query params as /transactions.
Controller: BalanceReportController, mapped to /api/analysis.
All three balance report endpoints accept optional dateFrom / dateTo query params (ISO dates).
Cross-reference trial balance per GL account (DA-039). Shows opening balance, total debit, total credit, and closing balance for each account.
Response — KolommenbalansReportDto with rows (one per account) and grand totals.
Period-by-period balance evolution table per GL account (DA-040).
Additional query param:
| Query param | Type | Default | Description |
|-------------|------|---------|-------------|
| granularity | string | MONTH | Period grouping: MONTH, QUARTER, or YEAR |
Response — PeriodebalansReportDto with a dynamic periodColumns map per account row.
GL balance broken down by journal (dagboek) type per account (DA-041).
The dagboek code is derived from the prefix before the first - in transactionId.
Response — JournaalbalansReportDto with a dynamic dagboekColumns map per account row.
Controller: RestrictedAccountsController — /api/analysis/{analysisId}/restricted-accounts
Identifies restricted liquid-asset accounts (G-rekeningen, escrow, waarborgrekeningen, geblokkeerde rekeningen, derdengelden). XAF carries no restriction field, so the auditor supplies a restricted-account list (CSV or per-account configuration) and keyword matches on the account description are returned as suggestions that only count towards restricted liquidity once confirmed. Confirmed G-rekeningen are checked against the Belastingdienst usage rules.
Response — RestrictedAccountsResultDto
{
"accounts": [
{
"accountId": "1120",
"accountDescription": "G-rekening",
"accountType": "Balance",
"netBalance": "7000.00",
"restrictedBalance": "5000.00",
"flagSource": "HEURISTIC",
"restrictionType": "G_REKENING",
"restrictionLabel": "G-rekening (Wet Keten- en Inlenersaansprakelijkheid)",
"status": "CONFIRMED",
"deblokkeringGranted": true,
"deblokkeringAmount": "2000.00",
"vrijBeschikbaar": false
}
],
"restrictedTotal": "5000.00",
"freeTotal": "95000.00",
"suggestedTotal": "0.00",
"confirmedCount": 1,
"suggestedCount": 0,
"findings": [
{
"code": "G_REKENING_USAGE_VIOLATION",
"accountId": "1120",
"transactionId": "BNK.17.1",
"transactionDate": "2025-03-31",
"amount": "2500.00",
"description": "Betaling onderaannemer",
"message": "Uitgaande boeking vanaf G-rekening zonder Belastingdienst ... als tegenpartij"
}
],
"noneFound": false,
"analysisNote": "1 beperkte rekening(en) bevestigd. ..."
}
status is CONFIRMED (auditor-supplied or confirmed suggestion; vrijBeschikbaar: false,
counted in restrictedTotal) or SUGGESTED (keyword match pending confirmation; still counted
in freeTotal). Rejected accounts are omitted.restrictedBalance equals netBalance unless a granted deblokkering is documented, in which
case the released deblokkeringAmount is subtracted (Belastingdienst rule: surplus is only
released after a granted deblokkering).restrictionType ∈ G_REKENING, ESCROW, WAARBORGREKENING, GEBLOKKEERD, DERDENGELDEN.findings[].code ∈ G_REKENING_USAGE_VIOLATION (outflow to a counterparty other than the
Belastingdienst or another G-rekening), G_REKENING_REFUND (inflow from the Belastingdienst:
refund of a payment without betalingskenmerk), G_REKENING_WITHOUT_PAYROLL (keyword-suggested
G-rekening while the administration has no payroll postings), MULTIPLE_G_REKENINGEN (more than
one active G-rekening in the fiscal year).athena.analysis.restricted-account-keywords
(default: g-rekening, escrow, waarborg, derdengeld, geblokkeerd).Errors — 404 if analysisId is not found.
Returns the session's restricted-account configuration as a list of
RestrictedAccountConfigEntryDto (accountCode, restrictionType, label, status
(CONFIRMED/REJECTED), deblokkeringGranted, deblokkeringAmount).
Uploads a CSV of restricted accounts; rows are merged into the configuration by account code.
Request — multipart/form-data, field file. Format account_code,restriction_type[,label]
(comma or semicolon separated). restriction_type is a RestrictionType name or a Dutch keyword
(g-rekening, escrow, waarborg, geblokkeerd, derdengeld). A header row whose first cell
contains "account" or "rekening" is skipped.
Response — the full configuration list. Errors — 400 if a row has an unknown type.
Confirms, rejects or annotates one account (used to accept or reject a keyword suggestion and to document a granted deblokkering).
Request
{
"restrictionType": "G_REKENING",
"label": "G-rekening Bouw BV",
"status": "CONFIRMED",
"deblokkeringGranted": true,
"deblokkeringAmount": "2000.00"
}
status defaults to CONFIRMED. Response — the full configuration list.
Errors — 400 if restrictionType is missing or deblokkeringAmount is negative.
Removes one account from the configuration; a keyword suggestion for that account becomes pending again. Response — the remaining configuration list.
Controller: ReconciliationController — /api/reconciliation
Reconciles the general ledger (already loaded under analysisId) against an external
trial balance (saldibalans) spreadsheet (DA-005). The XAF auditfile must already be
uploaded; only the trial balance file needs to be submitted here.
Request — multipart/form-data, field trialBalanceFile (.xlsx).
Response — ReconciliationResultDto listing matched, unmatched, and discrepancy entries.
Errors — 404 if analysisId is not found; 400 if the trial balance file cannot be parsed.
Controller: SupportingDocumentController — /api/documents
Tracks which supporting documents have been uploaded for a liquide-middelen audit engagement. Only file metadata (name, type) is stored; file contents are not persisted.
Returns the current document checklist status for the session.
Response — array of SupportingDocumentEntryDto:
[
{
"type": "BANK_STATEMENTS",
"required": true,
"uploaded": true,
"filename": "statements_jan.pdf",
"checkResult": null
}
]
checkResult is populated only for BANK_CONFIRMATION_LETTER uploads (bankverklaring content
validation result: company name, period-end date, balance amount, signature).
Registers a document upload for the given session. For BANK_CONFIRMATION_LETTER, the DOCX is
also validated for required content fields.
Request — multipart/form-data:
| Field | Description |
|-------|-------------|
| type | SupportingDocumentType enum: BANK_STATEMENTS, BANK_CONFIRMATION_LETTER, BANK_RECONCILIATION, DEBTOR_RECEIPTS |
| file | The document file |
Response — updated checklist array (same shape as GET).
Returns the per-session checklist configuration: which document types are required for this engagement.
Response — array of ChecklistConfigEntryDto:
[{ "type": "BANK_STATEMENTS", "required": true }]
Updates which document types are required for the session.
Request body
{ "requiredTypes": ["BANK_STATEMENTS", "BANK_CONFIRMATION_LETTER"] }
Response — updated checklist config array.
Accepts multiple files (including .zip archives), auto-classifies each by filename pattern,
and registers them against the appropriate SupportingDocumentType.
Request — multipart/form-data, field files (one or more files).
Response — BatchClassifyResponseDto:
{
"status": [/* updated checklist array */],
"unrecognized": ["unknown_file.txt"],
"skipped": ["auditfile.xaf"]
}
Controller: UploadLinkController — /api/analysis/{analysisId}/upload-link
Generates shareable, tokenized upload URLs that allow a client company to submit supporting documents without an Athena account.
Generates a new upload token for the session. The raw token is returned once; the frontend
constructs the shareable URL as {origin}/upload/{token}.
| Query param | Type | Default | Description |
|-------------|------|---------|-------------|
| createdBy | string | accountant | Accountant name/identifier for the audit trail |
Response — UploadLinkResponseDto:
{
"token": "abc123...",
"expiresAt": "2024-01-08T12:00:00",
"uploadUrl": "/upload/abc123..."
}
Lists currently active (non-expired) upload links for the session.
Response — array of UploadTokenSummaryDto:
[
{
"analysisId": "abc-123",
"createdBy": "accountant",
"createdAt": "2024-01-01T12:00:00",
"expiresAt": "2024-01-08T12:00:00",
"useCount": 2
}
]
Controller: ExternalUploadController — /api/public/upload
This surface is unauthenticated. The upload token IS the authentication. CORS is open to any origin. No financial or analysis data is exposed.
Accepted document types and extensions:
| Type | Accepted extension |
|------|--------------------|
| BANK_STATEMENTS | .pdf |
| BANK_CONFIRMATION_LETTER | .docx |
| BANK_RECONCILIATION | .xlsx |
| DEBTOR_RECEIPTS | .csv |
XML-like files (.xml, .xaf, .xsd, .xslt, .svg) are rejected regardless of declared type.
Max file size: 50 MB.
Returns the document checklist for the dossier associated with the token. Shows only which types are required and whether each has been uploaded — no financial data is exposed.
Response — same shape as GET /api/documents/{analysisId}.
Submits a supporting document via an upload token.
Request — multipart/form-data:
| Field | Description |
|-------|-------------|
| documentType | One of the accepted SupportingDocumentType names |
| file | The document file |
The token is rate-limited. File extension is validated against the declared type. Only file metadata is stored; file contents are not persisted.
Response
{ "status": "uploaded", "fileName": "statements_jan.pdf" }
Controller: AdminController — /api/admin
Clears all in-memory analysis sessions, supporting documents, upload tokens, and rate-limit
state. Returns 204 No Content. Intended for development and testing only.
Controller: FeatureFlagAdminController — /api/admin/feature-flags
Returns all registered feature flags and their current enabled state.
Response — array of FeatureFlagDto:
[
{ "key": "pdf-export", "enabled": true, "description": "PDF audit summary export" }
]
Enables or disables a feature flag by key (in-memory; resets on restart).
Request body
{ "enabled": false }
Response — updated FeatureFlagDto. Returns 400 Bad Request if key is unknown.
Reacties