Transaction categorization

Sort every transaction in a statement set, and every receipt or invoice, into a chart of accounts: your own vendor rules first, then AI restricted to accounts that actually exist in your chart.

A matched invoice with a valid payment date in a different year from bank posting appears once in the payment year. The bank row keeps its category and includes a review_note with both dates. The other year omits the pair. The requested year still needs its own access.

Annual categorized transactions retain readable rows when another statement row is unreadable. An unreadable amount is null, needs_input is true, and review_reason identifies what our reading missed.

Account names must contain a non-whitespace character. Category rule priorities must be integers. Settings updates must include large_purchase_threshold or work_description; send null to clear the work description. Vehicle deduction years must be integers from 2000 through 2100. Invalid values return 400.

Group Undo restores each row to its state immediately before confirmation. Return each undoToken in confirmedRows unchanged. A supplied token must be a string; other types return 400. Rows edited later are kept and counted in skippedCount, with details in failedRows.

Possible transfers need your confirmation. Opposite amounts alone stay in review with reason code transfer_suggestion and are not marked as transfers. Your rules take precedence. AI transfer suggestions always need confirmation, regardless of confidence.

Categorization uses the extracted data from an uploaded statement set, receipt or invoice. For bank statements, Arkrel reconciles the statement’s own extracted totals against its printed balances. It never changes an amount, and it never invents an account: every category it assigns points at a real row in your own chart of accounts.

Unknown transaction dates remain empty and need a date before they count in any tax year. Upload dates are never used as transaction dates.

The chart of accounts

A brand-new org starts with a standard chart the first time it’s used: a self-employed business chart mapped to IRS Schedule C (Form 1040), every expense account carries the exact line it maps to (e.g. “Line 18 Office expense”), plus a short personal-spending chart, so a mixed-use account’s personal transactions land somewhere honest instead of being forced into a business account. Fully editable afterward (PATCH /v1/chart-of-accounts/{id}), and importable from a QuickBooks or Xero chart-of-accounts CSV export. The standard chart also works without importing a file.

Creating or renaming an account to a name already used in the same scope returns HTTP 409 with ACCOUNT_NAME_EXISTS. Choose another name.

Rules, then AI

Rule creation and updates reject unknown values for amount_direction, match_field, and pattern_type with HTTP 400. Omitted fields on creation use the documented defaults.

Every transaction is checked against your own vendor rules first (a QuickBooks/Xero-style condition: description contains/starts with/exactly matches/matches a pattern, optionally gated by money-in/money-out direction and an amount range). A rule match is instant and free. Anything a rule doesn’t catch goes to an AI step whose answer is restricted to the account names that actually exist in your chart. It can never invent one, and it always comes back with a confidence score.

New orgs start with a few built-in rules for common self-employed patterns:

  • Incoming payments from Stripe, PayPal, Upwork, Uber, or DoorDash route to Business income.
  • An outgoing payment to a seller or contractor via Upwork, Fiverr, or Freelancer.com routes to Contract labor. A platform fee stays on Commissions and fees.
  • An explicit transfer to yourself routes to Owner’s draw. A bare ATM or cash withdrawal does not auto-categorize on its own. It’s genuinely ambiguous (a business expense or a personal draw), so it’s sent to your input unless you’ve created your own rule for your own withdrawals.

Group undo requires a non-negative integer rowIndex and a non-empty documentId for each row. If supplied, undoToken must be a non-empty string. Invalid rows return HTTP 400 with INVALID_ROWS before any changes are made.

Reviewing by merchant, not by row

The review screen groups your transactions by merchant instead of showing one row at a time, the same pattern QuickBooks and Xero use for their own bank rules. Each group shows the merchant, how many transactions, the total, the date range, and the suggested account. One action confirms the whole group at once and saves a rule, so the same merchant sorts itself automatically on every future upload too. You can still expand any group and change a single row.

Two kinds of transactions are never bulk-confirmed, even inside a group: a large or equipment-looking purchase, and a bare ATM or cash withdrawal. Both always stay individual, so they get the judgment call they need. A known mixed-use merchant (Amazon, Walmart, Costco, Target, Apple, PayPal, Venmo) always opens expanded, with a note that purchases there can be business or personal. Confirming that group never creates an automatic rule unless you choose to. Undo is available right after confirming a group, and reverses both the categorization and the rule.

GET /v1/statement-sets/{id}/categorization/groups returns the same merchant-grouped view; POST .../categorization/groups/confirm confirms a group by its own merchant_key; POST .../categorization/groups/undo undoes it.

Transfers and owner’s draws

A transfer between two of your own accounts, including paying a credit card from checking, is detected automatically (opposite-signed amounts of the same size, a few days apart, on two different account numbers) and marked as a transfer, never as income or an expense, so it’s never double-counted. This works even when the two accounts are uploaded as two separate statement sets.

Large purchases go to review

During automatic categorization, outgoing purchases that are not marked as transfers and whose amounts are at or above the organization’s review threshold go to review. The threshold defaults to $2,500 and is adjustable through PATCH /v1/chart-of-accounts/settings. Existing accepted or manually assigned categories are preserved. Schedule C line 13 depreciation needs a useful-life/placed-in-service decision that bank data alone can never supply. Purchases below the threshold continue through the normal categorization rules and any other applicable review requirements. Equipment-related wording alone does not trigger large-purchase review. The accountant CSV includes the recorded category and review status and leaves tax-treatment notes blank.

Under Treas. Reg. § 1.263(a)-1(f), an annual de minimis safe harbor election permits deduction of qualifying tangible-property costs that satisfy the applicable requirements. The limit is $2,500 per invoice or item substantiated by the invoice for taxpayers without an applicable financial statement, or $5,000 for taxpayers with one. The taxpayer must expense the costs in its books and records or applicable financial statement under accounting procedures in place at the beginning of the tax year; those procedures must be written for taxpayers with an applicable financial statement. The election statement is attached to the timely filed original federal return, including extensions, for that year.

Vehicle costs and how you deduct them

A “Car and truck expenses” candidate depends entirely on how you deduct your vehicle, so it’s never guessed at until you say: GET/PATCH /v1/tax-years/{year}/vehicle-deduction sets one of standard_mileage (the IRS per-mile rate already covers repairs, fuel, and insurance. A vehicle-cost transaction is marked non-deductible, never claimed again on Line 9), actual_expenses (real vehicle costs are the Line 9 deduction), or no_vehicle. Scoped per tax year, since the choice can change year to year. Unanswered for a year, a vehicle-cost transaction from that year always lands in Needs your input.

What kind of work you do

PATCH /v1/chart-of-accounts/settings also accepts work_description, a one-time, plain-English answer to “What kind of work do you do?” fed into the AI categorization step as context, the same idea apps like Keeper use to tailor categories to the job. It’s a hint only: the AI still only ever picks from your own chart, and a vague or wrong answer here never overrides a clearer signal.

Running categorization

GET /v1/statement-sets/{id}/categorization always runs a free, rule-only pass (never calls the AI on its own, so simply opening the page never costs anything). POST /v1/statement-sets/{id}/categorize runs the full pass, rules then AI:

http
POST /v1/statement-sets/sts_9f2a1c/categorize HTTP/1.1
Authorization: Bearer sk_live_...
json
{
  "transactions": [
    {
      "transaction": { "documentId": "doc_january", "rowIndex": 4, "date": "2026-01-11",
        "description": "STRIPE PAYOUT", "amount": 842.10 },
      "category": { "status": "categorized", "confidence": 1, "source": "rule", "is_transfer": false },
      "account": { "id": "acct_income", "name": "Business income", "type": "income",
        "tax_line": "Line 1 Gross receipts or sales" },
      "reconciled": true
    },
    {
      "transaction": { "documentId": "doc_january", "rowIndex": 9, "date": "2026-01-14",
        "description": "BEST BUY #4021", "amount": -3199.00 },
      "category": null,
      "account": null,
      "reconciled": true
    }
  ]
}

Correcting a category

A manual correction always becomes your own final answer, with one level of undo. Set create_rule to also learn a rule from the correction and apply it to the rest of the set right away:

http
PATCH /v1/statement-sets/sts_9f2a1c/categorization/rows/doc_january/9 HTTP/1.1
Authorization: Bearer sk_live_...
Content-Type: application/json

{
  "account_id": "acct_office",
  "create_rule": true
}

Needs your input

GET /v1/statement-sets/{id}/categorization/questions lists every transaction that still needs your own look, nothing decided it automatically, each with the date, amount, description, and whatever suggestion (if any) came back. Every one also carries a reason: one short, plain-English line explaining why (for example, “this looks like a large purchase or piece of equipment”, or “this looks like a vehicle expense, but you haven’t said how you deduct your vehicle yet”, or just “our best guess, but not confident enough to apply automatically”). Answer it with the same PATCH endpoint above.

Turn review items into clear client questions

A transaction needing review should become a question the client can answer from their records. Ask for the facts behind the payment: what was purchased, who received it, why it was paid, and which supporting paperwork is available. Your firm can then decide the accounting treatment.

Keep a question log in your firm’s usual secure client channel or workpaper. Maintain that record alongside the client’s Arkrel review so explanations stay connected to the activity they describe.

Make the transaction easy to find

Include the date, amount and currency, merchant description, account label and source reference. Use the statement filename and a page or row reference when needed. The client and reviewer should be able to find the same payment without guessing from a merchant name alone.

For example: “For the May 12 purchase of $186.40 at Amazon, what did you buy, how was it used for the business, and can you provide the order receipt?” Ask for the missing information rather than requiring the client to choose an accounting category.

Group questions only when the answer applies to every row

A combined question can help when several transactions depend on the same fact. Confirm that the explanation covers every item. A supplier used for both business and personal purchases may need separate answers.

During merchant review, the client can inspect the grouped activity and its individual transactions. Have them look at exceptions before confirming a wider pattern or applying a rule. One business purchase does not establish the purpose of every payment to that merchant.

Separate the client answer from the review decision

For each question, keep the client’s explanation, the supporting document reference, the reviewer’s decision and any remaining uncertainty. An answer such as “business purchase” may still need a follow-up about the item or its use.

Use simple statuses in the firm’s workpaper, such as waiting for client, answered and reviewed. Mark an item reviewed after someone checks the answer and decides the next action. A message received is an opportunity to review the evidence.

Return corrections to the client

The client makes agreed changes in their own Arkrel account. Keep the explanation and the firm’s review decision in your records, then check the updated output used for the engagement. If an answer is unavailable, preserve the question and name the person who will decide how it affects the work.

For questions about a payment’s purpose, the business and personal expense guide gives clients a useful starting point. Missing source evidence belongs in a separate document request.

Should we ask the client to pick the tax category?

Start by asking what happened and requesting the relevant evidence. The client supplies business context; your firm decides how that context affects its accounting or tax-preparation work.

Can one answer resolve every transaction from the same merchant?

Only apply the explanation to the transactions it actually covers. Ask about mixed-use merchants and individual exceptions before the client confirms a broader pattern in merchant review.

Deductions and excluded transactions

GET /v1/statement-sets/{id}/deductions groups every categorized or accepted business-expense transaction in USD by its Schedule C line. These rows are included in totals and their categories can still be changed. Rows needing input stay out until their categories are decided. Each transaction includes its original currency. Foreign and unresolved currencies appear in currencyReview with a reviewReason and stay out of confirmedTotal and possible totals. GET /v1/statement-sets/{id}/excluded lists exactly what got excluded as Owner’s draw or personal spending, so you can confirm nothing was mis-sorted in either direction.

Two separate signals, never merged

Every categorized transaction carries reconciled (whether its source document’s own extraction and balance checks passed) and category.confidence (how sure the categorization is) as two separate fields. They answer different questions and are never combined into one badge. A confidently categorized transaction on a document with a real balance mismatch is never shown as if everything about it were fine.

Receipts and invoices

A receipt or invoice is categorized as its own single transaction (its total) via GET/POST/PATCH /v1/documents/{id}/categorization. It’s also matched automatically to its bank or card transaction by exact amount, a date window, and merchant similarity. A match is never claimed by two different receipts, so nothing is ever double-counted.

Exports

The combined CSV and XLSX exports get an “Account” column. Xero’s CSV export switches to Xero’s own documented “precoded” format (adding an Account Code column) once every transaction in the set has an assigned account with a code, never a half-coded file. QuickBooks’ own plain bank-transaction CSV format ignores any category column on import (per Intuit’s own documentation), so its export is unchanged.