Last Updated: February 11, 2026
Base URL: https://api.trupocket.app/v1
Authentication: Personal Access Tokens (PATs) via Bearer header
Response Format: JSON
Currency: Integers in cents (1234 = $12.34)
Timestamps: Unix timestamps (seconds since epoch)
Documentation: Full API Reference | Privacy Policy | Terms of Service
Welcome developers! This guide will help you get started with the Trupocket API.
Authorization header for all API requestsPATs provide long-lived API access for all use cases. Ideal for scripts, CI/CD, server-to-server integrations, and general API usage.
tp_ prefix followed by 64 hexadecimal characterstp_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2Create a PAT from the Trupocket web app settings, or via the API (requires an existing PAT):
POST /v1/users/access-tokens
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
{
"name": "My Integration",
"expiresAt": 1767225600
}
# expiresAt is optional (Unix timestamp). Omit for tokens that never expire.
# Response (201):
{
"tokenID": "2BKHGx7z0pFFRyqPqQqRbCPp9xI",
"name": "My Integration",
"token": "tp_a1b2c3d4e5f6...",
"tokenPrefix": "tp_a1b2c3d4",
"expiresAt": 1767225600,
"createdAt": 1704067200
}
# IMPORTANT: The "token" field is only returned at creation!
# Copy and store it securely - you cannot retrieve it later.
curl -X GET "https://api.trupocket.app/v1/households" \
-H "Authorization: Bearer tp_your_personal_access_token_here"
# List all PATs
GET /v1/users/access-tokens
# Response:
{
"accessTokens": [
{
"tokenID": "2BKHGx7z0pFFRyqPqQqRbCPp9xI",
"name": "My Integration",
"tokenPrefix": "tp_a1b2c3d4",
"isActive": true,
"expiresAt": 1767225600,
"lastUsedAt": 1704153600,
"createdAt": 1704067200
}
],
"total": 1,
"limit": 3
}
# Update a PAT (rename or deactivate). Expiration is not editable —
# use the regenerate endpoint below to change it.
PATCH /v1/users/access-tokens/{tokenID}
{
"name": "Renamed Integration",
"isActive": false
}
# Regenerate a PAT (rotate the secret value; tokenID is preserved)
# The old token is invalidated immediately. The new full token value
# is returned only once in the response — store it before you reload.
# Omit "expiresAt" for no expiration; provide it to set a new one.
POST /v1/users/access-tokens/{tokenID}/regenerate
{
"expiresAt": 1767225600
}
# Delete a PAT
DELETE /v1/users/access-tokens/{tokenID}
GET /v1/users/access-tokensTrupocket runs a per-household Model Context Protocol (MCP) server so AI assistants (Claude, ChatGPT, Copilot, Gemini CLI, and any MCP-compatible client) can securely read a household's finances — and, on a write-capable plan, record to them. There are two ways to connect:
https://api.trupocket.app/v1/households/{householdID}/detailed-tracking/mcp
and authenticate with a PAT as a bearer token. See the
MCP (AI Access) specification (also listed in the
interactive docs) for the available tools and responses.
MCP access requires a Premium, Pro,
Developer, or Enterprise plan. The read tools are
available on every one of those plans. The create, update, and delete tools require a
Pro, Developer, or Enterprise plan
and — on the OAuth path — the trupocket-api/write scope. Personal Access
Tokens carry no scopes and satisfy that half of the gate on their own. An OAuth client
asks for write access by naming the scope in its authorization request
(scope=openid email profile trupocket-api/read trupocket-api/write); leave
it out and the token is read-only. A client that fails either gate simply does not see
the write tools in tools/list.
Base URL: https://api.trupocket.app/v1
Response Format: JSON
Date/Time Format: Unix timestamps (seconds since epoch)
Currency Format: Integers in smallest denomination (cents for USD)
| Plan | API Calls/Day | Transactions/Month | Households | Data History | Webhooks |
|---|---|---|---|---|---|
| Free | 20 | 60 | 1 | 90 days | No |
| Premium | 1,000 | Unlimited | Unlimited | 2 years | No |
| Pro | 5,000 | Unlimited | Unlimited | Unlimited | No |
| Developer | 10,000 | Unlimited | Unlimited | Unlimited | Yes |
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1699564800
User
└── Household(s)
├── Accounts (checking, savings, credit cards)
├── Categories (for budgeting)
├── Payees (merchants, employers)
├── Hashtags (custom tags)
├── Transactions
│ ├── Transaction Categories (split transactions)
│ └── Transaction Hashtags
└── Scheduled Transactions (recurring transactions)
0 (Detailed Tracking) - Full transaction register with budgeting0 (home) - Personal finances1 (office) - Business finances2 (home_office) - Mixed (sole proprietor, freelancer)chkg - Checking accountsvgs - Savings accountcc - Credit cardexpense - Money outincome - Money intransfer - Between your accountsHashtags are a powerful dynamic categorization tool that goes beyond traditional categories and budgets.
What are Hashtags?
Hashtags allow you to tag transactions with custom labels by including #hashtag in transaction memos. Unlike categories (which are budget-focused), hashtags provide flexible, cross-category grouping for analysis and reporting.
How Hashtags Work:
"Dinner with clients #business #taxdeductible"Categories vs Hashtags:
| Feature | Categories | Hashtags |
|---|---|---|
| Purpose | Budget tracking | Flexible grouping & analysis |
| Setup | Must create before use | Auto-created on first use |
| Per Transaction | 1-5 categories (required) | Unlimited hashtags (optional) |
| Budget Impact | Affects budget calculations | No budget impact |
| Use Cases | Monthly budgets, spending limits | Projects, tax tracking, trips, clients |
Common Use Cases:
#taxdeductible, #business, #charitable#kitchen-remodel, #wedding, #vacation2024#client-acme, #client-techcorp#birthday, #conference, #holiday#roommate, #reimbursable#savingforhouse, #debtpayoffExample Transaction with Hashtags:
{
"payee": "Home Depot",
"categories": [
{
"name": "Home Improvement",
"amount": 25000,
"memo": "Kitchen backsplash materials #kitchen-remodel #taxdeductible",
"dontImpactBudget": false,
"isCleared": true
}
]
}
# This transaction:
# - Budgeted under "Home Improvement" category
# - Tagged for project tracking (#kitchen-remodel)
# - Tagged for tax purposes (#taxdeductible)
# - Can be filtered/reported on by either hashtag
Filtering by Hashtags:
# Get all transactions for kitchen remodel project
GET /v1/households/{householdID}/detailed-tracking/transactions?hashtags=kitchen-remodel
# Get all tax-deductible transactions
GET /v1/households/{householdID}/detailed-tracking/transactions?hashtags=taxdeductible
# Get transactions for multiple projects
GET /v1/households/{householdID}/detailed-tracking/transactions?hashtags=kitchen-remodel,vacation2024
Pro Tips:
#vacation-hawaii-2024 instead of #trip#needreceipt, #verify# 1. Create an account at https://trupocket.app
# 2. Go to Settings > Access Tokens
# 3. Create a new Personal Access Token (PAT)
# 4. Copy and store the token securely - it's only shown once!
# Use your PAT in all API requests:
curl -X GET "https://api.trupocket.app/v1/users/who-am-i" \
-H "Authorization: Bearer tp_your_personal_access_token_here"
# Response:
{
"userID": "2BxFGhvDwSoabBEqf2V5ZgC3ifo",
"firstName": "John",
"email": "john@example.com",
"planID": "free"
}
POST /v1/households
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
{
"name": "Personal Finances",
"style": "dt",
"purpose": "home"
}
# Response:
{
"householdID": "2BKHJyVqwTkR1234567890ABCDE"
}
POST /v1/households/{householdID}/detailed-tracking/accounts
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
{
"type": "chkg",
"name": "Chase Checking",
"balance": 500000
}
# balance: 500000 = $5,000.00
# Response:
{
"accountID": "2BKHLmNpqRsTuvWxYzAbCdEfGhI"
}
POST /v1/households/{householdID}/detailed-tracking/categories
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
{
"name": "Groceries",
"budgetAmount": 40000
}
# budgetAmount: 40000 = $400.00/month
# Response:
{
"categoryID": "2BKHMnOpQrStUvWxYzAbCdEfGhI"
}
POST /v1/households/{householdID}/detailed-tracking/payees
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
{
"name": "Whole Foods"
}
# Response:
{
"payeeID": "2BKHOpQrStUvWxYzAbCdEfGhIjK"
}
POST /v1/households/{householdID}/detailed-tracking/transactions
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
{
"cashflow": "expense",
"type": "debit_credit",
"fromAccountID": "YOUR_ACCOUNT_ID",
"payee": "Whole Foods",
"date": 1699564800,
"categories": [
{
"name": "Groceries",
"amount": 8547,
"memo": "Weekly groceries #organic",
"dontImpactBudget": false,
"isCleared": true
}
]
}
# date: Unix timestamp for transaction date
# amount: 8547 = $85.47
# payee: Payee name (string, not ID)
# categories[].name: Category name (string, not ID)
# dontImpactBudget: false means this WILL impact the budget
# isCleared: true means this transaction has cleared the bank
# Transaction reduces account balance by $85.47
# Transaction reduces budget balance by $85.47
# Response:
{
"transactionID": "2BKHPqRsTuVwXyZaBcDeFgHiJkL",
"totalAmount": {
"currency": "USD",
"amount": 8547
}
}
# When you receive 401 Unauthorized:
# - If using a PAT: check that it hasn't been deactivated or expired
# - Generate a new PAT from the Trupocket web app if needed
# - PATs without an expiration date never expire unless manually deactivated
GET /v1/households/{householdID}
Authorization: Bearer YOUR_ACCESS_TOKEN
# Returns household with accounts, budgets, etc.
GET /v1/households/{householdID}/detailed-tracking/transactions
Authorization: Bearer YOUR_ACCESS_TOKEN
# Optional filters:
# ?accounts={accountID}
# ?categories={categoryID}
# ?fromDate={YYYY-MM-DD}
# ?toDate={YYYY-MM-DD}
GET /v1/households/{householdID}/detailed-tracking/accounts/{accountID}
Authorization: Bearer YOUR_ACCESS_TOKEN
# Returns account with current balance
GET /v1/households/{householdID}/detailed-tracking/categories
Authorization: Bearer YOUR_ACCESS_TOKEN
# Returns all categories with budget details:
# - budget.amount (allocated)
# - budget.balance (remaining)
POST /v1/households/{householdID}/detailed-tracking/scheduled-transactions
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
{
"cashflow": "expense",
"type": "ach",
"fromAccountID": "YOUR_ACCOUNT_ID",
"payee": "Rent Company",
"frequency": "monthly",
"frequencyDay": 1,
"startDate": 1699564800,
"shouldAutoGenerate": true,
"categories": [
{
"name": "Rent",
"amount": 150000,
"memo": "Monthly rent",
"impactBudget": true
}
]
}
# payee: Payee name (string, not ID)
# frequency: string enum, not a number (one_time, weekly, bi_weekly, twice_monthly,
# monthly, quarterly, bi_annually, annually, every_n_days)
# frequencyDay: 1 = 1st of month
# startDate: Unix timestamp for when schedule begins
# shouldAutoGenerate: true = automatically create transactions (omit and it defaults to false)
# categories[].name: Category name (string, not ID)
# amount: 150000 = $1,500.00
# impactBudget: true = this WILL impact the budget (scheduled uses impactBudget, not dontImpactBudget)
200 - Success201 - Created400 - Bad Request (validation error)401 - Unauthorized (invalid/expired token)403 - Forbidden (insufficient permissions)404 - Not Found429 - Too Many Requests (rate limit exceeded)500 - Internal Server ErrorAll API errors return a flat JSON structure with the following fields:
errorCode - Machine-readable error code (lowercase-kebab-case)errorMessage - Human-readable error descriptionerrorField - Field that caused the error (null if not field-specific){
"errorCode": "validation-error",
"errorMessage": "Invalid email format",
"errorField": "email"
}
| Error Code | Description | Solution |
|---|---|---|
unauthorized |
Invalid or expired token | Check PAT is active and not expired, or generate a new one |
rate-limit-exceeded |
Too many API calls | Wait for rate limit reset or upgrade plan |
validation-error |
Invalid request data | Check field requirements |
not-found |
Resource doesn't exist | Verify IDs are correct |
plan-limit-exceeded |
Exceeded plan limits | Upgrade to higher plan |
The simplest way to use the API is with Personal Access Tokens. No token refresh logic needed.
const axios = require('axios');
const API_BASE = 'https://api.trupocket.app/v1';
const PAT = process.env.TRUPOCKET_PAT; // Store in environment variable
const client = axios.create({
baseURL: API_BASE,
headers: {
'Authorization': `Bearer ${PAT}`,
'Content-Type': 'application/json'
}
});
// List households
const { data } = await client.get('/households');
console.log('Households:', data.households);
// Create a transaction
const transaction = await client.post(
`/households/${householdId}/detailed-tracking/transactions`,
{
cashflow: 'expense',
type: 'debit_credit',
fromAccountID: accountId,
payee: 'Coffee Shop',
date: Math.floor(Date.now() / 1000),
categories: [{ name: 'Food', amount: 500, memo: 'Morning coffee' }]
}
);
console.log('Created:', transaction.data.transactionID);
import os
import requests
import time
API_BASE = 'https://api.trupocket.app/v1'
PAT = os.environ['TRUPOCKET_PAT'] # Store in environment variable
headers = {
'Authorization': f'Bearer {PAT}',
'Content-Type': 'application/json'
}
# List households
response = requests.get(f'{API_BASE}/households', headers=headers)
print('Households:', response.json()['households'])
# Create a transaction
transaction = requests.post(
f'{API_BASE}/households/{household_id}/detailed-tracking/transactions',
headers=headers,
json={
'cashflow': 'expense',
'type': 'debit_credit',
'fromAccountID': account_id,
'payee': 'Coffee Shop',
'date': int(time.time()),
'categories': [{'name': 'Food', 'amount': 500, 'memo': 'Morning coffee'}]
}
)
print('Created:', transaction.json()['transactionID'])
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
const APIBase = "https://api.trupocket.app/v1"
type TrupocketClient struct {
PAT string
}
type TransactionRequest struct {
Cashflow string `json:"cashflow"`
Type string `json:"type"`
FromAccountID string `json:"fromAccountID"`
Payee string `json:"payee"`
Date int64 `json:"date"`
Categories []TransactionCategory `json:"categories"`
}
type TransactionCategory struct {
Name string `json:"name"`
Amount int64 `json:"amount"`
Memo string `json:"memo"`
DontImpactBudget bool `json:"dontImpactBudget"`
IsCleared bool `json:"isCleared"`
}
func NewTrupocketClient(pat string) *TrupocketClient {
return &TrupocketClient{PAT: pat}
}
func (c *TrupocketClient) makeRequest(method, endpoint string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest(method, APIBase+endpoint, body)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+c.PAT)
req.Header.Set("Content-Type", "application/json")
return (&http.Client{}).Do(req)
}
func (c *TrupocketClient) CreateTransaction(householdID, accountID, categoryName, payeeName string) error {
txn := TransactionRequest{
Cashflow: "expense",
Type: "debit_credit",
FromAccountID: accountID,
Payee: payeeName,
Date: time.Now().Unix(),
Categories: []TransactionCategory{
{
Name: categoryName,
Amount: 5000,
Memo: "Lunch",
DontImpactBudget: false,
IsCleared: true,
},
},
}
body, _ := json.Marshal(txn)
resp, err := c.makeRequest("POST", fmt.Sprintf("/households/%s/detailed-tracking/transactions", householdID), bytes.NewBuffer(body))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusTooManyRequests {
return fmt.Errorf("rate limit exceeded")
} else if resp.StatusCode != http.StatusCreated {
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
fmt.Println("Transaction created successfully")
return nil
}
func main() {
client := NewTrupocketClient(os.Getenv("TRUPOCKET_PAT"))
// List households
resp, err := client.makeRequest("GET", "/households", nil)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("Status:", resp.StatusCode)
}
Coming Soon: Webhooks will allow you to receive real-time notifications when events occur in Trupocket. The following shows the planned webhook format.
transaction.createdtransaction.updatedtransaction.deletedaccount.updatedbudget.exceededscheduled_transaction.generatedWhen implemented, webhook payloads will follow this structure:
{
"event": "transaction.created",
"timestamp": 1699564800,
"data": {
"householdID": "2BxFGhvDwSoabBEqf2V5ZgC3ifo",
"transactionID": "2BxFGi9K3xyMp6QwVN8ZjR4LaPn",
"amount": 5000,
"cashflow": "expense"
}
}
Math.floor(dollars * 100)cents / 100isCleared field for tracking cleared/pending transactionsX-RateLimit-Remaining header before bulk operations
Responses support gzip and zstd compression, which
materially shrinks large payloads (e.g. reports). Send an
Accept-Encoding: gzip or Accept-Encoding: zstd header to
opt in; the server negotiates automatically and only compresses responses large
enough to benefit. Most HTTP clients (browsers, fetch,
requests) advertise this and decode it transparently — no extra work.
If you set Accept-Encoding by hand on a raw client, you must decode the
response body yourself; with curl, prefer curl --compressed.
Recommended Next Steps: