How to Integrate TaxHacker with Other Tools: API Routes and Authentication Guide
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:
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:
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 totrueto generate a ZIP archive containing both the CSV and associated filesprogressId: Optional UUID for tracking long-running export jobs
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:
- Streams CSV data using the
fast-csvlibrary to minimize memory usage - Counts attachments beforehand to calculate total progress
- Updates progress every
PROGRESS_UPDATE_INTERVAL_MS(2 seconds) viaupdateProgressinmodels/progress.ts - Generates ZIP archives using JSZip when
includeAttachments=true, organizing files intofiles/<year>/<month>/subdirectories - Serves binary response with proper
Content-Typeheaders for immediate download or streaming
Client-Side Integration Example
Poll the progress endpoint while consuming the response stream:
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 for monitoring asynchronous operations like ZIP generation.
Progress Data Structure
Each progress record stores:
current: Number of processed itemstotal: Total items to processdata: 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:
// 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 returns exchange rates from xe.com with 24-hour caching:
GET /api/currency?from=USD&to=EUR&date=2023-03-15
Response format:
{ "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 verifies signatures and synchronizes the following fields:
membershipPlan: Current subscription tiermembershipExpiresAt: Subscription expiration timestampstorageLimit: Allocated storage bytesaiBalance: 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, supporting OpenAI, Google Gemini, Mistral, and OpenAI-compatible endpoints.
Adding Custom Providers
- Define the provider in the
PROVIDERSarray withkey,apiKeyName, andmodelName - Extend the request logic in
ai/providers/llmProvider.tsto handle the new provider key - Store credentials via the Settings API (
saveSettingsAction) or UI, which persists tomodels/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:
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 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
getSessioninlib/auth.tsor login API calls - Export data by calling
/api/export/transactionswith field selection and optional ZIP bundling; monitor via the progress model inmodels/progress.ts - Convert currencies using the xe.com scraper at
/api/currencywith historical date support - Sync subscriptions automatically via Stripe webhooks handled in
app/api/stripe/webhook/route.ts - Customize AI providers by extending
lib/llm-providers.tsandai/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.
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.
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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →