Tally is source-available under the Functional Source License. You provide your own Postgres database and Plaid account, and the application runs entirely on infrastructure you control. A working Sandbox instance takes about ten minutes to bring up from a fresh clone.
docker compose up -dThis provisions Postgres only. If a Postgres instance is already available, skip this step and point DATABASE_URL at it directly.
cp .env.example .envSet AUTH_SECRET (npx auth secret) and MASTER_KEY (openssl rand -base64 32). Leave every PLAID_* variable blank for now, mock mode does not require them.
Install dependencies, run migrations, and seed an admin user plus the category taxonomy:
npm install
npm run db:migrate
ADMIN_EMAIL=you@example.com ADMIN_PASSWORD=your-password-here npm run seed:user
npm run seed:categoriesnpm run devVisit http://localhost:3000 and sign in. Mock data is enabled by default in development: "Add account" connects a fixture institution with realistic accounts and transactions immediately, with no Plaid credentials required.
In a second terminal, start the background worker:
npm run workerThis is required for automatic, scheduled sync. The app's own "Sync now" action calls the sync engine directly and works without the worker running.
Every variable the application reads, and which of them are required for it to start at all.
| Variable | Required | Purpose |
|---|---|---|
| DATABASE_URL | Yes | Postgres connection string. |
| AUTH_SECRET | Yes | Session signing secret. Generate with npx auth secret. |
| APP_URL | Yes | The public URL this instance is reachable at. Must be an HTTPS origin in production. |
| MOCK_DATA | Optional | true forces mock mode, false forces live Plaid. Unset: development defaults to mock, production defaults to live. |
| PLAID_CLIENT_ID | Optional | From the Plaid dashboard. Only needed once mock mode is disabled. |
| PLAID_SECRET | Optional | From the Plaid dashboard, matching PLAID_ENV. |
| PLAID_ENV | Optional | sandbox or production. |
| PLAID_PRODUCTS | Optional | Comma-separated, defaults to transactions. Actively requested at Link time; each is billed once an item connects. |
| PLAID_ADDITIONAL_CONSENTED_PRODUCTS | Optional | Comma-separated, defaults to investments,liabilities. Consented during Link but billed only once actually fetched. |
| PLAID_COUNTRY_CODES | Optional | Comma-separated, defaults to US. |
| PLAID_REDIRECT_URI | Optional | Required for OAuth institutions. Must be registered in the Plaid dashboard first, separately per environment. |
| PLAID_WEBHOOK_URL | Optional | Where Plaid pushes sync events. Requires a public HTTPS URL (a tunnel in local development). |
| MASTER_KEY | Yes | 32-byte base64 key. Generate with openssl rand -base64 32. Encrypts Plaid access tokens at rest. |
| CRON_TZ | Optional | Timezone for the worker's cron schedules. Defaults to America/New_York. |
| ADMIN_EMAIL | Yes | Used once by npm run seed:user to create the single admin login. |
| ADMIN_PASSWORD | Yes | Used once by npm run seed:user. At least 8 characters. |
Mock mode resolves as follows: if MOCK_DATA is set, its value wins outright. If it is unset, development defaults to mock and production defaults to live.
In mock mode, "Add account" inserts a canned institution, with checking, savings, credit, and brokerage accounts, directly into Postgres. No network calls are made. Every downstream feature reads the same database rows a live connection would produce, so it exercises the same code paths.
npm run worker processes webhook-triggered and cron-scheduled syncs against the same database as the application. It is a separate, long-running process, not a request handler, and must be kept running continuously rather than invoked on demand.
If it stops, nothing syncs automatically. The application's manual "Sync now" action is unaffected, since it calls the sync engine directly rather than enqueuing a job.
Everything above describes a local instance running against Plaid Sandbox with mock data available as a fallback. Serving real accounts requires four additional decisions: Plaid access, environment configuration, process management, and transport security.
Plaid does not allow Sandbox credentials to read real accounts. Production access requires an approved application in the Plaid dashboard and is billed per connected item and product; Transactions and Investments are priced separately. Confirm current pricing before connecting real accounts.
Approval issues a second, distinct PLAID_CLIENT_ID / PLAID_SECRET pair, unrelated to the Sandbox pair already in use. A separate redirect URI must also be registered under the production application; Sandbox registrations do not carry over.
The application (npm run build then npm run start) and the worker (npm run worker) are two independent, long-running processes. Both must stay running continuously; a stopped worker fails silently, since manual sync keeps working while scheduled and webhook-triggered sync simply stop.
The docker-compose.yml included in this repository provisions Postgres only. Running the application and worker in containers means adding service definitions for them; running them directly on a host means supervising both with a process manager (systemd, pm2, or equivalent) configured to restart on failure and capture logs. A serverless request handler is not a substitute for the worker: it has no facility for a persistent job-queue listener, so a platform that runs the application as serverless functions still needs the worker hosted separately as a standing process.
A running instance holds a complete transaction history for every connected account. Back up Postgres on a fixed schedule, encrypt the backup files at rest, and store them somewhere other than the database host.
# Backup
pg_dump "$DATABASE_URL" -Fc -f tally-$(date +%Y%m%d).dump
# Restore into a fresh database
pg_restore -d "$DATABASE_URL" --clean --if-exists tally-20260101.dumpA cron job invoking the pg_dump command above on a schedule is sufficient at this scale. Restore from a backup periodically to confirm it is valid; an untested backup is not a verified one.
PLAID_SECRET: generate a replacement in the Plaid dashboard, update .env, restart the application and worker, and confirm a sync still succeeds before revoking the previous secret. Plaid secrets are never stored in the database, only transmitted to Plaid's API, so no data migration is required.
MASTER_KEY: this key is what every stored access token is encrypted under, so rotating it requires re-encrypting each one. Stop the application and worker, generate a new key, then run:
OLD_MASTER_KEY=<current key> NEW_MASTER_KEY=<new key> npm run rotate:master-keySet MASTER_KEY to the new value and restart. Do not discard the old key until the rotation script has completed successfully.
Tally is available under the Functional Source License 1.1, Apache 2.0 Future Grant (FSL-1.1-ALv2). It may be self-hosted, modified, and used for any purpose other than offering it, or a substantially similar service, to others commercially. Each release converts to Apache 2.0 automatically two years after publication.
# Functional Source License, Version 1.1, ALv2 Future License
## Abbreviation
FSL-1.1-ALv2
## Notice
Copyright 2026 Tejas Raibagi
## Terms and Conditions
### Licensor ("We")
The party offering the Software under these Terms and Conditions.
### The Software
The "Software" is each version of the software that we make available under
these Terms and Conditions, as indicated by our inclusion of these Terms and
Conditions with the Software.
### License Grant
Subject to your compliance with this License Grant and the Patents,
Redistribution and Trademark clauses below, we hereby grant you the right to
use, copy, modify, create derivative works, publicly perform, publicly display
and redistribute the Software for any Permitted Purpose identified below.
### Permitted Purpose
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
means making the Software available to others in a commercial product or
service that:
1. substitutes for the Software;
2. substitutes for any other product or service we offer using the Software
that exists as of the date we make the Software available; or
3. offers the same or substantially similar functionality as the Software.
Permitted Purposes specifically include using the Software:
1. for your internal use and access;
2. for non-commercial education;
3. for non-commercial research; and
4. in connection with professional services that you provide to a licensee
using the Software in accordance with these Terms and Conditions.
### Patents
To the extent your use for a Permitted Purpose would necessarily infringe our
patents, the license grant above includes a license under our patents. If you
make a claim against any party that the Software infringes or contributes to
the infringement of any patent, then your patent license to the Software ends
immediately.
### Redistribution
The Terms and Conditions apply to all copies, modifications and derivatives of
the Software.
If you redistribute any copies, modifications or derivatives of the Software,
you must include a copy of or a link to these Terms and Conditions and not
remove any copyright notices provided in or with the Software.
### Disclaimer
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
### Trademarks
Except for displaying the License Details and identifying us as the origin of
the Software, you have no right under these Terms and Conditions to use our
trademarks, trade names, service marks or product names.
## Grant of Future License
We hereby irrevocably grant you an additional license to use the Software under
the Apache License, Version 2.0 that is effective on the second anniversary of
the date we make the Software available. On or after that date, you may use the
Software under the Apache License, Version 2.0, in which case the following
will apply:
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.