Trupocket

Getting Started Guide

Last Updated: December 11, 2025

🚀 Quick Start

Base URL: https://api.trupocket.app/v1

Authentication: OAuth 2.0 Bearer tokens (1-hour expiration)

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

Table of Contents

1. Introduction

Welcome developers! This guide will help you get started with the Trupocket API.

What is Trupocket?

Quick Links


2. Authentication

Trupocket supports two authentication methods:

Personal Access Tokens (Recommended)

PATs provide long-lived API access without requiring OAuth sign-in. Ideal for scripts, CI/CD, and server-to-server integrations.

Token Format

Plan Limits

Creating a PAT

Create a PAT from the Trupocket web app settings, or via the API (requires OAuth authentication):

POST /v1/users/access-tokens
Authorization: Bearer YOUR_OAUTH_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.

Using a PAT

curl -X GET "https://api.trupocket.app/v1/households" \
  -H "Authorization: Bearer tp_your_personal_access_token_here"

Managing PATs

# 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, deactivate, or change expiration)
PATCH /v1/users/access-tokens/{tokenID}
{
  "name": "Renamed Integration",
  "isActive": false
}

# Delete a PAT
DELETE /v1/users/access-tokens/{tokenID}

PAT Security Best Practices

OAuth Sign-In (For User Sessions)

OAuth authentication is designed for web and mobile apps where users sign in interactively. Tokens expire after 1 hour.

Authentication Flow

  1. User registers via /v1/users/register endpoint
  2. User verifies email address
  3. User signs in via /v1/users/sign-in endpoint
  4. API returns access token (expires in 1 hour)
  5. Include access token in all API requests via Authorization header
  6. If token expires, sign in again to get a new token

Example: Sign In

POST /v1/users/sign-in
Content-Type: application/json

{
  "email": "john@example.com",
  "password": "SecurePassword123!"
}

# Response:
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresIn": 3600
}

Handling Token Expiration

When your OAuth token expires (after 1 hour), you'll receive:

{
  "errorCode": "unauthorized",
  "errorMessage": "Token expired or invalid",
  "errorField": null
}

Simply sign in again to get a new token.


3. API Overview

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)

Rate Limits by Plan

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
Developer 10,000 Unlimited Unlimited Unlimited Yes

Rate Limit Headers

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1699564800

4. Core Concepts

Hierarchy

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)

Household Styles

Household Purpose

Account Types

Transaction Cashflow

💡 Hashtags: Dynamic Transaction Grouping

Hashtags 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:

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:

Example 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:


5. Quick Start Tutorial

Step 1: Register a User

POST /v1/users/register
Content-Type: application/json

{
  "firstName": "John",
  "email": "john@example.com",
  "password": "SecurePassword123!",
  "agreeToTerms": true
}

# Response:
{
  "userID": "2BxFGhvDwSoabBEqf2V5ZgC3ifo"
}

# Note: Email verification required before sign-in

Step 2: Sign In

POST /v1/users/sign-in
Content-Type: application/json

{
  "email": "john@example.com",
  "password": "SecurePassword123!"
}

# Response:
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresIn": 3600
}

Step 3: Create a Household

POST /v1/households
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json

{
  "name": "Personal Finances",
  "style": "dt",
  "purpose": "home"
}

# Response:
{
  "householdID": "2BKHJyVqwTkR1234567890ABCDE"
}

Step 4: Create a Checking Account

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"
}

Step 5: Create a Category

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"
}

Step 6: Create a Payee

POST /v1/households/{householdID}/detailed-tracking/payees
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json

{
  "name": "Whole Foods"
}

# Response:
{
  "payeeID": "2BKHOpQrStUvWxYzAbCdEfGhIjK"
}

Step 7: Create a Transaction

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
  }
}

6. Common Workflows

Re-authenticate After Token Expiration

# When you receive 401 Unauthorized:
POST /v1/users/sign-in
Content-Type: application/json

{
  "email": "john@example.com",
  "password": "SecurePassword123!"
}

# Get new access_token and continue

Get Household Summary

GET /v1/households/{householdID}
Authorization: Bearer YOUR_ACCESS_TOKEN

# Returns household with accounts, budgets, etc.

List All Transactions

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 Account Balance

GET /v1/households/{householdID}/detailed-tracking/accounts/{accountID}
Authorization: Bearer YOUR_ACCESS_TOKEN

# Returns account with current balance

Get Budget Status

GET /v1/households/{householdID}/detailed-tracking/categories
Authorization: Bearer YOUR_ACCESS_TOKEN

# Returns all categories with budget details:
# - budget.amount (allocated)
# - budget.balance (remaining)

Create Scheduled Transaction (Recurring Bill)

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": 4,
  "frequencyDay": 1,
  "startDate": 1699564800,
  "shouldAutoGenerate": true,
  "categories": [
    {
      "name": "Rent",
      "amount": 150000,
      "memo": "Monthly rent",
      "impactBudget": true
    }
  ]
}

# payee: Payee name (string, not ID)
# frequency: 4 = monthly
# frequencyDay: 1 = 1st of month
# startDate: Unix timestamp for when schedule begins
# shouldAutoGenerate: true = automatically create transactions
# categories[].name: Category name (string, not ID)
# amount: 150000 = $1,500.00
# impactBudget: true = this WILL impact the budget (scheduled uses impactBudget, not dontImpactBudget)

7. Error Handling

HTTP Status Codes

Error Response Format

All API errors return a flat JSON structure with the following fields:

{
  "errorCode": "validation-error",
  "errorMessage": "Invalid email format",
  "errorField": "email"
}

Common Errors

Error Code Description Solution
unauthorized Invalid or expired token Sign in again to get new token
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

8. Code Examples

Quick Start with PATs (Recommended)

The simplest way to use the API is with Personal Access Tokens. No token refresh logic needed.

JavaScript/Node.js with PAT

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);

Python with PAT

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'])

OAuth Examples (For User Sessions)

Use these patterns when building apps where users sign in interactively and tokens need to be refreshed.

JavaScript/TypeScript (Node.js)

const axios = require('axios');

const API_BASE = 'https://api.trupocket.app/v1';
let accessToken = null;
const userEmail = 'john@example.com';
const userPassword = 'SecurePassword123!';

// Sign in and get access token
async function signIn() {
  try {
    const response = await axios.post(`${API_BASE}/users/sign-in`, {
      email: userEmail,
      password: userPassword
    });

    accessToken = response.data.token;
    console.log('Signed in successfully');
    return accessToken;
  } catch (error) {
    console.error('Sign in failed:', error.response?.data);
    throw error;
  }
}

// Create a transaction with automatic re-authentication
async function createTransaction(householdId, accountId, categoryName, payeeName) {
  try {
    const response = await axios.post(
      `${API_BASE}/households/${householdId}/detailed-tracking/transactions`,
      {
        cashflow: 'expense',
        type: 'debit_credit',
        fromAccountID: accountId,
        payee: payeeName,
        date: Math.floor(Date.now() / 1000),
        categories: [
          {
            name: categoryName,
            amount: 5000,
            memo: 'Lunch',
            dontImpactBudget: false,
            isCleared: true
          }
        ]
      },
      {
        headers: {
          'Authorization': `Bearer ${accessToken}`,
          'Content-Type': 'application/json'
        }
      }
    );

    console.log('Transaction created:', response.data);
    return response.data;
  } catch (error) {
    if (error.response?.status === 401) {
      // Token expired - sign in again and retry
      console.log('Token expired, re-authenticating...');
      await signIn();
      return createTransaction(householdId, accountId, categoryName, payeeName);
    } else if (error.response?.status === 429) {
      console.error('Rate limit exceeded');
    } else {
      console.error('Error:', error.response?.data);
    }
    throw error;
  }
}

// Initialize
signIn().then(() => {
  console.log('Ready to make API calls');
});

Python

import requests
import time

API_BASE = 'https://api.trupocket.app/v1'

class TrupocketClient:
    def __init__(self, email, password):
        self.email = email
        self.password = password
        self.access_token = None
        self.sign_in()

    def sign_in(self):
        """Sign in and get access token"""
        response = requests.post(
            f'{API_BASE}/users/sign-in',
            json={
                'email': self.email,
                'password': self.password
            }
        )

        if response.status_code == 200:
            self.access_token = response.json()['token']
            print('Signed in successfully')
        else:
            raise Exception(f'Sign in failed: {response.json()}')

    def _make_request(self, method, endpoint, **kwargs):
        """Make API request with automatic re-authentication"""
        headers = {
            'Authorization': f'Bearer {self.access_token}',
            'Content-Type': 'application/json'
        }

        response = requests.request(
            method,
            f'{API_BASE}{endpoint}',
            headers=headers,
            **kwargs
        )

        # If token expired, re-authenticate and retry
        if response.status_code == 401:
            print('Token expired, re-authenticating...')
            self.sign_in()
            headers['Authorization'] = f'Bearer {self.access_token}'
            response = requests.request(
                method,
                f'{API_BASE}{endpoint}',
                headers=headers,
                **kwargs
            )

        return response

    def create_transaction(self, household_id, account_id, category_name, payee_name):
        """Create a transaction"""
        payload = {
            'cashflow': 'expense',
            'type': 'debit_credit',
            'fromAccountID': account_id,
            'payee': payee_name,
            'date': int(time.time()),
            'categories': [
                {
                    'name': category_name,
                    'amount': 5000,
                    'memo': 'Lunch',
                    'dontImpactBudget': False,
                    'isCleared': True
                }
            ]
        }

        response = self._make_request('POST', f'/households/{household_id}/detailed-tracking/transactions', json=payload)

        if response.status_code == 201:
            print('Transaction created:', response.json())
            return response.json()
        elif response.status_code == 429:
            print('Rate limit exceeded')
        else:
            print('Error:', response.json())

        response.raise_for_status()

# Usage
client = TrupocketClient('john@example.com', 'SecurePassword123!')

Go

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"
)

const APIBase = "https://api.trupocket.app/v1"

type TrupocketClient struct {
    Email       string
    Password    string
    AccessToken string
}

type SignInRequest struct {
    Email    string `json:"email"`
    Password string `json:"password"`
}

type SignInResponse struct {
    Token     string `json:"token"`
    ExpiresIn int    `json:"expiresIn"`
}

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(email, password string) (*TrupocketClient, error) {
    client := &TrupocketClient{
        Email:    email,
        Password: password,
    }

    if err := client.SignIn(); err != nil {
        return nil, err
    }

    return client, nil
}

func (c *TrupocketClient) SignIn() error {
    req := SignInRequest{
        Email:    c.Email,
        Password: c.Password,
    }

    body, _ := json.Marshal(req)

    resp, err := http.Post(
        APIBase+"/users/sign-in",
        "application/json",
        bytes.NewBuffer(body),
    )
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("sign in failed: %d", resp.StatusCode)
    }

    var signInResp SignInResponse
    if err := json.NewDecoder(resp.Body).Decode(&signInResp); err != nil {
        return err
    }

    c.AccessToken = signInResp.Token
    fmt.Println("Signed in successfully")
    return nil
}

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.AccessToken)
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }

    // If token expired, re-authenticate and retry
    if resp.StatusCode == http.StatusUnauthorized {
        resp.Body.Close()
        fmt.Println("Token expired, re-authenticating...")
        if err := c.SignIn(); err != nil {
            return nil, err
        }

        // Retry request with new token
        req.Header.Set("Authorization", "Bearer "+c.AccessToken)
        return client.Do(req)
    }

    return resp, nil
}

func (c *TrupocketClient) CreateTransaction(householdID, accountID, categoryName, payeeName string) error {
    req := 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(req)

    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, err := NewTrupocketClient("john@example.com", "SecurePassword123!")
    if err != nil {
        panic(err)
    }

    // Now ready to make API calls
    fmt.Println("Ready to make API calls")
}

9. Webhooks (Developer Plan)

Coming Soon: Webhooks will allow you to receive real-time notifications when events occur in Trupocket. The following shows the planned webhook format.

Planned Webhook Events

Planned Webhook Payload Format

When implemented, webhook payloads will follow this structure:

{
  "event": "transaction.created",
  "timestamp": 1699564800,
  "data": {
    "householdID": "2BxFGhvDwSoabBEqf2V5ZgC3ifo",
    "transactionID": "2BxFGi9K3xyMp6QwVN8ZjR4LaPn",
    "amount": 5000,
    "cashflow": "expense"
  }
}

10. Best Practices

1. Authentication & Token Management

2. Currency Handling

3. Timezone Handling

4. Transaction Balancing

5. Rate Limiting

6. Error Recovery

7. Security

8. Data Validation

9. Performance


11. Testing & Development

Testing Checklist

Debugging Tips


12. API Limits & Quotas

Free Plan

Premium Plan ($2.99/mo)

Developer Plan ($29.99/mo)

Exceeding Limits


13. Support & Resources

Documentation

Support

Stay Updated

What's Next?

Recommended Next Steps:

  1. Review full API documentation at /docs
  2. Register a test account and experiment
  3. Build a simple integration following the Quick Start Tutorial
  4. Explore scheduled transactions for recurring bills
  5. Implement webhooks (Developer plan)

Need Help?