Trupocket

Getting Started Guide

Last Updated: November 17, 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

OAuth 2.0 Authentication

Trupocket uses OAuth 2.0 for secure authentication.

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
  5. Include access token in all API requests via Authorization header
  6. If token expires, sign in again to get a new token

Token Lifespan

Example: Registration

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

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

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

# Email verification required before sign-in

Example: Sign In

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

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

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

Example: Using Access Token

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

Handling Token Expiration

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

{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Token expired or invalid"
  }
}

Simply sign in again to get a new token:

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

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

Best Practices


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": 0,
  "purpose": 0
}

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

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

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request data",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format"
      }
    ]
  }
}

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

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 allow you to receive real-time notifications when events occur in Trupocket.

Supported Events (Planned)

Webhook Payload Example

{
  "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?