/pocketsmith-ts

API client for Pocketsmith

Primary LanguageTypeScriptMIT LicenseMIT

PocketSmith TypeScript Client

A fully-typed TypeScript client library for the PocketSmith API, automatically generated from their official OpenAPI specification.

Features

  • Fully typed - Complete TypeScript definitions for all API endpoints
  • Auto-generated - Built directly from PocketSmith's official OpenAPI spec
  • Zero boilerplate - Minimal configuration required
  • Modern - Built with openapi-fetch for excellent performance
  • Authentication - Supports both API keys and OAuth2

Installation

npm install pocketsmith-ts

Quick Start

Using API Key (Developer Key)

import { createPocketSmithClient } from 'pocketsmith-ts';

const client = createPocketSmithClient({
  apiKey: 'your-api-key-here'
});

// Get current user
const { data: user } = await client.GET('/me');
console.log(user);

// Get user's accounts
const { data: accounts } = await client.GET('/users/{id}/accounts', {
  params: { path: { id: user.id } }
});

Using OAuth2

const client = createPocketSmithClient({
  accessToken: 'your-oauth-token-here'
});

const { data: user } = await client.GET('/me');

Configuration

const client = createPocketSmithClient({
  // API base URL (optional, defaults to https://api.pocketsmith.com/v2)
  baseUrl: 'https://api.pocketsmith.com/v2',
  
  // For personal use - get from https://my.pocketsmith.com/api_keys
  apiKey: 'your-developer-key',
  
  // For OAuth apps - obtained through OAuth flow
  accessToken: 'your-oauth-token',
});

Understanding Accounts vs Transaction Accounts

PocketSmith has two types of account endpoints that serve different purposes:

Accounts (/accounts/{id})

High-level account groupings that represent conceptual account containers. These are used for organizing and categorizing your financial structure.

// Fetch all accounts for a user
const { data: accounts } = await client.GET('/users/{id}/accounts', {
  params: { path: { id: userId } }
});

// Get specific account details
const { data: account } = await client.GET('/accounts/{id}', {
  params: { path: { id: accountId } }
});

Transaction Accounts (/transaction_accounts/{id})

Individual accounts where transactions are actually posted. These represent the actual bank accounts, credit cards, etc. where money moves in and out.

// Fetch transaction accounts for an account
const { data: transactionAccounts } = await client.GET('/accounts/{id}/transaction_accounts', {
  params: { path: { id: accountId } }
});

// Get transactions for a specific transaction account
const { data: transactions } = await client.GET('/transaction_accounts/{id}/transactions', {
  params: { 
    path: { id: transactionAccountId },
    query: { 
      start_date: '2024-01-01',
      end_date: '2024-12-31' 
    }
  }
});

// Create a transaction in a specific transaction account
const { data: transaction } = await client.POST('/transaction_accounts/{id}/transactions', {
  params: { path: { id: transactionAccountId } },
  body: {
    payee: 'Coffee Shop',
    amount: -4.50,
    date: '2024-01-15'
  }
});

Examples

Fetching Transactions

Using Standard API Endpoints

const { data: transactions } = await client.GET('/users/{id}/transactions', {
  params: {
    path: { id: userId },
    query: {
      start_date: '2024-01-01',
      end_date: '2024-12-31',
      search: 'coffee',
      type: 'debit'
    }
  }
});

Using Convenience Methods

// Get transactions for a high-level account grouping
const { data: accountTransactions } = await client.transactions.getByAccount(123, {
  start_date: '2024-01-01',
  end_date: '2024-12-31',
  search: 'coffee'
});

// Get transactions for a specific transaction account (where transactions are posted)
const { data: transactionAccountTransactions } = await client.transactions.getByTransactionAccount(456, {
  start_date: '2024-01-01',
  end_date: '2024-12-31',
  type: 'debit'
});

Creating a Transaction

const { data: transaction } = await client.POST('/transaction_accounts/{id}/transactions', {
  params: {
    path: { id: transactionAccountId }
  },
  body: {
    payee: 'Coffee Shop',
    amount: -4.50,
    date: '2024-01-15',
    category_id: categoryId,
    note: 'Morning coffee'
  }
});

Managing Categories

// Create a category
const { data: category } = await client.POST('/users/{id}/categories', {
  params: { path: { id: userId } },
  body: {
    title: 'Coffee & Drinks',
    colour: '#8B4513',
    is_bill: false
  }
});

// Update a category
const { data: updated } = await client.PUT('/categories/{id}', {
  params: { path: { id: category.id } },
  body: {
    title: 'Coffee & Beverages',
    colour: '#654321'
  }
});

Error Handling

The client returns a { data, error } object for each request. For better error debugging, use the serializeError utility:

import { createPocketSmithClient, serializeError } from 'pocketsmith-ts';

const client = createPocketSmithClient({ apiKey: 'your-key' });
const { data, error } = await client.GET('/me');

if (error) {
  // Use serializeError to properly format errors (no more [object Object])
  console.error('API Error:', serializeError(error));
  console.error('Full error object:', error);
  // Handle error case
  return;
}

// Use data safely here
console.log(data.name);

Type Safety

All API responses and request bodies are fully typed:

// user is typed as components["schemas"]["User"]
const { data: user } = await client.GET('/me');

// TypeScript knows about all user properties
console.log(user.id, user.name, user.email);

// Request body is also typed
await client.POST('/users/{id}/categories', {
  params: { path: { id: userId } },
  body: {
    title: 'Required field',
    colour: '#FF0000', // Optional but typed
    // TypeScript will error if you use invalid properties
  }
});

Testing

Running Tests

# Run all tests
npm test

# Run only integration tests (requires API key)
npm run test:integration

# Run with coverage
npm test -- --coverage

Integration Tests

Integration tests require a valid PocketSmith API key. Set it as an environment variable:

export POCKETSMITH_API_KEY=your_api_key_here
npm run test:integration

Or create a .env file (copy from .env.example):

cp .env.example .env
# Edit .env with your API key

Note: Integration tests make real API calls to PocketSmith and may count against your API rate limits.

Development

Updating the OpenAPI Spec

The client is automatically generated from PocketSmith's official OpenAPI specification. To update:

npm run generate

This will:

  1. Download the latest OpenAPI spec from PocketSmith's GitHub repo
  2. Generate TypeScript types and interfaces
  3. Update the client accordingly

Building

npm run build

Development Mode

npm run dev

Authentication

Developer Keys

For personal use or simple integrations, you can generate a developer key:

  1. Log into PocketSmith
  2. Go to Settings → Security & Integrations
  3. Create a new developer key
  4. Use it with the apiKey option

OAuth2

For applications that other users will use:

  1. Contact PocketSmith at api@pocketsmith.com to register your app
  2. Implement the OAuth2 flow to get access tokens
  3. Use tokens with the accessToken option

See PocketSmith's OAuth documentation for details.

License

MIT

Contributing

  1. Fork the repository
  2. Create your feature branch
  3. Make your changes
  4. Add tests if applicable
  5. Submit a pull request

Author

John Barton - @joho

Links