Skip to content

Counterparties and Categorization

A counterparty is the merchant or payer on a bank transaction. Two things about it are configurable per business, and they are independent:

  • Naming decides what the counterparty is called (saved renames, naming rules, merges, resets, payee lines).
  • Categorization decides where its transactions are booked (counterparty defaults, the categorization profile, and the ledgers invoices post to).

Transactions, bank-feed sync and manual recategorization are covered in Transactions. This guide covers the settings that sit behind them. Everything here is scoped to the business in x-business-id; nothing edits shared data or another business.

All examples send Authorization: Bearer dz_your_token_here and x-business-id: YOUR_BUSINESS_ID. Successful responses use the {"success": true, "data": ...} envelope; the examples show data. Errors follow Error Handling.

Route Families

Family Base path What it holds
Counterparty defaults /api/v1/counterparty-defaults One category (plus memo and personal flag) per counterparty
Saved renames (aliases) /api/v1/counterparty-aliases The display names the business has saved
Naming /api/v1/counterparty-naming Name list, rename impact, merge, reset, inconsistencies, naming rules, payee lines
Categorization profile /api/v1/categorization-profile Business context for the AI and the automation switches
Posting defaults /api/v1/posting-defaults The ledgers invoices post to

Registry link and unlink routes (/api/v1/counterparty-registry/*) and bank rules (/api/v1/bank-rules) are internal and are not part of the public API, so they are not covered here. You can still read a registry entry's id (registry_id) from transactions, the names list and saved renames, and pass it where a route below accepts one. New customers and vendors are linked to the registry automatically; a business can turn that off with auto_link_new_counterparties on PUT /api/v1/businesses/{business_id} (see Businesses), which leaves existing links untouched.

Permissions and Plan Gating

Route group Read (GET) Write (POST, PUT, PATCH) Delete
Counterparty defaults read write delete
Saved renames, naming read write delete
Categorization profile read write n/a
Posting defaults read admin (PUT) n/a
  • Every /counterparty-defaults route, reads included, needs the AI Pro add-on. Without it the response is 403 with code BILLING_003. A business in its free trial, a demo business, and a business whose advisory firm plan bundles AI Pro (Enterprise) are unlocked.
  • Writes are refused with 403 (BILLING_001) once a trial has ended with no subscription. Reads keep working.
  • A read_only API token can call GET only; writes return 403 with AUTH_048.
  • POST /counterparty-naming/rename-impact and POST /counterparty-naming/rules/preview change nothing, but they are POST routes and need write permission.
  • POST routes return 201, except rename-impact, which returns 200. PATCH, PUT, GET and DELETE return 200.
  • The categorization profile routes are not add-on gated in the API. The Settings page asks for AI Pro before showing the profile fields, but a token with write permission can save the profile either way.

How a Counterparty Gets Its Name

A transaction's displayed counterparty is resolved in this order. The first tier that has a name wins:

  1. A name a person typed on the row (or a naming rule wrote), stored with method user.
  2. A saved rename (alias) for the row's registry entry, then for its raw name.
  3. A name taken from the business's own books (method matched: the customer or vendor of a linked invoice or bill, a per-party AR/AP ledger, or an own customer/vendor record).
  4. The linked registry entry's canonical name.
  5. The raw text from the bank.

counterparty_provenance on a transaction says which tier supplied the name (manual, naming_rule, alias, matched, registry, raw). Rows with method user or matched are protected: no automatic process (naming rules, sync, nightly merges, payee lines) rewrites them. Only a person's explicit action does.

Counterparty Defaults

A default pins one counterparty to one category ledger. It can also stamp a memo and mark matching transactions personal.

Where a default sits in categorization

The categorization engine tries these layers in order and the first match wins:

  1. Bank rules a person wrote
  2. Account-type routing (sales-tax remittance, loan principal, card autopay, owner draws)
  3. Counterparty default
  4. Bank rules the learner created automatically
  5. Merchant patterns, history, cross-business patterns, the bank's own category, then AI

So a default beats anything learned or inferred, but loses to a rule a person wrote. A default whose ledger cannot take the direction of the money (for example an expense default on a deposit) is skipped for that row and the next layer decides. A default is matched by registry entry first, then by the name the row is known by (the displayed name, the registry name, then the stored text, compared case-insensitively).

Defaults are user-configured, so they are never held back by the automation switches in the categorization profile: they book even when auto-categorization is off or the confidence line is high.

What a default does to existing and future transactions

Transactions Effect
Future (sync, upload, categorize-all) Matched rows book to the default ledger with categorization_method: counterparty_default, and the memo and personal flag ride along
Already uncategorized Creating or editing a default does not touch them by itself. They pick it up the next time categorization runs (POST /api/v1/transactions/categorize-all, suggest-categories, or a sync)
Already categorized Never moved automatically. Use the close verification fixer below to move them on purpose
Categorized by a person Skipped by the fixer unless you opt in with include_user_categorized=true
Closed periods Skipped by the fixer

Having a default also stops the counterparty audit from flagging that counterparty as "booked inconsistently".

Default fields

Field Type Notes
registry_id UUID Preferred key. Survives renames and raw-text variations
counterparty_name string, max 500 Used when there is no registry_id. Required in that case; matched case-insensitively and trimmed
default_ledger_id UUID Must be an active category ledger of this business. Bank, card and cash source accounts are refused
memo string, max 500 Stamped on matching transactions
mark_personal boolean Default false. Marks matching transactions as personal expenses

Responses add id, business_id, default_ledger_name, created_at, updated_at and conflicts.

List defaults

bash
curl "https://api.ondayzero.com/api/v1/counterparty-defaults" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID"
json
{
  "defaults": [
    {
      "id": "019ab37c-0d01-7000-8000-000000000001",
      "business_id": "019ab37c-0001-7000-8000-000000000000",
      "registry_id": "019ab37c-0e02-7000-8000-000000000002",
      "counterparty_name": null,
      "default_ledger_id": "019ab37c-0f03-7000-8000-000000000003",
      "default_ledger_name": "Software Subscriptions",
      "memo": "Monthly SaaS",
      "mark_personal": false,
      "created_at": "2026-10-01T14:02:11Z",
      "updated_at": "2026-10-01T14:02:11Z",
      "conflicts": []
    }
  ],
  "total": 1
}

The list is not paginated; total is the count of all defaults.

Check for conflicts before saving

bash
curl "https://api.ondayzero.com/api/v1/counterparty-defaults/conflicts?registry_id=REGISTRY_UUID&ledger_id=LEDGER_UUID" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID"

Query parameters are counterparty_name (max 500), registry_id and ledger_id, all optional. Pass ledger_id to see only the entries that book a different category; omit it to see every match. The response is {"conflicts": [...]}, each item {type, id, name, ledger_id, ledger_name} where type is:

  • bank_rule: an enabled bank rule whose counterparty condition matches this counterparty. A rule a person wrote outranks the default; an auto-learned rule loses to it.
  • default: another default for the same counterparty under the other key kind (a registry-keyed default and a name-keyed one for the same company).

Create a default

bash
curl -X POST "https://api.ondayzero.com/api/v1/counterparty-defaults" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "registry_id": "REGISTRY_UUID",
    "default_ledger_id": "LEDGER_UUID",
    "memo": "Monthly SaaS",
    "mark_personal": false
  }'

Returns 201 with the default. Its conflicts array lists bank rules and other defaults for the same counterparty that book a different category; this check is advisory and never fails the create.

  • A second default for the same key returns 409 (CONFLICT_005). Update the existing one instead.
  • Neither registry_id nor a non-blank counterparty_name is a 400 validation error.
  • A bank or card account as the target is 400 (VALIDATION_157), an inactive ledger 400 (VALIDATION_328), and an unknown ledger 404 (NOT_FOUND_014).

Update and delete

PATCH /api/v1/counterparty-defaults/{default_id} takes any of default_ledger_id, memo and mark_personal. The key (registry_id or counterparty_name) cannot change; delete and recreate to re-key. An empty memo clears it. The ledger is validated the same way as on create, and conflicts is returned.

DELETE /api/v1/counterparty-defaults/{default_id} removes the default and returns {"success": true, "message": "Counterparty default deleted successfully."}. Transactions already booked by it stay where they are; only future categorization changes. An unknown id is 404.

Set one category on many counterparties

bash
curl -X POST "https://api.ondayzero.com/api/v1/counterparty-defaults/bulk" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "default_ledger_id": "LEDGER_UUID",
    "memo": "Team meals",
    "mark_personal": false,
    "items": [
      { "registry_id": "REGISTRY_UUID_1" },
      { "counterparty_name": "Joes Pizza" },
      { "default_id": "EXISTING_DEFAULT_UUID" }
    ]
  }'

items takes 1 to 500 entries, each with default_id, registry_id or counterparty_name. An item that names an existing default (by default_id, or because its key already has one) is updated; the rest are created. Each item succeeds or fails on its own, and results follows request order:

json
{
  "results": [
    { "registry_id": "REGISTRY_UUID_1", "counterparty_name": null, "status": "created", "default": { "id": "...", "default_ledger_name": "Meals" }, "error": null },
    { "registry_id": null, "counterparty_name": "Joes Pizza", "status": "failed", "default": null, "error": "This entry already exists." }
  ],
  "created": 1,
  "updated": 0,
  "failed": 1,
  "conflicts_checked": true
}

Notes:

  • When every item fails for the same reason (for example the category is not an active category ledger), the request itself fails with that error instead of returning results.
  • Each saved default carries conflicts only when the batch saved 25 or fewer; otherwise conflicts_checked is false and you can call /counterparty-defaults/conflicts per counterparty.
  • memo is applied to new defaults; existing defaults keep their memo when you omit it. mark_personal is written to every item, so omitting it (default false) clears the flag on defaults that are updated.

Move already-categorized transactions

A default added after the books were categorized does not reach those rows. POST /api/v1/close/verification/apply-counterparty-defaults moves categorized rows in a period to their default's ledger. It is a dry run unless dry_run=false, accepts counterparty to limit it and a plan_hash from the preview, skips closed periods, and requires the CFO Suite add-on. See Close. To move a merchant's uncategorized rows and save a bank rule for future ones, use POST /api/v1/transactions/recategorize-by-counterparty (Transactions).

Saved Renames (Aliases)

A saved rename changes the name a business shows for a counterparty without touching the shared registry, so it cannot leak into another business. It applies to every transaction that matches its key and that nobody renamed by hand, including future ones.

There is no create endpoint here. Saved renames come from two places, both of which apply the payment-processor guard:

  • Renaming transactions with remember: true: PATCH /api/v1/transactions/batch/counterparty or PUT /api/v1/transactions/{transaction_id} (Transactions).
  • Merging counterparties.

Preview what a remembered rename would change

bash
curl -X POST "https://api.ondayzero.com/api/v1/counterparty-naming/rename-impact" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID" \
  -H "Content-Type: application/json" \
  -d '{ "transaction_ids": ["TXN_UUID_1", "TXN_UUID_2"], "new_name": "Amazon" }'

transaction_ids takes 1 to 50,000 UUIDs; new_name is 1 to 500 characters and must not be blank. Nothing is written. The response counts the other transactions a remember: true rename would relabel:

Field Meaning
other_rows_by_registry Other rows on the selected rows' registry entries
other_rows_by_name Other rows with one of the selected rows' raw names
registry_canonical_names Canonical names of the entries that would get the saved rename
would_alias_key registry, name, or null when no saved rename would be written
processor_guarded true when some rows belong to a payment processor that pays many parties (Gusto, ADP, PayPal, Stripe, Bill.com and similar). Those rows are renamed on their own, with no saved rename
wire / wire_other_rows true when some rows are wires or bank transfers (also renamed on their own), and how many other rows a saved rename would otherwise have caught

Rows renamed by hand are never counted or relabelled. The older GET /counterparty-naming/rename-impact?transaction_ids=a,b&new_name=... is deprecated, capped at 1,000 ids, and will be removed.

The rename itself reports what happened: PATCH /api/v1/transactions/batch/counterparty returns rows_renamed, aliases_created, other_rows_relabelled and, when wires were renamed on their own, remember_suppressed: "wire".

List saved renames

bash
curl "https://api.ondayzero.com/api/v1/counterparty-aliases?risky_only=false" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID"
json
{
  "aliases": [
    {
      "id": "019ab37c-1a01-7000-8000-000000000001",
      "business_id": "019ab37c-0001-7000-8000-000000000000",
      "registry_id": "019ab37c-0e02-7000-8000-000000000002",
      "counterparty_name": null,
      "display_name": "Amazon",
      "key_kind": "registry",
      "canonical_name": "Amazon.com Inc",
      "transaction_count": 42,
      "suspect": false,
      "suspect_reason": null,
      "risky": false,
      "risk_reason": null,
      "created_at": "2026-09-20T10:00:00Z",
      "updated_at": "2026-09-20T10:00:00Z"
    }
  ],
  "total": 1
}
  • key_kind is registry (every transaction linked to registry_id) or name (transactions whose stored counterparty equals counterparty_name, case-insensitively).
  • transaction_count is how many transactions display this name now. Rows renamed by hand are not counted, and a registry-keyed rename wins over a name-keyed one. It is exactly the set of rows that revert if you delete the rename.
  • suspect is true when the rename shows a payment processor's rows (Gusto, for example) as one of its payees. risky is true when it is bound to a bank or wire descriptor, or its source matches many distinct remembered names. suspect_reason and risk_reason explain why. risky_only=true returns only risky ones.

Edit and delete

PATCH /api/v1/counterparty-aliases/{alias_id} with {"display_name": "Amazon Web Services"} changes the shown name (1 to 500 characters, not blank). It is the only mutable field and returns the updated alias.

DELETE /api/v1/counterparty-aliases/{alias_id} removes the rename. The rows it named revert to their own stored name, or to the registry's canonical name when linked. Rows renamed by hand keep their name either way. An unknown id is 404.

Naming Tools

All routes below are under /api/v1/counterparty-naming and never edit the shared registry.

Existing names

GET /api/v1/counterparty-naming/names?q=amaz&limit=30 returns the business's distinct counterparty names (saved renames and registry names applied), most-used first, for a name picker. q is a substring filter (max 200 characters) and limit is 1 to 200 (default 30). Each item is {name, registry_id, txn_count}; the response has items and total.

Find likely duplicates

bash
curl "https://api.ondayzero.com/api/v1/counterparty-naming/inconsistencies?window_days=365&threshold=0.86&limit=100" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID"

A deterministic grouping of names that probably refer to the same counterparty. window_days (1 to 3650, default 365, only transactions in that window), threshold (0.5 to 1.0, default 0.86) and limit (1 to 500, default 100) are all optional. Each group has a stable id, a suggested_name, a reason (case_punctuation, suffix_or_number or similar), a score, txn_count and members of {counterparty, registry_id, txn_count}. The response also reports total_groups and names_scanned. Feed a group's members straight into a merge.

Merge counterparties

bash
curl -X POST "https://api.ondayzero.com/api/v1/counterparty-naming/merge" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "sources": [
      { "counterparty_name": "AMZN Mktp US", "registry_id": null },
      { "counterparty_name": "Amazon.com", "registry_id": "REGISTRY_UUID" }
    ],
    "target_name": "Amazon",
    "target_registry_id": "REGISTRY_UUID"
  }'

sources takes 1 to 200 groups, each {counterparty_name, registry_id?} as the name is displayed (the names list and the inconsistencies groups give you these). target_name is required and not blank; target_registry_id is optional.

json
{
  "target_name": "Amazon",
  "target_registry_id": "019ab37c-0e02-7000-8000-000000000002",
  "sources_merged": 2,
  "transactions_updated": 57,
  "aliases_written": 2,
  "dry_run": false,
  "plan_hash": null,
  "plan": [],
  "plan_truncated": 0
}

What it does:

  • Existing transactions: every transaction that currently displays one of the source names gets target_name as its stored name, marked as a person's edit so re-syncs keep it. A row renamed by hand to some other name does not display a source name and is never touched.
  • Registry link: each row keeps its registry link unless the target names an entry (target_registry_id, or the entry whose canonical name is exactly target_name), in which case every merged row links to it. An unknown target_registry_id is 404. Rows linked to a payment processor that pays many parties drop that link, since they are no longer the processor's rows.
  • Future transactions: saved renames are written so new transactions display the target too: one per registry entry involved and one per raw name of the unlinked rows (aliases_written). Processors that pay many parties get none.
  • Preview: the REST merge has no dry-run switch (the dry_run, plan_hash and plan fields are always empty here). Count first with names or inconsistencies.

Reset to the detected name

bash
curl -X POST "https://api.ondayzero.com/api/v1/counterparty-naming/reset" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID" \
  -H "Content-Type: application/json" \
  -d '{ "counterparty_name": "Amazon", "registry_id": "REGISTRY_UUID", "dry_run": true }'

Recomputes each transaction's name from its bank description (known merchant patterns first, then descriptor cleanup; no AI). It clears the registry link of every row it renames and removes the group's saved renames. Reset does overwrite names a person typed in that group, because undoing them is its purpose, so preview first.

  • dry_run: true writes nothing and returns transactions_updated and transactions_unchanged counts, names ({name, txn_count}), up to 200 plan rows ({transaction_id, from, to}), plan_truncated, and a plan_hash.
  • Send that plan_hash with dry_run: false to apply. If the rows changed in between, the apply is refused with 400 and code VALIDATION_640; preview again. Omitting plan_hash applies without the check.
  • transactions_unchanged counts rows whose description cannot be reduced to a clean name (wires, memos); they are left as they were.
  • counterparty_name scopes the reset: for a registry-linked group, the group's displayed name resets the whole group, while a name some of the rows actually carry resets only those rows (and leaves the group's saved rename in place).

Naming rules

A naming rule says: when a transaction's raw description (or current counterparty) matches a pattern, call it canonical_name. Rules run on new transactions as they are cleaned on ingest (bank sync), in ascending priority (lower first, default 100); the first match wins. Matching is case-insensitive.

Field Type Notes
pattern string, 1 to 500 Non-regex patterns need at least 2 characters; a regex must compile
match_type string contains (default), starts_with, exact, regex
canonical_name string, 1 to 500 The name to set
priority integer 0 to 10000, default 100
is_enabled boolean Default true
apply_to_existing boolean Create only, default false. Also renames matching history now
bash
curl -X POST "https://api.ondayzero.com/api/v1/counterparty-naming/rules/preview" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID" \
  -H "Content-Type: application/json" \
  -d '{ "pattern": "AMZN", "match_type": "contains" }'

The preview returns matched_transactions, user_edited_skipped (rows the rule will not rewrite), up to 8 sample rows {id, description, counterparty} and a plan_hash. Then create the rule:

bash
curl -X POST "https://api.ondayzero.com/api/v1/counterparty-naming/rules" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID" \
  -H "Content-Type: application/json" \
  -d '{ "pattern": "AMZN", "match_type": "contains", "canonical_name": "Amazon", "apply_to_existing": true }'

Other routes: GET /rules ({rules, total}), PATCH /rules/{rule_id} (any field except apply_to_existing; the pattern is revalidated against the match type), DELETE /rules/{rule_id}, and POST /rules/{rule_id}/apply, which runs a saved rule over history now and returns {rule_id, canonical_name, transactions_updated}. An unknown rule id is 404; a bad pattern is 400.

Effect on transactions:

  • Future: matching new transactions get the name on ingest. Deleting or disabling a rule stops that; it does not undo names already written.
  • Existing: only when apply_to_existing is set or you call /apply. Rows with method user (typed by a person, or written by a rule earlier) and matched (named from the books) are never rewritten, and rows already carrying the name are skipped. Renamed rows are marked as a person's edit, their registry link is cleared, and counterparty_provenance.source reads naming_rule. A disabled rule is not applied on create.
  • There is no dry-run switch on /apply in the REST API; use /rules/preview with the same pattern first.

Payee lines

Bank lines like GUSTO CND - Laura Combs - US contractor payment pay the person named in the description, not the processor. If those rows ended up filed under the processor, or one remembered rename made every processor row show as a single contractor, this pair repairs it:

  • GET /api/v1/counterparty-naming/payee-lines previews. Nothing changes. The response has transaction_count, a plan_hash, moves ({from_name, to_name, transaction_count}, up to 200), aliases (the processor saved renames that would be removed, with transaction_count) and review (up to 50 rows {transaction_id, date, description, stored_name, description_names, amount_cents} that need a person's judgement; review_count is the total).
  • POST /api/v1/counterparty-naming/payee-lines/apply applies exactly what the preview lists and returns {relabelled, aliases_removed}.

It removes only saved renames that show a processor's rows as one of its payees (a rename of "Gusto" to "Gusto Inc." is kept), and names each listed row for its payee as a manual rename would, with the registry link cleared. Rows already stored under some other name are never changed; they appear in review instead. Safe to repeat: a second call finds nothing left to do.

From Regent and MCP

The naming tools are also available in chat and to MCP clients (see MCP Server); every write pauses for approval.

Tool MCP mount What it does
find_counterparty_inconsistencies, list_saved_renames, list_counterparty_payee_lines counterparties Read the duplicate groups, remembered renames and payee-line preview
merge_counterparties, reset_counterparty_names, apply_counterparty_payee_lines counterparties The merge, reset and payee-line apply above. dry_run returns the preview and a plan_hash; pass the hash on the real call and it is refused if the rows changed
delete_saved_rename counterparties Removes one saved rename; dry_run shows what would change
bulk_update_counterparty counterparties Renames many transactions. With remember=true and dry_run=true it reports how many other rows the saved rename would relabel
unlink_master_from_registry counterparties Removes the registry link from one vendor or customer (for example a vendor linked to the wrong company). By default it also unlinks your transactions on that entry that carry the record's name; relink=true then looks for a fresh match. Names never change and the shared registry entry is untouched

Categorization Profile

One profile per business holds context the AI uses when it categorizes, plus the switches that decide what DayZero may book on its own.

bash
curl "https://api.ondayzero.com/api/v1/categorization-profile" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID"

GET returns the profile, or {} as data when none has been saved. If the profile has no accounting_basis, the business's own report basis is returned in its place.

bash
curl -X PUT "https://api.ondayzero.com/api/v1/categorization-profile" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "accounting_basis": "accrual",
    "industry": "SaaS",
    "key_personnel": ["Jane Smith"],
    "convention_overrides": { "stripe": "Merchant Fees" },
    "notes": "We capitalize software over $5k.",
    "auto_categorize_enabled": true,
    "auto_categorize_min_confidence": 0.9
  }'

PUT creates the profile or partially updates it: keys you omit are unchanged, and unknown keys are silently dropped. It returns the saved profile with id, business_id, created_at and updated_at.

Field Type Default Notes
accounting_basis string none cash or accrual (case-insensitive); anything else is 400. null falls back to the business basis
industry string none Free text that replaces the business type in the AI prompt
key_personnel string[] none Name fragments of owners and officers. Lowercased and trimmed on save; an empty list clears it. Transactions mentioning one route to owner equity or draws, ahead of static merchant patterns
convention_overrides object none {counterparty: ledger name} hints given to the AI. Keys are lowercased on save; an empty object clears it. Not a hard rule: use a counterparty default for that
tax_remittance_ledger_id UUID none Ledger that state sales-tax remittance payments book to instead of the standard one
notes string none Free-form business context added to the AI prompt
auto_categorize_enabled boolean true false stops DayZero booking machine guesses; they wait as suggestions. Must be a boolean
auto_categorize_min_confidence number or null null (0.8) 0.5 to 0.99. A guess below it waits for review. null restores the default
auto_match_on_sync boolean false Links bank payments to invoices and bills after each sync when the match scores 0.85 or higher
counterparty_auto_merge_enabled boolean true false opts the business out of the nightly merging of near-identical counterparty names

Effects:

  • Future transactions only. The profile shapes categorization runs from now on. It never moves transactions that are already categorized.
  • Never held back by the switches: answers from your own bank rules, counterparty defaults and account-type routing. auto_categorize_enabled and the confidence line apply only to inferred answers (history, patterns, the bank's category, AI). An AI answer is also booked only when another layer agrees with it.
  • A run started by a person ("categorize now") ignores the on/off switch but still respects the confidence line.
  • Invalid values return 400 with a message naming the field (for example accounting_basis must be one of ['accrual', 'cash']).

Posting Defaults

Posting defaults choose the ledgers invoices post to. An invoice (and a credit memo) credits Invoice Sales, debits Accounts Receivable, and moves Cost of Goods Sold and Inventory. Until you pick your own, the system ledger for each role is used.

Role (entries[].role) Field Ledger type required System ledger it replaces
invoice sales invoice_sales_ledger_id revenue invoice_sales
accounts receivable invoice_ar_ledger_id asset accounts_receivable
cost of goods sold invoice_cogs_ledger_id expense cost_of_goods_sold
inventory invoice_inventory_ledger_id asset inventory
bash
curl "https://api.ondayzero.com/api/v1/posting-defaults" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID"

The response has the four *_ledger_id fields (null for a role on the system ledger) and entries, one per role: {role, system_key, ledger_id, ledger_name, ledger_active}. A ledger_active of false means the chosen ledger was deactivated since; postings for that role are affected until you change it.

bash
curl -X PUT "https://api.ondayzero.com/api/v1/posting-defaults" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID" \
  -H "Content-Type: application/json" \
  -d '{ "invoice_sales_ledger_id": "REVENUE_LEDGER_UUID", "invoice_cogs_ledger_id": null }'

PUT needs admin permission. A field you omit is unchanged; null returns that role to the system ledger. Each chosen ledger is validated: an unknown ledger is 404 (NOT_FOUND_014), an inactive one 400 (VALIDATION_700), and one of the wrong account type 400 (VALIDATION_701).

Only new postings use the change. Journal entries already posted stay on the ledgers they used. A catalog item's own income ledger still wins over the Invoice Sales default for that line.

To see where existing invoice entries sit, call the read-only preview:

bash
curl "https://api.ondayzero.com/api/v1/posting-defaults/preview?limit=20" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID"

It returns invoice_journal_entries (finalized-invoice entries in the books), on_system_defaults, on_configured_ledgers, elsewhere, configured_roles and up to limit (1 to 200, default 20) samples of entries still on the system ledgers, as {journal_entry_id, description, date}. An entry with lines on both counts as configured. See Invoices and Credit Memos for the postings themselves.

What Is Never Overwritten

Setting Protects
Saved renames, merges, naming rules, payee lines Names a person typed (user) and names taken from your own books (matched)
Naming rule apply, payee lines apply Rows already named something else (payee lines list them under review)
Counterparty default, categorization profile Transactions that are already categorized (nothing moves unless you run the close verification fixer)
Close verification fixer Closed periods, and rows a person categorized unless include_user_categorized=true
Posting defaults Journal entries already posted
Reset Nothing in the group: it recomputes every row's name (preview with dry_run)

Not Covered

This guide does not cover bank rules, the counterparty registry routes, the Counterparties audit page data, or the assistant tools that wrap these routes (MCP Server). Those are internal or documented elsewhere.