# How to Integrate TaxHacker with Other Tools: API Routes and Authentication Guide

> Easily integrate TaxHacker with your tools using its HTTP API and cookie-based authentication. Access data export, currency conversion, and progress tracking features.

- Repository: [Vasily Zubarev/TaxHacker](https://github.com/vas3k/TaxHacker)
- Tags: how-to-guide
- Published: 2026-04-01

---

**You can integrate TaxHacker with external tools by authenticating via cookie-based sessions and calling its HTTP API endpoints for data export, currency conversion, and progress tracking.**

TaxHacker is a self-hosted Next.js 15+ application that exposes RESTful endpoints under the `/api` path. These integration points allow you to connect the receipt-processing engine to accounting software, CI pipelines, or custom automation scripts without modifying the core source code in the `vas3k/TaxHacker` repository.

## Authentication and Session Management

TaxHacker uses **better-auth** with cookie-based sessions to secure its routes. All API calls must include a valid session cookie obtained through the authentication flow.

For browser-based integrations, the cookie is automatically sent with requests. For server-to-server integrations, extract the session token using the `getSession` helper defined in [`lib/auth.ts`](https://github.com/vas3k/TaxHacker/blob/main/lib/auth.ts):

```typescript
import { getSession } from '@/lib/auth';

const session = await getSession();
if (!session) throw new Error('Not authenticated');
const cookie = `next-auth.session-token=${session.id}`;

```

When calling endpoints from external processes, include this cookie in the request headers:

```bash
curl -H "Cookie: next-auth.session-token=YOUR_TOKEN" \
     https://your-domain/api/currency?from=USD&to=EUR&date=2023-01-01

```

For long-lived automation scripts, create a dedicated service user in the database and reuse its session cookie as a pseudo-API key.

## Exporting Transactions via the REST API

The primary integration point for data extraction is the **transaction export endpoint** at `/api/export/transactions`. This route supports both CSV and ZIP output formats with optional attachment bundling.

### Request Parameters

Call the endpoint with the following query parameters:

- **`fields`**: Comma-separated list of field codes (e.g., `name,total,currencyCode`)
- **`includeAttachments`**: Set to `true` to generate a ZIP archive containing both the CSV and associated files
- **`progressId`**: Optional UUID for tracking long-running export jobs

```bash
GET /api/export/transactions?fields=name,total,currencyCode&includeAttachments=true&progressId=abc-123

```

### How the Export Works

The implementation in `app/(app)/export/transactions/route.ts` performs the following:

1. **Streams CSV data** using the `fast-csv` library to minimize memory usage
2. **Counts attachments** beforehand to calculate total progress
3. **Updates progress** every `PROGRESS_UPDATE_INTERVAL_MS` (2 seconds) via `updateProgress` in [`models/progress.ts`](https://github.com/vas3k/TaxHacker/blob/main/models/progress.ts)
4. **Generates ZIP archives** using JSZip when `includeAttachments=true`, organizing files into `files/<year>/<month>/` subdirectories
5. **Serves binary response** with proper `Content-Type` headers for immediate download or streaming

### Client-Side Integration Example

Poll the progress endpoint while consuming the response stream:

```typescript
async function exportTransactions(
  fields: string[],
  includeAttachments = false,
  onProgress?: (completed: number, total: number) => void
) {
  // Initialize progress tracking
  const progressId = crypto.randomUUID();
  await fetch('/api/progress', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ id: progressId, total: 0 })
  });

  // Start export
  const params = new URLSearchParams({
    fields: fields.join(','),
    includeAttachments: String(includeAttachments),
    progressId,
  });
  
  const res = await fetch(`/api/export/transactions?${params}`);
  if (!res.ok) throw new Error('Export failed');

  // Poll progress in parallel
  if (onProgress) {
    const interval = setInterval(async () => {
      const p = await fetch(`/api/progress/${progressId}`).then(r => r.json());
      onProgress(p.current ?? 0, p.total ?? 0);
      if (p.current === p.total) clearInterval(interval);
    }, 1000);
  }

  // Stream to file
  const blob = await res.blob();
  const url = window.URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = includeAttachments ? 'transactions.zip' : 'transactions.csv';
  a.click();
}

```

## Tracking Long-Running Jobs

TaxHacker implements a simple progress-tracking model in [`models/progress.ts`](https://github.com/vas3k/TaxHacker/blob/main/models/progress.ts) for monitoring asynchronous operations like ZIP generation.

### Progress Data Structure

Each progress record stores:
- **`current`**: Number of processed items
- **`total`**: Total items to process
- **`data`**: Optional metadata object

### Creating Progress Endpoints

While the core export logic updates progress internally, you can expose this to external tools by wrapping the model functions:

```typescript
// app/api/progress/[id]/route.ts
import { getSession } from '@/lib/auth';
import { getProgressById } from '@/models/progress';

export async function GET(request: Request) {
  const session = await getSession();
  if (!session) return new Response('Unauthorized', { status: 401 });
  
  const id = request.url.split('/').pop();
  const progress = await getProgressById(session.user.id, id);
  return Response.json(progress);
}

```

Use this endpoint to poll job status from external dashboards or automation scripts.

## Currency Conversion Endpoint

Convert historical currencies without managing third-party API keys by leveraging TaxHacker's scraping capability.

The `/api/currency` endpoint in [`app/api/currency/route.ts`](https://github.com/vas3k/TaxHacker/blob/main/app/api/currency/route.ts) returns exchange rates from xe.com with 24-hour caching:

```bash
GET /api/currency?from=USD&to=EUR&date=2023-03-15

```

Response format:

```json
{ "rate": 0.9134, "cached": false }

```

The handler validates dates and falls back to "yesterday" for same-day requests to ensure rate availability. Results are stored in `PoorManCache` to minimize external requests.

## Stripe Webhook Integration

When running TaxHacker in SaaS mode, webhook events from Stripe update user subscription data automatically.

The handler at [`app/api/stripe/webhook/route.ts`](https://github.com/vas3k/TaxHacker/blob/main/app/api/stripe/webhook/route.ts) verifies signatures and synchronizes the following fields:
- **`membershipPlan`**: Current subscription tier
- **`membershipExpiresAt`**: Subscription expiration timestamp
- **`storageLimit`**: Allocated storage bytes
- **`aiBalance`**: Remaining AI processing credits

Extend `handleUserSubscriptionUpdate` or add downstream HTTP calls after `updateUser` to trigger CRM updates or notification services.

## Configuring LLM Providers

TaxHacker abstracts LLM configuration through the `ProviderMeta` interface in [`lib/llm-providers.ts`](https://github.com/vas3k/TaxHacker/blob/main/lib/llm-providers.ts), supporting OpenAI, Google Gemini, Mistral, and OpenAI-compatible endpoints.

### Adding Custom Providers

1. **Define the provider** in the `PROVIDERS` array with `key`, `apiKeyName`, and `modelName`
2. **Extend the request logic** in [`ai/providers/llmProvider.ts`](https://github.com/vas3k/TaxHacker/blob/main/ai/providers/llmProvider.ts) to handle the new provider key
3. **Store credentials** via the Settings API (`saveSettingsAction`) or UI, which persists to [`models/settings.ts`](https://github.com/vas3k/TaxHacker/blob/main/models/settings.ts)

This abstraction allows you to switch invoice-parsing backends without changing integration code.

## Complete Integration Example: Automated Daily Export

The following Node.js script demonstrates a full server-to-server integration that authenticates, exports data with attachments, tracks progress, and uploads results to AWS S3:

```typescript
import fetch from 'node-fetch';
import { S3 } from '@aws-sdk/client-s3';
import { randomUUID } from 'crypto';

async function getAuthCookie(): Promise<string> {
  const loginRes = await fetch('https://taxhacker.local/api/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ 
      email: 'service@example.com', 
      password: process.env.SERVICE_PASSWORD 
    })
  });
  const setCookie = loginRes.headers.get('set-cookie')!;
  return setCookie.split(';')[0]; // Extracts next-auth.session-token=...
}

async function startExport(cookie: string) {
  const progressId = randomUUID();
  const params = new URLSearchParams({
    fields: 'name,total,currencyCode,issuedAt',
    includeAttachments: 'true',
    progressId,
  });
  
  const res = await fetch(
    `https://taxhacker.local/api/export/transactions?${params}`,
    { headers: { Cookie: cookie } }
  );
  
  if (!res.ok) throw new Error('Export request failed');
  return { stream: res.body!, progressId };
}

async function pollProgress(cookie: string, progressId: string) {
  while (true) {
    const r = await fetch(
      `https://taxhacker.local/api/progress/${progressId}`,
      { headers: { Cookie: cookie } }
    );
    const p = await r.json();
    console.log(`Progress: ${p.current || 0}/${p.total || '?'}`);
    if (p.current === p.total) break;
    await new Promise(r => setTimeout(r, 1000));
  }
}

async function uploadToS3(stream: NodeJS.ReadableStream, key: string) {
  const s3 = new S3({ region: process.env.AWS_REGION });
  await s3.putObject({
    Bucket: 'taxhacker-exports',
    Key: key,
    Body: stream,
    ContentType: 'application/zip',
  });
}

// Execute integration
(async () => {
  const cookie = await getAuthCookie();
  const { stream, progressId } = await startExport(cookie);
  
  // Monitor progress without blocking stream
  pollProgress(cookie, progressId).catch(console.error);
  
  await uploadToS3(stream, `daily-export-${Date.now()}.zip`);
  console.log('Export complete and uploaded');
})();

```

This script leverages the `fullPathForFile` helper from [`lib/files.ts`](https://github.com/vas3k/TaxHacker/blob/main/lib/files.ts) internally when the server bundles attachments, demonstrating how external tools can drive TaxHacker's complete export pipeline.

## Summary

- **Authenticate** using better-auth session cookies obtained via `getSession` in [`lib/auth.ts`](https://github.com/vas3k/TaxHacker/blob/main/lib/auth.ts) or login API calls
- **Export data** by calling `/api/export/transactions` with field selection and optional ZIP bundling; monitor via the progress model in [`models/progress.ts`](https://github.com/vas3k/TaxHacker/blob/main/models/progress.ts)
- **Convert currencies** using the xe.com scraper at `/api/currency` with historical date support
- **Sync subscriptions** automatically via Stripe webhooks handled in [`app/api/stripe/webhook/route.ts`](https://github.com/vas3k/TaxHacker/blob/main/app/api/stripe/webhook/route.ts)
- **Customize AI providers** by extending [`lib/llm-providers.ts`](https://github.com/vas3k/TaxHacker/blob/main/lib/llm-providers.ts) and [`ai/providers/llmProvider.ts`](https://github.com/vas3k/TaxHacker/blob/main/ai/providers/llmProvider.ts)
- **Stream large exports** directly to external storage without intermediate file storage using the binary response streams from the export route

## Frequently Asked Questions

### Can I use API keys instead of session cookies to integrate TaxHacker?

TaxHacker does not natively support API key authentication. As a workaround, create a dedicated service user account and extract its persistent session cookie from the `next-auth.session-token` header. Store this token securely in your external tool's environment variables and pass it as a `Cookie` header in all requests.

### How do I monitor export progress in real-time from an external dashboard?

Create a progress record via `POST /api/progress` with a generated UUID before starting the export. Pass this ID as the `progressId` query parameter to `/api/export/transactions`. Poll `GET /api/progress/:id` every 1-2 seconds to retrieve the `current` and `total` counts updated by the `updateProgress` function in [`models/progress.ts`](https://github.com/vas3k/TaxHacker/blob/main/models/progress.ts).

### Does the currency conversion endpoint require an external API key?

No. The `/api/currency` route scrapes xe.com and caches results for 24 hours using the internal `PoorManCache` mechanism. You do not need to configure third-party FX API credentials, though the endpoint respects rate limits through its caching layer in [`app/api/currency/route.ts`](https://github.com/vas3k/TaxHacker/blob/main/app/api/currency/route.ts).

### Can I modify which transaction fields are available for export?

Yes. The `fields` parameter accepts any comma-separated combination of fields defined in your Prisma schema for the `Transaction` model. The export route validates these against the user's available fields before streaming. To add custom fields, modify the schema in `prisma/schema.prisma` and regenerate the client, ensuring the field names match the query parameters passed to the export endpoint.