Categorize expenses
Send card or bank transactions and get each one sorted into a bucket such as travel, office supplies, software or meals, read from the merchant name, amount and memo. Your own category list works too. You pay per use, with no subscription.
Run it online
Run this on our servers with your account. Free tools run in your browser; this one bills your KIT balance per the price above.
Every bookkeeper knows the drudgery: a CSV of five hundred card transactions, each needing a category before the books close.
Why transaction descriptions are so hard to sort
Every bookkeeper knows the drudgery: a spreadsheet of five hundred card transactions, each needing a category before the books close. The descriptions are cryptic. A merchant string like SQ *DARK ROAST LLC tells a person almost nothing without context, and tagging thousands of rows by hand every month is not a job anyone wants. The tool reads the merchant name, the amount and any memo together, the way an experienced bookkeeper would, and assigns each line to the bucket it belongs in, such as travel, office supplies, software or meals.
Your own categories or a sensible default
You can send your own category list with the transactions, so the result matches the accounts you already use, or let it fall back on a sensible general-ledger default. Large batches are the normal case, not the exception: a month-end bank feed can be sorted in one go and handed to the accountant as a pre-sorted ledger instead of a raw spreadsheet. Expense apps can tag employee card payments the moment they land, so approval queues show a category instead of a blank. It removes the chore of tagging, not the bookkeeper's judgment on edge cases.
Who actually needs this
Bookkeepers reconciling client accounts, finance teams closing month-end, and expense-management tools that ingest raw bank feeds all hit the same wall: transaction descriptions are cryptic. A merchant string like 'SQ *DARK ROAST LLC' tells a human almost nothing without context, and manually tagging thousands of rows every month is not a job anyone wants. Expense categorization exists to remove that specific chore, not to replace the bookkeeper's judgment on edge cases.
What you can do with it
Month-end close automation
Pull the bank feed, categorize every transaction in one async batch, and hand the accountant a pre-sorted ledger instead of a raw CSV.
Expense app backends
Auto-tag employee card swipes the moment they land, so reimbursement approval queues show a category instead of a blank field.
Spend-analysis dashboards
Feed categorized transactions into a BI tool to chart spend by department or vendor type without manual tagging first.
FAQ
Is there a free tier?
There's no free tier — free tiers get abused and slow everyone down. No subscription, no tokens, no invented credits, and a failed task is never charged.
What happens if a transaction can't be categorized confidently?
It's still returned with a best-guess category and a note flagging lower confidence, so your review workflow can catch it instead of silently misfiling it.
Is this a replacement for an accountant?
No — it automates the repetitive sorting step so your bookkeeper or accountant can focus on judgment calls and exceptions instead of manual tagging.
Can I send bulk batches of thousands of transactions?
Yes, bulk is the expected use case; you send one array of transactions and pricing scales linearly per item with no arbitrary batch cap.
Can I see why a transaction landed in a category?
Yes, where it helps. Each line comes back with the category it was assigned and a short note on why that bucket was chosen, so a reviewer can spot-check the result without working the logic out again.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/text/expense-categorize \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"El informe trimestral cerró con 1,2 M€ de ingresos.","categories":["soporte","ventas","facturación"]}'const res = await fetch("https://api.kit.forhosting.com/text/expense-categorize", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"text": "El informe trimestral cerró con 1,2 M€ de ingresos.",
"categories": [
"soporte",
"ventas",
"facturación"
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/text/expense-categorize",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"text": "El informe trimestral cerró con 1,2 M€ de ingresos.",
"categories": [
"soporte",
"ventas",
"facturación"
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/text/expense-categorize", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"text":"El informe trimestral cerró con 1,2 M€ de ingresos.","categories":["soporte","ventas","facturación"]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"text":"El informe trimestral cerró con 1,2 M€ de ingresos.","categories":["soporte","ventas","facturación"]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/text/expense-categorize", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"text": "El informe trimestral cerró con 1,2 M€ de ingresos.",
"categories": [
"soporte",
"ventas",
"facturación"
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "text.expense_categorize",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_tokens | 20000 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
422 | task_failed | The task failed after 3 retries. You are never charged for it. |
Technical notes
This endpoint reads the merchant string, amount and any memo you supply, then assigns each line to the accounting bucket it belongs in — travel, office supplies, software, meals — so your ledger closes faster and your accountant stops guessing.
What you send and what comes back
Each transaction comes back with its assigned category and, where useful, a short note on why that bucket was chosen, so a human reviewer can spot-check without re-deriving the logic. You POST an array of transactions — description, amount, currency, and optionally your own category list — to /text/expense-categorize. The task runs asynchronously: you get a task_id immediately and the categorized result later, either pushed to your signed webhook or waiting behind a signed link valid for 24 hours.
How the categorization actually works
The engine reads the merchant name, transaction amount, and memo text together, matching patterns against your supplied taxonomy or a sensible general-ledger default if you don't provide one. It's the same instinct an experienced bookkeeper applies — 'this recurring $12.99 charge from a software vendor is a subscription, not a one-off purchase' — turned into something you can call from code instead of scrolling a spreadsheet.
Fitting it into a real pipeline
Bulk batches are the normal use case, not an exception: send 50 rows or 50,000, and pricing scales predictably with volume rather than punishing you for the size of your ledger. Because it's async and metered per item, you can batch a whole month of transactions in one call and let the webhook notify your accounting system when it's done — no polling loop needed.
Where it stops
This is pattern-based categorization, not tax advice or audit certification — ambiguous transactions (a $400 charge from a general retailer, say) will get a best-guess category with lower confidence signals baked into the note field, and your finance team should keep a review step for anything above your materiality threshold. That honesty is the point: a categorizer that silently guesses wrong is worse than one that flags uncertainty.
Integration use cases
Multi-entity bookkeeping firms
Run each client's transactions against their own chart of accounts by passing a custom taxonomy per request.
Technical questions
How does the expense categorization API decide a category?
It reads the merchant description, amount and memo together and matches them against your chart of accounts or a default general-ledger taxonomy, returning the best-fit category per transaction.
Can I use my own category list instead of a default one?
Yes, pass your taxonomy in the request and every transaction is matched against it instead of the built-in default.
How much does it cost?
$0.003 per request plus $0.0135 per item categorized, so a 200-transaction batch costs a predictable, fixed amount you can calculate before you send it.
How do I get the results?
The task runs asynchronously and returns a task_id immediately; results arrive at your signed webhook or via a signed link valid for 24 hours, whichever you configure.