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-defaultsroute, reads included, needs the AI Pro add-on. Without it the response is403with codeBILLING_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_onlyAPI token can callGETonly; writes return403withAUTH_048. POST /counterparty-naming/rename-impactandPOST /counterparty-naming/rules/previewchange nothing, but they arePOSTroutes and need write permission.POSTroutes return201, exceptrename-impact, which returns200.PATCH,PUT,GETandDELETEreturn200.- 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:
- A name a person typed on the row (or a naming rule wrote), stored with method
user. - A saved rename (alias) for the row's registry entry, then for its raw name.
- 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). - The linked registry entry's canonical name.
- 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:
- Bank rules a person wrote
- Account-type routing (sales-tax remittance, loan principal, card autopay, owner draws)
- Counterparty default
- Bank rules the learner created automatically
- 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
curl "https://api.ondayzero.com/api/v1/counterparty-defaults" \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID"{
"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
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
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_idnor a non-blankcounterparty_nameis a400validation error. - A bank or card account as the target is
400(VALIDATION_157), an inactive ledger400(VALIDATION_328), and an unknown ledger404(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
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:
{
"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
conflictsonly when the batch saved 25 or fewer; otherwiseconflicts_checkedisfalseand you can call/counterparty-defaults/conflictsper counterparty. memois applied to new defaults; existing defaults keep their memo when you omit it.mark_personalis written to every item, so omitting it (defaultfalse) 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/counterpartyorPUT /api/v1/transactions/{transaction_id}(Transactions). - Merging counterparties.
Preview what a remembered rename would change
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
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"{
"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_kindisregistry(every transaction linked toregistry_id) orname(transactions whose stored counterparty equalscounterparty_name, case-insensitively).transaction_countis 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.suspectistruewhen the rename shows a payment processor's rows (Gusto, for example) as one of its payees.riskyistruewhen it is bound to a bank or wire descriptor, or its source matches many distinct remembered names.suspect_reasonandrisk_reasonexplain why.risky_only=truereturns 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
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
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.
{
"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_nameas 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 exactlytarget_name), in which case every merged row links to it. An unknowntarget_registry_idis404. 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_hashandplanfields are always empty here). Count first withnamesorinconsistencies.
Reset to the detected name
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: truewrites nothing and returnstransactions_updatedandtransactions_unchangedcounts,names({name, txn_count}), up to 200planrows ({transaction_id, from, to}),plan_truncated, and aplan_hash.- Send that
plan_hashwithdry_run: falseto apply. If the rows changed in between, the apply is refused with400and codeVALIDATION_640; preview again. Omittingplan_hashapplies without the check. transactions_unchangedcounts rows whose description cannot be reduced to a clean name (wires, memos); they are left as they were.counterparty_namescopes 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 |
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:
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_existingis set or you call/apply. Rows with methoduser(typed by a person, or written by a rule earlier) andmatched(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, andcounterparty_provenance.sourcereadsnaming_rule. A disabled rule is not applied on create. - There is no dry-run switch on
/applyin the REST API; use/rules/previewwith 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-linespreviews. Nothing changes. The response hastransaction_count, aplan_hash,moves({from_name, to_name, transaction_count}, up to 200),aliases(the processor saved renames that would be removed, withtransaction_count) andreview(up to 50 rows{transaction_id, date, description, stored_name, description_names, amount_cents}that need a person's judgement;review_countis the total).POST /api/v1/counterparty-naming/payee-lines/applyapplies 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.
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.
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_enabledand 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
400with a message naming the field (for exampleaccounting_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 |
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.
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:
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.