API Changes Summary

Branch kh-vendor-ai-codingstaging · Generated 2026-06-22

This PR introduces the Accounting Columns settings section — a new org-level catalog of Nitra-managed accounting columns that members can enable/disable, with changes cascading into each member's customized display columns. It also migrates the Rutter Custom Field config update endpoint to a batch (array) request shape to match the new settings UX, and adds an internal auto-match-vendor step to the Rutter sync flow that creates/links vendors when the AI vendor-coding confidence is low. Finally, it splits transaction auto-vendor-matching onto its own autoMatchVendorOnTransaction flag (the existing autoMatchVendor becomes bill-scoped), settable via the accounting platform update endpoint.

This PR at a glance · 2 Added · 2 Updated · 2 Models · 1 Internal
Full navigation is in the sidebar on the left. On narrower screens, scroll the page or jump via the section anchors.

Added APIs Added

GET / POST /v1/accountingcolumnconfig/query

Returns the fixed Nitra-managed accounting column catalog merged with the org's enable config, scoped to the columns available on the org's connected accounting platform.

Request body

FieldTypeRequiredDefaultDescription
organizationIdINTEGERNoreq.organization.idThe organization to read columns for.
enabledBOOLEANNoWhen set, returns only rows whose enabled flag matches (true or false).

Response (200)

{
  "status": 200,
  "success": true,
  "accountingColumns": [
    {
      "id": 1,            // AccountingColumnConfigs row id, or null if no config row exists yet
      "columnKey": "glaccount",
      "name": "GL Account",
      "enabled": true,
      "required": true    // platform "Always On" column; cannot be disabled
    }
  ]
}
FE note: Rows are returned in the canonical catalog order. A required: true column is always enabled: true and must be rendered as locked/non-toggleable. When id is null, no config row exists yet — send columnKey (not id) to /update for that row. If the org has no connected platform, the full catalog is returned (display only) with nothing forced on.

Errors

StatusCode
400BAD_REQUEST_INVALID_ARGUMENTS
401UNAUTHORIZED
500INTERNAL_SERVER_ERROR

GET / POST /v1/accountingcolumnconfig/update

Batch-update the enabled status for accounting columns and cascade the change into each member's customized display column lists. All-or-nothing within a single transaction.

Request body

FieldTypeRequiredDefaultDescription
organizationIdINTEGERNoreq.organization.idMust equal the caller's org or the request is rejected with FORBIDDEN.
accountingColumnsARRAYYesMin 1 item. Each item is { id | columnKey, enabled }.
accountingColumns[].idINTEGERConditionalExisting AccountingColumnConfigs row id. Mutually exclusive with columnKey (exactly one required per item).
accountingColumns[].columnKeySTRINGConditionalCatalog column key (enum: ACCOUNTING_COLUMN_KEYS). Use when no config row exists yet. Mutually exclusive with id.
accountingColumns[].enabledBOOLEANYesWhether the column is enabled for the org.

Response (200)

{
  "status": 200,
  "success": true
}
Behavior: Validates the entire batch before any write — duplicate column keys in one batch, a column not available on the org's platform, or disabling a required column all reject the whole request. On enable, the column is appended to each member's customized column list (if missing); on disable, it is removed.

Errors

StatusCode
400BAD_REQUEST_INVALID_ARGUMENTS (incl. duplicate column keys in one batch)
400ACCOUNTINGCOLUMNCONFIG_BAD_REQUEST_ACCOUNTING_COLUMN_CONFIG_DOES_NOT_EXIST
400ACCOUNTINGCOLUMNCONFIG_BAD_REQUEST_COLUMN_NOT_AVAILABLE_ON_PLATFORM
400ACCOUNTINGCOLUMNCONFIG_BAD_REQUEST_CANNOT_DISABLE_REQUIRED_COLUMN
401UNAUTHORIZED
403FORBIDDEN (org mismatch / row belongs to another org)
500INTERNAL_SERVER_ERROR

Updated APIs Updated

GET / POST /v1/ruttercustomfieldconfig/update Breaking request change

Update enabled status for Rutter custom fields for the organization — now accepts a batch array instead of a single field.

What changed

Request body — before

{
  "id": 12,                          // OR rutterCustomFieldDefinitionId (xor)
  "rutterCustomFieldDefinitionId": 5,
  "organizationId": 99,
  "enabled": true
}

Request body — after

FieldTypeRequiredDefaultDescription
organizationIdINTEGERNoreq.organization.idMust equal the caller's org or rejected with FORBIDDEN.
customFieldsARRAYYesMin 1 item. Each item is { id | rutterCustomFieldDefinitionId, enabled }.
customFields[].idINTEGERConditionalExisting RutterCustomFieldConfigs row id. Mutually exclusive with rutterCustomFieldDefinitionId.
customFields[].rutterCustomFieldDefinitionIdINTEGERConditionalDefinition id, used when no config row exists yet. Mutually exclusive with id.
customFields[].enabledBOOLEANYesWhether the field is enabled for the org.

Response (200)

{
  "status": 200,
  "success": true
}

Errors

StatusCode
400BAD_REQUEST_INVALID_ARGUMENTS
400RUTTERCUSTOMFIELDCONFIG_BAD_REQUEST_RUTTER_CUSTOM_FIELD_CONFIG_DOES_NOT_EXIST
400RUTTERACCOUNTINGPLATFORM_BAD_REQUEST_RUTTER_ACCOUNTING_PLATFORM_DOES_NOT_EXIST
400RUTTERCUSTOMFIELDDEFINITION_BAD_REQUEST_RUTTER_CUSTOM_FIELD_DEFINITION_DOES_NOT_EXIST
403FORBIDDEN (org mismatch)

POST /v1/rutteraccountingplatforms/update New optional field

Update accounting platform settings. Adds a transaction-specific auto-match-vendor toggle, independent of the existing (now bill-scoped) autoMatchVendor flag.

What changed

Request body

FieldTypeRequiredDefaultDescription
idINTEGERYesThe Rutter accounting platform id.
defaultCardGLAccountIdSTRINGNoDefault card GL account id. Nullable (pass null to clear).
defaultCardPaymentGLAccountIdSTRINGNoDefault card payment GL account id. Nullable.
defaultCardCashbackGLAccountIdSTRINGNoDefault card cashback GL account id. Nullable.
defaultCardChargeGLAccountIdSTRINGNoDefault card charge GL account id. Nullable.
defaultReimbursementGLAccountIdSTRINGNoDefault reimbursement GL account id. Nullable.
autoMatchVendorBOOLEANNoAuto-match vendors when syncing bills. Nullable.
autoMatchVendorOnTransactionBOOLEANNoNew. Auto-match vendors when syncing transactions. Nullable.
Request rule: at least one of the optional fields (the five GL account ids, autoMatchVendor, or autoMatchVendorOnTransaction) must be present alongside id.

Response (200)

{
  "status": 200,
  "success": true,
  "platform": { "id": 1, "autoMatchVendor": true, "autoMatchVendorOnTransaction": true, ... }
}

Errors

StatusCode
400BAD_REQUEST_INVALID_ARGUMENTS
400RUTTERACCOUNTINGPLATFORM_BAD_REQUEST_RUTTER_ACCOUNTING_PLATFORM_DOES_NOT_EXIST
400RUTTERGENERALLEDGERACCOUNT_BAD_REQUEST_ACCOUNT_DOES_NOT_EXIST
400RUTTERACCOUNTINGPLATFORM_BAD_REQUEST_CARD_PAYMENT_ACCOUNT_TYPE_MUST_NOT_BE_BANK
400RUTTERACCOUNTINGPLATFORM_BAD_REQUEST_CARD_CASHBACK_ACCOUNT_TYPE_MUST_NOT_BE_BANK
401UNAUTHORIZED
403FORBIDDEN
500INTERNAL_SERVER_ERROR

Model / Payload Changes Schema

AccountingColumnConfig New table

New table AccountingColumnConfigs — one row per organization + column key.

Surfaced to the FE via /accountingcolumnconfig/query as { id, columnKey, name, enabled, required } (name and required are derived from the catalog/platform, not stored on the row).

RutterAccountingPlatform New column

New column on the existing RutterAccountingPlatforms table.

Surfaced to the FE via /rutteraccountingplatforms/read (the full platform object is serialized). Settable via /rutteraccountingplatforms/update.

Internal Changes No FE contract change

GET / POST /v1/rutteraccountingitems/updatesyncstatus Behavior only

Auto-match-vendor step added to the Rutter sync flow. Request and response contracts are unchanged.

FE impact: None to the request/response shape. Affected items may now appear in the existing failedAccountingItems array (e.g. QuickBooks Desktop vendor creation, or a Rutter create failure).

Documentation Gaps FYI

FileIssue
None — all changed actions have JSDoc headers and Joi schemas that match.