- Add Step 5 to bill entry: upload invoice attachment if available - Add batch attachment upload guidance to Batch Operations - Add validation checklist item for missing attachments - Add common mistake entry for missing invoice attachments
17 KiB
name, description
| name | description |
|---|---|
| grist-accounting | Use when working with the Grist double-entry accounting system - recording transactions, entering bills, tracking vendors, querying account balances, or generating financial reports |
Grist Double-Entry Accounting System
Overview
A complete double-entry accounting system for sole proprietorship service businesses. Every transaction creates balanced journal entries (debits = credits). Account balances roll up through parent-child hierarchy.
MCP Tools Available
| Tool | Purpose |
|---|---|
mcp__grist-accounting__list_documents |
List accessible Grist documents |
mcp__grist-accounting__list_tables |
List tables in a document |
mcp__grist-accounting__describe_table |
Get column schema for a table |
mcp__grist-accounting__get_records |
Fetch records (with optional filter, sort, limit) |
mcp__grist-accounting__add_records |
Insert new records, returns {"inserted_ids": [...]} |
mcp__grist-accounting__update_records |
Update existing records by ID |
mcp__grist-accounting__delete_records |
Delete records by ID |
mcp__grist-accounting__sql_query |
Run read-only SQL queries |
The document name is accounting for all operations.
Date Handling
All date fields use Unix timestamps (seconds since 1970-01-01 UTC).
| Date | Timestamp |
|---|---|
| Oct 1, 2025 | 1759363200 |
| Nov 1, 2025 | 1762041600 |
| Dec 1, 2025 | 1764633600 |
| Jan 1, 2026 | 1767312000 |
Python: int(datetime(2025, 10, 1).timestamp())
Complete Table Schemas
Accounts
| Column | Type | Notes |
|---|---|---|
| Code | Text | Account number (e.g., "5080") |
| Name | Text | Account name |
| Type | Choice | "Asset", "Liability", "Equity", "Income", "Expense" |
| Parent | Ref:Accounts | Parent account for hierarchy (0 = top-level) |
| Description | Text | |
| IsActive | Bool | |
| Balance | Formula | Calculated from transactions |
Vendors
| Column | Type | Notes |
|---|---|---|
| Name | Text | Vendor name |
| DefaultExpenseAccount | Ref:Accounts | Auto-fills on bill lines |
| PaymentTerms | Choice | "Due on Receipt", "Net 15", "Net 30" |
| Notes | Text | |
| IsActive | Bool | |
| Balance | Formula | Sum of unpaid bills |
Items
| Column | Type | Notes |
|---|---|---|
| Name | Text | Item name (e.g., "Software Subscription") |
| DefaultAccount | Ref:Accounts | Expense account for this item |
| DefaultDescription | Text | Auto-fills on bill lines |
| IsActive | Bool |
Bills
| Column | Type | Notes |
|---|---|---|
| Vendor | Ref:Vendors | Required |
| BillNumber | Text | Invoice number from vendor |
| BillDate | Date | Unix timestamp |
| DueDate | Date | Unix timestamp |
| Status | Choice | "Open", "Partial", "Paid" |
| Memo | Text | |
| EntryTransaction | Ref:Transactions | Link to journal entry |
| Attachment | Attachments | Invoice/receipt files (use ["L", id] format) |
| Amount | Formula | Sum of BillLines.Amount |
| AmountPaid | Formula | Sum of BillPayments.Amount |
| AmountDue | Formula | Amount - AmountPaid |
BillLines
| Column | Type | Notes |
|---|---|---|
| Bill | Ref:Bills | Required |
| Item | Ref:Items | Optional - auto-fills Account/Description |
| Account | Ref:Accounts | Expense account |
| Description | Text | |
| Amount | Numeric | Line item amount |
Transactions
| Column | Type | Notes |
|---|---|---|
| Date | Date | Unix timestamp |
| Description | Text | Transaction description |
| Reference | Text | Check number, invoice reference, etc. |
| Status | Choice | "Draft", "Posted", "Cleared" |
| Memo | Text | |
| Total | Formula | Sum of debits |
| IsBalanced | Formula | True if debits = credits |
TransactionLines
| Column | Type | Notes |
|---|---|---|
| Transaction | Ref:Transactions | Required |
| Account | Ref:Accounts | Required |
| Debit | Numeric | Debit amount (or 0) |
| Credit | Numeric | Credit amount (or 0) |
| Memo | Text |
BillPayments
| Column | Type | Notes |
|---|---|---|
| Bill | Ref:Bills | Required |
| Transaction | Ref:Transactions | Payment journal entry |
| Amount | Numeric | Payment amount |
| PaymentDate | Date | Unix timestamp |
Key Account IDs
| ID | Code | Name | Type |
|---|---|---|---|
| 4 | 2000 | Accounts Payable | Liability |
| 14 | 1001 | Checking Account | Asset |
| 22 | 2203 | Due to Owner | Liability |
| 36 | 5080 | Software & Subscriptions | Expense |
Query all accounts:
SELECT id, Code, Name, Type FROM Accounts WHERE IsActive = true ORDER BY Code
Account Types
| Type | Normal Balance | Increases With | Examples |
|---|---|---|---|
| Asset | Debit | Debit | Cash, AR, Prepaid |
| Liability | Credit | Credit | AP, Credit Cards, Due to Owner |
| Equity | Credit | Credit | Owner's Investment, Draws, Retained Earnings |
| Income | Credit | Credit | Service Revenue, Interest Income |
| Expense | Debit | Debit | Rent, Utilities, Office Supplies |
Complete Workflows
Create a Vendor
add_records("Vendors", [{
"Name": "Acme Corp",
"DefaultExpenseAccount": 36, # Software & Subscriptions
"PaymentTerms": "Due on Receipt",
"Notes": "Software vendor",
"IsActive": True
}])
# Returns: {"inserted_ids": [vendor_id]}
Create Items for Common Purchases
add_records("Items", [{
"Name": "Monthly Software",
"DefaultAccount": 36,
"DefaultDescription": "Monthly SaaS subscription",
"IsActive": True
}])
Complete Bill Entry (5 Steps)
Step 1: Create Bill Header
add_records("Bills", [{
"Vendor": 1, # vendor_id
"BillNumber": "INV-001",
"BillDate": 1759708800, # Unix timestamp
"DueDate": 1759708800,
"Status": "Open",
"Memo": "October services"
}])
# Returns: {"inserted_ids": [bill_id]}
Step 2: Create Bill Line(s)
add_records("BillLines", [{
"Bill": 1, # bill_id from step 1
"Item": 1, # optional - auto-fills Account/Description
"Account": 36, # expense account
"Description": "Monthly subscription",
"Amount": 100.00
}])
Step 3: Create Journal Entry
# Transaction header
add_records("Transactions", [{
"Date": 1759708800,
"Description": "Acme Corp - October services",
"Reference": "INV-001",
"Status": "Posted"
}])
# Returns: {"inserted_ids": [txn_id]}
# Transaction lines: Debit expense, Credit AP
add_records("TransactionLines", [
{"Transaction": 1, "Account": 36, "Debit": 100.00, "Credit": 0, "Memo": "Monthly subscription"},
{"Transaction": 1, "Account": 4, "Debit": 0, "Credit": 100.00, "Memo": "Monthly subscription"}
])
Step 4: Link Bill to Transaction
update_records("Bills", [{"id": 1, "fields": {"EntryTransaction": 1}}])
Step 5: Upload Invoice Attachment (if available)
If an invoice PDF is available, upload and link it:
# Get session token, then upload
./scripts/upload-attachment.sh invoice.pdf Bills 1 $TOKEN
Or for batch uploads, use a script (see Batch Operations).
Pay Bill from Checking Account
# Step 1: Create payment transaction
add_records("Transactions", [{
"Date": 1760832000,
"Description": "Payment - Acme Corp INV-001",
"Reference": "Check #1001",
"Status": "Cleared"
}])
# Returns: {"inserted_ids": [txn_id]}
# Step 2: Debit AP, Credit Checking
add_records("TransactionLines", [
{"Transaction": 2, "Account": 4, "Debit": 100.00, "Credit": 0, "Memo": "Pay INV-001"},
{"Transaction": 2, "Account": 14, "Debit": 0, "Credit": 100.00, "Memo": "Pay INV-001"}
])
# Step 3: Create BillPayment record
add_records("BillPayments", [{
"Bill": 1,
"Transaction": 2,
"Amount": 100.00,
"PaymentDate": 1760832000
}])
# Step 4: Update bill status
update_records("Bills", [{"id": 1, "fields": {"Status": "Paid"}}])
Pay Bill via Owner Reimbursement
When the owner pays a business expense personally:
# Step 1: Create payment transaction
add_records("Transactions", [{
"Date": 1760832000,
"Description": "Owner payment - Acme Corp INV-001",
"Reference": "Owner Reimb",
"Status": "Posted"
}])
# Step 2: Debit AP, Credit Due to Owner (not Checking)
add_records("TransactionLines", [
{"Transaction": 2, "Account": 4, "Debit": 100.00, "Credit": 0, "Memo": "Pay INV-001"},
{"Transaction": 2, "Account": 22, "Debit": 0, "Credit": 100.00, "Memo": "Owner paid"}
])
# Step 3: Create BillPayment record
add_records("BillPayments", [{
"Bill": 1,
"Transaction": 2,
"Amount": 100.00,
"PaymentDate": 1760832000
}])
# Step 4: Update bill status
update_records("Bills", [{"id": 1, "fields": {"Status": "Paid"}}])
Reimburse Owner
When business pays back the owner:
add_records("Transactions", [{
"Date": 1762041600,
"Description": "Owner reimbursement",
"Reference": "Transfer",
"Status": "Cleared"
}])
add_records("TransactionLines", [
{"Transaction": 3, "Account": 22, "Debit": 500.00, "Credit": 0, "Memo": "Reimburse owner"},
{"Transaction": 3, "Account": 14, "Debit": 0, "Credit": 500.00, "Memo": "Reimburse owner"}
])
Batch Operations
When entering multiple bills efficiently:
- Create all Bills first → collect inserted IDs
- Create all BillLines referencing bill IDs
- Create all Transactions → collect inserted IDs
- Create all TransactionLines referencing transaction IDs
- Update all Bills with EntryTransaction links in one call
- (If paying) Create payment transactions, lines, and BillPayments
- Upload invoice attachments if files are available
Batch Attachment Uploads
When invoice files are available, upload them after bill entry:
- Request session token with write permission (1 hour TTL for batch work)
- Create a mapping of bill_id → invoice file path
- Loop: upload each file, link to corresponding bill
# Example batch upload pattern
TOKEN=$(request_session_token with write permission)
for each (bill_id, invoice_path):
curl -X POST -H "Authorization: Bearer $TOKEN" \
-F "file=@$invoice_path" \
https://grist-mcp.bballou.com/api/v1/attachments
# Returns attachment_id
update_records("Bills", [{"id": bill_id, "fields": {"Attachment": ["L", attachment_id]}}])
Example batch update:
update_records("Bills", [
{"id": 1, "fields": {"EntryTransaction": 1}},
{"id": 2, "fields": {"EntryTransaction": 2}},
{"id": 3, "fields": {"EntryTransaction": 3}}
])
Common Queries
Unpaid Bills by Vendor
SELECT v.Name, b.BillNumber, b.BillDate, b.Amount, b.AmountDue
FROM Bills b
JOIN Vendors v ON b.Vendor = v.id
WHERE b.Status IN ('Open', 'Partial')
ORDER BY b.DueDate
Bills Summary by Vendor
SELECT v.Name as Vendor, COUNT(b.id) as Bills, SUM(b.Amount) as Total, SUM(b.AmountDue) as Due
FROM Bills b
JOIN Vendors v ON b.Vendor = v.id
GROUP BY v.Name
ORDER BY Total DESC
Account Balances (Non-Zero)
SELECT Code, Name, Type, Balance
FROM Accounts
WHERE Balance != 0
ORDER BY Code
Owner Reimbursement Balance
SELECT Balance FROM Accounts WHERE Code = '2203'
Expense Summary by Account
SELECT a.Code, a.Name, a.Balance
FROM Accounts a
WHERE a.Type = 'Expense' AND a.Balance != 0
ORDER BY a.Balance DESC
Transaction History for Account
SELECT t.Date, t.Description, t.Reference, tl.Debit, tl.Credit
FROM TransactionLines tl
JOIN Transactions t ON tl.Transaction = t.id
WHERE tl.Account = 36
ORDER BY t.Date DESC
Verify All Transactions Balance
SELECT id, Description, Total, IsBalanced
FROM Transactions
WHERE IsBalanced = false
Financial Reports
Balance Sheet
Shows Assets = Liabilities + Equity at a point in time.
Important: Parent accounts roll up child balances. Query only top-level parents (Parent = 0) to avoid double-counting.
-- Assets, Liabilities, Equity (top-level only)
SELECT Code, Name, Type, Balance
FROM Accounts
WHERE Type IN ('Asset', 'Liability', 'Equity')
AND Parent = 0
ORDER BY Type, Code
-- Net Income (for Equity section)
-- Query leaf expense accounts only (no children)
SELECT
COALESCE(SUM(CASE WHEN Type = 'Income' THEN Balance ELSE 0 END), 0) -
COALESCE(SUM(CASE WHEN Type = 'Expense' THEN Balance ELSE 0 END), 0) as NetIncome
FROM Accounts
WHERE Type IN ('Income', 'Expense')
AND id NOT IN (SELECT DISTINCT Parent FROM Accounts WHERE Parent != 0)
Presentation:
| Assets | |
|---|---|
| Cash & Bank Accounts | $X.XX |
| Accounts Receivable | $X.XX |
| Total Assets | $X.XX |
| Liabilities | |
|---|---|
| Accounts Payable | $X.XX |
| Due to Owner | $X.XX |
| Total Liabilities | $X.XX |
| Equity | |
|---|---|
| Retained Earnings | $X.XX |
| Net Income (Loss) | $X.XX |
| Total Equity | $X.XX |
| Total Liabilities + Equity | $X.XX |
Income Statement
Shows Revenue - Expenses = Net Income for a period.
-- All income and expense accounts (leaf accounts only)
SELECT Code, Name, Type, Balance
FROM Accounts
WHERE Type IN ('Income', 'Expense')
AND Balance != 0
AND id NOT IN (SELECT DISTINCT Parent FROM Accounts WHERE Parent != 0)
ORDER BY Type DESC, Code
Presentation:
| Income | |
|---|---|
| Service Revenue | $X.XX |
| Total Income | $X.XX |
| Expenses | |
|---|---|
| Software & Subscriptions | $X.XX |
| Professional Services | $X.XX |
| Total Expenses | $X.XX |
| Net Income (Loss) | $X.XX |
Trial Balance
Lists all accounts with non-zero balances. Debits should equal Credits.
SELECT
Code,
Name,
Type,
CASE WHEN Type IN ('Asset', 'Expense') THEN Balance ELSE 0 END as Debit,
CASE WHEN Type IN ('Liability', 'Equity', 'Income') THEN Balance ELSE 0 END as Credit
FROM Accounts
WHERE Balance != 0
AND id NOT IN (SELECT DISTINCT Parent FROM Accounts WHERE Parent != 0)
ORDER BY Code
Accounts Payable Aging
SELECT
v.Name as Vendor,
b.BillNumber,
b.BillDate,
b.DueDate,
b.AmountDue,
CASE
WHEN b.DueDate >= strftime('%s', 'now') THEN 'Current'
WHEN b.DueDate >= strftime('%s', 'now') - 2592000 THEN '1-30 Days'
WHEN b.DueDate >= strftime('%s', 'now') - 5184000 THEN '31-60 Days'
ELSE '60+ Days'
END as Aging
FROM Bills b
JOIN Vendors v ON b.Vendor = v.id
WHERE b.Status IN ('Open', 'Partial')
ORDER BY b.DueDate
Validation Checklist
After entering bills, verify:
- Total bills match expected:
SELECT SUM(Amount) FROM Bills - All transactions balanced:
SELECT * FROM Transactions WHERE IsBalanced = false - AP balance correct:
SELECT Balance FROM Accounts WHERE Code = '2000' - Expense accounts increased appropriately
- Vendor balances reflect unpaid bills
- Invoice attachments linked:
SELECT id, BillNumber FROM Bills WHERE Attachment IS NULL
Common Mistakes
| Mistake | Fix |
|---|---|
| Transaction not balanced | Ensure SUM(Debit) = SUM(Credit) before saving |
| Wrong debit/credit direction | Assets/Expenses increase with debit; Liabilities/Equity/Income increase with credit |
| Posting to parent account | Post to leaf accounts (1001 Checking, not 1000 Cash) |
| Forgetting AP entry for bills | Bills need both the expense debit AND the AP credit |
| Missing EntryTransaction link | Always update Bill.EntryTransaction after creating journal entry |
| Bill status not updated | Manually set Status to "Paid" after full payment |
| Using string dates | Dates must be Unix timestamps (seconds), not strings |
| Missing invoice attachments | Upload invoices after bill entry if files available |
Uploading Attachments
Attachments (invoices, receipts) are uploaded via the HTTP proxy endpoint, not MCP tools. This is efficient for binary files.
Workflow
- Request session token with write permission via MCP
- Upload file via
POST /api/v1/attachmentswith multipart/form-data - Link attachment to record via
update_records
Upload Script
Use scripts/upload-attachment.sh in this skill directory:
# Get session token first (via MCP request_session_token tool)
# Then run:
./scripts/upload-attachment.sh invoice.pdf Bills 13
# Or pass token directly:
./scripts/upload-attachment.sh invoice.pdf Bills 13 sess_abc123...
# Environment variable for custom endpoint:
GRIST_MCP_URL=https://custom.example.com ./scripts/upload-attachment.sh ...
Run ./scripts/upload-attachment.sh without arguments for full usage.
Linking Attachments Manually
Grist attachment columns use format: ["L", attachment_id]
update_records("Bills", [{"id": 13, "fields": {"Attachment": ["L", 1]}}])
Formula Columns (Auto-Calculated)
| Table.Column | Description |
|---|---|
| Accounts.Balance | OwnBalance + ChildrenBalance |
| Transactions.IsBalanced | True if sum of debits = sum of credits |
| Transactions.Total | Sum of debit amounts |
| Bills.Amount | Sum of BillLines.Amount |
| Bills.AmountPaid | Sum of BillPayments.Amount |
| Bills.AmountDue | Amount - AmountPaid |
| Vendors.Balance | Sum of AmountDue for unpaid bills |