# Accounting MCP Protocol

This file is published to <a href="https://mcp.g3nretailstack.com/accounting/PROTOCOL.md" target="_blank" rel="noopener noreferrer">https://mcp.g3nretailstack.com/accounting/PROTOCOL.md</a>.


## Usage patterns (headless)
- Stack-wide SOPs & operations catalog: <a href="https://doc.g3nretailstack.com/story/operations.html" target="_blank" rel="noopener noreferrer">/story/operations.html</a>.
- Super-usecase scenarios + QA status: <a href="https://doc.g3nretailstack.com/story/super-usecases.html" target="_blank" rel="noopener noreferrer">/story/super-usecases.html</a>.
- This protocol stays contract-only; use the catalogs for workflow expectations.

## Base URL
- API Gateway: `https://api.g3nretailstack.com/accounting`
- Health check: `GET /accounting/stat` — requires `requireSession` (any authenticated user).

## Auth + tenancy
- Auth placement: header auth is canonical for org-scoped APIs; body auth is accepted for compatibility where documented. Exceptions: USM and UTL require body auth. See [/common/headers-identity.html](https://doc.g3nretailstack.com/common/headers-identity.html).
- Every tenant call requires `x-orgcode`.
- Auth is either:
  - `x-session-guid` (user session), OR
  - `x-api-key` (org-bound service account)
- Non-associated callers receive `404 not-found` (anti-enumeration).
- Optional cost attribution: provide `x-cccode` (or request field `cccode`).
- Request context (where required) must match header/body identity: `orgcode`, `logical_guid`, `channel_code`, `cccode`, `context_source`, and `roles` mismatches return `invalid-input`.
- `session_guid` is never emitted in responses; use `stats.session_fingerprint` for correlation.
- Facility context: `x-logical-guid` (required for operational actions; see OpenAPI per-path). `x-channel-code` (required where documented; see OpenAPI per-path).

## Roles
- Read: `acct_view`, `acct_export_admin`, `finance_audit` (owner implied).
- Export admin: `acct_export_admin`, `finance_audit`.
- Comment/inbox write: same as export admin roles.

## Surfaces
- Contract-only surface. Implemented and deployed; see `/accounting/openapi.yaml` for the current surface definition.

## Endpoint inventory (OpenAPI parity)
The endpoints below are implemented and defined in `/accounting/openapi.yaml`. Request/response schema names reference OpenAPI component schemas.

| Method | Path | Request schema | Response schema |
| --- | --- | --- | --- |
| GET | /stat | — | StatEnvelope |
| POST | /comment | CommentAddRequest | CommentEnvelope |
| POST | /comment/get | CommentGetRequest | CommentEnvelope |
| POST | /comment/list | CommentListRequest | CommentListEnvelope |
| POST | /comment/report | CommentReportRequest | CommentReportEnvelope |
| POST | /comment/revise | CommentReviseRequest | CommentEnvelope |
| POST | /comment/status | CommentStatusRequest | CommentEnvelope |
| POST | /event/get | EventGetRequest | EventEnvelope |
| POST | /event/list | EventListRequest | EventListEnvelope |
| POST | /event/record | EventRecordRequest | EventEnvelope |
| POST | /export/batch/create | ExportBatchCreateRequest | ExportBatchEnvelope |
| POST | /export/batch/get | ExportBatchGetRequest | ExportBatchEnvelope |
| POST | /export/batch/list | ExportBatchListRequest | ExportBatchListEnvelope |
| POST | /export/batch/repair | ExportBatchRepairRequest | ExportBatchEnvelope |
| POST | /export/batch/status/set | ExportBatchStatusSetRequest | ExportBatchEnvelope |
| POST | /export/create | ExportCreateRequest | ExportEnvelope |
| POST | /export/get | ExportGetRequest | ExportEnvelope |
| POST | /export/list | ExportListRequest | ExportListEnvelope |
| POST | /export/schedule/create | ExportScheduleCreateRequest | ExportScheduleEnvelope |
| POST | /export/schedule/get | ExportScheduleGetRequest | ExportScheduleEnvelope |
| POST | /export/schedule/list | ExportScheduleListRequest | ExportScheduleListEnvelope |
| POST | /export/schedule/status/set | ExportScheduleStatusSetRequest | ExportScheduleEnvelope |
| POST | /inbox/create | InboxCreateRequest | InboxEnvelope |
| POST | /inbox/get | InboxGetRequest | InboxEnvelope |
| POST | /inbox/list | InboxListRequest | InboxListEnvelope |
| POST | /inbox/state | InboxStateRequest | InboxEnvelope |
| POST | /inbox/status | InboxStatusRequest | InboxEnvelope |
| POST | /lineage/list | LineageListRequest | LineageListEnvelope |
| POST | /reconcile/report | ReconcileReportRequest | ReconcileReportEnvelope |
| POST | /storage/usage/get | StorageUsageGetRequest | StorageUsageEnvelope |
| POST | /storage/usage/list | StorageUsageListRequest | StorageUsageListEnvelope |
| POST | /storage/usage/reconcile | StorageUsageReconcileRequest | StorageUsageReconcileEnvelope |
| POST | /storage/usage/record | StorageUsageRecordRequest | StorageUsageEnvelope |
| POST | /usage/rollup/get | UsageRollupGetRequest | UsageRollupEnvelope |
| POST | /usage/rollup/list | UsageRollupListRequest | UsageRollupListEnvelope |
| POST | /invoice/set | InvoiceSetRequest | InvoiceEnvelope |
| POST | /invoice/get | InvoiceGetRequest | InvoiceEnvelope |
| POST | /invoice/list | InvoiceListRequest | InvoiceListEnvelope |
| POST | /invoice/status/set | InvoiceStatusSetRequest | InvoiceEnvelope |
| POST | /invoice/payment/apply | InvoicePaymentApplyRequest | ArPaymentEnvelope |
| POST | /invoice/payment/list | InvoicePaymentListRequest | ArPaymentListEnvelope |
| POST | /ar/aging/report | ArAgingReportRequest | ArAgingReportEnvelope |
| POST | /ar/balance | ArBalanceRequest | ArBalanceEnvelope |
| POST | /ar/statement | ArStatementRequest | ArStatementEnvelope |
| POST | /ar/credit-hold/check | (inline) | Envelope |
| POST | /rollup/financial/get | FinancialRollupGetRequest | FinancialRollupEnvelope |
| POST | /rollup/financial/list | FinancialRollupListRequest | FinancialRollupListEnvelope |
| POST | /rollup/financial/export | (inline) | Envelope |
| POST | /commission/get | (inline) | Envelope |
| POST | /commission/list | (inline) | Envelope |
| POST | /commission/adjust | (inline) | Envelope |
| POST | /commission/split/set | (inline) | Envelope |
| POST | /commission/statement | (inline) | Envelope |
| POST | /commission/payout/create | (inline) | Envelope |
| POST | /commission/payout/get | (inline) | Envelope |
| POST | /commission/payout/list | (inline) | Envelope |
| POST | /commission/payout/status/set | (inline) | Envelope |
| POST | /dunning/policy/set | (inline) | Envelope |
| POST | /dunning/policy/get | (inline) | Envelope |
| POST | /dunning/action/create | (inline) | Envelope |
| POST | /dunning/action/list | (inline) | Envelope |
| POST | /dunning/evaluate | (inline) | Envelope |
| POST | /consignment/settlement/create | (inline) | Envelope |
| POST | /consignment/settlement/get | (inline) | Envelope |
| POST | /consignment/settlement/list | (inline) | Envelope |
| POST | /consignment/settlement/approve | (inline) | Envelope |
| POST | /consignment/settlement/status/set | (inline) | Envelope |
| POST | /franchise/royalty/rule/set | (inline) | Envelope |
| POST | /franchise/royalty/rule/get | (inline) | Envelope |
| POST | /franchise/royalty/calculate | (inline) | Envelope |
| POST | /franchise/royalty/get | (inline) | Envelope |
| POST | /franchise/royalty/list | (inline) | Envelope |
| POST | /franchise/royalty/status/set | (inline) | Envelope |
| POST | /inference/config/set | (inline) | Envelope |
| POST | /inference/config/get | (inline) | Envelope |
| POST | /inference/snapshot/create | (inline) | Envelope |

## Error tags
Common tags (see [/common/error-tags.html](https://doc.g3nretailstack.com/common/error-tags.html) for definitions): `validation-error`, `unauthorized`, `forbidden`, `not-found`, `expected-revision-required`, `conflict`, `invalid-state`, `throttled`, `internal-error`.

## Example envelopes
Success envelope (shape-only):
```json
{
  "success": true,
  "data": { "example": "see schema for fields" },
  "stats": { "service": "accounting", "call": "accounting_example", "timestamp_utc": "2026-01-01T00:00:00Z", "build": { "build_major": "MONDAY", "build_minor": "0000000000", "build_id": "MONDAY-0000000000" } }
}
```

Error envelope (shape-only):
```json
{
  "success": false,
  "error": {
    "error_code": "accounting.validation_failed",
    "http_status": 400,
    "retryable": false,
    "major": { "tag": "validation-error", "message": { "en_US": "Invalid request." } }
  },
  "stats": { "service": "accounting", "call": "accounting_example", "timestamp_utc": "2026-01-01T00:00:00Z", "build": { "build_major": "MONDAY", "build_minor": "0000000000", "build_id": "MONDAY-0000000000" } }
}
```
- Tax audit export runs use `export_kind=tax_audit` with optional tax filters in export create.
- Comments: `/accounting/comment` (add/get/list/revise/status/report)
- Inbox: `/accounting/inbox` (create/get/list/status/state)

## Internal jobs (operator-only)
These jobs run via EventBridge-scheduled Lambda invocations (not API Gateway). The entry Lambda dispatches on `{ "source": "aws.events", "job": "<name>" }`. All accept optional `as_of_utc` (ISO timestamp), `limit` (page size), and `dry_run` (boolean) fields.

### `export-schedule-sweep`
Scans active `export_schedule` records whose `next_run_at` has elapsed, creates an export + initial batch for each, then advances `next_run_at` using the schedule's cadence (interval or cron). Cursor-based; resumes across invocations.

Request: `{ "job": "export-schedule-sweep", "as_of_utc?": string, "limit?": number, "dry_run?": boolean }`

Response shape:
```json
{ "job": "export-schedule-sweep", "run_id": "uuid", "as_of": "ISO", "limit": 50, "scanned": 0, "matched": 0, "processed": 0, "scheduled": 0, "conflicts": 0, "errors": 0, "skipped_cron": 0, "next_cursor": null }
```

### `storage-usage-sweep`
Walks S3 buckets across all services (events, changelog, exports, attachments) to compute per-org storage usage within a rolling time window. Records each measurement as a storage-usage entry. Cursor-based; resumes across invocations. Sources are configurable via `ACCOUNTING_STORAGE_USAGE_SOURCES` or default to all service buckets.

Request: `{ "job": "storage-usage-sweep", "as_of_utc?": string, "limit?": number, "dry_run?": boolean }`

Response shape:
```json
{ "job": "storage-usage-sweep", "run_id": "uuid", "as_of": "ISO", "window": { "from_utc": "ISO", "to_utc": "ISO" }, "scanned_prefixes": 0, "processed_prefixes": 0, "usage_records": 0, "object_bytes": 0, "object_count": 0, "record_bytes": 0, "record_items": 0, "errors": 0 }
```

### `storage-usage-reconcile`
Single-org variant of the storage-usage sweep. Requires `orgcode`, `from_utc`, and `to_utc`. Walks the same S3 source list but scopes to one org. Cursor-based; the caller can pass `cursor` from a prior partial response.

Request: `{ "job": "storage-usage-reconcile", "orgcode": string, "from_utc": string, "to_utc": string, "limit?": number, "dry_run?": boolean, "cursor?": object, "reason?": string, "source_refs?": array }`

Response shape:
```json
{ "job": "storage-usage-reconcile", "run_id": "uuid", "orgcode": "ACME", "window": { "from_utc": "ISO", "to_utc": "ISO" }, "scanned_prefixes": 0, "processed_prefixes": 0, "usage_records": 0, "object_bytes": 0, "object_count": 0, "record_bytes": 0, "record_items": 0, "errors": 0, "partial": false, "cursor": null }
```

### `usage-rollup`
Aggregates per-service API-usage records from `<service>-usage-*` S3 buckets for a target date (defaults to yesterday). Breaks down totals and per-cccode by surface (api_calls, deliveries, bandwidth, errors). Upserts rollup items into DynamoDB with TTL. Skips if the target date was already completed (unless `force` is set). Cursor-based; resumes across invocations.

Request: `{ "job": "usage-rollup", "as_of_utc?": string, "dt?": "YYYY-MM-DD", "orgcode?": string, "limit?": number, "dry_run?": boolean, "force?": boolean }`

Response shape:
```json
{ "job": "usage-rollup", "run_id": "uuid", "as_of": "ISO", "dt": "2026-02-10", "window": { "from_utc": "ISO", "to_utc": "ISO" }, "scanned_prefixes": 0, "processed_prefixes": 0, "objects": 0, "records": 0, "errors": 0, "partial": false }
```


## Idempotency & retries
- All **GET / list / resolve / search** calls are safe to retry with identical inputs (read-only, no side effects).
- **POST mutations** that accept `expected_revision` use optimistic concurrency: on `409 conflict` or `428 expected-revision-required`, re-read the record, obtain the current `revision`, and retry with the updated value.
- Creates are generally **not** idempotent. Prefer caller-provided `code` (where supported) and verify existence before retrying a failed create.
- Bulk or scheduled jobs that accept an `idempotency_key` will de-duplicate within the documented time window.

## Known pitfalls
- **Missing `expected_revision`**: most state-changing operations require it; omitting it returns `428` with the current revision in `error.details`.
- **Stale revision**: reading a record, waiting, then writing with an outdated `revision` triggers `409`. Always use the latest revision from the most recent read.
- **Pagination cursors**: `next_token` is opaque JSON. Do not modify, decode, or persist cursors across sessions — they may change format between deploys.
- **Anti-enumeration 404**: some org-scoped reads return `404` even when the record exists, if the caller is not associated with the org. Treat `404` as ambiguous; verify caller association before assuming "not found".

## OpenAPI
- Contract schema: <a href="https://doc.g3nretailstack.com/accounting/openapi.yaml" target="_blank" rel="noopener noreferrer">https://doc.g3nretailstack.com/accounting/openapi.yaml</a>


_Build MONDAY-1776194870 • 2026-04-14T19:27:50.000Z • [© 1999 Microhouse Systems Inc. All rights reserved.](https://doc.g3nretailstack.com/common/copyright-license.html)_
